diff --git a/.env.dev b/.env.dev index 39f64d03fd5aa335668f7c58e936e3d7dfcc663d..40774610bb72e98787d8ee831551c4a64148fda6 100644 --- a/.env.dev +++ b/.env.dev @@ -12,6 +12,7 @@ MUSIC_DIRECTORY_PATH=/music BROWSABLE_API_ENABLED=True FORWARDED_PROTO=http LDAP_ENABLED=False +FUNKWHALE_SPA_HTML_ROOT=http://nginx/front/ # Uncomment this if you're using traefik/https # FORCE_HTTPS_URLS=True diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bbe1bd02f201ab915520d5125316084bed48ad16..8bab09fdbf630fe6fcc822d10c98ebfd93ee6452 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -114,7 +114,7 @@ black: before_script: - pip install black script: - - black --exclude "/(\.git|\.hg|\.mypy_cache|\.tox|\.venv|_build|buck-out|build|dist|migrations)/" --check --diff api/ + - black --check --diff api/ flake8: image: python:3.6 @@ -281,6 +281,7 @@ build_api: paths: - api script: + - rm -rf api/tests - (if [ "$CI_COMMIT_REF_NAME" == "develop" ]; then ./scripts/set-api-build-metadata.sh $(echo $CI_COMMIT_SHA | cut -c 1-8); fi); - chmod -R 750 api - echo Done! diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index aa8893527488c342268623718bca5189b30102e7..1570482a6645ac0f3db22588b37b55424ca8d9e5 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -1,6 +1,9 @@ <!-- Hi there! You are reporting a bug on this project, and we want to thank you! +If it's the first time you post here, please take a moment to read our Code of Conduct +(https://funkwhale.audio/code-of-conduct/) and ensure your issue respect our guidelines. + To ensure your bug report is as useful as possible, please try to stick to the following structure. You can leave the parts text between `<!- ->` markers untouched, they won't be displayed in your final message. diff --git a/.gitlab/issue_templates/Feature request.md b/.gitlab/issue_templates/Feature request.md index 404f9c9defa4118bbf445ce899f08ce7066e96ce..355f9a19c8e0b83fc8fbafa9adff5d6f49a191c4 100644 --- a/.gitlab/issue_templates/Feature request.md +++ b/.gitlab/issue_templates/Feature request.md @@ -1,6 +1,10 @@ <!-- Hi there! You are about to share feature request or an idea, and we want to thank you! + +If it's the first time you post here, please take a moment to read our Code of Conduct +(https://funkwhale.audio/code-of-conduct/) and ensure your issue respect our guidelines. + To ensure we can deal with your idea or request, please try to stick to the following structure. You can leave the parts text between `<!- ->` markers untouched, they won't be displayed in your final message. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2beb658c2c30bd2c1d2d13f49c9573faf6d2148a..2722502c094c3982ea988b0f987544fc25e1a0de 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -172,6 +172,10 @@ and metadata. Launch all services ^^^^^^^^^^^^^^^^^^^ +Before the first Funkwhale launch, it is required to run this:: + + docker-compose -f dev.yml run --rm front yarn run i18n-compile + Then you can run everything with:: docker-compose -f dev.yml up front api nginx celeryworker @@ -276,7 +280,8 @@ When working on federation with traefik, ensure you have this in your ``env``:: EXTERNAL_REQUESTS_VERIFY_SSL=false # this ensure you don't have incorrect urls pointing to http resources FUNKWHALE_PROTOCOL=https - + # Disable host ports binding for the nginx container, as traefik is serving everything + NGINX_PORTS_MAPPING=80 Typical workflow for a contribution ----------------------------------- @@ -513,13 +518,15 @@ It's possible to nest multiple component parts to reach a higher level of detail - ``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/README.rst b/README.rst index 093a4773cdb39a218845b16d701a370d569855c3..12fe29175b30e430c6e0752bca74fac88c649e66 100644 --- a/README.rst +++ b/README.rst @@ -31,4 +31,9 @@ are outlined in `CONTRIBUTING <CONTRIBUTING.rst>`_. Translate ^^^^^^^^^ -Translators willing to help can refer to `TRANSLATORS <TRANSLATORS>`_ for instructions. +Translators willing to help can refer to `TRANSLATORS <TRANSLATORS.rst>`_ for instructions. + +Code of Conduct +--------------- + +`Our Code of Conduct <https://funkwhale.audio/code-of-conduct/>`_ applies to all the community spaces, including our GitLab instance. Please, take a moment to read it. diff --git a/api/compose/django/entrypoint.sh b/api/compose/django/entrypoint.sh index 3fc06a4165676ccdeafcf0d7b09769c8a27df9f0..5259f49f81c88268ea9ea6c1288c38e8d9343a1e 100755 --- a/api/compose/django/entrypoint.sh +++ b/api/compose/django/entrypoint.sh @@ -22,5 +22,6 @@ fi if [ -d "frontend" ]; then mkdir -p /frontend cp -r frontend/* /frontend/ + export FUNKWHALE_SPA_HTML_ROOT=/frontend/index.html fi exec "$@" diff --git a/api/config/api_urls.py b/api/config/api_urls.py index 3cb7ec36daf2b7b9f564c7c3d33df1dd58e7303a..a40ff3047a33b3b66dc8ef1bf2342c7c082cfc0e 100644 --- a/api/config/api_urls.py +++ b/api/config/api_urls.py @@ -5,6 +5,7 @@ from rest_framework.urlpatterns import format_suffix_patterns from rest_framework_jwt import views as jwt_views from funkwhale_api.activity import views as activity_views +from funkwhale_api.common import views as common_views from funkwhale_api.music import views from funkwhale_api.playlists import views as playlists_views from funkwhale_api.subsonic.views import SubsonicViewSet @@ -24,6 +25,7 @@ router.register(r"playlists", playlists_views.PlaylistViewSet, "playlists") router.register( r"playlist-tracks", playlists_views.PlaylistTrackViewSet, "playlist-tracks" ) +router.register(r"mutations", common_views.MutationViewSet, "mutations") v1_patterns = router.urls subsonic_router = routers.SimpleRouter(trailing_slash=False) @@ -40,6 +42,12 @@ v1_patterns += [ r"^manage/", include(("funkwhale_api.manage.urls", "manage"), namespace="manage"), ), + url( + r"^moderation/", + include( + ("funkwhale_api.moderation.urls", "moderation"), namespace="moderation" + ), + ), url( r"^federation/", include( @@ -67,6 +75,10 @@ v1_patterns += [ r"^users/", include(("funkwhale_api.users.api_urls", "users"), namespace="users"), ), + url( + r"^oauth/", + include(("funkwhale_api.users.oauth.urls", "oauth"), namespace="oauth"), + ), url(r"^token/$", jwt_views.obtain_jwt_token, name="token"), url(r"^token/refresh/$", jwt_views.refresh_jwt_token, name="token_refresh"), ] diff --git a/api/config/asgi.py b/api/config/asgi.py index 886178cc28ab9640bfdfa6efca1289402c2cbcf7..b4a8105de7117efe9820da558bc10f019e8eb960 100644 --- a/api/config/asgi.py +++ b/api/config/asgi.py @@ -1,9 +1,9 @@ import os -import django +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") + +import django # noqa django.setup() from .routing import application # noqa - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 45480c9ea356f861c972a2867340099ffce60e91..5d65366864470578c353d67ca1f96b7ec348c8c9 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -29,7 +29,6 @@ env_file = env("ENV_FILE", default=None) if env_file: # we have an explicitely specified env file # so we try to load and it fail loudly if it does not exist - print("ENV_FILE", env_file) env.read_env(env_file) else: # we try to load from .env and config/.env @@ -79,7 +78,7 @@ FUNKWHALE_SPA_HTML_CACHE_DURATION = env.int( "FUNKWHALE_SPA_HTML_CACHE_DURATION", default=60 * 15 ) FUNKWHALE_EMBED_URL = env( - "FUNKWHALE_EMBED_URL", default=FUNKWHALE_SPA_HTML_ROOT + "embed.html" + "FUNKWHALE_EMBED_URL", default=FUNKWHALE_URL + "/front/embed.html" ) APP_NAME = "Funkwhale" @@ -94,6 +93,9 @@ FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool( ) # XXX: deprecated, see #186 FEDERATION_ACTOR_FETCH_DELAY = env.int("FEDERATION_ACTOR_FETCH_DELAY", default=60 * 12) +FEDERATION_SERVICE_ACTOR_USERNAME = env( + "FEDERATION_SERVICE_ACTOR_USERNAME", default="service" +) ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=[]) + [FUNKWHALE_HOSTNAME] # APP CONFIGURATION @@ -119,6 +121,7 @@ THIRD_PARTY_APPS = ( "allauth.account", # registration "allauth.socialaccount", # registration "corsheaders", + "oauth2_provider", "rest_framework", "rest_framework.authtoken", "taggit", @@ -147,9 +150,10 @@ if RAVEN_ENABLED: # Apps specific for this project go here. LOCAL_APPS = ( - "funkwhale_api.common", + "funkwhale_api.common.apps.CommonConfig", "funkwhale_api.activity.apps.ActivityConfig", "funkwhale_api.users", # custom users app + "funkwhale_api.users.oauth", # Your stuff: custom apps go here "funkwhale_api.instance", "funkwhale_api.music", @@ -181,10 +185,6 @@ MIDDLEWARE = ( "funkwhale_api.users.middleware.RecordActivityMiddleware", ) -# MIGRATIONS CONFIGURATION -# ------------------------------------------------------------------------------ -MIGRATION_MODULES = {"sites": "funkwhale_api.contrib.sites.migrations"} - # DEBUG # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug @@ -220,6 +220,16 @@ DATABASES = { "default": env.db("DATABASE_URL") } DATABASES["default"]["ATOMIC_REQUESTS"] = True +DATABASES["default"]["CONN_MAX_AGE"] = env("DB_CONN_MAX_AGE", default=60 * 60) + +MIGRATION_MODULES = { + # see https://github.com/jazzband/django-oauth-toolkit/issues/634 + # swappable models are badly designed in oauth2_provider + # ignore migrations and provide our own models. + "oauth2_provider": None, + "sites": "funkwhale_api.contrib.sites.migrations", +} + # # DATABASES = { # 'default': { @@ -296,6 +306,25 @@ STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles"))) STATIC_URL = env("STATIC_URL", default="/staticfiles/") DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage" +PROXY_MEDIA = env.bool("PROXY_MEDIA", default=True) +AWS_DEFAULT_ACL = None +AWS_QUERYSTRING_AUTH = env.bool("AWS_QUERYSTRING_AUTH", default=not PROXY_MEDIA) +AWS_S3_MAX_MEMORY_SIZE = env.int( + "AWS_S3_MAX_MEMORY_SIZE", default=1000 * 1000 * 1000 * 20 +) +AWS_QUERYSTRING_EXPIRE = env.int("AWS_QUERYSTRING_EXPIRE", default=3600) +AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default=None) + +if AWS_ACCESS_KEY_ID: + AWS_ACCESS_KEY_ID = AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY") + AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME") + AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", default=None) + AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default=None) + AWS_S3_SIGNATURE_VERSION = "s3v4" + AWS_LOCATION = env("AWS_LOCATION", default="") + DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" + # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS STATICFILES_DIRS = (str(APPS_DIR.path("static")),) @@ -341,6 +370,23 @@ AUTH_USER_MODEL = "users.User" LOGIN_REDIRECT_URL = "users:redirect" LOGIN_URL = "account_login" +# OAuth configuration +from funkwhale_api.users.oauth import scopes # noqa + +OAUTH2_PROVIDER = { + "SCOPES": {s.id: s.label for s in scopes.SCOPES_BY_ID.values()}, + "ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https", "urn"], + # we keep expired tokens for 15 days, for tracability + "REFRESH_TOKEN_EXPIRE_SECONDS": 3600 * 24 * 15, + "AUTHORIZATION_CODE_EXPIRE_SECONDS": 5 * 60, + "ACCESS_TOKEN_EXPIRE_SECONDS": 60 * 60 * 10, + "OAUTH2_SERVER_CLASS": "funkwhale_api.users.oauth.server.OAuth2Server", +} +OAUTH2_PROVIDER_APPLICATION_MODEL = "users.Application" +OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = "users.AccessToken" +OAUTH2_PROVIDER_GRANT_MODEL = "users.Grant" +OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = "users.RefreshToken" + # LDAP AUTHENTICATION CONFIGURATION # ------------------------------------------------------------------------------ AUTH_LDAP_ENABLED = env.bool("LDAP_ENABLED", default=False) @@ -448,16 +494,28 @@ CELERY_TASK_TIME_LIMIT = 300 CELERY_BEAT_SCHEDULE = { "federation.clean_music_cache": { "task": "federation.clean_music_cache", - "schedule": crontab(hour="*/2"), + "schedule": crontab(minute="0", hour="*/2"), "options": {"expires": 60 * 2}, }, "music.clean_transcoding_cache": { "task": "music.clean_transcoding_cache", - "schedule": crontab(hour="*"), + "schedule": crontab(minute="0", hour="*"), "options": {"expires": 60 * 2}, }, + "oauth.clear_expired_tokens": { + "task": "oauth.clear_expired_tokens", + "schedule": crontab(minute="0", hour="0"), + "options": {"expires": 60 * 60 * 24}, + }, + "federation.refresh_nodeinfo_known_nodes": { + "task": "federation.refresh_nodeinfo_known_nodes", + "schedule": crontab(minute="0", hour="*"), + "options": {"expires": 60 * 60}, + }, } +NODEINFO_REFRESH_DELAY = env.int("NODEINFO_REFRESH_DELAY", default=3600 * 24) + JWT_AUTH = { "JWT_ALLOW_REFRESH": True, "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7), @@ -475,7 +533,6 @@ CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_CREDENTIALS = True REST_FRAMEWORK = { - "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_PAGINATION_CLASS": "funkwhale_api.common.pagination.FunkwhalePagination", "PAGE_SIZE": 25, "DEFAULT_PARSER_CLASSES": ( @@ -485,11 +542,15 @@ REST_FRAMEWORK = { "funkwhale_api.federation.parsers.ActivityParser", ), "DEFAULT_AUTHENTICATION_CLASSES": ( + "oauth2_provider.contrib.rest_framework.OAuth2Authentication", "funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS", "funkwhale_api.common.authentication.BearerTokenHeaderAuth", "funkwhale_api.common.authentication.JSONWebTokenAuthentication", - "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.SessionAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": ( + "funkwhale_api.users.oauth.permissions.ScopePermission", ), "DEFAULT_FILTER_BACKENDS": ( "rest_framework.filters.OrderingFilter", diff --git a/api/config/settings/local.py b/api/config/settings/local.py index d6a8ce484caf782cdd8e9ed2ea66efbdb816fc31..632eb320156901f8e24be123796d4b899a27ba8f 100644 --- a/api/config/settings/local.py +++ b/api/config/settings/local.py @@ -62,19 +62,6 @@ CELERY_TASK_ALWAYS_EAGER = False # Your local stuff: Below this line define 3rd party library settings -LOGGING = { - "version": 1, - "handlers": {"console": {"level": "DEBUG", "class": "logging.StreamHandler"}}, - "loggers": { - "django.request": { - "handlers": ["console"], - "propagate": True, - "level": "DEBUG", - }, - "django_auth_ldap": {"handlers": ["console"], "level": "DEBUG"}, - "": {"level": "DEBUG", "handlers": ["console"]}, - }, -} CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS] diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py index cfadc246cf92752e860fe2e755cfa5490f13c789..0b5261daabd8e7acee055db10fd37de63fa4214f 100644 --- a/api/funkwhale_api/__init__.py +++ b/api/funkwhale_api/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "0.18.3" +__version__ = "0.19.0-rc2" __version_info__ = tuple( [ int(num) if num.isdigit() else num diff --git a/api/funkwhale_api/common/admin.py b/api/funkwhale_api/common/admin.py index 4124a69b895fbdc5fe51e44d53b99810ac113a7d..3ec6f1f449cf1382e3c2677e0c2e9f8f1cba5319 100644 --- a/api/funkwhale_api/common/admin.py +++ b/api/funkwhale_api/common/admin.py @@ -1,6 +1,9 @@ from django.contrib.admin import register as initial_register, site, ModelAdmin # noqa from django.db.models.fields.related import RelatedField +from . import models +from . import tasks + def register(model): """ @@ -17,3 +20,28 @@ def register(model): return initial_register(model)(modeladmin) return decorator + + +def apply(modeladmin, request, queryset): + queryset.update(is_approved=True) + for id in queryset.values_list("id", flat=True): + tasks.apply_mutation.delay(mutation_id=id) + + +apply.short_description = "Approve and apply" + + +@register(models.Mutation) +class MutationAdmin(ModelAdmin): + list_display = [ + "uuid", + "type", + "created_by", + "creation_date", + "applied_date", + "is_approved", + "is_applied", + ] + search_fields = ["created_by__preferred_username"] + list_filter = ["type", "is_approved", "is_applied"] + actions = [apply] diff --git a/api/funkwhale_api/common/apps.py b/api/funkwhale_api/common/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..cd671be291395b438ebd15a9caa42f53a81a51c6 --- /dev/null +++ b/api/funkwhale_api/common/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig, apps + +from . import mutations + + +class CommonConfig(AppConfig): + name = "funkwhale_api.common" + + def ready(self): + super().ready() + + app_names = [app.name for app in apps.app_configs.values()] + mutations.registry.autodiscover(app_names) diff --git a/api/funkwhale_api/common/decorators.py b/api/funkwhale_api/common/decorators.py index 71992eff3f9eacfe546df84d77a8faf4924138c2..49a2fb1939cd80d72111bcbec5139734e0ecd314 100644 --- a/api/funkwhale_api/common/decorators.py +++ b/api/funkwhale_api/common/decorators.py @@ -1,5 +1,17 @@ -from rest_framework import response +from django.db import transaction + from rest_framework import decorators +from rest_framework import exceptions +from rest_framework import response +from rest_framework import status + +from . import filters +from . import models +from . import mutations as common_mutations +from . import serializers +from . import signals +from . import tasks +from . import utils def action_route(serializer_class): @@ -12,3 +24,69 @@ def action_route(serializer_class): return response.Response(result, status=200) return action + + +def mutations_route(types): + """ + Given a queryset and a list of mutation types, return a view + that can be included in any viewset, and serve: + + GET /{id}/mutations/ - list of mutations for the given object + POST /{id}/mutations/ - create a mutation for the given object + """ + + @transaction.atomic + def mutations(self, request, *args, **kwargs): + obj = self.get_object() + if request.method == "GET": + queryset = models.Mutation.objects.get_for_target(obj).filter( + type__in=types + ) + queryset = queryset.order_by("-creation_date") + filterset = filters.MutationFilter(request.GET, queryset=queryset) + page = self.paginate_queryset(filterset.qs) + if page is not None: + serializer = serializers.APIMutationSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = serializers.APIMutationSerializer(queryset, many=True) + return response.Response(serializer.data) + if request.method == "POST": + if not request.user.is_authenticated: + raise exceptions.NotAuthenticated() + serializer = serializers.APIMutationSerializer( + data=request.data, context={"registry": common_mutations.registry} + ) + serializer.is_valid(raise_exception=True) + if not common_mutations.registry.has_perm( + actor=request.user.actor, + type=serializer.validated_data["type"], + obj=obj, + perm="approve" + if serializer.validated_data.get("is_approved", False) + else "suggest", + ): + raise exceptions.PermissionDenied() + + final_payload = common_mutations.registry.get_validated_payload( + type=serializer.validated_data["type"], + payload=serializer.validated_data["payload"], + obj=obj, + ) + mutation = serializer.save( + created_by=request.user.actor, + target=obj, + payload=final_payload, + is_approved=serializer.validated_data.get("is_approved", None), + ) + if mutation.is_approved: + utils.on_commit(tasks.apply_mutation.delay, mutation_id=mutation.pk) + + utils.on_commit( + signals.mutation_created.send, sender=None, mutation=mutation + ) + return response.Response(serializer.data, status=status.HTTP_201_CREATED) + + return decorators.action( + methods=["get", "post"], detail=True, required_scope="edits" + )(mutations) diff --git a/api/funkwhale_api/common/factories.py b/api/funkwhale_api/common/factories.py new file mode 100644 index 0000000000000000000000000000000000000000..6919f9c3771ec81c9e4019cfa9e42d4d30e99494 --- /dev/null +++ b/api/funkwhale_api/common/factories.py @@ -0,0 +1,25 @@ +import factory + +from funkwhale_api.factories import registry, NoUpdateOnCreate + +from funkwhale_api.federation import factories as federation_factories + + +@registry.register +class MutationFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): + fid = factory.Faker("federation_url") + uuid = factory.Faker("uuid4") + created_by = factory.SubFactory(federation_factories.ActorFactory) + summary = factory.Faker("paragraph") + type = "update" + + class Meta: + model = "common.Mutation" + + @factory.post_generation + def target(self, create, extracted, **kwargs): + if not create: + # Simple build, do nothing. + return + self.target = extracted + self.save() diff --git a/api/funkwhale_api/common/fields.py b/api/funkwhale_api/common/fields.py index a0f10efe3a22cb2c903336a073b905f06994e69c..b8e217ba4eca9ef0954236124a577a88cf4e72d5 100644 --- a/api/funkwhale_api/common/fields.py +++ b/api/funkwhale_api/common/fields.py @@ -1,4 +1,5 @@ import django_filters +from django import forms from django.db import models from . import search @@ -46,5 +47,8 @@ class SmartSearchFilter(django_filters.CharFilter): def filter(self, qs, value): if not value: return qs - cleaned = self.config.clean(value) + try: + cleaned = self.config.clean(value) + except (forms.ValidationError): + return qs.none() return search.apply(qs, cleaned) diff --git a/api/funkwhale_api/common/filters.py b/api/funkwhale_api/common/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..364a1fba18a276cef05a34976a95749408132dc9 --- /dev/null +++ b/api/funkwhale_api/common/filters.py @@ -0,0 +1,152 @@ +from django import forms +from django.db.models import Q + +from django_filters import widgets +from django_filters import rest_framework as filters + +from . import fields +from . import models +from . import search + + +class NoneObject(object): + def __eq__(self, other): + return other.__class__ == NoneObject + + +NONE = NoneObject() +NULL_BOOLEAN_CHOICES = [ + (True, True), + ("true", True), + ("True", True), + ("1", True), + ("yes", True), + (False, False), + ("false", False), + ("False", False), + ("0", False), + ("no", False), + ("None", NONE), + ("none", NONE), + ("Null", NONE), + ("null", NONE), +] + + +class CoerceChoiceField(forms.ChoiceField): + """ + Same as forms.ChoiceField but will return the second value + in the choices tuple instead of the user provided one + """ + + def clean(self, value): + if value is None: + return value + v = super().clean(value) + try: + return [b for a, b in self.choices if v == a][0] + except IndexError: + raise forms.ValidationError("Invalid value {}".format(value)) + + +class NullBooleanFilter(filters.ChoiceFilter): + field_class = CoerceChoiceField + + def __init__(self, *args, **kwargs): + self.choices = NULL_BOOLEAN_CHOICES + kwargs["choices"] = self.choices + super().__init__(*args, **kwargs) + + def filter(self, qs, value): + if value in ["", None]: + return qs + if value == NONE: + value = None + qs = self.get_method(qs)( + **{"%s__%s" % (self.field_name, self.lookup_expr): value} + ) + return qs.distinct() if self.distinct else qs + + +def clean_null_boolean_filter(v): + v = CoerceChoiceField(choices=NULL_BOOLEAN_CHOICES).clean(v) + if v == NONE: + v = None + + return v + + +def get_null_boolean_filter(name): + return {"handler": lambda v: Q(**{name: clean_null_boolean_filter(v)})} + + +class DummyTypedMultipleChoiceField(forms.TypedMultipleChoiceField): + def valid_value(self, value): + return True + + +class QueryArrayWidget(widgets.QueryArrayWidget): + """ + Until https://github.com/carltongibson/django-filter/issues/1047 is fixed + """ + + def value_from_datadict(self, data, files, name): + data = data.copy() + return super().value_from_datadict(data, files, name) + + +class MultipleQueryFilter(filters.TypedMultipleChoiceFilter): + field_class = DummyTypedMultipleChoiceField + + def __init__(self, *args, **kwargs): + kwargs["widget"] = QueryArrayWidget() + super().__init__(*args, **kwargs) + self.lookup_expr = "in" + + +def filter_target(value): + + config = { + "artist": ["artist", "target_id", int], + "album": ["album", "target_id", int], + "track": ["track", "target_id", int], + } + parts = value.lower().split(" ") + if parts[0].strip() not in config: + raise forms.ValidationError("Improper target") + + conf = config[parts[0].strip()] + + query = Q(target_content_type__model=conf[0]) + if len(parts) > 1: + _, lookup_field, validator = conf + try: + lookup_value = validator(parts[1].strip()) + except TypeError: + raise forms.ValidationError("Imparsable target id") + return query & Q(**{lookup_field: lookup_value}) + + return query + + +class MutationFilter(filters.FilterSet): + is_approved = NullBooleanFilter("is_approved") + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "summary": {"to": "summary"}, + "fid": {"to": "fid"}, + "type": {"to": "type"}, + }, + filter_fields={ + "domain": {"to": "created_by__domain__name__iexact"}, + "is_approved": get_null_boolean_filter("is_approved"), + "target": {"handler": filter_target}, + "is_applied": {"to": "is_applied"}, + }, + ) + ) + + class Meta: + model = models.Mutation + fields = ["is_approved", "is_applied", "type"] diff --git a/api/funkwhale_api/common/migrations/0002_mutation.py b/api/funkwhale_api/common/migrations/0002_mutation.py new file mode 100644 index 0000000000000000000000000000000000000000..f1f756fd3a173e462954e6760e5b73f976532061 --- /dev/null +++ b/api/funkwhale_api/common/migrations/0002_mutation.py @@ -0,0 +1,91 @@ +# Generated by Django 2.1.5 on 2019-01-31 15:44 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("federation", "0017_auto_20190130_0926"), + ("contenttypes", "0002_remove_content_type_name"), + ("common", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Mutation", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("fid", models.URLField(db_index=True, max_length=500, unique=True)), + ( + "uuid", + models.UUIDField(db_index=True, default=uuid.uuid4, unique=True), + ), + ("type", models.CharField(db_index=True, max_length=100)), + ("is_approved", models.NullBooleanField(default=None)), + ("is_applied", models.NullBooleanField(default=None)), + ( + "creation_date", + models.DateTimeField( + db_index=True, default=django.utils.timezone.now + ), + ), + ( + "applied_date", + models.DateTimeField(blank=True, db_index=True, null=True), + ), + ("summary", models.TextField(max_length=2000, blank=True, null=True)), + ("payload", django.contrib.postgres.fields.jsonb.JSONField()), + ( + "previous_state", + django.contrib.postgres.fields.jsonb.JSONField( + null=True, default=None + ), + ), + ("target_id", models.IntegerField(null=True)), + ( + "approved_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="approved_mutations", + to="federation.Actor", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_mutations", + to="federation.Actor", + ), + ), + ( + "target_content_type", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="targeting_mutations", + to="contenttypes.ContentType", + ), + ), + ], + ) + ] diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py index 9a9d858d80abcae67c8840d101c49353ebaa5118..52a02cad9433f4e79972b6cfe858534c830cb513 100644 --- a/api/funkwhale_api/common/models.py +++ b/api/funkwhale_api/common/models.py @@ -1,5 +1,18 @@ +import uuid + +from django.contrib.postgres.fields import JSONField +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder +from django.db import connections, models, transaction from django.db.models import Lookup from django.db.models.fields import Field +from django.db.models.sql.compiler import SQLCompiler +from django.utils import timezone +from django.urls import reverse + +from funkwhale_api.federation import utils as federation_utils @Field.register_lookup @@ -11,3 +24,129 @@ class NotEqual(Lookup): rhs, rhs_params = self.process_rhs(compiler, connection) params = lhs_params + rhs_params return "%s <> %s" % (lhs, rhs), params + + +class NullsLastSQLCompiler(SQLCompiler): + def get_order_by(self): + result = super().get_order_by() + if result and self.connection.vendor == "postgresql": + return [ + ( + expr, + ( + sql + " NULLS LAST" if not sql.endswith(" NULLS LAST") else sql, + params, + is_ref, + ), + ) + for (expr, (sql, params, is_ref)) in result + ] + return result + + +class NullsLastQuery(models.sql.query.Query): + """Use a custom compiler to inject 'NULLS LAST' (for PostgreSQL).""" + + def get_compiler(self, using=None, connection=None): + if using is None and connection is None: + raise ValueError("Need either using or connection") + if using: + connection = connections[using] + return NullsLastSQLCompiler(self, connection, using) + + +class NullsLastQuerySet(models.QuerySet): + def __init__(self, model=None, query=None, using=None, hints=None): + super().__init__(model, query, using, hints) + self.query = query or NullsLastQuery(self.model) + + +class LocalFromFidQuerySet: + def local(self, include=True): + host = settings.FEDERATION_HOSTNAME + query = models.Q(fid__startswith="http://{}/".format(host)) | models.Q( + fid__startswith="https://{}/".format(host) + ) + if include: + return self.filter(query) + else: + return self.filter(~query) + + +class MutationQuerySet(models.QuerySet): + def get_for_target(self, target): + content_type = ContentType.objects.get_for_model(target) + return self.filter(target_content_type=content_type, target_id=target.pk) + + +class Mutation(models.Model): + fid = models.URLField(unique=True, max_length=500, db_index=True) + uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4) + created_by = models.ForeignKey( + "federation.Actor", + related_name="created_mutations", + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + approved_by = models.ForeignKey( + "federation.Actor", + related_name="approved_mutations", + on_delete=models.SET_NULL, + null=True, + blank=True, + ) + + type = models.CharField(max_length=100, db_index=True) + # None = no choice, True = approved, False = refused + is_approved = models.NullBooleanField(default=None) + + # None = not applied, True = applied, False = failed + is_applied = models.NullBooleanField(default=None) + creation_date = models.DateTimeField(default=timezone.now, db_index=True) + applied_date = models.DateTimeField(null=True, blank=True, db_index=True) + summary = models.TextField(max_length=2000, null=True, blank=True) + + payload = JSONField(encoder=DjangoJSONEncoder) + previous_state = JSONField(null=True, default=None, encoder=DjangoJSONEncoder) + + target_id = models.IntegerField(null=True) + target_content_type = models.ForeignKey( + ContentType, + null=True, + on_delete=models.CASCADE, + related_name="targeting_mutations", + ) + target = GenericForeignKey("target_content_type", "target_id") + + objects = MutationQuerySet.as_manager() + + def get_federation_id(self): + if self.fid: + return self.fid + + return federation_utils.full_url( + reverse("federation:edits-detail", kwargs={"uuid": self.uuid}) + ) + + def save(self, **kwargs): + if not self.pk and not self.fid: + self.fid = self.get_federation_id() + + return super().save(**kwargs) + + @transaction.atomic + def apply(self): + from . import mutations + + if self.is_applied: + raise ValueError("Mutation was already applied") + + previous_state = mutations.registry.apply( + type=self.type, obj=self.target, payload=self.payload + ) + self.previous_state = previous_state + self.is_applied = True + self.applied_date = timezone.now() + self.save(update_fields=["is_applied", "applied_date", "previous_state"]) + return previous_state diff --git a/api/funkwhale_api/common/mutations.py b/api/funkwhale_api/common/mutations.py new file mode 100644 index 0000000000000000000000000000000000000000..dfc8ba85e525bcfe442739b97966c794b6fbb58f --- /dev/null +++ b/api/funkwhale_api/common/mutations.py @@ -0,0 +1,164 @@ +import persisting_theory + +from rest_framework import serializers + +from django.db import models, transaction + + +class ConfNotFound(KeyError): + pass + + +class Registry(persisting_theory.Registry): + look_into = "mutations" + + def connect(self, type, klass, perm_checkers=None): + def decorator(serializer_class): + t = self.setdefault(type, {}) + t[klass] = { + "serializer_class": serializer_class, + "perm_checkers": perm_checkers or {}, + } + return serializer_class + + return decorator + + @transaction.atomic + def apply(self, type, obj, payload): + conf = self.get_conf(type, obj) + serializer = conf["serializer_class"](obj, data=payload) + serializer.is_valid(raise_exception=True) + previous_state = serializer.get_previous_state(obj, serializer.validated_data) + serializer.apply(obj, serializer.validated_data) + return previous_state + + def is_valid(self, type, obj, payload): + conf = self.get_conf(type, obj) + serializer = conf["serializer_class"](obj, data=payload) + return serializer.is_valid(raise_exception=True) + + def get_validated_payload(self, type, obj, payload): + conf = self.get_conf(type, obj) + serializer = conf["serializer_class"](obj, data=payload) + serializer.is_valid(raise_exception=True) + return serializer.payload_serialize(serializer.validated_data) + + def has_perm(self, perm, type, obj, actor): + if perm not in ["approve", "suggest"]: + raise ValueError("Invalid permission {}".format(perm)) + conf = self.get_conf(type, obj) + checker = conf["perm_checkers"].get(perm) + if not checker: + return False + return checker(obj=obj, actor=actor) + + def get_conf(self, type, obj): + try: + type_conf = self[type] + except KeyError: + raise ConfNotFound("{} is not a registered mutation".format(type)) + + try: + conf = type_conf[obj.__class__] + except KeyError: + try: + conf = type_conf[None] + except KeyError: + raise ConfNotFound( + "No mutation configuration found for {}".format(obj.__class__) + ) + return conf + + +class MutationSerializer(serializers.Serializer): + def apply(self, obj, validated_data): + raise NotImplementedError() + + def post_apply(self, obj, validated_data): + pass + + def get_previous_state(self, obj, validated_data): + return + + def payload_serialize(self, data): + return data + + +class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer): + serialized_relations = {} + + def __init__(self, *args, **kwargs): + # we force partial mode, because update mutations are partial + kwargs.setdefault("partial", True) + super().__init__(*args, **kwargs) + + @transaction.atomic + def apply(self, obj, validated_data): + r = self.update(obj, validated_data) + self.post_apply(r, validated_data) + return r + + def validate(self, validated_data): + if not validated_data: + raise serializers.ValidationError("You must update at least one field") + + return super().validate(validated_data) + + def db_serialize(self, validated_data): + data = {} + # ensure model fields are serialized properly + for key, value in list(validated_data.items()): + if not isinstance(value, models.Model): + data[key] = value + continue + field = self.serialized_relations[key] + data[key] = getattr(value, field) + return data + + def payload_serialize(self, data): + data = super().payload_serialize(data) + # we use our serialized_relations configuration + # to ensure we store ids instead of model instances in our json + # payload + for field, attr in self.serialized_relations.items(): + try: + obj = data[field] + except KeyError: + continue + if obj is None: + data[field] = None + else: + data[field] = getattr(obj, attr) + return data + + def create(self, validated_data): + validated_data = self.db_serialize(validated_data) + return super().create(validated_data) + + def get_previous_state(self, obj, validated_data): + return get_update_previous_state( + obj, + *list(validated_data.keys()), + serialized_relations=self.serialized_relations + ) + + +def get_update_previous_state(obj, *fields, serialized_relations={}): + if not fields: + raise ValueError("You need to provide at least one field") + + state = {} + for field in fields: + value = getattr(obj, field) + if isinstance(value, models.Model): + # we store the related object id and repr for better UX + id_field = serialized_relations[field] + related_value = getattr(value, id_field) + state[field] = {"value": related_value, "repr": str(value)} + else: + state[field] = {"value": value} + + return state + + +registry = Registry() diff --git a/api/funkwhale_api/common/pagination.py b/api/funkwhale_api/common/pagination.py index e5068bce209da72523077a0d1dee0b7938eba422..ec7c27dc4f9cd25fc80a02fc47a156fdb42d061b 100644 --- a/api/funkwhale_api/common/pagination.py +++ b/api/funkwhale_api/common/pagination.py @@ -1,6 +1,29 @@ -from rest_framework.pagination import PageNumberPagination +from rest_framework.pagination import PageNumberPagination, _positive_int class FunkwhalePagination(PageNumberPagination): page_size_query_param = "page_size" - max_page_size = 50 + default_max_page_size = 50 + default_page_size = None + view = None + + def paginate_queryset(self, queryset, request, view=None): + self.view = view + return super().paginate_queryset(queryset, request, view) + + def get_page_size(self, request): + max_page_size = ( + getattr(self.view, "max_page_size", 0) or self.default_max_page_size + ) + page_size = getattr(self.view, "default_page_size", 0) or max_page_size + if self.page_size_query_param: + try: + return _positive_int( + request.query_params[self.page_size_query_param], + strict=True, + cutoff=max_page_size, + ) + except (KeyError, ValueError): + pass + + return page_size diff --git a/api/funkwhale_api/common/permissions.py b/api/funkwhale_api/common/permissions.py index 4ab405eb47c224231c8526c7c0c025c74089999f..237fc4ae4141813e505c98df76d2b0ea165ea7cb 100644 --- a/api/funkwhale_api/common/permissions.py +++ b/api/funkwhale_api/common/permissions.py @@ -47,6 +47,6 @@ class OwnerPermission(BasePermission): owner_field = getattr(view, "owner_field", "user") owner = operator.attrgetter(owner_field)(obj) - if owner != request.user: + if not owner or not request.user.is_authenticated or owner != request.user: raise Http404 return True diff --git a/api/funkwhale_api/common/search.py b/api/funkwhale_api/common/search.py index 70aecd632f6e77109dc3e8d51e06695acd19d6cf..4e42fd3462a1ddaa35f27a4c90f4bffd6a74f465 100644 --- a/api/funkwhale_api/common/search.py +++ b/api/funkwhale_api/common/search.py @@ -65,6 +65,9 @@ def apply(qs, config_data): q = config_data.get(k) if q: qs = qs.filter(q) + distinct = config_data.get("distinct", False) + if distinct: + qs = qs.distinct() return qs @@ -77,13 +80,28 @@ class SearchConfig: def clean(self, query): tokens = parse_query(query) cleaned_data = {} - cleaned_data["types"] = self.clean_types(filter_tokens(tokens, ["is"])) cleaned_data["search_query"] = self.clean_search_query( - filter_tokens(tokens, [None, "in"]) + filter_tokens(tokens, [None, "in"] + list(self.search_fields.keys())) + ) + unhandled_tokens = [ + t + for t in tokens + if t["key"] not in [None, "is", "in"] + list(self.search_fields.keys()) + ] + cleaned_data["filter_query"], matching_filters = self.clean_filter_query( + unhandled_tokens ) - unhandled_tokens = [t for t in tokens if t["key"] not in [None, "is", "in"]] - cleaned_data["filter_query"] = self.clean_filter_query(unhandled_tokens) + if matching_filters: + cleaned_data["distinct"] = any( + [ + self.filter_fields[k].get("distinct", False) + for k in matching_filters + if k in self.filter_fields + ] + ) + else: + cleaned_data["distinct"] = False return cleaned_data def clean_search_query(self, tokens): @@ -95,24 +113,67 @@ class SearchConfig: } or set(self.search_fields.keys()) fields_subset = set(self.search_fields.keys()) & fields_subset to_fields = [self.search_fields[k]["to"] for k in fields_subset] + + specific_field_query = None + for token in tokens: + if token["key"] not in self.search_fields: + continue + to = self.search_fields[token["key"]]["to"] + try: + field = token["field"] + value = field.clean(token["value"]) + except KeyError: + # no cleaning to apply + value = token["value"] + q = Q(**{"{}__icontains".format(to): value}) + if not specific_field_query: + specific_field_query = q + else: + specific_field_query &= q query_string = " ".join([t["value"] for t in filter_tokens(tokens, [None])]) - return get_query(query_string, sorted(to_fields)) + unhandled_tokens_query = get_query(query_string, sorted(to_fields)) + + if specific_field_query and unhandled_tokens_query: + return unhandled_tokens_query & specific_field_query + elif specific_field_query: + return specific_field_query + elif unhandled_tokens_query: + return unhandled_tokens_query + return None def clean_filter_query(self, tokens): if not self.filter_fields or not tokens: - return + return None, [] matching = [t for t in tokens if t["key"] in self.filter_fields] - queries = [ - Q(**{self.filter_fields[t["key"]]["to"]: t["value"]}) for t in matching - ] + queries = [self.get_filter_query(token) for token in matching] query = None for q in queries: if not query: query = q else: query = query & q - return query + return query, [m["key"] for m in matching] + + def get_filter_query(self, token): + raw_value = token["value"] + try: + field = self.filter_fields[token["key"]]["field"] + value = field.clean(raw_value) + except KeyError: + # no cleaning to apply + value = raw_value + try: + query_field = self.filter_fields[token["key"]]["to"] + return Q(**{query_field: value}) + except KeyError: + pass + + # we don't have a basic filter -> field mapping, this likely means we + # have a dynamic handler in the config + handler = self.filter_fields[token["key"]]["handler"] + value = handler(value) + return value def clean_types(self, tokens): if not self.types: diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py index fafa6152d09edf95052f2346b16e3135756a6114..59b513f37aa057d843df6a4a5405381be71c2c8f 100644 --- a/api/funkwhale_api/common/serializers.py +++ b/api/funkwhale_api/common/serializers.py @@ -10,6 +10,8 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ +from . import models + class RelatedField(serializers.RelatedField): default_error_messages = { @@ -216,3 +218,57 @@ class StripExifImageField(serializers.ImageField): return SimpleUploadedFile( file_obj.name, content, content_type=file_obj.content_type ) + + +from funkwhale_api.federation import serializers as federation_serializers # noqa + +TARGET_ID_TYPE_MAPPING = { + "music.Track": ("id", "track"), + "music.Artist": ("id", "artist"), + "music.Album": ("id", "album"), +} + + +class APIMutationSerializer(serializers.ModelSerializer): + created_by = federation_serializers.APIActorSerializer(read_only=True) + target = serializers.SerializerMethodField() + + class Meta: + model = models.Mutation + fields = [ + "fid", + "uuid", + "type", + "creation_date", + "applied_date", + "is_approved", + "is_applied", + "created_by", + "approved_by", + "summary", + "payload", + "previous_state", + "target", + ] + read_only_fields = [ + "uuid", + "creation_date", + "fid", + "is_applied", + "created_by", + "approved_by", + "previous_state", + ] + + def get_target(self, obj): + target = obj.target + if not target: + return + + id_field, type = TARGET_ID_TYPE_MAPPING[target._meta.label] + return {"type": type, "id": getattr(target, id_field), "repr": str(target)} + + def validate_type(self, value): + if value not in self.context["registry"]: + raise serializers.ValidationError("Invalid mutation type {}".format(value)) + return value diff --git a/api/funkwhale_api/common/signals.py b/api/funkwhale_api/common/signals.py new file mode 100644 index 0000000000000000000000000000000000000000..1d8e953ccf7fed7033579ea68ce5aeb1f047bed9 --- /dev/null +++ b/api/funkwhale_api/common/signals.py @@ -0,0 +1,6 @@ +import django.dispatch + +mutation_created = django.dispatch.Signal(providing_args=["mutation"]) +mutation_updated = django.dispatch.Signal( + providing_args=["mutation", "old_is_approved", "new_is_approved"] +) diff --git a/api/funkwhale_api/common/tasks.py b/api/funkwhale_api/common/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..994b0bdfff13a27a5eec0e99a87c72c11b39287d --- /dev/null +++ b/api/funkwhale_api/common/tasks.py @@ -0,0 +1,59 @@ +from django.db import transaction +from django.dispatch import receiver + + +from funkwhale_api.common import channels +from funkwhale_api.taskapp import celery + +from . import models +from . import serializers +from . import signals + + +@celery.app.task(name="common.apply_mutation") +@transaction.atomic +@celery.require_instance( + models.Mutation.objects.exclude(is_applied=True).select_for_update(), "mutation" +) +def apply_mutation(mutation): + mutation.apply() + + +@receiver(signals.mutation_created) +def broadcast_mutation_created(mutation, **kwargs): + group = "instance_activity" + channels.group_send( + group, + { + "type": "event.send", + "text": "", + "data": { + "type": "mutation.created", + "mutation": serializers.APIMutationSerializer(mutation).data, + "pending_review_count": models.Mutation.objects.filter( + is_approved=None + ).count(), + }, + }, + ) + + +@receiver(signals.mutation_updated) +def broadcast_mutation_update(mutation, old_is_approved, new_is_approved, **kwargs): + group = "instance_activity" + channels.group_send( + group, + { + "type": "event.send", + "text": "", + "data": { + "type": "mutation.updated", + "mutation": serializers.APIMutationSerializer(mutation).data, + "pending_review_count": models.Mutation.objects.filter( + is_approved=None + ).count(), + "old_is_approved": old_is_approved, + "new_is_approved": new_is_approved, + }, + }, + ) diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py index 3a54b718af0bed70dc76dbd84b5e691691f76a13..57bcba932f89008be946ea62e429659c45a12426 100644 --- a/api/funkwhale_api/common/utils.py +++ b/api/funkwhale_api/common/utils.py @@ -149,6 +149,27 @@ def order_for_search(qs, field): return qs.annotate(__size=models.functions.Length(field)).order_by("__size") +def recursive_getattr(obj, key, permissive=False): + """ + Given a dictionary such as {'user': {'name': 'Bob'}} and + a dotted string such as user.name, returns 'Bob'. + + If the value is not present, returns None + """ + v = obj + for k in key.split("."): + try: + v = v.get(k) + except (TypeError, AttributeError): + if not permissive: + raise + return + if v is None: + return + + return v + + def replace_prefix(queryset, field, old, new): """ Given a queryset of objects and a field name, will find objects @@ -172,3 +193,38 @@ def replace_prefix(queryset, field, old, new): models.functions.Substr(field, len(old) + 1, output_field=models.CharField()), ) return qs.update(**{field: update}) + + +def concat_dicts(*dicts): + n = {} + for d in dicts: + n.update(d) + + return n + + +def get_updated_fields(conf, data, obj): + """ + Given a list of fields, a dict and an object, will return the dict keys/values + that differ from the corresponding fields on the object. + """ + final_conf = [] + for c in conf: + if isinstance(c, str): + final_conf.append((c, c)) + else: + final_conf.append(c) + + final_data = {} + + for data_field, obj_field in final_conf: + try: + data_value = data[data_field] + except KeyError: + continue + + obj_value = getattr(obj, obj_field) + if obj_value != data_value: + final_data[obj_field] = data_value + + return final_data diff --git a/api/funkwhale_api/common/views.py b/api/funkwhale_api/common/views.py new file mode 100644 index 0000000000000000000000000000000000000000..db39c56d1afbb5eed7fa0ba7c89693fb32c57ab4 --- /dev/null +++ b/api/funkwhale_api/common/views.py @@ -0,0 +1,123 @@ +from django.db import transaction + +from rest_framework.decorators import action +from rest_framework import exceptions +from rest_framework import mixins +from rest_framework import permissions +from rest_framework import response +from rest_framework import viewsets + +from . import filters +from . import models +from . import mutations +from . import serializers +from . import signals +from . import tasks +from . import utils + + +class SkipFilterForGetObject: + def get_object(self, *args, **kwargs): + setattr(self.request, "_skip_filters", True) + return super().get_object(*args, **kwargs) + + def filter_queryset(self, queryset): + if getattr(self.request, "_skip_filters", False): + return queryset + return super().filter_queryset(queryset) + + +class MutationViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + lookup_field = "uuid" + queryset = ( + models.Mutation.objects.all() + .exclude(target_id=None) + .order_by("-creation_date") + .select_related("created_by", "approved_by") + .prefetch_related("target") + ) + serializer_class = serializers.APIMutationSerializer + permission_classes = [permissions.IsAuthenticated] + ordering_fields = ("creation_date",) + filterset_class = filters.MutationFilter + + def perform_destroy(self, instance): + if instance.is_applied: + raise exceptions.PermissionDenied("You cannot delete an applied mutation") + + actor = self.request.user.actor + is_owner = actor == instance.created_by + + if not any( + [ + is_owner, + mutations.registry.has_perm( + perm="approve", type=instance.type, obj=instance.target, actor=actor + ), + ] + ): + raise exceptions.PermissionDenied() + + return super().perform_destroy(instance) + + @action(detail=True, methods=["post"]) + @transaction.atomic + def approve(self, request, *args, **kwargs): + instance = self.get_object() + if instance.is_applied: + return response.Response( + {"error": "This mutation was already applied"}, status=403 + ) + actor = self.request.user.actor + can_approve = mutations.registry.has_perm( + perm="approve", type=instance.type, obj=instance.target, actor=actor + ) + + if not can_approve: + raise exceptions.PermissionDenied() + previous_is_approved = instance.is_approved + instance.approved_by = actor + instance.is_approved = True + instance.save(update_fields=["approved_by", "is_approved"]) + utils.on_commit(tasks.apply_mutation.delay, mutation_id=instance.id) + utils.on_commit( + signals.mutation_updated.send, + sender=None, + mutation=instance, + old_is_approved=previous_is_approved, + new_is_approved=instance.is_approved, + ) + return response.Response({}, status=200) + + @action(detail=True, methods=["post"]) + @transaction.atomic + def reject(self, request, *args, **kwargs): + instance = self.get_object() + if instance.is_applied: + return response.Response( + {"error": "This mutation was already applied"}, status=403 + ) + actor = self.request.user.actor + can_approve = mutations.registry.has_perm( + perm="approve", type=instance.type, obj=instance.target, actor=actor + ) + + if not can_approve: + raise exceptions.PermissionDenied() + previous_is_approved = instance.is_approved + instance.approved_by = actor + instance.is_approved = False + instance.save(update_fields=["approved_by", "is_approved"]) + utils.on_commit( + signals.mutation_updated.send, + sender=None, + mutation=instance, + old_is_approved=previous_is_approved, + new_is_approved=instance.is_approved, + ) + return response.Response({}, status=200) diff --git a/api/funkwhale_api/factories.py b/api/funkwhale_api/factories.py index 5db75fd58d74756689fb48b7df072c635acc99f3..3517ea007981b4745887791a807f4bad32e30f99 100644 --- a/api/funkwhale_api/factories.py +++ b/api/funkwhale_api/factories.py @@ -1,6 +1,11 @@ +import uuid import factory import persisting_theory +from django.conf import settings + +from faker.providers import internet as internet_provider + class FactoriesRegistry(persisting_theory.Registry): look_into = "factories" @@ -39,3 +44,22 @@ class NoUpdateOnCreate: @classmethod def _after_postgeneration(cls, instance, create, results=None): return + + +class FunkwhaleProvider(internet_provider.Provider): + """ + Our own faker data generator, since built-in ones are sometimes + not random enough + """ + + def federation_url(self, prefix="", local=False): + def path_generator(): + return "{}/{}".format(prefix, uuid.uuid4()) + + domain = settings.FEDERATION_HOSTNAME if local else self.domain_name() + protocol = "https" + path = path_generator() + return "{}://{}/{}".format(protocol, domain, path) + + +factory.Faker.add_provider(FunkwhaleProvider) diff --git a/api/funkwhale_api/favorites/filters.py b/api/funkwhale_api/favorites/filters.py index a355593d91bea643277086fe98d7e81a1653e998..cf8048b8d712d5e8da5dbab80df2093b29fbf794 100644 --- a/api/funkwhale_api/favorites/filters.py +++ b/api/funkwhale_api/favorites/filters.py @@ -1,11 +1,10 @@ -from django_filters import rest_framework as filters - from funkwhale_api.common import fields +from funkwhale_api.moderation import filters as moderation_filters from . import models -class TrackFavoriteFilter(filters.FilterSet): +class TrackFavoriteFilter(moderation_filters.HiddenContentFilterSet): q = fields.SearchFilter( search_fields=["track__title", "track__artist__name", "track__album__title"] ) @@ -13,3 +12,6 @@ class TrackFavoriteFilter(filters.FilterSet): class Meta: model = models.TrackFavorite fields = ["user", "q"] + hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ + "TRACK_FAVORITE" + ] diff --git a/api/funkwhale_api/favorites/views.py b/api/funkwhale_api/favorites/views.py index d54b79cea376c6598012f707cbb50784adb6d33a..dce285d85c65061fe81a86ceaa1886a6efe35ee7 100644 --- a/api/funkwhale_api/favorites/views.py +++ b/api/funkwhale_api/favorites/views.py @@ -1,6 +1,5 @@ from rest_framework import mixins, status, viewsets from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response from django.db.models import Prefetch @@ -9,6 +8,7 @@ from funkwhale_api.activity import record from funkwhale_api.common import fields, permissions from funkwhale_api.music.models import Track from funkwhale_api.music import utils as music_utils +from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters, models, serializers @@ -24,10 +24,11 @@ class TrackFavoriteViewSet( serializer_class = serializers.UserTrackFavoriteSerializer queryset = models.TrackFavorite.objects.all().select_related("user") permission_classes = [ - permissions.ConditionalAuthentication, + oauth_permissions.ScopePermission, permissions.OwnerPermission, - IsAuthenticatedOrReadOnly, ] + required_scope = "favorites" + anonymous_policy = "setting" owner_checks = ["write"] def get_serializer_class(self): diff --git a/api/funkwhale_api/federation/activity.py b/api/funkwhale_api/federation/activity.py index 2436044d7ef390d7f0c41b717e3c344ee6d27330..979b8aa1befb356e1c423163d1d863de96393b30 100644 --- a/api/funkwhale_api/federation/activity.py +++ b/api/funkwhale_api/federation/activity.py @@ -9,9 +9,13 @@ from django.db.models import Q from funkwhale_api.common import channels from funkwhale_api.common import utils as funkwhale_utils +from . import contexts + +recursive_getattr = funkwhale_utils.recursive_getattr + logger = logging.getLogger(__name__) -PUBLIC_ADDRESS = "https://www.w3.org/ns/activitystreams#Public" +PUBLIC_ADDRESS = contexts.AS.Public ACTIVITY_TYPES = [ "Accept", @@ -82,16 +86,19 @@ OBJECT_TYPES = ( BROADCAST_TO_USER_ACTIVITIES = ["Follow", "Accept"] -def should_reject(id, actor_id=None, payload={}): +def should_reject(fid, actor_id=None, payload={}): + if fid is None and actor_id is None: + return False + from funkwhale_api.moderation import models as moderation_models policies = moderation_models.InstancePolicy.objects.active() media_types = ["Audio", "Artist", "Album", "Track", "Library", "Image"] relevant_values = [ - recursive_gettattr(payload, "type", permissive=True), - recursive_gettattr(payload, "object.type", permissive=True), - recursive_gettattr(payload, "target.type", permissive=True), + recursive_getattr(payload, "type", permissive=True), + recursive_getattr(payload, "object.type", permissive=True), + recursive_getattr(payload, "target.type", permissive=True), ] # if one of the payload types match our internal media types, then # we apply policies that reject media @@ -100,9 +107,12 @@ def should_reject(id, actor_id=None, payload={}): else: policy_type = Q(block_all=True) - query = policies.matching_url_query(id) & policy_type - if actor_id: + if fid: + query = policies.matching_url_query(fid) & policy_type + if fid and actor_id: query |= policies.matching_url_query(actor_id) & policy_type + elif actor_id: + query = policies.matching_url_query(actor_id) & policy_type return policies.filter(query).exists() @@ -111,6 +121,7 @@ def receive(activity, on_behalf_of): from . import models from . import serializers from . import tasks + from .routes import inbox # we ensure the activity has the bare minimum structure before storing # it in our database @@ -118,8 +129,12 @@ def receive(activity, on_behalf_of): data=activity, context={"actor": on_behalf_of, "local_recipients": True} ) serializer.is_valid(raise_exception=True) + if not inbox.get_matching_handlers(activity): + # discard unhandlable activity + return + if should_reject( - id=serializer.validated_data["id"], + fid=serializer.validated_data.get("id"), actor_id=serializer.validated_data["actor"].fid, payload=activity, ): @@ -350,30 +365,9 @@ class OutboxRouter(Router): return activities -def recursive_gettattr(obj, key, permissive=False): - """ - Given a dictionary such as {'user': {'name': 'Bob'}} and - a dotted string such as user.name, returns 'Bob'. - - If the value is not present, returns None - """ - v = obj - for k in key.split("."): - try: - v = v.get(k) - except (TypeError, AttributeError): - if not permissive: - raise - return - if v is None: - return - - return v - - def match_route(route, payload): for key, value in route.items(): - payload_value = recursive_gettattr(payload, key) + payload_value = recursive_getattr(payload, key, permissive=True) if payload_value != value: return False @@ -417,6 +411,27 @@ def prepare_deliveries_and_inbox_items(recipient_list, type): remote_inbox_urls.add(actor.shared_inbox_url or actor.inbox_url) urls.append(r["target"].followers_url) + elif isinstance(r, dict) and r["type"] == "instances_with_followers": + # we want to broadcast the activity to other instances service actors + # when we have at least one follower from this instance + follows = ( + models.LibraryFollow.objects.filter(approved=True) + .exclude(actor__domain_id=settings.FEDERATION_HOSTNAME) + .exclude(actor__domain=None) + .union( + models.Follow.objects.filter(approved=True) + .exclude(actor__domain_id=settings.FEDERATION_HOSTNAME) + .exclude(actor__domain=None) + ) + ) + actors = models.Actor.objects.filter( + managed_domains__name__in=follows.values_list( + "actor__domain_id", flat=True + ) + ) + values = actors.values("shared_inbox_url", "inbox_url") + for v in values: + remote_inbox_urls.add(v["shared_inbox_url"] or v["inbox_url"]) deliveries = [models.Delivery(inbox_url=url) for url in remote_inbox_urls] inbox_items = [ models.InboxItem(actor=actor, type=type) for actor in local_recipients diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py index c7a0c7c6b61c0bdbe83253286b5cb156b0370ba0..95997f95257dfcfd59f143447373e767bb52ae6f 100644 --- a/api/funkwhale_api/federation/actors.py +++ b/api/funkwhale_api/federation/actors.py @@ -5,8 +5,9 @@ from django.conf import settings from django.utils import timezone from funkwhale_api.common import preferences, session +from funkwhale_api.users import models as users_models -from . import models, serializers +from . import keys, models, serializers logger = logging.getLogger(__name__) @@ -28,7 +29,7 @@ def get_actor_data(actor_url): def get_actor(fid, skip_cache=False): if not skip_cache: try: - actor = models.Actor.objects.get(fid=fid) + actor = models.Actor.objects.select_related().get(fid=fid) except models.Actor.DoesNotExist: actor = None fetch_delta = datetime.timedelta( @@ -42,3 +43,23 @@ def get_actor(fid, skip_cache=False): serializer.is_valid(raise_exception=True) return serializer.save(last_fetch_date=timezone.now()) + + +def get_service_actor(): + name, domain = ( + settings.FEDERATION_SERVICE_ACTOR_USERNAME, + settings.FEDERATION_HOSTNAME, + ) + try: + return models.Actor.objects.select_related().get( + preferred_username=name, domain__name=domain + ) + except models.Actor.DoesNotExist: + pass + + args = users_models.get_actor_data(name) + private, public = keys.get_key_pair() + args["private_key"] = private.decode("utf-8") + args["public_key"] = public.decode("utf-8") + args["type"] = "Service" + return models.Actor.objects.create(**args) diff --git a/api/funkwhale_api/federation/admin.py b/api/funkwhale_api/federation/admin.py index 8c9bbe31c8062bda8fd9192b14170f5289620b13..263af80cb175e1235ebfd654a3daf142c19ea927 100644 --- a/api/funkwhale_api/federation/admin.py +++ b/api/funkwhale_api/federation/admin.py @@ -30,6 +30,14 @@ class DomainAdmin(admin.ModelAdmin): search_fields = ["name"] +@admin.register(models.Fetch) +class FetchAdmin(admin.ModelAdmin): + list_display = ["url", "actor", "status", "creation_date", "fetch_date", "detail"] + search_fields = ["url", "actor__username"] + list_filter = ["status"] + list_select_related = True + + @admin.register(models.Activity) class ActivityAdmin(admin.ModelAdmin): list_display = ["type", "fid", "url", "actor", "creation_date"] diff --git a/api/funkwhale_api/federation/api_serializers.py b/api/funkwhale_api/federation/api_serializers.py index 9041ed28a40b52a49c63c44b07d845077bc309d7..dbc655a47d11c677f7b98ec1a34cbbeacc6882c0 100644 --- a/api/funkwhale_api/federation/api_serializers.py +++ b/api/funkwhale_api/federation/api_serializers.py @@ -144,3 +144,19 @@ class InboxItemActionSerializer(common_serializers.ActionSerializer): def handle_read(self, objects): return objects.update(is_read=True) + + +class FetchSerializer(serializers.ModelSerializer): + actor = federation_serializers.APIActorSerializer() + + class Meta: + model = models.Fetch + fields = [ + "id", + "url", + "actor", + "status", + "detail", + "creation_date", + "fetch_date", + ] diff --git a/api/funkwhale_api/federation/api_urls.py b/api/funkwhale_api/federation/api_urls.py index e1e451bff957d3d6dc78f4610bbdb0e77f0069c2..bd2258de961f8d0c80f8b2a3b73d5dd6a9a8ca07 100644 --- a/api/funkwhale_api/federation/api_urls.py +++ b/api/funkwhale_api/federation/api_urls.py @@ -3,6 +3,7 @@ from rest_framework import routers from . import api_views router = routers.SimpleRouter() +router.register(r"fetches", api_views.FetchViewSet, "fetches") router.register(r"follows/library", api_views.LibraryFollowViewSet, "library-follows") router.register(r"inbox", api_views.InboxItemViewSet, "inbox") router.register(r"libraries", api_views.LibraryViewSet, "libraries") diff --git a/api/funkwhale_api/federation/api_views.py b/api/funkwhale_api/federation/api_views.py index 549bac917f843fda93ac4b6056845c64ef6cda5b..5f6f50d8fa5e273f7b678787f324422f1f3f9211 100644 --- a/api/funkwhale_api/federation/api_views.py +++ b/api/funkwhale_api/federation/api_views.py @@ -10,6 +10,7 @@ from rest_framework import response from rest_framework import viewsets from funkwhale_api.music import models as music_models +from funkwhale_api.users.oauth import permissions as oauth_permissions from . import activity from . import api_serializers @@ -43,7 +44,8 @@ class LibraryFollowViewSet( .select_related("actor", "target__actor") ) serializer_class = api_serializers.LibraryFollowSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "follows" filterset_class = filters.LibraryFollowFilter ordering_fields = ("creation_date",) @@ -100,7 +102,8 @@ class LibraryViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): .annotate(_uploads_count=Count("uploads")) ) serializer_class = api_serializers.LibrarySerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" def get_queryset(self): qs = super().get_queryset() @@ -132,6 +135,7 @@ class LibraryViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): try: library = utils.retrieve_ap_object( fid, + actor=request.user.actor, queryset=self.queryset, serializer_class=serializers.LibrarySerializer, ) @@ -168,7 +172,8 @@ class InboxItemViewSet( .order_by("-activity__creation_date") ) serializer_class = api_serializers.InboxItemSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "notifications" filterset_class = filters.InboxItemFilter ordering_fields = ("activity__creation_date",) @@ -185,3 +190,10 @@ class InboxItemViewSet( serializer.is_valid(raise_exception=True) result = serializer.save() return response.Response(result, status=200) + + +class FetchViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): + + queryset = models.Fetch.objects.select_related("actor") + serializer_class = api_serializers.FetchSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py index dd7a142dfe483aa9ee51b1d50de72c4c52a3ce4c..75e0332421feb115eac1ee58acc2906b2ae06ae5 100644 --- a/api/funkwhale_api/federation/authentication.py +++ b/api/funkwhale_api/federation/authentication.py @@ -1,11 +1,13 @@ import cryptography import logging +import datetime from django.contrib.auth.models import AnonymousUser -from rest_framework import authentication, exceptions as rest_exceptions +from django.utils import timezone +from rest_framework import authentication, exceptions as rest_exceptions from funkwhale_api.moderation import models as moderation_models -from . import actors, exceptions, keys, signing, utils +from . import actors, exceptions, keys, signing, tasks, utils logger = logging.getLogger(__name__) @@ -57,6 +59,15 @@ class SignatureAuthentication(authentication.BaseAuthentication): actor = actors.get_actor(actor_url, skip_cache=True) signing.verify_django(request, actor.public_key.encode("utf-8")) + # we trigger a nodeinfo update on the actor's domain, if needed + fetch_delay = 24 * 3600 + now = timezone.now() + last_fetch = actor.domain.nodeinfo_fetch_date + if not last_fetch or ( + last_fetch < (now - datetime.timedelta(seconds=fetch_delay)) + ): + tasks.update_domain_nodeinfo(domain_name=actor.domain.name) + actor.domain.refresh_from_db() return actor def authenticate(self, request): diff --git a/api/funkwhale_api/federation/contexts.py b/api/funkwhale_api/federation/contexts.py new file mode 100644 index 0000000000000000000000000000000000000000..0873bcd46b04d715e861a9244a1327187d50167d --- /dev/null +++ b/api/funkwhale_api/federation/contexts.py @@ -0,0 +1,333 @@ +CONTEXTS = [ + { + "shortId": "LDP", + "contextUrl": None, + "documentUrl": "http://www.w3.org/ns/ldp", + "document": { + "@context": { + "ldp": "http://www.w3.org/ns/ldp#", + "id": "@id", + "type": "@type", + "Container": "ldp:Container", + "BasicContainer": "ldp:BasicContainer", + "DirectContainer": "ldp:DirectContainer", + "IndirectContainer": "ldp:IndirectContainer", + "hasMemberRelation": {"@id": "ldp:hasMemberRelation", "@type": "@id"}, + "isMemberOfRelation": {"@id": "ldp:isMemberOfRelation", "@type": "@id"}, + "membershipResource": {"@id": "ldp:membershipResource", "@type": "@id"}, + "insertedContentRelation": { + "@id": "ldp:insertedContentRelation", + "@type": "@id", + }, + "contains": {"@id": "ldp:contains", "@type": "@id"}, + "member": {"@id": "ldp:member", "@type": "@id"}, + "constrainedBy": {"@id": "ldp:constrainedBy", "@type": "@id"}, + "Resource": "ldp:Resource", + "RDFSource": "ldp:RDFSource", + "NonRDFSource": "ldp:NonRDFSource", + "MemberSubject": "ldp:MemberSubject", + "PreferContainment": "ldp:PreferContainment", + "PreferMembership": "ldp:PreferMembership", + "PreferMinimalContainer": "ldp:PreferMinimalContainer", + "PageSortCriterion": "ldp:PageSortCriterion", + "pageSortCriteria": { + "@id": "ldp:pageSortCriteria", + "@type": "@id", + "@container": "@list", + }, + "pageSortPredicate": {"@id": "ldp:pageSortPredicate", "@type": "@id"}, + "pageSortOrder": {"@id": "ldp:pageSortOrder", "@type": "@id"}, + "pageSortCollation": {"@id": "ldp:pageSortCollation", "@type": "@id"}, + "Ascending": "ldp:Ascending", + "Descending": "ldp:Descending", + "Page": "ldp:Page", + "pageSequence": {"@id": "ldp:pageSequence", "@type": "@id"}, + "inbox": {"@id": "ldp:inbox", "@type": "@id"}, + } + }, + }, + { + "shortId": "AS", + "contextUrl": None, + "documentUrl": "https://www.w3.org/ns/activitystreams", + "document": { + "@context": { + "@vocab": "_:", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "as": "https://www.w3.org/ns/activitystreams#", + "ldp": "http://www.w3.org/ns/ldp#", + "id": "@id", + "type": "@type", + "Accept": "as:Accept", + "Activity": "as:Activity", + "IntransitiveActivity": "as:IntransitiveActivity", + "Add": "as:Add", + "Announce": "as:Announce", + "Application": "as:Application", + "Arrive": "as:Arrive", + "Article": "as:Article", + "Audio": "as:Audio", + "Block": "as:Block", + "Collection": "as:Collection", + "CollectionPage": "as:CollectionPage", + "Relationship": "as:Relationship", + "Create": "as:Create", + "Delete": "as:Delete", + "Dislike": "as:Dislike", + "Document": "as:Document", + "Event": "as:Event", + "Follow": "as:Follow", + "Flag": "as:Flag", + "Group": "as:Group", + "Ignore": "as:Ignore", + "Image": "as:Image", + "Invite": "as:Invite", + "Join": "as:Join", + "Leave": "as:Leave", + "Like": "as:Like", + "Link": "as:Link", + "Mention": "as:Mention", + "Note": "as:Note", + "Object": "as:Object", + "Offer": "as:Offer", + "OrderedCollection": "as:OrderedCollection", + "OrderedCollectionPage": "as:OrderedCollectionPage", + "Organization": "as:Organization", + "Page": "as:Page", + "Person": "as:Person", + "Place": "as:Place", + "Profile": "as:Profile", + "Question": "as:Question", + "Reject": "as:Reject", + "Remove": "as:Remove", + "Service": "as:Service", + "TentativeAccept": "as:TentativeAccept", + "TentativeReject": "as:TentativeReject", + "Tombstone": "as:Tombstone", + "Undo": "as:Undo", + "Update": "as:Update", + "Video": "as:Video", + "View": "as:View", + "Listen": "as:Listen", + "Read": "as:Read", + "Move": "as:Move", + "Travel": "as:Travel", + "IsFollowing": "as:IsFollowing", + "IsFollowedBy": "as:IsFollowedBy", + "IsContact": "as:IsContact", + "IsMember": "as:IsMember", + "subject": {"@id": "as:subject", "@type": "@id"}, + "relationship": {"@id": "as:relationship", "@type": "@id"}, + "actor": {"@id": "as:actor", "@type": "@id"}, + "attributedTo": {"@id": "as:attributedTo", "@type": "@id"}, + "attachment": {"@id": "as:attachment", "@type": "@id"}, + "bcc": {"@id": "as:bcc", "@type": "@id"}, + "bto": {"@id": "as:bto", "@type": "@id"}, + "cc": {"@id": "as:cc", "@type": "@id"}, + "context": {"@id": "as:context", "@type": "@id"}, + "current": {"@id": "as:current", "@type": "@id"}, + "first": {"@id": "as:first", "@type": "@id"}, + "generator": {"@id": "as:generator", "@type": "@id"}, + "icon": {"@id": "as:icon", "@type": "@id"}, + "image": {"@id": "as:image", "@type": "@id"}, + "inReplyTo": {"@id": "as:inReplyTo", "@type": "@id"}, + "items": {"@id": "as:items", "@type": "@id"}, + "instrument": {"@id": "as:instrument", "@type": "@id"}, + "orderedItems": { + "@id": "as:items", + "@type": "@id", + "@container": "@list", + }, + "last": {"@id": "as:last", "@type": "@id"}, + "location": {"@id": "as:location", "@type": "@id"}, + "next": {"@id": "as:next", "@type": "@id"}, + "object": {"@id": "as:object", "@type": "@id"}, + "oneOf": {"@id": "as:oneOf", "@type": "@id"}, + "anyOf": {"@id": "as:anyOf", "@type": "@id"}, + "closed": {"@id": "as:closed", "@type": "xsd:dateTime"}, + "origin": {"@id": "as:origin", "@type": "@id"}, + "accuracy": {"@id": "as:accuracy", "@type": "xsd:float"}, + "prev": {"@id": "as:prev", "@type": "@id"}, + "preview": {"@id": "as:preview", "@type": "@id"}, + "replies": {"@id": "as:replies", "@type": "@id"}, + "result": {"@id": "as:result", "@type": "@id"}, + "audience": {"@id": "as:audience", "@type": "@id"}, + "partOf": {"@id": "as:partOf", "@type": "@id"}, + "tag": {"@id": "as:tag", "@type": "@id"}, + "target": {"@id": "as:target", "@type": "@id"}, + "to": {"@id": "as:to", "@type": "@id"}, + "url": {"@id": "as:url", "@type": "@id"}, + "altitude": {"@id": "as:altitude", "@type": "xsd:float"}, + "content": "as:content", + "contentMap": {"@id": "as:content", "@container": "@language"}, + "name": "as:name", + "nameMap": {"@id": "as:name", "@container": "@language"}, + "duration": {"@id": "as:duration", "@type": "xsd:duration"}, + "endTime": {"@id": "as:endTime", "@type": "xsd:dateTime"}, + "height": {"@id": "as:height", "@type": "xsd:nonNegativeInteger"}, + "href": {"@id": "as:href", "@type": "@id"}, + "hreflang": "as:hreflang", + "latitude": {"@id": "as:latitude", "@type": "xsd:float"}, + "longitude": {"@id": "as:longitude", "@type": "xsd:float"}, + "mediaType": "as:mediaType", + "published": {"@id": "as:published", "@type": "xsd:dateTime"}, + "radius": {"@id": "as:radius", "@type": "xsd:float"}, + "rel": "as:rel", + "startIndex": { + "@id": "as:startIndex", + "@type": "xsd:nonNegativeInteger", + }, + "startTime": {"@id": "as:startTime", "@type": "xsd:dateTime"}, + "summary": "as:summary", + "summaryMap": {"@id": "as:summary", "@container": "@language"}, + "totalItems": { + "@id": "as:totalItems", + "@type": "xsd:nonNegativeInteger", + }, + "units": "as:units", + "updated": {"@id": "as:updated", "@type": "xsd:dateTime"}, + "width": {"@id": "as:width", "@type": "xsd:nonNegativeInteger"}, + "describes": {"@id": "as:describes", "@type": "@id"}, + "formerType": {"@id": "as:formerType", "@type": "@id"}, + "deleted": {"@id": "as:deleted", "@type": "xsd:dateTime"}, + "inbox": {"@id": "ldp:inbox", "@type": "@id"}, + "outbox": {"@id": "as:outbox", "@type": "@id"}, + "following": {"@id": "as:following", "@type": "@id"}, + "followers": {"@id": "as:followers", "@type": "@id"}, + "streams": {"@id": "as:streams", "@type": "@id"}, + "preferredUsername": "as:preferredUsername", + "endpoints": {"@id": "as:endpoints", "@type": "@id"}, + "uploadMedia": {"@id": "as:uploadMedia", "@type": "@id"}, + "proxyUrl": {"@id": "as:proxyUrl", "@type": "@id"}, + "liked": {"@id": "as:liked", "@type": "@id"}, + "oauthAuthorizationEndpoint": { + "@id": "as:oauthAuthorizationEndpoint", + "@type": "@id", + }, + "oauthTokenEndpoint": {"@id": "as:oauthTokenEndpoint", "@type": "@id"}, + "provideClientKey": {"@id": "as:provideClientKey", "@type": "@id"}, + "signClientKey": {"@id": "as:signClientKey", "@type": "@id"}, + "sharedInbox": {"@id": "as:sharedInbox", "@type": "@id"}, + "Public": {"@id": "as:Public", "@type": "@id"}, + "source": "as:source", + "likes": {"@id": "as:likes", "@type": "@id"}, + "shares": {"@id": "as:shares", "@type": "@id"}, + # Added manually + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + } + }, + }, + { + "shortId": "SEC", + "contextUrl": None, + "documentUrl": "https://w3id.org/security/v1", + "document": { + "@context": { + "id": "@id", + "type": "@type", + "dc": "http://purl.org/dc/terms/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "EncryptedMessage": "sec:EncryptedMessage", + "GraphSignature2012": "sec:GraphSignature2012", + "LinkedDataSignature2015": "sec:LinkedDataSignature2015", + "LinkedDataSignature2016": "sec:LinkedDataSignature2016", + "CryptographicKey": "sec:Key", + "authenticationTag": "sec:authenticationTag", + "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", + "cipherAlgorithm": "sec:cipherAlgorithm", + "cipherData": "sec:cipherData", + "cipherKey": "sec:cipherKey", + "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, + "creator": {"@id": "dc:creator", "@type": "@id"}, + "digestAlgorithm": "sec:digestAlgorithm", + "digestValue": "sec:digestValue", + "domain": "sec:domain", + "encryptionKey": "sec:encryptionKey", + "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "initializationVector": "sec:initializationVector", + "iterationCount": "sec:iterationCount", + "nonce": "sec:nonce", + "normalizationAlgorithm": "sec:normalizationAlgorithm", + "owner": {"@id": "sec:owner", "@type": "@id"}, + "password": "sec:password", + "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, + "privateKeyPem": "sec:privateKeyPem", + "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, + "publicKeyBase58": "sec:publicKeyBase58", + "publicKeyPem": "sec:publicKeyPem", + "publicKeyWif": "sec:publicKeyWif", + "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, + "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, + "salt": "sec:salt", + "signature": "sec:signature", + "signatureAlgorithm": "sec:signingAlgorithm", + "signatureValue": "sec:signatureValue", + } + }, + }, + { + "shortId": "FW", + "contextUrl": None, + "documentUrl": "https://funkwhale.audio/ns", + "document": { + "@context": { + "id": "@id", + "type": "@type", + "as": "https://www.w3.org/ns/activitystreams#", + "fw": "https://funkwhale.audio/ns#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "Album": "fw:Album", + "Track": "fw:Track", + "Artist": "fw:Artist", + "Library": "fw:Library", + "bitrate": {"@id": "fw:bitrate", "@type": "xsd:nonNegativeInteger"}, + "size": {"@id": "fw:size", "@type": "xsd:nonNegativeInteger"}, + "position": {"@id": "fw:position", "@type": "xsd:nonNegativeInteger"}, + "disc": {"@id": "fw:disc", "@type": "xsd:nonNegativeInteger"}, + "library": {"@id": "fw:library", "@type": "@id"}, + "track": {"@id": "fw:track", "@type": "@id"}, + "cover": {"@id": "fw:cover", "@type": "as:Link"}, + "album": {"@id": "fw:album", "@type": "@id"}, + "artists": {"@id": "fw:artists", "@type": "@id", "@container": "@list"}, + "released": {"@id": "fw:released", "@type": "xsd:date"}, + "musicbrainzId": "fw:musicbrainzId", + "license": {"@id": "fw:license", "@type": "@id"}, + "copyright": "fw:copyright", + } + }, + }, +] + +CONTEXTS_BY_ID = {c["shortId"]: c for c in CONTEXTS} + + +class NS: + def __init__(self, conf): + self.conf = conf + self.baseUrl = self.conf["document"]["@context"][self.conf["shortId"].lower()] + + def __repr__(self): + return "<{}: {}>".format(self.conf["shortId"], self.baseUrl) + + def __getattr__(self, key): + if key not in self.conf["document"]["@context"]: + raise AttributeError( + "{} is not a valid property of context {}".format(key, self.baseUrl) + ) + return self.baseUrl + key + + +class NoopContext: + def __getattr__(self, key): + return "_:{}".format(key) + + +NOOP = NoopContext() +AS = NS(CONTEXTS_BY_ID["AS"]) +LDP = NS(CONTEXTS_BY_ID["LDP"]) +SEC = NS(CONTEXTS_BY_ID["SEC"]) +FW = NS(CONTEXTS_BY_ID["FW"]) diff --git a/api/funkwhale_api/federation/decorators.py b/api/funkwhale_api/federation/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..3d2d62567613f7899eea0c2a9356812150bcd160 --- /dev/null +++ b/api/funkwhale_api/federation/decorators.py @@ -0,0 +1,49 @@ +from django.db import transaction + +from rest_framework import decorators +from rest_framework import permissions +from rest_framework import response +from rest_framework import status + +from funkwhale_api.common import utils as common_utils + +from . import api_serializers +from . import filters +from . import models +from . import tasks +from . import utils + + +def fetches_route(): + @transaction.atomic + def fetches(self, request, *args, **kwargs): + obj = self.get_object() + if request.method == "GET": + queryset = models.Fetch.objects.get_for_object(obj).select_related("actor") + queryset = queryset.order_by("-creation_date") + filterset = filters.FetchFilter(request.GET, queryset=queryset) + page = self.paginate_queryset(filterset.qs) + if page is not None: + serializer = api_serializers.FetchSerializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = api_serializers.FetchSerializer(queryset, many=True) + return response.Response(serializer.data) + if request.method == "POST": + if utils.is_local(obj.fid): + return response.Response( + {"detail": "Cannot fetch a local object"}, status=400 + ) + + fetch = models.Fetch.objects.create( + url=obj.fid, actor=request.user.actor, object=obj + ) + common_utils.on_commit(tasks.fetch.delay, fetch_id=fetch.pk) + serializer = api_serializers.FetchSerializer(fetch) + return response.Response(serializer.data, status=status.HTTP_201_CREATED) + + return decorators.action( + methods=["get", "post"], + detail=True, + permission_classes=[permissions.IsAuthenticated], + )(fetches) diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py index f54f6867861230e3b2bc7ffd4fcf1adbcd61fe3a..14bb4e8c96ea150a09fbf95bdbbbcb021f270a8c 100644 --- a/api/funkwhale_api/federation/factories.py +++ b/api/funkwhale_api/federation/factories.py @@ -69,11 +69,21 @@ def create_user(actor): @registry.register class DomainFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): name = factory.Faker("domain_name") + nodeinfo_fetch_date = factory.LazyFunction(lambda: timezone.now()) class Meta: model = "federation.Domain" django_get_or_create = ("name",) + @factory.post_generation + def with_service_actor(self, create, extracted, **kwargs): + if not create or not extracted: + return + + self.service_actor = ActorFactory(domain=self) + self.save(update_fields=["service_actor"]) + return self.service_actor + @registry.register class ActorFactory(NoUpdateOnCreate, factory.DjangoModelFactory): @@ -156,7 +166,7 @@ class MusicLibraryFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): @registry.register -class LibraryScan(NoUpdateOnCreate, factory.django.DjangoModelFactory): +class LibraryScanFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): library = factory.SubFactory(MusicLibraryFactory) actor = factory.SubFactory(ActorFactory) total_files = factory.LazyAttribute(lambda o: o.library.uploads_count) @@ -165,6 +175,14 @@ class LibraryScan(NoUpdateOnCreate, factory.django.DjangoModelFactory): model = "music.LibraryScan" +@registry.register +class FetchFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): + actor = factory.SubFactory(ActorFactory) + + class Meta: + model = "federation.Fetch" + + @registry.register class ActivityFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): actor = factory.SubFactory(ActorFactory) diff --git a/api/funkwhale_api/federation/fields.py b/api/funkwhale_api/federation/fields.py index 3523396dbceb86a2aa8c84c1767cad9b6f767db1..8a8a1eb2de2059a2b6edeeec629c81147c84381b 100644 --- a/api/funkwhale_api/federation/fields.py +++ b/api/funkwhale_api/federation/fields.py @@ -1,6 +1,9 @@ +import django_filters + from rest_framework import serializers from . import models +from . import utils class ActorRelatedField(serializers.EmailField): @@ -16,3 +19,15 @@ class ActorRelatedField(serializers.EmailField): ) except models.Actor.DoesNotExist: raise serializers.ValidationError("Invalid actor name") + + +class DomainFromURLFilter(django_filters.CharFilter): + def __init__(self, *args, **kwargs): + self.url_field = kwargs.pop("url_field", "fid") + super().__init__(*args, **kwargs) + + def filter(self, qs, value): + if not value: + return qs + query = utils.get_domain_query_from_url(value, self.url_field) + return qs.filter(query) diff --git a/api/funkwhale_api/federation/filters.py b/api/funkwhale_api/federation/filters.py index 3a8b76ceee9e706b806fddef3f82076f89077979..bfc48bcfbfcc6f9c4d3b33794523a5e4b833abc4 100644 --- a/api/funkwhale_api/federation/filters.py +++ b/api/funkwhale_api/federation/filters.py @@ -46,3 +46,14 @@ class InboxItemFilter(django_filters.FilterSet): def filter_before(self, queryset, field_name, value): return queryset.filter(pk__lte=value) + + +class FetchFilter(django_filters.FilterSet): + ordering = django_filters.OrderingFilter( + # tuple-mapping retains order + fields=(("creation_date", "creation_date"), ("fetch_date", "fetch_date")) + ) + + class Meta: + model = models.Fetch + fields = ["status", "object_id", "url"] diff --git a/api/funkwhale_api/federation/jsonld.py b/api/funkwhale_api/federation/jsonld.py new file mode 100644 index 0000000000000000000000000000000000000000..ad67323f239bb7be2fb42be90a75091915971eba --- /dev/null +++ b/api/funkwhale_api/federation/jsonld.py @@ -0,0 +1,287 @@ +import aiohttp +import asyncio +import functools + +import pyld.jsonld +from django.conf import settings +import pyld.documentloader.requests +from rest_framework import serializers +from rest_framework.fields import empty +from . import contexts + + +def cached_contexts(loader): + functools.wraps(loader) + + def load(url, *args, **kwargs): + for cached in contexts.CONTEXTS: + if url == cached["documentUrl"]: + return cached + return loader(url, *args, **kwargs) + + return load + + +def get_document_loader(): + loader = pyld.documentloader.requests.requests_document_loader( + verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL + ) + return cached_contexts(loader) + + +def expand(doc, options=None, insert_fw_context=True): + options = options or {} + options.setdefault("documentLoader", get_document_loader()) + if isinstance(doc, str): + doc = options["documentLoader"](doc)["document"] + if insert_fw_context: + fw = contexts.CONTEXTS_BY_ID["FW"]["documentUrl"] + try: + insert_context(fw, doc) + except KeyError: + # probably an already expanded document + pass + result = pyld.jsonld.expand(doc, options=options) + try: + # jsonld.expand returns a list, which is useless for us + return result[0] + except IndexError: + raise ValueError("Impossible to expand this jsonld document") + + +def insert_context(ctx, doc): + """ + In some situations, we may want to add a default context to an existing document. + This function enable that (this will mutate the original document) + """ + existing = doc["@context"] + if isinstance(existing, list): + if ctx not in existing: + existing = existing[:] + existing.append(ctx) + doc["@context"] = existing + else: + doc["@context"] = [existing, ctx] + return doc + + +def get_session(): + return aiohttp.ClientSession(raise_for_status=True) + + +async def fetch_json(url, session, cache=None, lock=None): + async with session.get(url) as response: + response.raise_for_status() + return url, await response.json() + + +async def fetch_many(*ids, references=None): + """ + Given a list of object ids, will fetch the remote + representations for those objects, expand them + and return a dictionnary with id as the key and expanded document as the values + """ + ids = set(ids) + results = references if references is not None else {} + + if not ids: + return results + + async with get_session() as session: + tasks = [fetch_json(url, session) for url in ids if url not in results] + tasks_results = await asyncio.gather(*tasks) + + for url, payload in tasks_results: + results[url] = payload + + return results + + +DEFAULT_PREPARE_CONFIG = { + "type": {"property": "@type", "keep": "first"}, + "id": {"property": "@id"}, +} + + +def dereference(value, references): + """ + Given a payload and a dictonary containing ids and objects, will replace + all the matching objects in the payload by the one in the references dictionary. + """ + + def replace(obj, id): + try: + matching = references[id] + except KeyError: + return + # we clear the current dict, and replace its content by the matching obj + obj.clear() + obj.update(matching) + + if isinstance(value, dict): + if "@id" in value: + replace(value, value["@id"]) + else: + for attr in value.values(): + dereference(attr, references) + + elif isinstance(value, list): + # we loop on nested objects and trigger dereferencing + for obj in value: + dereference(obj, references) + + return value + + +def get_value(value, keep=None, attr=None): + + if keep == "first": + value = value[0] + if attr: + value = value[attr] + + elif attr: + value = [obj[attr] for obj in value if attr in obj] + + return value + + +def prepare_for_serializer(payload, config, fallbacks={}): + """ + Json-ld payloads, as returned by expand are quite complex to handle, because + every attr is basically a list of dictionnaries. To make code simpler, + we use this function to clean the payload a little bit, base on the config object. + + Config is a dictionnary, with keys being serializer field names, and values + being dictionaries describing how to handle this field. + """ + final_payload = {} + final_config = {} + final_config.update(DEFAULT_PREPARE_CONFIG) + final_config.update(config) + for field, field_config in final_config.items(): + try: + value = get_value( + payload[field_config["property"]], + keep=field_config.get("keep"), + attr=field_config.get("attr"), + ) + except (IndexError, KeyError): + aliases = field_config.get("aliases", []) + noop = object() + value = noop + if not aliases: + continue + + for a in aliases: + try: + value = get_value( + payload[a], + keep=field_config.get("keep"), + attr=field_config.get("attr"), + ) + except (IndexError, KeyError): + continue + + break + + if value is noop: + continue + + final_payload[field] = value + + for key, choices in fallbacks.items(): + if key in final_payload: + # initial attr was found, no need to rely on fallbacks + continue + + for choice in choices: + if choice not in final_payload: + continue + + final_payload[key] = final_payload[choice] + + return final_payload + + +def get_ids(v): + if isinstance(v, dict) and "@id" in v: + yield v["@id"] + + if isinstance(v, list): + for obj in v: + yield from get_ids(obj) + + +def get_default_context(): + return ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {}] + + +def get_default_context_fw(): + return [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {}, + "https://funkwhale.audio/ns", + ] + + +class JsonLdSerializer(serializers.Serializer): + def run_validation(self, data=empty): + if data and data is not empty and self.context.get("expand", True): + try: + data = expand(data) + except ValueError: + raise serializers.ValidationError( + "{} is not a valid jsonld document".format(data) + ) + try: + config = self.Meta.jsonld_mapping + except AttributeError: + config = {} + try: + fallbacks = self.Meta.jsonld_fallbacks + except AttributeError: + fallbacks = {} + data = prepare_for_serializer(data, config, fallbacks=fallbacks) + dereferenced_fields = [ + k + for k, c in config.items() + if k in data and c.get("dereference", False) + ] + dereferenced_ids = set() + for field in dereferenced_fields: + for i in get_ids(data[field]): + dereferenced_ids.add(i) + + if dereferenced_ids: + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + references = self.context.setdefault("references", {}) + loop.run_until_complete( + fetch_many(*dereferenced_ids, references=references) + ) + data = dereference(data, references) + return super().run_validation(data) + + +def first_attr(property, attr, aliases=[]): + return {"property": property, "keep": "first", "attr": attr, "aliases": aliases} + + +def first_val(property, aliases=[]): + return first_attr(property, "@value", aliases=aliases) + + +def first_id(property, aliases=[]): + return first_attr(property, "@id", aliases=aliases) + + +def first_obj(property, aliases=[]): + return {"property": property, "keep": "first", "aliases": aliases} + + +def raw(property, aliases=[]): + return {"property": property, "aliases": aliases} diff --git a/api/funkwhale_api/federation/migrations/0017_auto_20190130_0926.py b/api/funkwhale_api/federation/migrations/0017_auto_20190130_0926.py new file mode 100644 index 0000000000000000000000000000000000000000..7c025fa48bf637cced238317b09a26b1d3b46ea7 --- /dev/null +++ b/api/funkwhale_api/federation/migrations/0017_auto_20190130_0926.py @@ -0,0 +1,36 @@ +# Generated by Django 2.1.5 on 2019-01-30 09:26 + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import funkwhale_api.common.validators +import funkwhale_api.federation.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('federation', '0016_auto_20181227_1605'), + ] + + operations = [ + migrations.RemoveField( + model_name='actor', + name='old_domain', + ), + migrations.AddField( + model_name='domain', + name='service_actor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='managed_domains', to='federation.Actor'), + ), + migrations.AlterField( + model_name='domain', + name='name', + field=models.CharField(max_length=255, primary_key=True, serialize=False, validators=[funkwhale_api.common.validators.DomainValidator()]), + ), + migrations.AlterField( + model_name='domain', + name='nodeinfo', + field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=funkwhale_api.federation.models.empty_dict, max_length=50000), + ), + ] diff --git a/api/funkwhale_api/federation/migrations/0018_fetch.py b/api/funkwhale_api/federation/migrations/0018_fetch.py new file mode 100644 index 0000000000000000000000000000000000000000..11789024fd022cfe25a2cded4984eda34cde6c9d --- /dev/null +++ b/api/funkwhale_api/federation/migrations/0018_fetch.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1.7 on 2019-04-17 14:57 + +import django.contrib.postgres.fields.jsonb +import django.core.serializers.json +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import funkwhale_api.federation.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('federation', '0017_auto_20190130_0926'), + ] + + operations = [ + migrations.CreateModel( + name='Fetch', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.URLField(db_index=True, max_length=500)), + ('creation_date', models.DateTimeField(default=django.utils.timezone.now)), + ('fetch_date', models.DateTimeField(blank=True, null=True)), + ('object_id', models.IntegerField(null=True)), + ('status', models.CharField(choices=[('pending', 'Pending'), ('errored', 'Errored'), ('finished', 'Finished'), ('skipped', 'Skipped')], default='pending', max_length=20)), + ('detail', django.contrib.postgres.fields.jsonb.JSONField(default=funkwhale_api.federation.models.empty_dict, encoder=django.core.serializers.json.DjangoJSONEncoder, max_length=50000)), + ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fetches', to='federation.Actor')), + ('object_content_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ], + ), + ] diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 843d538e0dd438e1778b6970b010ea50018d06fb..d530c62df1007d4012609eec6fd530ed347cacb2 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -1,4 +1,5 @@ import tempfile +import urllib.parse import uuid from django.conf import settings @@ -43,10 +44,24 @@ class FederationMixin(models.Model): class Meta: abstract = True + @property + def is_local(self): + return federation_utils.is_local(self.fid) + + @property + def domain_name(self): + if not self.fid: + return + + parsed = urllib.parse.urlparse(self.fid) + return parsed.hostname + class ActorQuerySet(models.QuerySet): def local(self, include=True): - return self.exclude(user__isnull=include) + if include: + return self.filter(domain__name=settings.FEDERATION_HOSTNAME) + return self.exclude(domain__name=settings.FEDERATION_HOSTNAME) def with_current_usage(self): qs = self @@ -96,7 +111,13 @@ class Domain(models.Model): creation_date = models.DateTimeField(default=timezone.now) nodeinfo_fetch_date = models.DateTimeField(default=None, null=True, blank=True) nodeinfo = JSONField(default=empty_dict, max_length=50000, blank=True) - + service_actor = models.ForeignKey( + "Actor", + related_name="managed_domains", + on_delete=models.SET_NULL, + null=True, + blank=True, + ) objects = DomainQuerySet.as_manager() def __str__(self): @@ -143,6 +164,10 @@ class Domain(models.Model): ) return data + @property + def is_local(self): + return self.name == settings.FEDERATION_HOSTNAME + class Actor(models.Model): ap_type = "Actor" @@ -256,6 +281,76 @@ class Actor(models.Model): self.private_key = v[0].decode("utf-8") self.public_key = v[1].decode("utf-8") + def can_manage(self, obj): + attributed_to = getattr(obj, "attributed_to_id", None) + if attributed_to is not None and attributed_to == self.pk: + # easiest case, the obj is attributed to the actor + return True + + if self.domain.service_actor_id != self.pk: + # actor is not system actor, so there is no way the actor can manage + # the object + return False + + # actor is service actor of its domain, so if the fid domain + # matches, we consider the actor has the permission to manage + # the object + domain = self.domain_id + return obj.fid.startswith("http://{}/".format(domain)) or obj.fid.startswith( + "https://{}/".format(domain) + ) + + +FETCH_STATUSES = [ + ("pending", "Pending"), + ("errored", "Errored"), + ("finished", "Finished"), + ("skipped", "Skipped"), +] + + +class FetchQuerySet(models.QuerySet): + def get_for_object(self, object): + content_type = ContentType.objects.get_for_model(object) + return self.filter(object_content_type=content_type, object_id=object.pk) + + +class Fetch(models.Model): + url = models.URLField(max_length=500, db_index=True) + creation_date = models.DateTimeField(default=timezone.now) + fetch_date = models.DateTimeField(null=True, blank=True) + object_id = models.IntegerField(null=True) + object_content_type = models.ForeignKey( + ContentType, null=True, on_delete=models.CASCADE + ) + object = GenericForeignKey("object_content_type", "object_id") + status = models.CharField(default="pending", choices=FETCH_STATUSES, max_length=20) + detail = JSONField( + default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder, blank=True + ) + actor = models.ForeignKey(Actor, related_name="fetches", on_delete=models.CASCADE) + + objects = FetchQuerySet.as_manager() + + def save(self, **kwargs): + if not self.url and self.object: + self.url = self.object.fid + + super().save(**kwargs) + + @property + def serializers(self): + from . import contexts + from . import serializers + + return { + contexts.FW.Artist: serializers.ArtistSerializer, + contexts.FW.Album: serializers.AlbumSerializer, + contexts.FW.Track: serializers.TrackSerializer, + contexts.AS.Audio: serializers.UploadSerializer, + contexts.FW.Library: serializers.LibrarySerializer, + } + class InboxItem(models.Model): """ @@ -297,7 +392,9 @@ class Activity(models.Model): uuid = models.UUIDField(default=uuid.uuid4, unique=True) fid = models.URLField(unique=True, max_length=500, null=True, blank=True) url = models.URLField(max_length=500, null=True, blank=True) - payload = JSONField(default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder) + payload = JSONField( + default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder, blank=True + ) creation_date = models.DateTimeField(default=timezone.now, db_index=True) type = models.CharField(db_index=True, null=True, max_length=100) @@ -413,7 +510,7 @@ class LibraryTrack(models.Model): album_title = models.CharField(max_length=500) title = models.CharField(max_length=500) metadata = JSONField( - default=empty_dict, max_length=10000, encoder=DjangoJSONEncoder + default=empty_dict, max_length=10000, encoder=DjangoJSONEncoder, blank=True ) @property diff --git a/api/funkwhale_api/federation/renderers.py b/api/funkwhale_api/federation/renderers.py index d72c4c06a298b5bb143d620ad4a0c2a72759c3d7..a92658e595b749783a4cf3a00320dc7b1f0f8c9a 100644 --- a/api/funkwhale_api/federation/renderers.py +++ b/api/funkwhale_api/federation/renderers.py @@ -1,8 +1,17 @@ from rest_framework.renderers import JSONRenderer -class ActivityPubRenderer(JSONRenderer): - media_type = "application/activity+json" +def get_ap_renderers(): + MEDIA_TYPES = [ + ("APActivity", "application/activity+json"), + ("APLD", "application/ld+json"), + ("APJSON", "application/json"), + ] + + return [ + type(name, (JSONRenderer,), {"media_type": media_type}) + for name, media_type in MEDIA_TYPES + ] class WebfingerRenderer(JSONRenderer): diff --git a/api/funkwhale_api/federation/routes.py b/api/funkwhale_api/federation/routes.py index 0295aa46ce4004648075371be181b64072c773e9..bae2812cea0c678d1024e96a4b50233528cb8e2e 100644 --- a/api/funkwhale_api/federation/routes.py +++ b/api/funkwhale_api/federation/routes.py @@ -3,6 +3,7 @@ import logging from funkwhale_api.music import models as music_models from . import activity +from . import actors from . import serializers logger = logging.getLogger(__name__) @@ -269,3 +270,113 @@ def outbox_delete_audio(context): serializer.data, to=[{"type": "followers", "target": library}] ), } + + +def handle_library_entry_update(payload, context, queryset, serializer_class): + actor = context["actor"] + obj_id = payload["object"].get("id") + if not obj_id: + logger.debug("Discarding update of empty obj") + return + + try: + obj = queryset.select_related("attributed_to").get(fid=obj_id) + except queryset.model.DoesNotExist: + logger.debug("Discarding update of unkwnown obj %s", obj_id) + return + if not actor.can_manage(obj): + logger.debug( + "Discarding unauthorize update of obj %s from %s", obj_id, actor.fid + ) + return + + serializer = serializer_class(obj, data=payload["object"]) + if serializer.is_valid(): + serializer.save() + else: + logger.debug( + "Discarding update of obj %s because of payload errors: %s", + obj_id, + serializer.errors, + ) + + +@inbox.register({"type": "Update", "object.type": "Track"}) +def inbox_update_track(payload, context): + return handle_library_entry_update( + payload, + context, + queryset=music_models.Track.objects.all(), + serializer_class=serializers.TrackSerializer, + ) + + +@inbox.register({"type": "Update", "object.type": "Artist"}) +def inbox_update_artist(payload, context): + return handle_library_entry_update( + payload, + context, + queryset=music_models.Artist.objects.all(), + serializer_class=serializers.ArtistSerializer, + ) + + +@inbox.register({"type": "Update", "object.type": "Album"}) +def inbox_update_album(payload, context): + return handle_library_entry_update( + payload, + context, + queryset=music_models.Album.objects.all(), + serializer_class=serializers.AlbumSerializer, + ) + + +@outbox.register({"type": "Update", "object.type": "Track"}) +def outbox_update_track(context): + track = context["track"] + serializer = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.TrackSerializer(track).data} + ) + + yield { + "type": "Update", + "actor": actors.get_service_actor(), + "payload": with_recipients( + serializer.data, + to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}], + ), + } + + +@outbox.register({"type": "Update", "object.type": "Album"}) +def outbox_update_album(context): + album = context["album"] + serializer = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.AlbumSerializer(album).data} + ) + + yield { + "type": "Update", + "actor": actors.get_service_actor(), + "payload": with_recipients( + serializer.data, + to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}], + ), + } + + +@outbox.register({"type": "Update", "object.type": "Artist"}) +def outbox_update_artist(context): + artist = context["artist"] + serializer = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.ArtistSerializer(artist).data} + ) + + yield { + "type": "Update", + "actor": actors.get_service_actor(), + "payload": with_recipients( + serializer.data, + to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}], + ), + } diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index f873e197f9afe405ecb8c9dd46a70d84e7dce67f..473585fe7f56d1d519bf2e721bc982da4b31fbb5 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -1,30 +1,35 @@ import logging import mimetypes import urllib.parse +import uuid from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import Paginator from rest_framework import serializers from funkwhale_api.common import utils as funkwhale_utils +from funkwhale_api.music import licenses from funkwhale_api.music import models as music_models +from funkwhale_api.music import tasks as music_tasks -from . import activity, models, utils +from . import activity, actors, contexts, jsonld, models, tasks, utils -AP_CONTEXT = [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, -] +AP_CONTEXT = jsonld.get_default_context() logger = logging.getLogger(__name__) -class LinkSerializer(serializers.Serializer): - type = serializers.ChoiceField(choices=["Link"]) +class LinkSerializer(jsonld.JsonLdSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Link]) href = serializers.URLField(max_length=500) mediaType = serializers.CharField() + class Meta: + jsonld_mapping = { + "href": jsonld.first_id(contexts.AS.href), + "mediaType": jsonld.first_val(contexts.AS.mediaType), + } + def __init__(self, *args, **kwargs): self.allowed_mimetypes = kwargs.pop("allowed_mimetypes", []) super().__init__(*args, **kwargs) @@ -45,18 +50,52 @@ class LinkSerializer(serializers.Serializer): ) -class ActorSerializer(serializers.Serializer): +class EndpointsSerializer(jsonld.JsonLdSerializer): + sharedInbox = serializers.URLField(max_length=500, required=False) + + class Meta: + jsonld_mapping = {"sharedInbox": jsonld.first_id(contexts.AS.sharedInbox)} + + +class PublicKeySerializer(jsonld.JsonLdSerializer): + publicKeyPem = serializers.CharField(trim_whitespace=False) + + class Meta: + jsonld_mapping = {"publicKeyPem": jsonld.first_val(contexts.SEC.publicKeyPem)} + + +class ActorSerializer(jsonld.JsonLdSerializer): id = serializers.URLField(max_length=500) outbox = serializers.URLField(max_length=500) inbox = serializers.URLField(max_length=500) - type = serializers.ChoiceField(choices=models.TYPE_CHOICES) + type = serializers.ChoiceField( + choices=[getattr(contexts.AS, c[0]) for c in models.TYPE_CHOICES] + ) preferredUsername = serializers.CharField() manuallyApprovesFollowers = serializers.NullBooleanField(required=False) name = serializers.CharField(required=False, max_length=200) summary = serializers.CharField(max_length=None, required=False) followers = serializers.URLField(max_length=500) following = serializers.URLField(max_length=500, required=False, allow_null=True) - publicKey = serializers.JSONField(required=False) + publicKey = PublicKeySerializer(required=False) + endpoints = EndpointsSerializer(required=False) + + class Meta: + jsonld_mapping = { + "outbox": jsonld.first_id(contexts.AS.outbox), + "inbox": jsonld.first_id(contexts.LDP.inbox), + "following": jsonld.first_id(contexts.AS.following), + "followers": jsonld.first_id(contexts.AS.followers), + "preferredUsername": jsonld.first_val(contexts.AS.preferredUsername), + "summary": jsonld.first_val(contexts.AS.summary), + "name": jsonld.first_val(contexts.AS.name), + "publicKey": jsonld.first_obj(contexts.SEC.publicKey), + "manuallyApprovesFollowers": jsonld.first_val( + contexts.AS.manuallyApprovesFollowers + ), + "mediaType": jsonld.first_val(contexts.AS.mediaType), + "endpoints": jsonld.first_obj(contexts.AS.endpoints), + } def to_representation(self, instance): ret = { @@ -91,7 +130,7 @@ class ActorSerializer(serializers.Serializer): if instance.user.avatar: ret["icon"] = { "type": "Image", - "mediaType": mimetypes.guess_type(instance.user.avatar.path)[0], + "mediaType": mimetypes.guess_type(instance.user.avatar_path)[0], "url": utils.full_url(instance.user.avatar.crop["400x400"].url), } except ObjectDoesNotExist: @@ -114,17 +153,25 @@ class ActorSerializer(serializers.Serializer): if maf is not None: kwargs["manually_approves_followers"] = maf domain = urllib.parse.urlparse(kwargs["fid"]).netloc - kwargs["domain"] = models.Domain.objects.get_or_create(pk=domain)[0] - for endpoint, url in self.initial_data.get("endpoints", {}).items(): + domain, domain_created = models.Domain.objects.get_or_create(pk=domain) + if domain_created and not domain.is_local: + # first time we see the domain, we trigger nodeinfo fetching + tasks.update_domain_nodeinfo(domain_name=domain.name) + + kwargs["domain"] = domain + for endpoint, url in self.validated_data.get("endpoints", {}).items(): if endpoint == "sharedInbox": kwargs["shared_inbox_url"] = url break try: - kwargs["public_key"] = self.initial_data["publicKey"]["publicKeyPem"] + kwargs["public_key"] = self.validated_data["publicKey"]["publicKeyPem"] except KeyError: pass return kwargs + def validate_type(self, v): + return v.split("#")[-1] + def build(self): d = self.prepare_missing_fields() return models.Actor(**d) @@ -251,11 +298,29 @@ class FollowSerializer(serializers.Serializer): follow_class = models.Follow defaults = kwargs defaults["fid"] = self.validated_data["id"] - return follow_class.objects.update_or_create( + approved = kwargs.pop("approved", None) + follow, created = follow_class.objects.update_or_create( actor=self.validated_data["actor"], target=self.validated_data["object"], defaults=defaults, - )[0] + ) + if not created: + # We likely received a new follow when we had an existing one in database + # this can happen when two instances are out of sync, e.g because some + # messages are not delivered properly. In this case, we don't change + # the follow approved status and return the follow as is. + # We set a new UUID to ensure the follow urls are updated properly + # cf #830 + follow.uuid = uuid.uuid4() + follow.save(update_fields=["uuid"]) + return follow + + # it's a brand new follow, we use the approved value stored earlier + if approved != follow.approved: + follow.approved = approved + follow.save(update_fields=["approved"]) + + return follow def to_representation(self, instance): return { @@ -507,14 +572,40 @@ def get_additional_fields(data): return additional_fields -class PaginatedCollectionSerializer(serializers.Serializer): - type = serializers.ChoiceField(choices=["Collection"]) +PAGINATED_COLLECTION_JSONLD_MAPPING = { + "totalItems": jsonld.first_val(contexts.AS.totalItems), + "actor": jsonld.first_id(contexts.AS.actor), + "attributedTo": jsonld.first_id(contexts.AS.attributedTo), + "first": jsonld.first_id(contexts.AS.first), + "last": jsonld.first_id(contexts.AS.last), + "partOf": jsonld.first_id(contexts.AS.partOf), +} + + +class PaginatedCollectionSerializer(jsonld.JsonLdSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Collection]) totalItems = serializers.IntegerField(min_value=0) - actor = serializers.URLField(max_length=500) + actor = serializers.URLField(max_length=500, required=False) + attributedTo = serializers.URLField(max_length=500, required=False) id = serializers.URLField(max_length=500) first = serializers.URLField(max_length=500) last = serializers.URLField(max_length=500) + class Meta: + jsonld_mapping = PAGINATED_COLLECTION_JSONLD_MAPPING + + def validate(self, validated_data): + d = super().validate(validated_data) + actor = d.get("actor") + attributed_to = d.get("attributedTo") + if not actor and not attributed_to: + raise serializers.ValidationError( + "You need to provide at least actor or attributedTo" + ) + + d["attributedTo"] = attributed_to or actor + return d + def to_representation(self, conf): paginator = Paginator(conf["items"], conf.get("page_size", 20)) first = funkwhale_utils.set_query_parameter(conf["id"], page=1) @@ -522,7 +613,9 @@ class PaginatedCollectionSerializer(serializers.Serializer): last = funkwhale_utils.set_query_parameter(conf["id"], page=paginator.num_pages) d = { "id": conf["id"], + # XXX Stable release: remove the obsolete actor field "actor": conf["actor"].fid, + "attributedTo": conf["actor"].fid, "totalItems": paginator.count, "type": conf.get("type", "Collection"), "current": current, @@ -536,64 +629,77 @@ class PaginatedCollectionSerializer(serializers.Serializer): class LibrarySerializer(PaginatedCollectionSerializer): - type = serializers.ChoiceField(choices=["Library"]) + type = serializers.ChoiceField( + choices=[contexts.AS.Collection, contexts.FW.Library] + ) name = serializers.CharField() summary = serializers.CharField(allow_blank=True, allow_null=True, required=False) followers = serializers.URLField(max_length=500) audience = serializers.ChoiceField( - choices=["", None, "https://www.w3.org/ns/activitystreams#Public"], + choices=["", "./", None, "https://www.w3.org/ns/activitystreams#Public"], required=False, allow_null=True, allow_blank=True, ) + class Meta: + jsonld_mapping = funkwhale_utils.concat_dicts( + PAGINATED_COLLECTION_JSONLD_MAPPING, + { + "name": jsonld.first_val(contexts.AS.name), + "summary": jsonld.first_val(contexts.AS.summary), + "audience": jsonld.first_id(contexts.AS.audience), + "followers": jsonld.first_id(contexts.AS.followers), + }, + ) + def to_representation(self, library): conf = { "id": library.fid, "name": library.name, "summary": library.description, "page_size": 100, + # XXX Stable release: remove the obsolete actor field "actor": library.actor, + "attributedTo": library.actor, "items": library.uploads.for_federation(), "type": "Library", } r = super().to_representation(conf) r["audience"] = ( - "https://www.w3.org/ns/activitystreams#Public" - if library.privacy_level == "everyone" - else "" + contexts.AS.Public if library.privacy_level == "everyone" else "" ) r["followers"] = library.followers_url return r def create(self, validated_data): actor = utils.retrieve_ap_object( - validated_data["actor"], + validated_data["attributedTo"], + actor=self.context.get("fetch_actor"), queryset=models.Actor, serializer_class=ActorSerializer, ) + privacy = {"": "me", "./": "me", None: "me", contexts.AS.Public: "everyone"} library, created = music_models.Library.objects.update_or_create( fid=validated_data["id"], actor=actor, defaults={ "uploads_count": validated_data["totalItems"], "name": validated_data["name"], - "description": validated_data["summary"], + "description": validated_data.get("summary"), "followers_url": validated_data["followers"], - "privacy_level": "everyone" - if validated_data["audience"] - == "https://www.w3.org/ns/activitystreams#Public" - else "me", + "privacy_level": privacy[validated_data["audience"]], }, ) return library -class CollectionPageSerializer(serializers.Serializer): - type = serializers.ChoiceField(choices=["CollectionPage"]) +class CollectionPageSerializer(jsonld.JsonLdSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.CollectionPage]) totalItems = serializers.IntegerField(min_value=0) items = serializers.ListField() - actor = serializers.URLField(max_length=500) + actor = serializers.URLField(max_length=500, required=False) + attributedTo = serializers.URLField(max_length=500, required=False) id = serializers.URLField(max_length=500) first = serializers.URLField(max_length=500) last = serializers.URLField(max_length=500) @@ -601,6 +707,19 @@ class CollectionPageSerializer(serializers.Serializer): prev = serializers.URLField(max_length=500, required=False) partOf = serializers.URLField(max_length=500) + class Meta: + jsonld_mapping = { + "totalItems": jsonld.first_val(contexts.AS.totalItems), + "items": jsonld.raw(contexts.AS.items), + "actor": jsonld.first_id(contexts.AS.actor), + "attributedTo": jsonld.first_id(contexts.AS.attributedTo), + "first": jsonld.first_id(contexts.AS.first), + "last": jsonld.first_id(contexts.AS.last), + "next": jsonld.first_id(contexts.AS.next), + "prev": jsonld.first_id(contexts.AS.prev), + "partOf": jsonld.first_id(contexts.AS.partOf), + } + def validate_items(self, v): item_serializer = self.context.get("item_serializer") if not item_serializer: @@ -626,7 +745,9 @@ class CollectionPageSerializer(serializers.Serializer): d = { "id": id, "partOf": conf["id"], + # XXX Stable release: remove the obsolete actor field "actor": conf["actor"].fid, + "attributedTo": conf["actor"].fid, "totalItems": page.paginator.count, "type": "CollectionPage", "first": first, @@ -654,14 +775,46 @@ class CollectionPageSerializer(serializers.Serializer): return d -class MusicEntitySerializer(serializers.Serializer): +MUSIC_ENTITY_JSONLD_MAPPING = { + "name": jsonld.first_val(contexts.AS.name), + "published": jsonld.first_val(contexts.AS.published), + "musicbrainzId": jsonld.first_val(contexts.FW.musicbrainzId), + "attributedTo": jsonld.first_id(contexts.AS.attributedTo), +} + + +class MusicEntitySerializer(jsonld.JsonLdSerializer): id = serializers.URLField(max_length=500) published = serializers.DateTimeField() musicbrainzId = serializers.UUIDField(allow_null=True, required=False) name = serializers.CharField(max_length=1000) + attributedTo = serializers.URLField(max_length=500, allow_null=True, required=False) + updateable_fields = [] + + def update(self, instance, validated_data): + attributed_to_fid = validated_data.get("attributedTo") + if attributed_to_fid: + validated_data["attributedTo"] = actors.get_actor(attributed_to_fid) + updated_fields = funkwhale_utils.get_updated_fields( + self.updateable_fields, validated_data, instance + ) + if updated_fields: + return music_tasks.update_library_entity(instance, updated_fields) + + return instance class ArtistSerializer(MusicEntitySerializer): + updateable_fields = [ + ("name", "name"), + ("musicbrainzId", "mbid"), + ("attributedTo", "attributed_to"), + ] + + class Meta: + model = music_models.Artist + jsonld_mapping = MUSIC_ENTITY_JSONLD_MAPPING + def to_representation(self, instance): d = { "type": "Artist", @@ -669,6 +822,9 @@ class ArtistSerializer(MusicEntitySerializer): "name": instance.name, "published": instance.creation_date.isoformat(), "musicbrainzId": str(instance.mbid) if instance.mbid else None, + "attributedTo": instance.attributed_to.fid + if instance.attributed_to + else None, } if self.context.get("include_ap_context", self.parent is None): @@ -682,6 +838,23 @@ class AlbumSerializer(MusicEntitySerializer): cover = LinkSerializer( allowed_mimetypes=["image/*"], allow_null=True, required=False ) + updateable_fields = [ + ("name", "title"), + ("musicbrainzId", "mbid"), + ("attributedTo", "attributed_to"), + ("released", "release_date"), + ] + + class Meta: + model = music_models.Album + jsonld_mapping = funkwhale_utils.concat_dicts( + MUSIC_ENTITY_JSONLD_MAPPING, + { + "released": jsonld.first_val(contexts.FW.released), + "artists": jsonld.first_attr(contexts.FW.artists, "@list"), + "cover": jsonld.first_obj(contexts.FW.cover), + }, + ) def to_representation(self, instance): d = { @@ -698,34 +871,21 @@ class AlbumSerializer(MusicEntitySerializer): instance.artist, context={"include_ap_context": False} ).data ], + "attributedTo": instance.attributed_to.fid + if instance.attributed_to + else None, } if instance.cover: d["cover"] = { "type": "Link", "href": utils.full_url(instance.cover.url), - "mediaType": mimetypes.guess_type(instance.cover.path)[0] + "mediaType": mimetypes.guess_type(instance.cover_path)[0] or "image/jpeg", } if self.context.get("include_ap_context", self.parent is None): d["@context"] = AP_CONTEXT return d - def get_create_data(self, validated_data): - artist_data = validated_data["artists"][0] - artist = ArtistSerializer( - context={"activity": self.context.get("activity")} - ).create(artist_data) - - return { - "mbid": validated_data.get("musicbrainzId"), - "fid": validated_data["id"], - "title": validated_data["name"], - "creation_date": validated_data["published"], - "artist": artist, - "release_date": validated_data.get("released"), - "from_activity": self.context.get("activity"), - } - class TrackSerializer(MusicEntitySerializer): position = serializers.IntegerField(min_value=0, allow_null=True, required=False) @@ -735,6 +895,30 @@ class TrackSerializer(MusicEntitySerializer): license = serializers.URLField(allow_null=True, required=False) copyright = serializers.CharField(allow_null=True, required=False) + updateable_fields = [ + ("name", "title"), + ("musicbrainzId", "mbid"), + ("attributedTo", "attributed_to"), + ("disc", "disc_number"), + ("position", "position"), + ("copyright", "copyright"), + ("license", "license"), + ] + + class Meta: + model = music_models.Track + jsonld_mapping = funkwhale_utils.concat_dicts( + MUSIC_ENTITY_JSONLD_MAPPING, + { + "album": jsonld.first_obj(contexts.FW.album), + "artists": jsonld.first_attr(contexts.FW.artists, "@list"), + "copyright": jsonld.first_val(contexts.FW.copyright), + "disc": jsonld.first_val(contexts.FW.disc), + "license": jsonld.first_id(contexts.FW.license), + "position": jsonld.first_val(contexts.FW.position), + }, + ) + def to_representation(self, instance): d = { "type": "Track", @@ -756,6 +940,9 @@ class TrackSerializer(MusicEntitySerializer): "album": AlbumSerializer( instance.album, context={"include_ap_context": False} ).data, + "attributedTo": instance.attributed_to.fid + if instance.attributed_to + else None, } if self.context.get("include_ap_context", self.parent is None): @@ -765,16 +952,56 @@ class TrackSerializer(MusicEntitySerializer): def create(self, validated_data): from funkwhale_api.music import tasks as music_tasks - metadata = music_tasks.federation_audio_track_to_metadata(validated_data) + references = {} + actors_to_fetch = set() + actors_to_fetch.add( + funkwhale_utils.recursive_getattr( + validated_data, "attributedTo", permissive=True + ) + ) + actors_to_fetch.add( + funkwhale_utils.recursive_getattr( + validated_data, "album.attributedTo", permissive=True + ) + ) + artists = ( + funkwhale_utils.recursive_getattr( + validated_data, "artists", permissive=True + ) + or [] + ) + album_artists = ( + funkwhale_utils.recursive_getattr( + validated_data, "album.artists", permissive=True + ) + or [] + ) + for artist in artists + album_artists: + actors_to_fetch.add(artist.get("attributedTo")) + + for url in actors_to_fetch: + if not url: + continue + references[url] = actors.get_actor(url) + + metadata = music_tasks.federation_audio_track_to_metadata( + validated_data, references + ) + from_activity = self.context.get("activity") if from_activity: metadata["from_activity_id"] = from_activity.pk track = music_tasks.get_track_from_import_metadata(metadata, update_cover=True) return track + def update(self, obj, validated_data): + if validated_data.get("license"): + validated_data["license"] = licenses.match(validated_data["license"]) + return super().update(obj, validated_data) + -class UploadSerializer(serializers.Serializer): - type = serializers.ChoiceField(choices=["Audio"]) +class UploadSerializer(jsonld.JsonLdSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Audio]) id = serializers.URLField(max_length=500) library = serializers.URLField(max_length=500) url = LinkSerializer(allowed_mimetypes=["audio/*"]) @@ -786,6 +1013,19 @@ class UploadSerializer(serializers.Serializer): track = TrackSerializer(required=True) + class Meta: + model = music_models.Upload + jsonld_mapping = { + "track": jsonld.first_obj(contexts.FW.track), + "library": jsonld.first_id(contexts.FW.library), + "url": jsonld.first_obj(contexts.AS.url), + "published": jsonld.first_val(contexts.AS.published), + "updated": jsonld.first_val(contexts.AS.updated), + "duration": jsonld.first_val(contexts.AS.duration), + "bitrate": jsonld.first_val(contexts.FW.bitrate), + "size": jsonld.first_val(contexts.FW.size), + } + def validate_url(self, v): try: v["href"] @@ -870,26 +1110,6 @@ class UploadSerializer(serializers.Serializer): return d -class CollectionSerializer(serializers.Serializer): - def to_representation(self, conf): - d = { - "id": conf["id"], - "actor": conf["actor"].fid, - "totalItems": len(conf["items"]), - "type": "Collection", - "items": [ - conf["item_serializer"]( - i, context={"actor": conf["actor"], "include_ap_context": False} - ).data - for i in conf["items"] - ], - } - - if self.context.get("include_ap_context", True): - d["@context"] = AP_CONTEXT - return d - - class NodeInfoLinkSerializer(serializers.Serializer): href = serializers.URLField() rel = serializers.URLField() diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py index e7aafc2e850e21e6014a9a452992a2c1d02c7639..f6471ef215f9a2528786407f7b24a29045e008e2 100644 --- a/api/funkwhale_api/federation/tasks.py +++ b/api/funkwhale_api/federation/tasks.py @@ -1,9 +1,11 @@ import datetime +import json import logging import os import requests from django.conf import settings +from django.db import transaction from django.db.models import Q, F from django.utils import timezone from dynamic_preferences.registries import global_preferences_registry @@ -11,13 +13,17 @@ from requests.exceptions import RequestException from funkwhale_api.common import preferences from funkwhale_api.common import session +from funkwhale_api.common import utils as common_utils from funkwhale_api.music import models as music_models from funkwhale_api.taskapp import celery +from . import actors +from . import jsonld from . import keys from . import models, signing from . import serializers from . import routes +from . import utils logger = logging.getLogger(__name__) @@ -97,7 +103,8 @@ def dispatch_outbox(activity): inbox_items = activity.inbox_items.filter(is_read=False).select_related() if inbox_items.exists(): - dispatch_inbox.delay(activity_id=activity.pk, call_handlers=False) + call_handlers = activity.type in ["Follow"] + dispatch_inbox.delay(activity_id=activity.pk, call_handlers=call_handlers) if not preferences.get("federation__enabled"): # federation is disabled, we only deliver to local recipients @@ -185,9 +192,44 @@ def update_domain_nodeinfo(domain): nodeinfo = {"status": "ok", "payload": fetch_nodeinfo(domain.name)} except (requests.RequestException, serializers.serializers.ValidationError) as e: nodeinfo = {"status": "error", "error": str(e)} + + service_actor_id = common_utils.recursive_getattr( + nodeinfo, "payload.metadata.actorId", permissive=True + ) + try: + domain.service_actor = ( + utils.retrieve_ap_object( + service_actor_id, + actor=actors.get_service_actor(), + queryset=models.Actor, + serializer_class=serializers.ActorSerializer, + ) + if service_actor_id + else None + ) + except (serializers.serializers.ValidationError, RequestException) as e: + logger.warning( + "Cannot fetch system actor for domain %s: %s", domain.name, str(e) + ) domain.nodeinfo_fetch_date = now domain.nodeinfo = nodeinfo - domain.save(update_fields=["nodeinfo", "nodeinfo_fetch_date"]) + domain.save(update_fields=["nodeinfo", "nodeinfo_fetch_date", "service_actor"]) + + +@celery.app.task(name="federation.refresh_nodeinfo_known_nodes") +def refresh_nodeinfo_known_nodes(): + """ + Trigger a node info refresh on all nodes that weren't refreshed since + settings.NODEINFO_REFRESH_DELAY + """ + limit = timezone.now() - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY) + candidates = models.Domain.objects.external().exclude( + nodeinfo_fetch_date__gte=limit + ) + names = candidates.values_list("name", flat=True) + logger.info("Launching periodic nodeinfo refresh on %s domains", len(names)) + for domain_name in names: + update_domain_nodeinfo.delay(domain_name=domain_name) def delete_qs(qs): @@ -240,3 +282,83 @@ def rotate_actor_key(actor): actor.private_key = pair[0].decode() actor.public_key = pair[1].decode() actor.save(update_fields=["private_key", "public_key"]) + + +@celery.app.task(name="federation.fetch") +@transaction.atomic +@celery.require_instance( + models.Fetch.objects.filter(status="pending").select_related("actor"), "fetch" +) +def fetch(fetch): + actor = fetch.actor + auth = signing.get_auth(actor.private_key, actor.private_key_id) + + def error(code, **kwargs): + fetch.status = "errored" + fetch.fetch_date = timezone.now() + fetch.detail = {"error_code": code} + fetch.detail.update(kwargs) + fetch.save(update_fields=["fetch_date", "status", "detail"]) + + try: + response = session.get_session().get( + auth=auth, + url=fetch.url, + timeout=5, + verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL, + headers={"Content-Type": "application/activity+json"}, + ) + logger.debug("Remote answered with %s", response.status_code) + response.raise_for_status() + except requests.exceptions.HTTPError as e: + return error("http", status_code=e.response.status_code if e.response else None) + except requests.exceptions.Timeout: + return error("timeout") + except requests.exceptions.ConnectionError as e: + return error("connection", message=str(e)) + except requests.RequestException as e: + return error("request", message=str(e)) + except Exception as e: + return error("unhandled", message=str(e)) + + try: + payload = response.json() + except json.decoder.JSONDecodeError: + return error("invalid_json") + + try: + doc = jsonld.expand(payload) + except ValueError: + return error("invalid_jsonld") + + try: + type = doc.get("@type", [])[0] + except IndexError: + return error("missing_jsonld_type") + try: + serializer_class = fetch.serializers[type] + model = serializer_class.Meta.model + except (KeyError, AttributeError): + fetch.status = "skipped" + fetch.fetch_date = timezone.now() + fetch.detail = {"reason": "unhandled_type", "type": type} + return fetch.save(update_fields=["fetch_date", "status", "detail"]) + try: + id = doc.get("@id") + except IndexError: + existing = None + else: + existing = model.objects.filter(fid=id).first() + + serializer = serializer_class(existing, data=payload) + if not serializer.is_valid(): + return error("validation", validation_errors=serializer.errors) + try: + serializer.save() + except Exception as e: + error("save", message=str(e)) + raise + + fetch.status = "finished" + fetch.fetch_date = timezone.now() + return fetch.save(update_fields=["fetch_date", "status"]) diff --git a/api/funkwhale_api/federation/urls.py b/api/funkwhale_api/federation/urls.py index f8347d1ebf47c2f300d6b7d0941809d7457d7ed2..f7d5006da0cdf50df601da96e78fc5b8e97d00ca 100644 --- a/api/funkwhale_api/federation/urls.py +++ b/api/funkwhale_api/federation/urls.py @@ -8,6 +8,7 @@ music_router = routers.SimpleRouter(trailing_slash=False) router.register(r"federation/shared", views.SharedViewSet, "shared") router.register(r"federation/actors", views.ActorViewSet, "actors") +router.register(r"federation/edits", views.EditViewSet, "edits") router.register(r".well-known", views.WellKnownViewSet, "well-known") music_router.register(r"libraries", views.MusicLibraryViewSet, "libraries") diff --git a/api/funkwhale_api/federation/utils.py b/api/funkwhale_api/federation/utils.py index e49a4dd63c4f8140c8f8e52ed13cb095585927c7..8f73c57350a2290f4fea9e106859b460052a24c0 100644 --- a/api/funkwhale_api/federation/utils.py +++ b/api/funkwhale_api/federation/utils.py @@ -1,6 +1,7 @@ import unicodedata import re from django.conf import settings +from django.db.models import Q from funkwhale_api.common import session from funkwhale_api.moderation import models as moderation_models @@ -61,7 +62,7 @@ def slugify_username(username): def retrieve_ap_object( - fid, actor=None, serializer_class=None, queryset=None, apply_instance_policies=True + fid, actor, serializer_class=None, queryset=None, apply_instance_policies=True ): from . import activity @@ -100,10 +101,33 @@ def retrieve_ap_object( except KeyError: pass else: - if apply_instance_policies and activity.should_reject(id=id, payload=data): + if apply_instance_policies and activity.should_reject(fid=id, payload=data): raise exceptions.BlockedActorOrDomain() if not serializer_class: return data - serializer = serializer_class(data=data) + serializer = serializer_class(data=data, context={"fetch_actor": actor}) serializer.is_valid(raise_exception=True) return serializer.save() + + +def get_domain_query_from_url(domain, url_field="fid"): + """ + Given a domain name and a field, will return a Q() object + to match objects that have this domain in the given field. + """ + + query = Q(**{"{}__startswith".format(url_field): "http://{}/".format(domain)}) + query = query | Q( + **{"{}__startswith".format(url_field): "https://{}/".format(domain)} + ) + return query + + +def is_local(url): + if not url: + return True + + d = settings.FEDERATION_HOSTNAME + return url.startswith("http://{}/".format(d)) or url.startswith( + "https://{}/".format(d) + ) diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 3b322e915144bec01e536640022402a1b20429bf..97bcebbfb374d5a765f30386ab430935d84f68f5 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -7,6 +7,7 @@ from rest_framework.decorators import action from funkwhale_api.common import preferences from funkwhale_api.music import models as music_models +from funkwhale_api.music import utils as music_utils from . import activity, authentication, models, renderers, serializers, utils, webfinger @@ -21,7 +22,7 @@ class FederationMixin(object): class SharedViewSet(FederationMixin, viewsets.GenericViewSet): permission_classes = [] authentication_classes = [authentication.SignatureAuthentication] - renderer_classes = [renderers.ActivityPubRenderer] + renderer_classes = renderers.get_ap_renderers() @action(methods=["post"], detail=False) def inbox(self, request, *args, **kwargs): @@ -38,7 +39,7 @@ class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericV lookup_field = "preferred_username" authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] + renderer_classes = renderers.get_ap_renderers() queryset = models.Actor.objects.local().select_related("user") serializer_class = serializers.ActorSerializer @@ -69,6 +70,15 @@ class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericV return response.Response({}) +class EditViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): + lookup_field = "uuid" + authentication_classes = [authentication.SignatureAuthentication] + permission_classes = [] + renderer_classes = renderers.get_ap_renderers() + # queryset = common_models.Mutation.objects.local().select_related() + # serializer_class = serializers.ActorSerializer + + class WellKnownViewSet(viewsets.GenericViewSet): authentication_classes = [] permission_classes = [] @@ -137,7 +147,7 @@ class MusicLibraryViewSet( ): authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] + renderer_classes = renderers.get_ap_renderers() serializer_class = serializers.LibrarySerializer queryset = music_models.Library.objects.all().select_related("actor") lookup_field = "uuid" @@ -192,18 +202,27 @@ class MusicUploadViewSet( ): authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] - queryset = music_models.Upload.objects.none() + renderer_classes = renderers.get_ap_renderers() + queryset = music_models.Upload.objects.local().select_related( + "library__actor", "track__artist", "track__album__artist" + ) + serializer_class = serializers.UploadSerializer lookup_field = "uuid" + def get_queryset(self): + queryset = super().get_queryset() + actor = music_utils.get_actor_from_request(self.request) + return queryset.playable_by(actor) + class MusicArtistViewSet( FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet ): authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] - queryset = music_models.Artist.objects.none() + renderer_classes = renderers.get_ap_renderers() + queryset = music_models.Artist.objects.local() + serializer_class = serializers.ArtistSerializer lookup_field = "uuid" @@ -212,8 +231,9 @@ class MusicAlbumViewSet( ): authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] - queryset = music_models.Album.objects.none() + renderer_classes = renderers.get_ap_renderers() + queryset = music_models.Album.objects.local().select_related("artist") + serializer_class = serializers.AlbumSerializer lookup_field = "uuid" @@ -222,6 +242,9 @@ class MusicTrackViewSet( ): authentication_classes = [authentication.SignatureAuthentication] permission_classes = [] - renderer_classes = [renderers.ActivityPubRenderer] - queryset = music_models.Track.objects.none() + renderer_classes = renderers.get_ap_renderers() + queryset = music_models.Track.objects.local().select_related( + "album__artist", "artist" + ) + serializer_class = serializers.TrackSerializer lookup_field = "uuid" diff --git a/api/funkwhale_api/history/filters.py b/api/funkwhale_api/history/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..30bc78f6a9d0a10394a1d76bf2e5ea4de0c6e8aa --- /dev/null +++ b/api/funkwhale_api/history/filters.py @@ -0,0 +1,12 @@ +from funkwhale_api.moderation import filters as moderation_filters + +from . import models + + +class ListeningFilter(moderation_filters.HiddenContentFilterSet): + class Meta: + model = models.Listening + hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ + "LISTENING" + ] + fields = ["hidden"] diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py index 56c30af36511e8cf124645dfcd00f835e3a04287..30219629a4b0c5a2852d0cff1f9f04605604a654 100644 --- a/api/funkwhale_api/history/views.py +++ b/api/funkwhale_api/history/views.py @@ -1,5 +1,4 @@ from rest_framework import mixins, viewsets -from rest_framework.permissions import IsAuthenticatedOrReadOnly from django.db.models import Prefetch @@ -7,7 +6,9 @@ from funkwhale_api.activity import record from funkwhale_api.common import fields, permissions from funkwhale_api.music.models import Track from funkwhale_api.music import utils as music_utils -from . import models, serializers +from . import filters, models, serializers + +from funkwhale_api.users.oauth import permissions as oauth_permissions class ListeningViewSet( @@ -19,12 +20,15 @@ class ListeningViewSet( serializer_class = serializers.ListeningSerializer queryset = models.Listening.objects.all().select_related("user") + permission_classes = [ - permissions.ConditionalAuthentication, + oauth_permissions.ScopePermission, permissions.OwnerPermission, - IsAuthenticatedOrReadOnly, ] + required_scope = "listenings" + anonymous_policy = "setting" owner_checks = ["write"] + filterset_class = filters.ListeningFilter def get_serializer_class(self): if self.request.method.lower() in ["head", "get", "options"]: diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py index 286cb0284f9d922c74b10d10b4cd5ee4f3a70d1f..f45fbb129402ac0437147ac8eff1147f99335d1b 100644 --- a/api/funkwhale_api/instance/nodeinfo.py +++ b/api/funkwhale_api/instance/nodeinfo.py @@ -2,6 +2,8 @@ import memoize.djangocache import funkwhale_api from funkwhale_api.common import preferences +from funkwhale_api.federation import actors +from funkwhale_api.music import utils as music_utils from . import stats @@ -19,6 +21,7 @@ def get(): "openRegistrations": preferences.get("users__registration_enabled"), "usage": {"users": {"total": 0, "activeHalfyear": 0, "activeMonth": 0}}, "metadata": { + "actorId": actors.get_service_actor().fid, "private": preferences.get("instance__nodeinfo_private"), "shortDescription": preferences.get("instance__short_description"), "longDescription": preferences.get("instance__long_description"), @@ -32,6 +35,7 @@ def get(): "common__api_authentication_required" ), }, + "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, }, } if share_stats: diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py index ea6311033333ad0603a1f88d52fd51bda44ceeaa..1800c3dbc7e60bcc022f24dd3e64d6a0d45b8697 100644 --- a/api/funkwhale_api/instance/views.py +++ b/api/funkwhale_api/instance/views.py @@ -5,7 +5,7 @@ from rest_framework import views from rest_framework.response import Response from funkwhale_api.common import preferences -from funkwhale_api.users.permissions import HasUserPermission +from funkwhale_api.users.oauth import permissions as oauth_permissions from . import nodeinfo @@ -14,8 +14,8 @@ NODEINFO_2_CONTENT_TYPE = "application/json; profile=http://nodeinfo.diaspora.so class AdminSettings(preferences_viewsets.GlobalPreferencesViewSet): pagination_class = None - permission_classes = (HasUserPermission,) - required_permissions = ["settings"] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "instance:settings" class InstanceSettings(views.APIView): diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index edae49f991bcb9f2a415f8f829df1a1e7d7c5248..984b83133734e81a87194de73016a4f0ca5ee11f 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -1,27 +1,238 @@ +from django import forms +from django.db.models import Q +from django.conf import settings + +import django_filters from django_filters import rest_framework as filters from funkwhale_api.common import fields from funkwhale_api.common import search from funkwhale_api.federation import models as federation_models +from funkwhale_api.federation import utils as federation_utils from funkwhale_api.moderation import models as moderation_models from funkwhale_api.music import models as music_models from funkwhale_api.users import models as users_models +class ActorField(forms.CharField): + def clean(self, value): + value = super().clean(value) + if not value: + return value + + parts = value.split("@") + + return { + "username": parts[0], + "domain": parts[1] if len(parts) > 1 else settings.FEDERATION_HOSTNAME, + } + + +def get_actor_filter(actor_field): + def handler(v): + if not v: + return Q(**{actor_field: None}) + return Q( + **{ + "{}__preferred_username__iexact".format(actor_field): v["username"], + "{}__domain__name__iexact".format(actor_field): v["domain"], + } + ) + + return {"field": ActorField(), "handler": handler} + + +class ManageArtistFilterSet(filters.FilterSet): + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "name": {"to": "name"}, + "fid": {"to": "fid"}, + "mbid": {"to": "mbid"}, + }, + filter_fields={ + "uuid": {"to": "uuid"}, + "domain": { + "handler": lambda v: federation_utils.get_domain_query_from_url(v) + }, + "library_id": { + "to": "tracks__uploads__library_id", + "field": forms.IntegerField(), + "distinct": True, + }, + }, + ) + ) + + class Meta: + model = music_models.Artist + fields = ["q", "name", "mbid", "fid"] + + +class ManageAlbumFilterSet(filters.FilterSet): + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "title": {"to": "title"}, + "fid": {"to": "fid"}, + "artist": {"to": "artist__name"}, + "mbid": {"to": "mbid"}, + }, + filter_fields={ + "uuid": {"to": "uuid"}, + "artist_id": {"to": "artist_id", "field": forms.IntegerField()}, + "domain": { + "handler": lambda v: federation_utils.get_domain_query_from_url(v) + }, + "library_id": { + "to": "tracks__uploads__library_id", + "field": forms.IntegerField(), + "distinct": True, + }, + }, + ) + ) + + class Meta: + model = music_models.Album + fields = ["q", "title", "mbid", "fid", "artist"] + + +class ManageTrackFilterSet(filters.FilterSet): + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "title": {"to": "title"}, + "fid": {"to": "fid"}, + "mbid": {"to": "mbid"}, + "artist": {"to": "artist__name"}, + "album": {"to": "album__title"}, + "album_artist": {"to": "album__artist__name"}, + "copyright": {"to": "copyright"}, + }, + filter_fields={ + "album_id": {"to": "album_id", "field": forms.IntegerField()}, + "album_artist_id": { + "to": "album__artist_id", + "field": forms.IntegerField(), + }, + "artist_id": {"to": "artist_id", "field": forms.IntegerField()}, + "uuid": {"to": "uuid"}, + "license": {"to": "license"}, + "domain": { + "handler": lambda v: federation_utils.get_domain_query_from_url(v) + }, + "library_id": { + "to": "uploads__library_id", + "field": forms.IntegerField(), + "distinct": True, + }, + }, + ) + ) + + class Meta: + model = music_models.Track + fields = ["q", "title", "mbid", "fid", "artist", "album", "license"] + + +class ManageLibraryFilterSet(filters.FilterSet): + ordering = django_filters.OrderingFilter( + # tuple-mapping retains order + fields=( + ("creation_date", "creation_date"), + ("_uploads_count", "uploads_count"), + ("followers_count", "followers_count"), + ) + ) + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "name": {"to": "name"}, + "description": {"to": "description"}, + "fid": {"to": "fid"}, + }, + filter_fields={ + "uuid": {"to": "uuid"}, + "artist_id": { + "to": "uploads__track__artist_id", + "field": forms.IntegerField(), + "distinct": True, + }, + "album_id": { + "to": "uploads__track__album_id", + "field": forms.IntegerField(), + "distinct": True, + }, + "track_id": { + "to": "uploads__track__id", + "field": forms.IntegerField(), + "distinct": True, + }, + "domain": {"to": "actor__domain_id"}, + "account": get_actor_filter("actor"), + "privacy_level": {"to": "privacy_level"}, + }, + ) + ) + domain = filters.CharFilter("actor__domain_id") + + class Meta: + model = music_models.Library + fields = ["q", "name", "fid", "privacy_level", "domain"] + + class ManageUploadFilterSet(filters.FilterSet): - q = fields.SearchFilter( - search_fields=[ - "track__title", - "track__album__title", - "track__artist__name", - "source", - ] + ordering = django_filters.OrderingFilter( + # tuple-mapping retains order + fields=( + ("creation_date", "creation_date"), + ("modification_date", "modification_date"), + ("accessed_date", "accessed_date"), + ("size", "size"), + ("bitrate", "bitrate"), + ("duration", "duration"), + ) + ) + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "source": {"to": "source"}, + "fid": {"to": "fid"}, + "track": {"to": "track__title"}, + "album": {"to": "track__album__title"}, + "artist": {"to": "track__artist__name"}, + }, + filter_fields={ + "uuid": {"to": "uuid"}, + "library_id": {"to": "library_id", "field": forms.IntegerField()}, + "artist_id": {"to": "track__artist_id", "field": forms.IntegerField()}, + "album_id": {"to": "track__album_id", "field": forms.IntegerField()}, + "track_id": {"to": "track__id", "field": forms.IntegerField()}, + "domain": {"to": "library__actor__domain_id"}, + "import_reference": {"to": "import_reference"}, + "type": {"to": "mimetype"}, + "status": {"to": "import_status"}, + "account": get_actor_filter("library__actor"), + "privacy_level": {"to": "library__privacy_level"}, + }, + ) ) + domain = filters.CharFilter("library__actor__domain_id") + privacy_level = filters.CharFilter("library__privacy_level") class Meta: model = music_models.Upload - fields = ["q", "track__album", "track__artist", "track"] + fields = [ + "q", + "fid", + "privacy_level", + "domain", + "mimetype", + "import_reference", + "import_status", + ] class ManageDomainFilterSet(filters.FilterSet): @@ -43,6 +254,7 @@ class ManageActorFilterSet(filters.FilterSet): "type": {"to": "type"}, }, filter_fields={ + "uuid": {"to": "uuid"}, "domain": {"to": "domain__name__iexact"}, "username": {"to": "preferred_username__iexact"}, "email": {"to": "user__email__iexact"}, @@ -60,7 +272,15 @@ class ManageActorFilterSet(filters.FilterSet): class ManageUserFilterSet(filters.FilterSet): - q = fields.SearchFilter(search_fields=["username", "email", "name"]) + q = fields.SmartSearchFilter( + config=search.SearchConfig( + search_fields={ + "name": {"to": "name"}, + "username": {"to": "username"}, + "email": {"to": "email"}, + } + ) + ) class Meta: model = users_models.User diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index ed50d86777d30c7d8fc255944f5034f70a3f4bef..add9364e86a3fc9e7f82873fbbea15949a59fde4 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -9,72 +9,12 @@ from funkwhale_api.federation import fields as federation_fields from funkwhale_api.federation import tasks as federation_tasks from funkwhale_api.moderation import models as moderation_models from funkwhale_api.music import models as music_models +from funkwhale_api.music import serializers as music_serializers from funkwhale_api.users import models as users_models from . import filters -class ManageUploadArtistSerializer(serializers.ModelSerializer): - class Meta: - model = music_models.Artist - fields = ["id", "mbid", "creation_date", "name"] - - -class ManageUploadAlbumSerializer(serializers.ModelSerializer): - artist = ManageUploadArtistSerializer() - - class Meta: - model = music_models.Album - fields = ( - "id", - "mbid", - "title", - "artist", - "release_date", - "cover", - "creation_date", - ) - - -class ManageUploadTrackSerializer(serializers.ModelSerializer): - artist = ManageUploadArtistSerializer() - album = ManageUploadAlbumSerializer() - - class Meta: - model = music_models.Track - fields = ("id", "mbid", "title", "album", "artist", "creation_date", "position") - - -class ManageUploadSerializer(serializers.ModelSerializer): - track = ManageUploadTrackSerializer() - - class Meta: - model = music_models.Upload - fields = ( - "id", - "path", - "source", - "filename", - "mimetype", - "track", - "duration", - "mimetype", - "creation_date", - "bitrate", - "size", - "path", - ) - - -class ManageUploadActionSerializer(common_serializers.ActionSerializer): - actions = [common_serializers.Action("delete", allow_all=False)] - filterset_class = filters.ManageUploadFilterSet - - @transaction.atomic - def handle_delete(self, objects): - return objects.delete() - - class PermissionsSerializer(serializers.Serializer): def to_representation(self, o): return o.get_permissions(defaults=self.context.get("default_permissions")) @@ -216,10 +156,7 @@ class ManageDomainActionSerializer(common_serializers.ActionSerializer): common_utils.on_commit(federation_tasks.purge_actors.delay, domains=list(ids)) -class ManageActorSerializer(serializers.ModelSerializer): - uploads_count = serializers.SerializerMethodField() - user = ManageUserSerializer() - +class ManageBaseActorSerializer(serializers.ModelSerializer): class Meta: model = federation_models.Actor fields = [ @@ -238,6 +175,17 @@ class ManageActorSerializer(serializers.ModelSerializer): "outbox_url", "shared_inbox_url", "manually_approves_followers", + ] + read_only_fields = ["creation_date", "instance_policy"] + + +class ManageActorSerializer(ManageBaseActorSerializer): + uploads_count = serializers.SerializerMethodField() + user = ManageUserSerializer() + + class Meta: + model = federation_models.Actor + fields = ManageBaseActorSerializer.Meta.fields + [ "uploads_count", "user", "instance_policy", @@ -339,3 +287,256 @@ class ManageInstancePolicySerializer(serializers.ModelSerializer): ) return instance + + +class ManageBaseArtistSerializer(serializers.ModelSerializer): + domain = serializers.CharField(source="domain_name") + + class Meta: + model = music_models.Artist + fields = ["id", "fid", "mbid", "name", "creation_date", "domain", "is_local"] + + +class ManageBaseAlbumSerializer(serializers.ModelSerializer): + cover = music_serializers.cover_field + domain = serializers.CharField(source="domain_name") + + class Meta: + model = music_models.Album + fields = [ + "id", + "fid", + "mbid", + "title", + "creation_date", + "release_date", + "cover", + "domain", + "is_local", + ] + + +class ManageNestedTrackSerializer(serializers.ModelSerializer): + domain = serializers.CharField(source="domain_name") + + class Meta: + model = music_models.Track + fields = [ + "id", + "fid", + "mbid", + "title", + "creation_date", + "position", + "disc_number", + "domain", + "is_local", + "copyright", + "license", + ] + + +class ManageNestedAlbumSerializer(ManageBaseAlbumSerializer): + + tracks_count = serializers.SerializerMethodField() + + class Meta: + model = music_models.Album + fields = ManageBaseAlbumSerializer.Meta.fields + ["tracks_count"] + + def get_tracks_count(self, obj): + return getattr(obj, "tracks_count", None) + + +class ManageArtistSerializer(ManageBaseArtistSerializer): + albums = ManageNestedAlbumSerializer(many=True) + tracks = ManageNestedTrackSerializer(many=True) + attributed_to = ManageBaseActorSerializer() + + class Meta: + model = music_models.Artist + fields = ManageBaseArtistSerializer.Meta.fields + [ + "albums", + "tracks", + "attributed_to", + ] + + +class ManageNestedArtistSerializer(ManageBaseArtistSerializer): + pass + + +class ManageAlbumSerializer(ManageBaseAlbumSerializer): + tracks = ManageNestedTrackSerializer(many=True) + attributed_to = ManageBaseActorSerializer() + artist = ManageNestedArtistSerializer() + + class Meta: + model = music_models.Album + fields = ManageBaseAlbumSerializer.Meta.fields + [ + "artist", + "tracks", + "attributed_to", + ] + + +class ManageTrackAlbumSerializer(ManageBaseAlbumSerializer): + artist = ManageNestedArtistSerializer() + + class Meta: + model = music_models.Album + fields = ManageBaseAlbumSerializer.Meta.fields + ["artist"] + + +class ManageTrackSerializer(ManageNestedTrackSerializer): + artist = ManageNestedArtistSerializer() + album = ManageTrackAlbumSerializer() + attributed_to = ManageBaseActorSerializer() + uploads_count = serializers.SerializerMethodField() + + class Meta: + model = music_models.Track + fields = ManageNestedTrackSerializer.Meta.fields + [ + "artist", + "album", + "attributed_to", + "uploads_count", + ] + + def get_uploads_count(self, obj): + return getattr(obj, "uploads_count", None) + + +class ManageTrackActionSerializer(common_serializers.ActionSerializer): + actions = [common_serializers.Action("delete", allow_all=False)] + filterset_class = filters.ManageTrackFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() + + +class ManageAlbumActionSerializer(common_serializers.ActionSerializer): + actions = [common_serializers.Action("delete", allow_all=False)] + filterset_class = filters.ManageAlbumFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() + + +class ManageArtistActionSerializer(common_serializers.ActionSerializer): + actions = [common_serializers.Action("delete", allow_all=False)] + filterset_class = filters.ManageArtistFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() + + +class ManageLibraryActionSerializer(common_serializers.ActionSerializer): + actions = [common_serializers.Action("delete", allow_all=False)] + filterset_class = filters.ManageLibraryFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() + + +class ManageUploadActionSerializer(common_serializers.ActionSerializer): + actions = [common_serializers.Action("delete", allow_all=False)] + filterset_class = filters.ManageUploadFilterSet + + @transaction.atomic + def handle_delete(self, objects): + return objects.delete() + + +class ManageLibrarySerializer(serializers.ModelSerializer): + domain = serializers.CharField(source="domain_name") + actor = ManageBaseActorSerializer() + uploads_count = serializers.SerializerMethodField() + followers_count = serializers.SerializerMethodField() + + class Meta: + model = music_models.Library + fields = [ + "id", + "uuid", + "fid", + "url", + "name", + "description", + "domain", + "is_local", + "creation_date", + "privacy_level", + "uploads_count", + "followers_count", + "followers_url", + "actor", + ] + + def get_uploads_count(self, obj): + return getattr(obj, "_uploads_count", obj.uploads_count) + + def get_followers_count(self, obj): + return getattr(obj, "followers_count", None) + + +class ManageNestedLibrarySerializer(serializers.ModelSerializer): + domain = serializers.CharField(source="domain_name") + actor = ManageBaseActorSerializer() + + class Meta: + model = music_models.Library + fields = [ + "id", + "uuid", + "fid", + "url", + "name", + "description", + "domain", + "is_local", + "creation_date", + "privacy_level", + "followers_url", + "actor", + ] + + +class ManageUploadSerializer(serializers.ModelSerializer): + track = ManageNestedTrackSerializer() + library = ManageNestedLibrarySerializer() + domain = serializers.CharField(source="domain_name") + + class Meta: + model = music_models.Upload + fields = ( + "id", + "uuid", + "fid", + "domain", + "is_local", + "audio_file", + "listen_url", + "source", + "filename", + "mimetype", + "duration", + "mimetype", + "bitrate", + "size", + "creation_date", + "accessed_date", + "modification_date", + "metadata", + "import_date", + "import_details", + "import_status", + "import_metadata", + "import_reference", + "track", + "library", + ) diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py index 4c220fe0ef28598eab3feec59c9fb19ecf33bd86..2d5da59e3ed6b8656c3438fb9121819e18079d47 100644 --- a/api/funkwhale_api/manage/urls.py +++ b/api/funkwhale_api/manage/urls.py @@ -7,6 +7,10 @@ federation_router = routers.SimpleRouter() federation_router.register(r"domains", views.ManageDomainViewSet, "domains") library_router = routers.SimpleRouter() +library_router.register(r"albums", views.ManageAlbumViewSet, "albums") +library_router.register(r"artists", views.ManageArtistViewSet, "artists") +library_router.register(r"libraries", views.ManageLibraryViewSet, "libraries") +library_router.register(r"tracks", views.ManageTrackViewSet, "tracks") library_router.register(r"uploads", views.ManageUploadViewSet, "uploads") moderation_router = routers.SimpleRouter() diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py index 99d9020315882a3c461b3e8fe44eb1310635b0f5..83981116c59c69f9b350ab23e1182ee930e07464 100644 --- a/api/funkwhale_api/manage/views.py +++ b/api/funkwhale_api/manage/views.py @@ -1,39 +1,278 @@ from rest_framework import mixins, response, viewsets from rest_framework import decorators as rest_decorators + +from django.db.models import Count, Prefetch, Q, Sum, OuterRef, Subquery +from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 +from funkwhale_api.common import models as common_models from funkwhale_api.common import preferences, decorators +from funkwhale_api.favorites import models as favorites_models from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import tasks as federation_tasks +from funkwhale_api.history import models as history_models from funkwhale_api.music import models as music_models from funkwhale_api.moderation import models as moderation_models +from funkwhale_api.playlists import models as playlists_models from funkwhale_api.users import models as users_models -from funkwhale_api.users.permissions import HasUserPermission + from . import filters, serializers +def get_stats(tracks, target): + data = {} + tracks = list(tracks.values_list("pk", flat=True)) + uploads = music_models.Upload.objects.filter(track__in=tracks) + data["listenings"] = history_models.Listening.objects.filter( + track__in=tracks + ).count() + data["mutations"] = common_models.Mutation.objects.get_for_target(target).count() + data["playlists"] = ( + playlists_models.PlaylistTrack.objects.filter(track__in=tracks) + .values_list("playlist", flat=True) + .distinct() + .count() + ) + data["track_favorites"] = favorites_models.TrackFavorite.objects.filter( + track__in=tracks + ).count() + data["libraries"] = uploads.values_list("library", flat=True).distinct().count() + data["uploads"] = uploads.count() + data.update(get_media_stats(uploads)) + return data + + +def get_media_stats(uploads): + data = {} + data["media_total_size"] = uploads.aggregate(v=Sum("size"))["v"] or 0 + data["media_downloaded_size"] = ( + uploads.with_file().aggregate(v=Sum("size"))["v"] or 0 + ) + return data + + +class ManageArtistViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + queryset = ( + music_models.Artist.objects.all() + .order_by("-id") + .select_related("attributed_to") + .prefetch_related( + "tracks", + Prefetch( + "albums", + queryset=music_models.Album.objects.annotate( + tracks_count=Count("tracks") + ), + ), + ) + ) + serializer_class = serializers.ManageArtistSerializer + filterset_class = filters.ManageArtistFilterSet + required_scope = "instance:libraries" + ordering_fields = ["creation_date", "name"] + + @rest_decorators.action(methods=["get"], detail=True) + def stats(self, request, *args, **kwargs): + artist = self.get_object() + tracks = music_models.Track.objects.filter( + Q(artist=artist) | Q(album__artist=artist) + ) + data = get_stats(tracks, artist) + return response.Response(data, status=200) + + @rest_decorators.action(methods=["post"], detail=False) + def action(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = serializers.ManageArtistActionSerializer( + request.data, queryset=queryset + ) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return response.Response(result, status=200) + + +class ManageAlbumViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + queryset = ( + music_models.Album.objects.all() + .order_by("-id") + .select_related("attributed_to", "artist") + .prefetch_related("tracks") + ) + serializer_class = serializers.ManageAlbumSerializer + filterset_class = filters.ManageAlbumFilterSet + required_scope = "instance:libraries" + ordering_fields = ["creation_date", "title", "release_date"] + + @rest_decorators.action(methods=["get"], detail=True) + def stats(self, request, *args, **kwargs): + album = self.get_object() + data = get_stats(album.tracks.all(), album) + return response.Response(data, status=200) + + @rest_decorators.action(methods=["post"], detail=False) + def action(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = serializers.ManageAlbumActionSerializer( + request.data, queryset=queryset + ) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return response.Response(result, status=200) + + +uploads_subquery = ( + music_models.Upload.objects.filter(track_id=OuterRef("pk")) + .order_by() + .values("track_id") + .annotate(track_count=Count("track_id")) + .values("track_count") +) + + +class ManageTrackViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + queryset = ( + music_models.Track.objects.all() + .order_by("-id") + .select_related("attributed_to", "artist", "album__artist") + .annotate(uploads_count=Coalesce(Subquery(uploads_subquery), 0)) + ) + serializer_class = serializers.ManageTrackSerializer + filterset_class = filters.ManageTrackFilterSet + required_scope = "instance:libraries" + ordering_fields = [ + "creation_date", + "title", + "album__release_date", + "position", + "disc_number", + ] + + @rest_decorators.action(methods=["get"], detail=True) + def stats(self, request, *args, **kwargs): + track = self.get_object() + data = get_stats(track.__class__.objects.filter(pk=track.pk), track) + return response.Response(data, status=200) + + @rest_decorators.action(methods=["post"], detail=False) + def action(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = serializers.ManageTrackActionSerializer( + request.data, queryset=queryset + ) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return response.Response(result, status=200) + + +uploads_subquery = ( + music_models.Upload.objects.filter(library_id=OuterRef("pk")) + .order_by() + .values("library_id") + .annotate(library_count=Count("library_id")) + .values("library_count") +) + +follows_subquery = ( + federation_models.LibraryFollow.objects.filter(target_id=OuterRef("pk")) + .order_by() + .values("target_id") + .annotate(library_count=Count("target_id")) + .values("library_count") +) + + +class ManageLibraryViewSet( + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + lookup_field = "uuid" + queryset = ( + music_models.Library.objects.all() + .order_by("-id") + .select_related("actor") + .annotate( + followers_count=Coalesce(Subquery(follows_subquery), 0), + _uploads_count=Coalesce(Subquery(uploads_subquery), 0), + ) + ) + serializer_class = serializers.ManageLibrarySerializer + filterset_class = filters.ManageLibraryFilterSet + required_scope = "instance:libraries" + + @rest_decorators.action(methods=["get"], detail=True) + def stats(self, request, *args, **kwargs): + library = self.get_object() + uploads = library.uploads.all() + tracks = uploads.values_list("track", flat=True).distinct() + albums = ( + music_models.Track.objects.filter(pk__in=tracks) + .values_list("album", flat=True) + .distinct() + ) + artists = set( + music_models.Album.objects.filter(pk__in=albums).values_list( + "artist", flat=True + ) + ) | set( + music_models.Track.objects.filter(pk__in=tracks).values_list( + "artist", flat=True + ) + ) + + data = { + "uploads": uploads.count(), + "followers": library.received_follows.count(), + "tracks": tracks.count(), + "albums": albums.count(), + "artists": len(artists), + } + data.update(get_media_stats(uploads.all())) + return response.Response(data, status=200) + + @rest_decorators.action(methods=["post"], detail=False) + def action(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = serializers.ManageTrackActionSerializer( + request.data, queryset=queryset + ) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return response.Response(result, status=200) + + class ManageUploadViewSet( - mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, ): + lookup_field = "uuid" queryset = ( music_models.Upload.objects.all() - .select_related("track__artist", "track__album__artist") .order_by("-id") + .select_related("library__actor", "track__artist", "track__album__artist") ) serializer_class = serializers.ManageUploadSerializer filterset_class = filters.ManageUploadFilterSet - permission_classes = (HasUserPermission,) - required_permissions = ["library"] - ordering_fields = [ - "accessed_date", - "modification_date", - "creation_date", - "track__artist__name", - "bitrate", - "size", - "duration", - ] + required_scope = "instance:libraries" @rest_decorators.action(methods=["post"], detail=False) def action(self, request, *args, **kwargs): @@ -55,8 +294,7 @@ class ManageUserViewSet( queryset = users_models.User.objects.all().order_by("-id") serializer_class = serializers.ManageUserSerializer filterset_class = filters.ManageUserFilterSet - permission_classes = (HasUserPermission,) - required_permissions = ["settings"] + required_scope = "instance:users" ordering_fields = ["date_joined", "last_activity", "username"] def get_serializer_context(self): @@ -80,8 +318,7 @@ class ManageInvitationViewSet( ) serializer_class = serializers.ManageInvitationSerializer filterset_class = filters.ManageInvitationFilterSet - permission_classes = (HasUserPermission,) - required_permissions = ["settings"] + required_scope = "instance:invitations" ordering_fields = ["creation_date", "expiration_date"] def perform_create(self, serializer): @@ -114,8 +351,7 @@ class ManageDomainViewSet( ) serializer_class = serializers.ManageDomainSerializer filterset_class = filters.ManageDomainFilterSet - permission_classes = (HasUserPermission,) - required_permissions = ["moderation"] + required_scope = "instance:domains" ordering_fields = [ "name", "creation_date", @@ -125,6 +361,10 @@ class ManageDomainViewSet( "instance_policy", ] + def perform_create(self, serializer): + domain = serializer.save() + federation_tasks.update_domain_nodeinfo(domain_name=domain.name) + @rest_decorators.action(methods=["get"], detail=True) def nodeinfo(self, request, *args, **kwargs): domain = self.get_object() @@ -153,7 +393,7 @@ class ManageActorViewSet( ) serializer_class = serializers.ManageActorSerializer filterset_class = filters.ManageActorFilterSet - permission_classes = (HasUserPermission,) + required_scope = "instance:accounts" required_permissions = ["moderation"] ordering_fields = [ "name", @@ -199,8 +439,7 @@ class ManageInstancePolicyViewSet( ) serializer_class = serializers.ManageInstancePolicySerializer filterset_class = filters.ManageInstancePolicyFilterSet - permission_classes = (HasUserPermission,) - required_permissions = ["moderation"] + required_scope = "instance:policies" ordering_fields = ["id", "creation_date"] def perform_create(self, serializer): diff --git a/api/funkwhale_api/moderation/admin.py b/api/funkwhale_api/moderation/admin.py index 5e421255ed344d61335edbb588f7792d07ac21ec..9f8340030e3aa4108ee7f63a87b0c2f8a50e85d9 100644 --- a/api/funkwhale_api/moderation/admin.py +++ b/api/funkwhale_api/moderation/admin.py @@ -28,3 +28,10 @@ class InstancePolicyAdmin(admin.ModelAdmin): "summary", ] list_select_related = True + + +@admin.register(models.UserFilter) +class UserFilterAdmin(admin.ModelAdmin): + list_display = ["uuid", "user", "target_artist", "creation_date"] + search_fields = ["target_artist__name", "user__username", "user__email"] + list_select_related = True diff --git a/api/funkwhale_api/moderation/factories.py b/api/funkwhale_api/moderation/factories.py index aba5256c9cba399cf2e345101eb0d9958f1b13f4..8829caa2bacf60b444b18984c71b24b5f8b788d0 100644 --- a/api/funkwhale_api/moderation/factories.py +++ b/api/funkwhale_api/moderation/factories.py @@ -2,6 +2,8 @@ import factory from funkwhale_api.factories import registry, NoUpdateOnCreate from funkwhale_api.federation import factories as federation_factories +from funkwhale_api.music import factories as music_factories +from funkwhale_api.users import factories as users_factories @registry.register @@ -21,3 +23,17 @@ class InstancePolicyFactory(NoUpdateOnCreate, factory.DjangoModelFactory): for_actor = factory.Trait( target_actor=factory.SubFactory(federation_factories.ActorFactory) ) + + +@registry.register +class UserFilterFactory(NoUpdateOnCreate, factory.DjangoModelFactory): + user = factory.SubFactory(users_factories.UserFactory) + target_artist = None + + class Meta: + model = "moderation.UserFilter" + + class Params: + for_artist = factory.Trait( + target_artist=factory.SubFactory(music_factories.ArtistFactory) + ) diff --git a/api/funkwhale_api/moderation/filters.py b/api/funkwhale_api/moderation/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..ddf183045869bd01eeb71e2f3cfe80341e57e244 --- /dev/null +++ b/api/funkwhale_api/moderation/filters.py @@ -0,0 +1,69 @@ +from django.db.models import Q + +from django_filters import rest_framework as filters + + +USER_FILTER_CONFIG = { + "ARTIST": {"target_artist": ["pk"]}, + "ALBUM": {"target_artist": ["artist__pk"]}, + "TRACK": {"target_artist": ["artist__pk", "album__artist__pk"]}, + "LISTENING": {"target_artist": ["track__album__artist__pk", "track__artist__pk"]}, + "TRACK_FAVORITE": { + "target_artist": ["track__album__artist__pk", "track__artist__pk"] + }, +} + + +def get_filtered_content_query(config, user): + final_query = None + for filter_field, model_fields in config.items(): + query = None + ids = user.content_filters.values_list(filter_field, flat=True) + for model_field in model_fields: + q = Q(**{"{}__in".format(model_field): ids}) + if query: + query |= q + else: + query = q + + final_query = query + return final_query + + +class HiddenContentFilterSet(filters.FilterSet): + """ + A filterset that include a "hidden" param: + - hidden=true : list user hidden/filtered objects + - hidden=false : list all objects user hidden/filtered objects + - not specified: hidden=false + + Usage: + + class MyFilterSet(HiddenContentFilterSet): + class Meta: + hidden_content_fields_mapping = {'target_artist': ['pk']} + + Will map UserContentFilter.artist values to the pk field of the filtered model. + + """ + + hidden = filters.BooleanFilter(field_name="_", method="filter_hidden_content") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.data = self.data.copy() + self.data.setdefault("hidden", False) + + def filter_hidden_content(self, queryset, name, value): + user = self.request.user + if not user.is_authenticated: + # no filter to apply + return queryset + + config = self.__class__.Meta.hidden_content_fields_mapping + final_query = get_filtered_content_query(config, user) + + if value is True: + return queryset.filter(final_query) + else: + return queryset.exclude(final_query) diff --git a/api/funkwhale_api/moderation/migrations/0002_auto_20190213_0927.py b/api/funkwhale_api/moderation/migrations/0002_auto_20190213_0927.py new file mode 100644 index 0000000000000000000000000000000000000000..2832a34ef669b2d10aec088d6bae4347bb88091b --- /dev/null +++ b/api/funkwhale_api/moderation/migrations/0002_auto_20190213_0927.py @@ -0,0 +1,57 @@ +# Generated by Django 2.1.5 on 2019-02-13 09:27 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("music", "0037_auto_20190103_1757"), + ("moderation", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="UserFilter", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("uuid", models.UUIDField(default=uuid.uuid4, unique=True)), + ( + "creation_date", + models.DateTimeField(default=django.utils.timezone.now), + ), + ( + "target_artist", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="user_filters", + to="music.Artist", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="content_filters", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AlterUniqueTogether( + name="userfilter", unique_together={("user", "target_artist")} + ), + ] diff --git a/api/funkwhale_api/moderation/models.py b/api/funkwhale_api/moderation/models.py index c184bbda8117929da26940a1197b36c6cb75f6f4..7ade5d05a1e5a1ff0bfb0e259b5770b100a77489 100644 --- a/api/funkwhale_api/moderation/models.py +++ b/api/funkwhale_api/moderation/models.py @@ -73,3 +73,22 @@ class InstancePolicy(models.Model): return {"type": "actor", "obj": self.target_actor} if self.target_domain_id: return {"type": "domain", "obj": self.target_domain} + + +class UserFilter(models.Model): + uuid = models.UUIDField(default=uuid.uuid4, unique=True) + creation_date = models.DateTimeField(default=timezone.now) + target_artist = models.ForeignKey( + "music.Artist", on_delete=models.CASCADE, related_name="user_filters" + ) + user = models.ForeignKey( + "users.User", on_delete=models.CASCADE, related_name="content_filters" + ) + + class Meta: + unique_together = ("user", "target_artist") + + @property + def target(self): + if self.target_artist: + return {"type": "artist", "obj": self.target_artist} diff --git a/api/funkwhale_api/moderation/serializers.py b/api/funkwhale_api/moderation/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..20c34242102d4f302ac29a9781a059c08fef2fb9 --- /dev/null +++ b/api/funkwhale_api/moderation/serializers.py @@ -0,0 +1,45 @@ +from rest_framework import serializers + +from funkwhale_api.music import models as music_models +from . import models + + +class FilteredArtistSerializer(serializers.ModelSerializer): + class Meta: + model = music_models.Artist + fields = ["id", "name"] + + +class TargetSerializer(serializers.Serializer): + type = serializers.ChoiceField(choices=["artist"]) + id = serializers.CharField() + + def to_representation(self, value): + if value["type"] == "artist": + data = FilteredArtistSerializer(value["obj"]).data + data.update({"type": "artist"}) + return data + + def to_internal_value(self, value): + if value["type"] == "artist": + field = serializers.PrimaryKeyRelatedField( + queryset=music_models.Artist.objects.all() + ) + value["obj"] = field.to_internal_value(value["id"]) + return value + + +class UserFilterSerializer(serializers.ModelSerializer): + target = TargetSerializer() + + class Meta: + model = models.UserFilter + fields = ["uuid", "target", "creation_date"] + read_only_fields = ["uuid", "creation_date"] + + def validate(self, data): + target = data.pop("target") + if target["type"] == "artist": + data["target_artist"] = target["obj"] + + return data diff --git a/api/funkwhale_api/moderation/urls.py b/api/funkwhale_api/moderation/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..05d2e7a9223774db0329f66ae295255cb88d9b7a --- /dev/null +++ b/api/funkwhale_api/moderation/urls.py @@ -0,0 +1,8 @@ +from rest_framework import routers + +from . import views + +router = routers.SimpleRouter() +router.register(r"content-filters", views.UserFilterViewSet, "content-filters") + +urlpatterns = router.urls diff --git a/api/funkwhale_api/moderation/views.py b/api/funkwhale_api/moderation/views.py new file mode 100644 index 0000000000000000000000000000000000000000..4d4e3e039abdd68858fe96df79ad965a02e49771 --- /dev/null +++ b/api/funkwhale_api/moderation/views.py @@ -0,0 +1,41 @@ +from django.db import IntegrityError + +from rest_framework import mixins +from rest_framework import response +from rest_framework import status +from rest_framework import viewsets + +from . import models +from . import serializers + + +class UserFilterViewSet( + mixins.ListModelMixin, + mixins.CreateModelMixin, + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + viewsets.GenericViewSet, +): + lookup_field = "uuid" + queryset = ( + models.UserFilter.objects.all() + .order_by("-creation_date") + .select_related("target_artist") + ) + serializer_class = serializers.UserFilterSerializer + required_scope = "filters" + ordering_fields = ("creation_date",) + + def create(self, request, *args, **kwargs): + try: + return super().create(request, *args, **kwargs) + except IntegrityError: + content = {"detail": "A content filter already exists for this object"} + return response.Response(content, status=status.HTTP_400_BAD_REQUEST) + + def get_queryset(self): + qs = super().get_queryset() + return qs.filter(user=self.request.user) + + def perform_create(self, serializer): + serializer.save(user=self.request.user) diff --git a/api/funkwhale_api/music/admin.py b/api/funkwhale_api/music/admin.py index b2f001527f3285b24958fd55ce9f1fdd98262984..584653ab9ae7d76d3a959f40f72ef6e700a33667 100644 --- a/api/funkwhale_api/music/admin.py +++ b/api/funkwhale_api/music/admin.py @@ -39,22 +39,6 @@ class ImportJobAdmin(admin.ModelAdmin): list_filter = ["status"] -@admin.register(models.Work) -class WorkAdmin(admin.ModelAdmin): - list_display = ["title", "mbid", "language", "nature"] - list_select_related = True - search_fields = ["title"] - list_filter = ["language", "nature"] - - -@admin.register(models.Lyrics) -class LyricsAdmin(admin.ModelAdmin): - list_display = ["url", "id", "url"] - list_select_related = True - search_fields = ["url", "work__title"] - list_filter = ["work__language"] - - @admin.register(models.Upload) class UploadAdmin(admin.ModelAdmin): list_display = [ diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py index cd2a91ccb1d72766493b8998189ea8bc58e654e4..430c82439805957c007c1a3c80aa8d02e2dcefb8 100644 --- a/api/funkwhale_api/music/factories.py +++ b/api/funkwhale_api/music/factories.py @@ -64,6 +64,12 @@ class ArtistFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): class Meta: model = "music.Artist" + class Params: + attributed = factory.Trait( + attributed_to=factory.SubFactory(federation_factories.ActorFactory) + ) + local = factory.Trait(fid=factory.Faker("federation_url", local=True)) + @registry.register class AlbumFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): @@ -79,6 +85,15 @@ class AlbumFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): class Meta: model = "music.Album" + class Params: + attributed = factory.Trait( + attributed_to=factory.SubFactory(federation_factories.ActorFactory) + ) + + local = factory.Trait( + fid=factory.Faker("federation_url", local=True), artist__local=True + ) + @registry.register class TrackFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): @@ -94,6 +109,15 @@ class TrackFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): class Meta: model = "music.Track" + class Params: + attributed = factory.Trait( + attributed_to=factory.SubFactory(federation_factories.ActorFactory) + ) + + local = factory.Trait( + fid=factory.Faker("federation_url", local=True), album__local=True + ) + @factory.post_generation def license(self, created, extracted, **kwargs): if not created: @@ -140,27 +164,6 @@ class UploadVersionFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): model = "music.UploadVersion" -@registry.register -class WorkFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): - mbid = factory.Faker("uuid4") - language = "eng" - nature = "song" - title = factory.Faker("sentence", nb_words=3) - - class Meta: - model = "music.Work" - - -@registry.register -class LyricsFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): - work = factory.SubFactory(WorkFactory) - url = factory.Faker("url") - content = factory.Faker("paragraphs", nb=4) - - class Meta: - model = "music.Lyrics" - - @registry.register class TagFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): name = factory.SelfAttribute("slug") diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py index 76bc93b6776c8b4c1ae294381b448fb47efbe9b5..fa5a10f6d4397ed296d8710d6b76d6dc6646ec28 100644 --- a/api/funkwhale_api/music/filters.py +++ b/api/funkwhale_api/music/filters.py @@ -1,13 +1,15 @@ from django_filters import rest_framework as filters from funkwhale_api.common import fields +from funkwhale_api.common import filters as common_filters from funkwhale_api.common import search +from funkwhale_api.moderation import filters as moderation_filters from . import models from . import utils -class ArtistFilter(filters.FilterSet): +class ArtistFilter(moderation_filters.HiddenContentFilterSet): q = fields.SearchFilter(search_fields=["name"]) playable = filters.BooleanFilter(field_name="_", method="filter_playable") @@ -17,25 +19,29 @@ class ArtistFilter(filters.FilterSet): "name": ["exact", "iexact", "startswith", "icontains"], "playable": "exact", } + hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ARTIST"] def filter_playable(self, queryset, name, value): actor = utils.get_actor_from_request(self.request) return queryset.playable_by(actor, value) -class TrackFilter(filters.FilterSet): +class TrackFilter(moderation_filters.HiddenContentFilterSet): q = fields.SearchFilter(search_fields=["title", "album__title", "artist__name"]) playable = filters.BooleanFilter(field_name="_", method="filter_playable") + id = common_filters.MultipleQueryFilter(coerce=int) class Meta: model = models.Track fields = { "title": ["exact", "iexact", "startswith", "icontains"], "playable": ["exact"], + "id": ["exact"], "artist": ["exact"], "album": ["exact"], "license": ["exact"], } + hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["TRACK"] def filter_playable(self, queryset, name, value): actor = utils.get_actor_from_request(self.request) @@ -85,13 +91,14 @@ class UploadFilter(filters.FilterSet): return queryset.playable_by(actor, value) -class AlbumFilter(filters.FilterSet): +class AlbumFilter(moderation_filters.HiddenContentFilterSet): playable = filters.BooleanFilter(field_name="_", method="filter_playable") - q = fields.SearchFilter(search_fields=["title", "artist__name" "source"]) + q = fields.SearchFilter(search_fields=["title", "artist__name"]) class Meta: model = models.Album fields = ["playable", "q", "artist"] + hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ALBUM"] def filter_playable(self, queryset, name, value): actor = utils.get_actor_from_request(self.request) diff --git a/api/funkwhale_api/music/importers.py b/api/funkwhale_api/music/importers.py index 28763a4951386ac458ce554dd141f654c1f1040c..add3993c8f749f3cf29da0f6b3cbce7ed1503937 100644 --- a/api/funkwhale_api/music/importers.py +++ b/api/funkwhale_api/music/importers.py @@ -47,4 +47,4 @@ class Mapping(object): ) -registry = {"Artist": Importer, "Track": Importer, "Album": Importer, "Work": Importer} +registry = {"Artist": Importer, "Track": Importer, "Album": Importer} diff --git a/api/funkwhale_api/music/lyrics.py b/api/funkwhale_api/music/lyrics.py deleted file mode 100644 index 6d5f20e44d54320db9ebc4fb5b344de6585ba6cf..0000000000000000000000000000000000000000 --- a/api/funkwhale_api/music/lyrics.py +++ /dev/null @@ -1,31 +0,0 @@ -import urllib.request - -from bs4 import BeautifulSoup - - -def _get_html(url): - with urllib.request.urlopen(url) as response: - html = response.read() - return html.decode("utf-8") - - -def extract_content(html): - soup = BeautifulSoup(html, "html.parser") - return soup.find_all("div", class_="lyricbox")[0].contents - - -def clean_content(contents): - final_content = "" - for e in contents: - if e == "\n": - continue - if e.name == "script": - continue - if e.name == "br": - final_content += "\n" - continue - try: - final_content += e.text - except AttributeError: - final_content += str(e) - return final_content diff --git a/api/funkwhale_api/music/management/commands/check_inplace_files.py b/api/funkwhale_api/music/management/commands/check_inplace_files.py new file mode 100644 index 0000000000000000000000000000000000000000..f274ee589086f5446ef83c7791c33688c072d014 --- /dev/null +++ b/api/funkwhale_api/music/management/commands/check_inplace_files.py @@ -0,0 +1,76 @@ +import os +from argparse import RawTextHelpFormatter + +from django.core.management.base import BaseCommand + +from django.db import transaction + +from funkwhale_api.music import models + + +def progress(buffer, count, total, status=""): + bar_len = 60 + filled_len = int(round(bar_len * count / float(total))) + + bar = "=" * filled_len + "-" * (bar_len - filled_len) + + buffer.write("[%s] %s/%s ...%s\r" % (bar, count, total, status)) + buffer.flush() + + +class Command(BaseCommand): + help = """ + Loop through all in-place imported files in the database, and verify + that the corresponding files are present on the filesystem. If some files are not + found and --no-dry-run is specified, the corresponding database objects will be deleted. + """ + + def create_parser(self, *args, **kwargs): + parser = super().create_parser(*args, **kwargs) + parser.formatter_class = RawTextHelpFormatter + return parser + + def add_arguments(self, parser): + parser.add_argument( + "--no-dry-run", + action="store_false", + dest="dry_run", + default=True, + help="Disable dry run mode and apply pruning for real on the database", + ) + + @transaction.atomic + def handle(self, *args, **options): + candidates = models.Upload.objects.filter(source__startswith="file://") + candidates = candidates.filter(audio_file__in=["", None]) + total = candidates.count() + self.stdout.write("Checking {} in-place imported files…".format(total)) + + missing = [] + for i, row in enumerate(candidates.values("id", "source")): + path = row["source"].replace("file://", "") + progress(self.stdout, i + 1, total) + if not os.path.exists(path): + missing.append((path, row["id"])) + + if missing: + for path, _ in missing: + self.stdout.write(" {}".format(path)) + self.stdout.write( + "The previous {} paths are referenced in database, but not found on disk!".format( + len(missing) + ) + ) + + else: + self.stdout.write("All in-place imports have a matching on-disk file") + return + + to_delete = candidates.filter(pk__in=[id for _, id in missing]) + if options["dry_run"]: + self.stdout.write( + "Nothing was deleted, rerun this command with --no-dry-run to apply the changes" + ) + else: + self.stdout.write("Deleting {} uploads…".format(to_delete.count())) + to_delete.delete() diff --git a/api/funkwhale_api/music/management/commands/prune_library.py b/api/funkwhale_api/music/management/commands/prune_library.py new file mode 100644 index 0000000000000000000000000000000000000000..e06ee0fdb6581ed593c1ab1b08bc6b7da3294316 --- /dev/null +++ b/api/funkwhale_api/music/management/commands/prune_library.py @@ -0,0 +1,145 @@ +from argparse import RawTextHelpFormatter + +from django.core.management.base import BaseCommand +from django.core.management.base import CommandError + +from django.db import transaction + +from funkwhale_api.music import models, tasks + + +class Command(BaseCommand): + help = """ + Remove tracks, albums and artists that are not associated with any file from the instance library: + + - Tracks without uploads are deleted, if the --tracks flag is passed + - Albums without tracks are deleted, if the --albums flag is passed + - Artists without albums are deleted, if the --artists flag is passed + + Tracks with associated favorites, playlists or listening won't be deleted + by default, unless you pass the corresponding --ignore-* flags. + + """ + + def create_parser(self, *args, **kwargs): + parser = super().create_parser(*args, **kwargs) + parser.formatter_class = RawTextHelpFormatter + return parser + + def add_arguments(self, parser): + parser.add_argument( + "--no-dry-run", + action="store_false", + dest="dry_run", + default=True, + help="Disable dry run mode and apply pruning for real on the database", + ) + parser.add_argument( + "--artists", + action="store_true", + dest="prune_artists", + default=False, + help="Prune artists without albums/tracks", + ) + parser.add_argument( + "--albums", + action="store_true", + dest="prune_albums", + default=False, + help="Prune albums without tracks", + ) + parser.add_argument( + "--tracks", + action="store_true", + dest="prune_tracks", + default=False, + help="Prune tracks without uploads", + ) + + parser.add_argument( + "--ignore-favorites", + action="store_false", + dest="exclude_favorites", + default=True, + help="Allow favorited tracks to be pruned", + ) + + parser.add_argument( + "--ignore-playlists", + action="store_false", + dest="exclude_playlists", + default=True, + help="Allow tracks included in playlists to be pruned", + ) + + parser.add_argument( + "--ignore-listenings", + action="store_false", + dest="exclude_listenings", + default=True, + help="Allow tracks with listening history to be pruned", + ) + + @transaction.atomic + def handle(self, *args, **options): + if not any( + [options["prune_albums"], options["prune_tracks"], options["prune_artists"]] + ): + raise CommandError( + "You need to provide at least one of the --tracks, --albums or --artists flags" + ) + + if options["dry_run"]: + self.stdout.write("Dry-run on, will not commit anything") + else: + self.stdout.write("Dry-run off, *pruning for real*") + self.stdout.write("") + if options["prune_tracks"]: + prunable = tasks.get_prunable_tracks( + exclude_favorites=options["exclude_favorites"], + exclude_playlists=options["exclude_playlists"], + exclude_listenings=options["exclude_listenings"], + ) + pruned_total = prunable.count() + total = models.Track.objects.count() + if options["dry_run"]: + self.stdout.write( + "Would prune {}/{} tracks".format(pruned_total, total) + ) + else: + self.stdout.write("Deleting {}/{} tracks…".format(pruned_total, total)) + prunable.delete() + + if options["prune_albums"]: + prunable = tasks.get_prunable_albums() + pruned_total = prunable.count() + total = models.Album.objects.count() + if options["dry_run"]: + self.stdout.write( + "Would prune {}/{} albums".format(pruned_total, total) + ) + else: + self.stdout.write("Deleting {}/{} albums…".format(pruned_total, total)) + prunable.delete() + + if options["prune_artists"]: + prunable = tasks.get_prunable_artists() + pruned_total = prunable.count() + total = models.Artist.objects.count() + if options["dry_run"]: + self.stdout.write( + "Would prune {}/{} artists".format(pruned_total, total) + ) + else: + self.stdout.write("Deleting {}/{} artists…".format(pruned_total, total)) + prunable.delete() + + self.stdout.write("") + if options["dry_run"]: + self.stdout.write( + "Nothing was pruned, rerun this command with --no-dry-run to apply the changes" + ) + else: + self.stdout.write("Pruning completed!") + + self.stdout.write("") diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py index 7a105e432ca441783f9778dbad6c5858aeb7f54b..387b8ffe7de425ecca879999e39d671d619caff8 100644 --- a/api/funkwhale_api/music/metadata.py +++ b/api/funkwhale_api/music/metadata.py @@ -8,7 +8,8 @@ import mutagen.oggtheora import mutagen.oggvorbis import mutagen.flac -from django import forms +from rest_framework import serializers +from rest_framework.compat import Mapping logger = logging.getLogger(__name__) NODEFAULT = object() @@ -122,85 +123,23 @@ def get_mp3_recording_id(f, k): raise TagNotFound(k) -def convert_position(v): - try: - return int(v) - except ValueError: - # maybe the position is of the form "1/4" - pass - - try: - return int(v.split("/")[0]) - except (ValueError, AttributeError, IndexError): - pass - - -class FirstUUIDField(forms.UUIDField): - def to_python(self, value): - try: - # sometimes, Picard leaves two uuids in the field, separated - # by a slash or a ; - value = value.split(";")[0].split("/")[0].strip() - except (AttributeError, IndexError, TypeError): - pass - - return super().to_python(value) - - -def get_date(value): - ADDITIONAL_FORMATS = ["%Y-%d-%m %H:%M"] # deezer date format - try: - parsed = pendulum.parse(str(value)) - return datetime.date(parsed.year, parsed.month, parsed.day) - except pendulum.exceptions.ParserError: - pass - - for date_format in ADDITIONAL_FORMATS: - try: - parsed = datetime.datetime.strptime(value, date_format) - except ValueError: - continue - else: - return datetime.date(parsed.year, parsed.month, parsed.day) - - raise ParseError("{} cannot be parsed as a date".format(value)) - - -def split_and_return_first(separator): - def inner(v): - return v.split(separator)[0].strip() - - return inner - - -VALIDATION = { - "musicbrainz_artistid": FirstUUIDField(), - "musicbrainz_albumid": FirstUUIDField(), - "musicbrainz_recordingid": FirstUUIDField(), - "musicbrainz_albumartistid": FirstUUIDField(), -} +VALIDATION = {} CONF = { "OggOpus": { "getter": lambda f, k: f[k][0], "fields": { - "track_number": { - "field": "TRACKNUMBER", - "to_application": convert_position, - }, - "disc_number": {"field": "DISCNUMBER", "to_application": convert_position}, + "position": {"field": "TRACKNUMBER"}, + "disc_number": {"field": "DISCNUMBER"}, "title": {}, "artist": {}, - "album_artist": { - "field": "albumartist", - "to_application": split_and_return_first(";"), - }, + "album_artist": {"field": "albumartist"}, "album": {}, - "date": {"field": "date", "to_application": get_date}, + "date": {"field": "date"}, "musicbrainz_albumid": {}, "musicbrainz_artistid": {}, "musicbrainz_albumartistid": {}, - "musicbrainz_recordingid": {"field": "musicbrainz_trackid"}, + "mbid": {"field": "musicbrainz_trackid"}, "license": {}, "copyright": {}, }, @@ -208,23 +147,17 @@ CONF = { "OggVorbis": { "getter": lambda f, k: f[k][0], "fields": { - "track_number": { - "field": "TRACKNUMBER", - "to_application": convert_position, - }, - "disc_number": {"field": "DISCNUMBER", "to_application": convert_position}, + "position": {"field": "TRACKNUMBER"}, + "disc_number": {"field": "DISCNUMBER"}, "title": {}, "artist": {}, - "album_artist": { - "field": "albumartist", - "to_application": split_and_return_first(";"), - }, + "album_artist": {"field": "albumartist"}, "album": {}, - "date": {"field": "date", "to_application": get_date}, + "date": {"field": "date"}, "musicbrainz_albumid": {}, "musicbrainz_artistid": {}, "musicbrainz_albumartistid": {}, - "musicbrainz_recordingid": {"field": "musicbrainz_trackid"}, + "mbid": {"field": "musicbrainz_trackid"}, "license": {}, "copyright": {}, "pictures": { @@ -236,20 +169,17 @@ CONF = { "OggTheora": { "getter": lambda f, k: f[k][0], "fields": { - "track_number": { - "field": "TRACKNUMBER", - "to_application": convert_position, - }, - "disc_number": {"field": "DISCNUMBER", "to_application": convert_position}, + "position": {"field": "TRACKNUMBER"}, + "disc_number": {"field": "DISCNUMBER"}, "title": {}, "artist": {}, "album_artist": {"field": "albumartist"}, "album": {}, - "date": {"field": "date", "to_application": get_date}, + "date": {"field": "date"}, "musicbrainz_albumid": {"field": "MusicBrainz Album Id"}, "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"}, "musicbrainz_albumartistid": {"field": "MusicBrainz Album Artist Id"}, - "musicbrainz_recordingid": {"field": "MusicBrainz Track Id"}, + "mbid": {"field": "MusicBrainz Track Id"}, "license": {}, "copyright": {}, }, @@ -258,20 +188,17 @@ CONF = { "getter": get_id3_tag, "clean_pictures": clean_id3_pictures, "fields": { - "track_number": {"field": "TRCK", "to_application": convert_position}, - "disc_number": {"field": "TPOS", "to_application": convert_position}, + "position": {"field": "TRCK"}, + "disc_number": {"field": "TPOS"}, "title": {"field": "TIT2"}, "artist": {"field": "TPE1"}, "album_artist": {"field": "TPE2"}, "album": {"field": "TALB"}, - "date": {"field": "TDRC", "to_application": get_date}, + "date": {"field": "TDRC"}, "musicbrainz_albumid": {"field": "MusicBrainz Album Id"}, "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"}, "musicbrainz_albumartistid": {"field": "MusicBrainz Album Artist Id"}, - "musicbrainz_recordingid": { - "field": "UFID", - "getter": get_mp3_recording_id, - }, + "mbid": {"field": "UFID", "getter": get_mp3_recording_id}, "pictures": {}, "license": {"field": "WCOP"}, "copyright": {"field": "TCOP"}, @@ -281,20 +208,17 @@ CONF = { "getter": get_flac_tag, "clean_pictures": clean_flac_pictures, "fields": { - "track_number": { - "field": "tracknumber", - "to_application": convert_position, - }, - "disc_number": {"field": "discnumber", "to_application": convert_position}, + "position": {"field": "tracknumber"}, + "disc_number": {"field": "discnumber"}, "title": {}, "artist": {}, "album_artist": {"field": "albumartist"}, "album": {}, - "date": {"field": "date", "to_application": get_date}, + "date": {"field": "date"}, "musicbrainz_albumid": {}, "musicbrainz_artistid": {}, "musicbrainz_albumartistid": {}, - "musicbrainz_recordingid": {"field": "musicbrainz_trackid"}, + "mbid": {"field": "musicbrainz_trackid"}, "test": {}, "pictures": {}, "license": {}, @@ -304,7 +228,7 @@ CONF = { } ALL_FIELDS = [ - "track_number", + "position", "disc_number", "title", "artist", @@ -314,13 +238,13 @@ ALL_FIELDS = [ "musicbrainz_albumid", "musicbrainz_artistid", "musicbrainz_albumartistid", - "musicbrainz_recordingid", + "mbid", "license", "copyright", ] -class Metadata(object): +class Metadata(Mapping): def __init__(self, filething, kind=mutagen.File): self._file = kind(filething) if self._file is None: @@ -368,6 +292,21 @@ class Metadata(object): else: return self.fallback.get(key, default=default) + def all(self): + """ + Return a dict with all support metadata fields, if they are available + """ + final = {} + for field in self._conf["fields"]: + if field in ["pictures"]: + continue + value = self.get(field, None) + if value is None: + continue + final[field] = str(value) + + return final + def _get_from_self(self, key, default=NODEFAULT): try: field_conf = self._conf["fields"][key] @@ -390,25 +329,6 @@ class Metadata(object): v = field.to_python(v) return v - def all(self, ignore_parse_errors=True): - """ - Return a dict containing all metadata of the file - """ - - data = {} - for field in ALL_FIELDS: - try: - data[field] = self.get(field, None) - except (TagNotFound, forms.ValidationError): - data[field] = None - except ParseError as e: - if not ignore_parse_errors: - raise - logger.warning("Unparsable field {}: {}".format(field, str(e))) - data[field] = None - - return data - def get_picture(self, *picture_types): if not picture_types: raise ValueError("You need to request at least one picture type") @@ -430,3 +350,192 @@ class Metadata(object): for p in pictures: if p["type"] == ptype: return p + + def __getitem__(self, key): + return self.get(key) + + def __len__(self): + return 1 + + def __iter__(self): + for field in self._conf["fields"]: + yield field + + +class ArtistField(serializers.Field): + def __init__(self, *args, **kwargs): + self.for_album = kwargs.pop("for_album", False) + super().__init__(*args, **kwargs) + + def get_value(self, data): + if self.for_album: + keys = [("names", "album_artist"), ("mbids", "musicbrainz_albumartistid")] + else: + keys = [("names", "artist"), ("mbids", "musicbrainz_artistid")] + + final = {} + for field, key in keys: + final[field] = data.get(key, None) + + return final + + def to_internal_value(self, data): + # we have multiple values that can be separated by various separators + separators = [";"] + # we get a list like that if tagged via musicbrainz + # ae29aae4-abfb-4609-8f54-417b1f4d64cc; 3237b5a8-ae44-400c-aa6d-cea51f0b9074; + raw_mbids = data["mbids"] + used_separator = None + mbids = [raw_mbids] + if raw_mbids: + if "/" in raw_mbids: + # it's a featuring, we can't handle this now + mbids = [] + else: + for separator in separators: + if separator in raw_mbids: + used_separator = separator + mbids = [m.strip() for m in raw_mbids.split(separator)] + break + + # now, we split on artist names, using the same separator as the one used + # by mbids, if any + if used_separator and mbids: + names = [n.strip() for n in data["names"].split(used_separator)] + else: + names = [data["names"]] + + final = [] + for i, name in enumerate(names): + try: + mbid = mbids[i] + except IndexError: + mbid = None + artist = {"name": name, "mbid": mbid} + final.append(artist) + + field = serializers.ListField(child=ArtistSerializer(), min_length=1) + + return field.to_internal_value(final) + + +class AlbumField(serializers.Field): + def get_value(self, data): + return data + + def to_internal_value(self, data): + try: + title = data.get("album") + except TagNotFound: + raise serializers.ValidationError("Missing album tag") + final = { + "title": title, + "release_date": data.get("date", None), + "mbid": data.get("musicbrainz_albumid", None), + } + artists_field = ArtistField(for_album=True) + payload = artists_field.get_value(data) + try: + artists = artists_field.to_internal_value(payload) + except serializers.ValidationError as e: + artists = [] + logger.debug("Ignoring validation error on album artists: %s", e) + album_serializer = AlbumSerializer(data=final) + album_serializer.is_valid(raise_exception=True) + album_serializer.validated_data["artists"] = artists + return album_serializer.validated_data + + +class CoverDataField(serializers.Field): + def get_value(self, data): + return data + + def to_internal_value(self, data): + return data.get_picture("cover_front", "other") + + +class PermissiveDateField(serializers.CharField): + def to_internal_value(self, value): + if not value: + return None + value = super().to_internal_value(str(value)) + ADDITIONAL_FORMATS = [ + "%Y-%d-%m %H:%M", # deezer date format + "%Y-%W", # weird date format based on week number, see #718 + ] + + for date_format in ADDITIONAL_FORMATS: + try: + parsed = datetime.datetime.strptime(value, date_format) + except ValueError: + continue + else: + return datetime.date(parsed.year, parsed.month, parsed.day) + + try: + parsed = pendulum.parse(str(value)) + return datetime.date(parsed.year, parsed.month, parsed.day) + except pendulum.exceptions.ParserError: + pass + + return None + + +class ArtistSerializer(serializers.Serializer): + name = serializers.CharField() + mbid = serializers.UUIDField(required=False, allow_null=True) + + +class AlbumSerializer(serializers.Serializer): + title = serializers.CharField() + mbid = serializers.UUIDField(required=False, allow_null=True) + release_date = PermissiveDateField(required=False, allow_null=True) + + +class PositionField(serializers.CharField): + def to_internal_value(self, v): + v = super().to_internal_value(v) + if not v: + return v + + try: + return int(v) + except ValueError: + # maybe the position is of the form "1/4" + pass + + try: + return int(v.split("/")[0]) + except (ValueError, AttributeError, IndexError): + pass + + +class TrackMetadataSerializer(serializers.Serializer): + title = serializers.CharField() + position = PositionField(allow_null=True, required=False) + disc_number = PositionField(allow_null=True, required=False) + copyright = serializers.CharField(allow_null=True, required=False) + license = serializers.CharField(allow_null=True, required=False) + mbid = serializers.UUIDField(allow_null=True, required=False) + + album = AlbumField() + artists = ArtistField() + cover_data = CoverDataField() + + +class FakeMetadata(Mapping): + def __init__(self, data, picture=None): + self.data = data + self.picture = None + + def __getitem__(self, key): + return self.data[key] + + def __len__(self): + return len(self.data) + + def __iter__(self): + yield from self.data + + def get_picture(self, *args): + return self.picture diff --git a/api/funkwhale_api/music/migrations/0038_attributed_to.py b/api/funkwhale_api/music/migrations/0038_attributed_to.py new file mode 100644 index 0000000000000000000000000000000000000000..d1ac8cfd4c4f5c5106f483fa1da8ab11852c659b --- /dev/null +++ b/api/funkwhale_api/music/migrations/0038_attributed_to.py @@ -0,0 +1,48 @@ +# Generated by Django 2.1.7 on 2019-04-09 09:33 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("federation", "0017_auto_20190130_0926"), + ("music", "0037_auto_20190103_1757"), + ] + + operations = [ + migrations.AddField( + model_name="artist", + name="attributed_to", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="attributed_artists", + to="federation.Actor", + ), + ), + migrations.AddField( + model_name="album", + name="attributed_to", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="attributed_albums", + to="federation.Actor", + ), + ), + migrations.AddField( + model_name="track", + name="attributed_to", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="attributed_tracks", + to="federation.Actor", + ), + ), + ] diff --git a/api/funkwhale_api/music/migrations/0039_auto_20190423_0820.py b/api/funkwhale_api/music/migrations/0039_auto_20190423_0820.py new file mode 100644 index 0000000000000000000000000000000000000000..06ea1af3d8aeb397d3dc7bd3030972c9e40142a8 --- /dev/null +++ b/api/funkwhale_api/music/migrations/0039_auto_20190423_0820.py @@ -0,0 +1,31 @@ +# Generated by Django 2.1.7 on 2019-04-23 08:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('music', '0038_attributed_to'), + ] + + operations = [ + migrations.RemoveField( + model_name='lyrics', + name='work', + ), + migrations.RemoveField( + model_name='work', + name='from_activity', + ), + migrations.RemoveField( + model_name='track', + name='work', + ), + migrations.DeleteModel( + name='Lyrics', + ), + migrations.DeleteModel( + name='Work', + ), + ] diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 4ba8327179eebbe9f57628de37ab5faeb739ed5d..309eb1266dd5fd8db1446ff8981a89de4a1fecf9 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -3,9 +3,9 @@ import logging import mimetypes import os import tempfile +import urllib.parse import uuid -import markdown import pendulum import pydub from django.conf import settings @@ -24,6 +24,7 @@ from versatileimagefield.image_warmer import VersatileImageFieldWarmer from funkwhale_api import musicbrainz from funkwhale_api.common import fields +from funkwhale_api.common import models as common_models from funkwhale_api.common import session from funkwhale_api.common import utils as common_utils from funkwhale_api.federation import models as federation_models @@ -113,6 +114,18 @@ class APIModelMixin(models.Model): return super().save(**kwargs) + @property + def is_local(self): + return federation_utils.is_local(self.fid) + + @property + def domain_name(self): + if not self.fid: + return + + parsed = urllib.parse.urlparse(self.fid) + return parsed.hostname + class License(models.Model): code = models.CharField(primary_key=True, max_length=100) @@ -141,7 +154,7 @@ class License(models.Model): logger.warning("%s do not match any registered license", self.code) -class ArtistQuerySet(models.QuerySet): +class ArtistQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): def with_albums_count(self): return self.annotate(_albums_count=models.Count("albums")) @@ -177,6 +190,16 @@ class Artist(APIModelMixin): "mbid": {"musicbrainz_field_name": "id"}, "name": {"musicbrainz_field_name": "name"}, } + # Music entities are attributed to actors, to validate that updates occur + # from an authorized account. On top of that, we consider the instance actor + # can update anything under it's own domain + attributed_to = models.ForeignKey( + "federation.Actor", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="attributed_artists", + ) api = musicbrainz.api.artists objects = ArtistQuerySet.as_manager() @@ -215,7 +238,7 @@ def import_tracks(instance, cleaned_data, raw_data): importers.load(Track, track_cleaned_data, track_data, Track.import_hooks) -class AlbumQuerySet(models.QuerySet): +class AlbumQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): def with_tracks_count(self): return self.annotate(_tracks_count=models.Count("tracks")) @@ -253,6 +276,16 @@ class Album(APIModelMixin): TYPE_CHOICES = (("album", "Album"),) type = models.CharField(choices=TYPE_CHOICES, max_length=30, default="album") + # Music entities are attributed to actors, to validate that updates occur + # from an authorized account. On top of that, we consider the instance actor + # can update anything under it's own domain + attributed_to = models.ForeignKey( + "federation.Actor", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="attributed_albums", + ) api_includes = ["artist-credits", "recordings", "media", "release-groups"] api = musicbrainz.api.releases federation_namespace = "albums" @@ -313,6 +346,16 @@ class Album(APIModelMixin): def __str__(self): return self.title + @property + def cover_path(self): + if not self.cover: + return None + try: + return self.cover.path + except NotImplementedError: + # external storage + return self.cover.name + @property def tags(self): t = [] @@ -345,78 +388,7 @@ def import_album(v): return a -def link_recordings(instance, cleaned_data, raw_data): - tracks = [r["target"] for r in raw_data["recording-relation-list"]] - Track.objects.filter(mbid__in=tracks).update(work=instance) - - -def import_lyrics(instance, cleaned_data, raw_data): - try: - url = [ - url_data - for url_data in raw_data["url-relation-list"] - if url_data["type"] == "lyrics" - ][0]["target"] - except (IndexError, KeyError): - return - l, _ = Lyrics.objects.get_or_create(work=instance, url=url) - - return l - - -class Work(APIModelMixin): - language = models.CharField(max_length=20) - nature = models.CharField(max_length=50) - title = models.CharField(max_length=255) - - api = musicbrainz.api.works - api_includes = ["url-rels", "recording-rels"] - musicbrainz_model = "work" - federation_namespace = "works" - - musicbrainz_mapping = { - "mbid": {"musicbrainz_field_name": "id"}, - "title": {"musicbrainz_field_name": "title"}, - "language": {"musicbrainz_field_name": "language"}, - "nature": {"musicbrainz_field_name": "type", "converter": lambda v: v.lower()}, - } - import_hooks = [import_lyrics, link_recordings] - - def fetch_lyrics(self): - lyric = self.lyrics.first() - if lyric: - return lyric - data = self.api.get(self.mbid, includes=["url-rels"])["work"] - lyric = import_lyrics(self, {}, data) - - return lyric - - def get_federation_id(self): - if self.fid: - return self.fid - - return None - - -class Lyrics(models.Model): - uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4) - work = models.ForeignKey( - Work, related_name="lyrics", null=True, blank=True, on_delete=models.CASCADE - ) - url = models.URLField(unique=True) - content = models.TextField(null=True, blank=True) - - @property - def content_rendered(self): - return markdown.markdown( - self.content, - safe_mode=True, - enable_attributes=False, - extensions=["markdown.extensions.nl2br"], - ) - - -class TrackQuerySet(models.QuerySet): +class TrackQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): def for_nested_serialization(self): return self.select_related().select_related("album__artist", "artist") @@ -465,9 +437,6 @@ class Track(APIModelMixin): album = models.ForeignKey( Album, related_name="tracks", null=True, blank=True, on_delete=models.CASCADE ) - work = models.ForeignKey( - Work, related_name="tracks", null=True, blank=True, on_delete=models.CASCADE - ) license = models.ForeignKey( License, null=True, @@ -475,11 +444,21 @@ class Track(APIModelMixin): on_delete=models.DO_NOTHING, related_name="tracks", ) + # Music entities are attributed to actors, to validate that updates occur + # from an authorized account. On top of that, we consider the instance actor + # can update anything under it's own domain + attributed_to = models.ForeignKey( + "federation.Actor", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="attributed_tracks", + ) copyright = models.CharField(max_length=500, null=True, blank=True) federation_namespace = "tracks" musicbrainz_model = "recording" api = musicbrainz.api.recordings - api_includes = ["artist-credits", "releases", "media", "tags", "work-rels"] + api_includes = ["artist-credits", "releases", "media", "tags"] musicbrainz_mapping = { "mbid": {"musicbrainz_field_name": "id"}, "title": {"musicbrainz_field_name": "title"}, @@ -508,20 +487,6 @@ class Track(APIModelMixin): self.artist = self.album.artist super().save(**kwargs) - def get_work(self): - if self.work: - return self.work - data = self.api.get(self.mbid, includes=["work-rels"]) - try: - work_data = data["recording"]["work-relation-list"][0]["work"] - except (IndexError, KeyError): - return - work, _ = Work.get_or_create_from_api(mbid=work_data["id"]) - return work - - def get_lyrics_url(self): - return reverse("api:v1:tracks-lyrics", kwargs={"pk": self.pk}) - @property def full_name(self): try: @@ -605,7 +570,7 @@ class Track(APIModelMixin): return licenses.LICENSES_BY_ID.get(self.license_id) -class UploadQuerySet(models.QuerySet): +class UploadQuerySet(common_models.NullsLastQuerySet): def playable_by(self, actor, include=True): libraries = Library.objects.viewable_by(actor) @@ -677,12 +642,12 @@ class Upload(models.Model): # metadata from federation metadata = JSONField( - default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder + default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder, blank=True ) import_date = models.DateTimeField(null=True, blank=True) # optionnal metadata provided during import import_metadata = JSONField( - default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder + default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder, blank=True ) # status / error details for the import import_status = models.CharField( @@ -694,20 +659,32 @@ class Upload(models.Model): # optionnal metadata about import results (error messages, etc.) import_details = JSONField( - default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder + default=empty_dict, max_length=50000, encoder=DjangoJSONEncoder, blank=True ) from_activity = models.ForeignKey( - "federation.Activity", null=True, on_delete=models.SET_NULL + "federation.Activity", null=True, on_delete=models.SET_NULL, blank=True ) objects = UploadQuerySet.as_manager() - def download_audio_from_remote(self, user): + @property + def is_local(self): + return federation_utils.is_local(self.fid) + + @property + def domain_name(self): + if not self.fid: + return + + parsed = urllib.parse.urlparse(self.fid) + return parsed.hostname + + def download_audio_from_remote(self, actor): from funkwhale_api.common import session from funkwhale_api.federation import signing - if user.is_authenticated and user.actor: - auth = signing.get_auth(user.actor.private_key, user.actor.private_key_id) + if actor: + auth = signing.get_auth(actor.private_key, actor.private_key_id) else: auth = None @@ -812,23 +789,35 @@ class Upload(models.Model): def listen_url(self): return self.track.listen_url + "?upload={}".format(self.uuid) - def get_transcoded_version(self, format): - mimetype = utils.EXTENSION_TO_MIMETYPE[format] - existing_versions = list(self.versions.filter(mimetype=mimetype)) + def get_transcoded_version(self, format, max_bitrate=None): + if format: + mimetype = utils.EXTENSION_TO_MIMETYPE[format] + else: + mimetype = self.mimetype or "audio/mpeg" + format = utils.MIMETYPE_TO_EXTENSION[mimetype] + + existing_versions = self.versions.filter(mimetype=mimetype) + if max_bitrate is not None: + # we don't want to transcode if a 320kbps version is available + # and we're requestiong 300kbps + acceptable_max_bitrate = max_bitrate * 1.2 + acceptable_min_bitrate = max_bitrate * 0.8 + existing_versions = existing_versions.filter( + bitrate__gte=acceptable_min_bitrate, bitrate__lte=acceptable_max_bitrate + ).order_by("-bitrate") if existing_versions: # we found an existing version, no need to transcode again return existing_versions[0] - return self.create_transcoded_version(mimetype, format) + return self.create_transcoded_version(mimetype, format, bitrate=max_bitrate) @transaction.atomic - def create_transcoded_version(self, mimetype, format): + def create_transcoded_version(self, mimetype, format, bitrate): # we create the version with an empty file, then # we'll write to it f = ContentFile(b"") - version = self.versions.create( - mimetype=mimetype, bitrate=self.bitrate or 128000, size=0 - ) + bitrate = min(bitrate or 320000, self.bitrate or 320000) + version = self.versions.create(mimetype=mimetype, bitrate=bitrate, size=0) # we keep the same name, but we update the extension new_name = os.path.splitext(os.path.basename(self.audio_file.name))[ 0 @@ -838,6 +827,7 @@ class Upload(models.Model): audio=self.get_audio_segment(), output=version.audio_file, output_format=utils.MIMETYPE_TO_EXTENSION[mimetype], + bitrate=str(bitrate), ) version.size = version.audio_file.size version.save(update_fields=["size"]) @@ -850,6 +840,16 @@ class Upload(models.Model): return return self.source.lstrip("file://") + @property + def audio_file_path(self): + if not self.audio_file: + return None + try: + return self.audio_file.path + except NotImplementedError: + # external storage + return self.audio_file.name + MIMETYPE_CHOICES = [(mt, ext) for ext, mt in utils.AUDIO_EXTENSIONS_AND_MIMETYPE] @@ -872,6 +872,16 @@ class UploadVersion(models.Model): def filename(self): return self.upload.filename + @property + def audio_file_path(self): + if not self.audio_file: + return None + try: + return self.audio_file.path + except NotImplementedError: + # external storage + return self.audio_file.name + IMPORT_STATUS_CHOICES = ( ("pending", "Pending"), diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py new file mode 100644 index 0000000000000000000000000000000000000000..9fd91fb506d245e0ea50f5ef9cab28c7e7ee0dc1 --- /dev/null +++ b/api/funkwhale_api/music/mutations.py @@ -0,0 +1,62 @@ +from funkwhale_api.common import mutations +from funkwhale_api.federation import routes + +from . import models + + +def can_suggest(obj, actor): + return obj.is_local + + +def can_approve(obj, actor): + return obj.is_local and actor.user and actor.user.get_permissions()["library"] + + +@mutations.registry.connect( + "update", + models.Track, + perm_checkers={"suggest": can_suggest, "approve": can_approve}, +) +class TrackMutationSerializer(mutations.UpdateMutationSerializer): + serialized_relations = {"license": "code"} + + class Meta: + model = models.Track + fields = ["license", "title", "position", "copyright"] + + def post_apply(self, obj, validated_data): + routes.outbox.dispatch( + {"type": "Update", "object": {"type": "Track"}}, context={"track": obj} + ) + + +@mutations.registry.connect( + "update", + models.Artist, + perm_checkers={"suggest": can_suggest, "approve": can_approve}, +) +class ArtistMutationSerializer(mutations.UpdateMutationSerializer): + class Meta: + model = models.Artist + fields = ["name"] + + def post_apply(self, obj, validated_data): + routes.outbox.dispatch( + {"type": "Update", "object": {"type": "Artist"}}, context={"artist": obj} + ) + + +@mutations.registry.connect( + "update", + models.Album, + perm_checkers={"suggest": can_suggest, "approve": can_approve}, +) +class AlbumMutationSerializer(mutations.UpdateMutationSerializer): + class Meta: + model = models.Album + fields = ["title", "release_date"] + + def post_apply(self, obj, validated_data): + routes.outbox.dispatch( + {"type": "Update", "object": {"type": "Album"}}, context={"album": obj} + ) diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py index 192fc024cbd890f01aa5ef079c139a0039c6a373..867d15d8ddb864292263be107b159fb095f7de1e 100644 --- a/api/funkwhale_api/music/serializers.py +++ b/api/funkwhale_api/music/serializers.py @@ -43,6 +43,7 @@ class ArtistAlbumSerializer(serializers.ModelSerializer): model = models.Album fields = ( "id", + "fid", "mbid", "title", "artist", @@ -51,6 +52,7 @@ class ArtistAlbumSerializer(serializers.ModelSerializer): "creation_date", "tracks_count", "is_playable", + "is_local", ) def get_tracks_count(self, o): @@ -68,13 +70,13 @@ class ArtistWithAlbumsSerializer(serializers.ModelSerializer): class Meta: model = models.Artist - fields = ("id", "mbid", "name", "creation_date", "albums") + fields = ("id", "fid", "mbid", "name", "creation_date", "albums", "is_local") class ArtistSimpleSerializer(serializers.ModelSerializer): class Meta: model = models.Artist - fields = ("id", "mbid", "name", "creation_date") + fields = ("id", "fid", "mbid", "name", "creation_date", "is_local") class AlbumTrackSerializer(serializers.ModelSerializer): @@ -87,6 +89,7 @@ class AlbumTrackSerializer(serializers.ModelSerializer): model = models.Track fields = ( "id", + "fid", "mbid", "title", "album", @@ -99,6 +102,7 @@ class AlbumTrackSerializer(serializers.ModelSerializer): "duration", "copyright", "license", + "is_local", ) def get_uploads(self, obj): @@ -125,6 +129,7 @@ class AlbumSerializer(serializers.ModelSerializer): model = models.Album fields = ( "id", + "fid", "mbid", "title", "artist", @@ -133,6 +138,7 @@ class AlbumSerializer(serializers.ModelSerializer): "cover", "creation_date", "is_playable", + "is_local", ) def get_tracks(self, o): @@ -156,12 +162,14 @@ class TrackAlbumSerializer(serializers.ModelSerializer): model = models.Album fields = ( "id", + "fid", "mbid", "title", "artist", "release_date", "cover", "creation_date", + "is_local", ) @@ -182,7 +190,6 @@ class TrackUploadSerializer(serializers.ModelSerializer): class TrackSerializer(serializers.ModelSerializer): artist = ArtistSimpleSerializer(read_only=True) album = TrackAlbumSerializer(read_only=True) - lyrics = serializers.SerializerMethodField() uploads = serializers.SerializerMethodField() listen_url = serializers.SerializerMethodField() @@ -190,6 +197,7 @@ class TrackSerializer(serializers.ModelSerializer): model = models.Track fields = ( "id", + "fid", "mbid", "title", "album", @@ -197,16 +205,13 @@ class TrackSerializer(serializers.ModelSerializer): "creation_date", "position", "disc_number", - "lyrics", "uploads", "listen_url", "copyright", "license", + "is_local", ) - def get_lyrics(self, obj): - return obj.get_lyrics_url() - def get_listen_url(self, obj): return obj.listen_url @@ -367,12 +372,6 @@ class SimpleAlbumSerializer(serializers.ModelSerializer): fields = ("id", "mbid", "title", "release_date", "cover") -class LyricsSerializer(serializers.ModelSerializer): - class Meta: - model = models.Lyrics - fields = ("id", "work", "content", "content_rendered") - - class TrackActivitySerializer(activity_serializers.ModelSerializer): type = serializers.SerializerMethodField() name = serializers.CharField(source="title") @@ -387,6 +386,10 @@ class TrackActivitySerializer(activity_serializers.ModelSerializer): return "Audio" +def get_embed_url(type, id): + return settings.FUNKWHALE_EMBED_URL + "?type={}&id={}".format(type, id) + + class OembedSerializer(serializers.Serializer): format = serializers.ChoiceField(choices=["json"]) url = serializers.URLField() @@ -466,6 +469,36 @@ class OembedSerializer(serializers.Serializer): "library_artist", kwargs={"pk": album.artist.pk} ) ) + elif match.url_name == "library_artist": + qs = models.Artist.objects.filter(pk=int(match.kwargs["pk"])) + try: + artist = qs.get() + except models.Artist.DoesNotExist: + raise serializers.ValidationError( + "No artist matching id {}".format(match.kwargs["pk"]) + ) + embed_type = "artist" + embed_id = artist.pk + album = ( + artist.albums.filter(cover__isnull=False) + .exclude(cover="") + .order_by("-id") + .first() + ) + + if album and album.cover: + data["thumbnail_url"] = federation_utils.full_url( + album.cover.crop["400x400"].url + ) + data["thumbnail_width"] = 400 + data["thumbnail_height"] = 400 + data["title"] = artist.name + data["description"] = artist.name + data["author_name"] = artist.name + data["height"] = 400 + data["author_url"] = federation_utils.full_url( + common_utils.spa_reverse("library_artist", kwargs={"pk": artist.pk}) + ) else: raise serializers.ValidationError( "Unsupported url: {}".format(validated_data["url"]) @@ -473,10 +506,7 @@ class OembedSerializer(serializers.Serializer): data[ "html" ] = '<iframe width="{}" height="{}" scrolling="no" frameborder="no" src="{}"></iframe>'.format( - data["width"], - data["height"], - settings.FUNKWHALE_EMBED_URL - + "?type={}&id={}".format(embed_type, embed_id), + data["width"], data["height"], get_embed_url(embed_type, embed_id) ) return data diff --git a/api/funkwhale_api/music/spa_views.py b/api/funkwhale_api/music/spa_views.py index e71612caefd427578936e4285ba7bc9e6acd6cbb..7fafedf618ed013d08824d4e22cf73d705e3fdd3 100644 --- a/api/funkwhale_api/music/spa_views.py +++ b/api/funkwhale_api/music/spa_views.py @@ -2,10 +2,25 @@ import urllib.parse from django.conf import settings from django.urls import reverse +from django.db.models import Q from funkwhale_api.common import utils from . import models +from . import serializers + + +def get_twitter_card_metas(type, id): + return [ + {"tag": "meta", "property": "twitter:card", "content": "player"}, + { + "tag": "meta", + "property": "twitter:player", + "content": serializers.get_embed_url(type, id), + }, + {"tag": "meta", "property": "twitter:player:width", "content": "600"}, + {"tag": "meta", "property": "twitter:player:height", "content": "400"}, + ] def library_track(request, pk): @@ -72,6 +87,8 @@ def library_track(request, pk): ), } ) + # twitter player is also supported in various software + metas += get_twitter_card_metas(type="track", id=obj.pk) return metas @@ -131,6 +148,8 @@ def library_album(request, pk): ), } ) + # twitter player is also supported in various software + metas += get_twitter_card_metas(type="album", id=obj.pk) return metas @@ -165,4 +184,22 @@ def library_artist(request, pk): } ) + if ( + models.Upload.objects.filter(Q(track__artist=obj) | Q(track__album__artist=obj)) + .playable_by(None) + .exists() + ): + metas.append( + { + "tag": "link", + "rel": "alternate", + "type": "application/json+oembed", + "href": ( + utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + + "?format=json&url={}".format(urllib.parse.quote_plus(artist_url)) + ), + } + ) + # twitter player is also supported in various software + metas += get_twitter_card_metas(type="artist", id=obj.pk) return metas diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index 946d7a411f9de1e1eb6089fd148974718e20ea65..ff3cde440a40eeb021e43127e5944d043dd151d7 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -17,7 +17,6 @@ from funkwhale_api.federation import library as lb from funkwhale_api.taskapp import celery from . import licenses -from . import lyrics as lyrics_utils from . import models from . import metadata from . import signals @@ -70,16 +69,6 @@ def get_cover_from_fs(dir_path): return {"mimetype": m, "content": c.read()} -@celery.app.task(name="Lyrics.fetch_content") -@celery.require_instance(models.Lyrics, "lyrics") -def fetch_content(lyrics): - html = lyrics_utils._get_html(lyrics.url) - content = lyrics_utils.extract_content(html) - cleaned_content = lyrics_utils.clean_content(content) - lyrics.content = cleaned_content - lyrics.save(update_fields=["content"]) - - @celery.app.task(name="music.start_library_scan") @celery.require_instance( models.LibraryScan.objects.select_related().filter(status="pending"), "library_scan" @@ -151,10 +140,11 @@ class UploadImportError(ValueError): super().__init__(code) -def fail_import(upload, error_code): +def fail_import(upload, error_code, detail=None, **fields): old_status = upload.import_status upload.import_status = "errored" - upload.import_details = {"error_code": error_code} + upload.import_details = {"error_code": error_code, "detail": detail} + upload.import_details.update(fields) upload.import_date = timezone.now() upload.save(update_fields=["import_details", "import_status", "import_date"]) @@ -182,20 +172,32 @@ def process_upload(upload): old_status = upload.import_status audio_file = upload.get_audio_file() additional_data = {} + + m = metadata.Metadata(audio_file) try: - if not audio_file: - # we can only rely on user proveded data - final_metadata = import_metadata - else: - # we use user provided data and data from the file itself - m = metadata.Metadata(audio_file) - file_metadata = m.all() - final_metadata = collections.ChainMap( - additional_data, import_metadata, file_metadata - ) - additional_data["cover_data"] = m.get_picture("cover_front", "other") - additional_data["upload_source"] = upload.source - track = get_track_from_import_metadata(final_metadata) + serializer = metadata.TrackMetadataSerializer(data=m) + serializer.is_valid() + except Exception: + fail_import(upload, "unknown_error") + raise + if not serializer.is_valid(): + detail = serializer.errors + try: + metadata_dump = m.all() + except Exception as e: + logger.warn("Cannot dump metadata for file %s: %s", audio_file, str(e)) + return fail_import( + upload, "invalid_metadata", detail=detail, file_metadata=metadata_dump + ) + + final_metadata = collections.ChainMap( + additional_data, serializer.validated_data, import_metadata + ) + additional_data["upload_source"] = upload.source + try: + track = get_track_from_import_metadata( + final_metadata, attributed_to=upload.library.actor + ) except UploadImportError as e: return fail_import(upload, e.code) except Exception: @@ -271,48 +273,54 @@ def process_upload(upload): ) -def federation_audio_track_to_metadata(payload): +def federation_audio_track_to_metadata(payload, references): """ Given a valid payload as returned by federation.serializers.TrackSerializer.validated_data, returns a correct metadata payload for use with get_track_from_import_metadata. """ - musicbrainz_recordingid = payload.get("musicbrainzId") - musicbrainz_artistid = payload["artists"][0].get("musicbrainzId") - musicbrainz_albumartistid = payload["album"]["artists"][0].get("musicbrainzId") - musicbrainz_albumid = payload["album"].get("musicbrainzId") - new_data = { "title": payload["name"], - "album": payload["album"]["name"], - "track_number": payload.get("position") or 1, + "position": payload.get("position") or 1, "disc_number": payload.get("disc"), - "artist": payload["artists"][0]["name"], - "album_artist": payload["album"]["artists"][0]["name"], - "date": payload["album"].get("released"), "license": payload.get("license"), "copyright": payload.get("copyright"), - # musicbrainz - "musicbrainz_recordingid": str(musicbrainz_recordingid) - if musicbrainz_recordingid - else None, - "musicbrainz_artistid": str(musicbrainz_artistid) - if musicbrainz_artistid - else None, - "musicbrainz_albumartistid": str(musicbrainz_albumartistid) - if musicbrainz_albumartistid - else None, - "musicbrainz_albumid": str(musicbrainz_albumid) - if musicbrainz_albumid + "attributed_to": references.get(payload.get("attributedTo")), + "mbid": str(payload.get("musicbrainzId")) + if payload.get("musicbrainzId") else None, + "album": { + "title": payload["album"]["name"], + "fdate": payload["album"]["published"], + "fid": payload["album"]["id"], + "attributed_to": references.get(payload["album"].get("attributedTo")), + "mbid": str(payload["album"]["musicbrainzId"]) + if payload["album"].get("musicbrainzId") + else None, + "release_date": payload["album"].get("released"), + "artists": [ + { + "fid": a["id"], + "name": a["name"], + "fdate": a["published"], + "attributed_to": references.get(a.get("attributedTo")), + "mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None, + } + for a in payload["album"]["artists"] + ], + }, + "artists": [ + { + "fid": a["id"], + "name": a["name"], + "fdate": a["published"], + "attributed_to": references.get(a.get("attributedTo")), + "mbid": str(a["musicbrainzId"]) if a.get("musicbrainzId") else None, + } + for a in payload["artists"] + ], # federation "fid": payload["id"], - "artist_fid": payload["artists"][0]["id"], - "album_artist_fid": payload["album"]["artists"][0]["id"], - "album_fid": payload["album"]["id"], "fdate": payload["published"], - "album_fdate": payload["album"]["published"], - "album_artist_fdate": payload["album"]["artists"][0]["published"], - "artist_fdate": payload["artists"][0]["published"], } cover = payload["album"].get("cover") if cover: @@ -380,8 +388,8 @@ def sort_candidates(candidates, important_fields): @transaction.atomic -def get_track_from_import_metadata(data, update_cover=False): - track = _get_track(data) +def get_track_from_import_metadata(data, update_cover=False, attributed_to=None): + track = _get_track(data, attributed_to=attributed_to) if update_cover and track and not track.album.cover: update_album_cover( track.album, @@ -391,7 +399,7 @@ def get_track_from_import_metadata(data, update_cover=False): return track -def _get_track(data): +def _get_track(data, attributed_to=None): track_uuid = getter(data, "funkwhale", "track", "uuid") if track_uuid: @@ -405,8 +413,8 @@ def _get_track(data): return track from_activity_id = data.get("from_activity_id", None) - track_mbid = data.get("musicbrainz_recordingid", None) - album_mbid = data.get("musicbrainz_albumid", None) + track_mbid = data.get("mbid", None) + album_mbid = getter(data, "album", "mbid") track_fid = getter(data, "fid") query = None @@ -428,12 +436,16 @@ def _get_track(data): pass # get / create artist and album artist - artist_mbid = data.get("musicbrainz_artistid", None) - artist_fid = data.get("artist_fid", None) - artist_name = data["artist"] - query = Q(name__iexact=artist_name) + artists = getter(data, "artists", default=[]) + artist = artists[0] + artist_mbid = artist.get("mbid", None) + artist_fid = artist.get("fid", None) + artist_name = artist["name"] + if artist_mbid: - query |= Q(mbid=artist_mbid) + query = Q(mbid=artist_mbid) + else: + query = Q(name__iexact=artist_name) if artist_fid: query |= Q(fid=artist_fid) defaults = { @@ -441,21 +453,24 @@ def _get_track(data): "mbid": artist_mbid, "fid": artist_fid, "from_activity_id": from_activity_id, + "attributed_to": artist.get("attributed_to", attributed_to), } - if data.get("artist_fdate"): - defaults["creation_date"] = data.get("artist_fdate") + if artist.get("fdate"): + defaults["creation_date"] = artist.get("fdate") artist = get_best_candidate_or_create( models.Artist, query, defaults=defaults, sort_fields=["mbid", "fid"] )[0] - album_artist_name = data.get("album_artist") or artist_name + album_artists = getter(data, "album", "artists", default=artists) or artists + album_artist = album_artists[0] + album_artist_name = album_artist.get("name") if album_artist_name == artist_name: album_artist = artist else: query = Q(name__iexact=album_artist_name) - album_artist_mbid = data.get("musicbrainz_albumartistid", None) - album_artist_fid = data.get("album_artist_fid", None) + album_artist_mbid = album_artist.get("mbid", None) + album_artist_fid = album_artist.get("fid", None) if album_artist_mbid: query |= Q(mbid=album_artist_mbid) if album_artist_fid: @@ -465,32 +480,38 @@ def _get_track(data): "mbid": album_artist_mbid, "fid": album_artist_fid, "from_activity_id": from_activity_id, + "attributed_to": album_artist.get("attributed_to", attributed_to), } - if data.get("album_artist_fdate"): - defaults["creation_date"] = data.get("album_artist_fdate") + if album_artist.get("fdate"): + defaults["creation_date"] = album_artist.get("fdate") album_artist = get_best_candidate_or_create( models.Artist, query, defaults=defaults, sort_fields=["mbid", "fid"] )[0] # get / create album - album_title = data["album"] - album_fid = data.get("album_fid", None) - query = Q(title__iexact=album_title, artist=album_artist) + album = data["album"] + album_title = album["title"] + album_fid = album.get("fid", None) + if album_mbid: - query |= Q(mbid=album_mbid) + query = Q(mbid=album_mbid) + else: + query = Q(title__iexact=album_title, artist=album_artist) + if album_fid: query |= Q(fid=album_fid) defaults = { "title": album_title, "artist": album_artist, "mbid": album_mbid, - "release_date": data.get("date"), + "release_date": album.get("release_date"), "fid": album_fid, "from_activity_id": from_activity_id, + "attributed_to": album.get("attributed_to", attributed_to), } - if data.get("album_fdate"): - defaults["creation_date"] = data.get("album_fdate") + if album.get("fdate"): + defaults["creation_date"] = album.get("fdate") album = get_best_candidate_or_create( models.Album, query, defaults=defaults, sort_fields=["mbid", "fid"] @@ -498,8 +519,8 @@ def _get_track(data): # get / create track track_title = data["title"] - track_number = data.get("track_number", 1) - query = Q(title__iexact=track_title, artist=artist, album=album) + position = data.get("position", 1) + query = Q(title__iexact=track_title, artist=artist, album=album, position=position) if track_mbid: query |= Q(mbid=track_mbid) if track_fid: @@ -509,10 +530,11 @@ def _get_track(data): "album": album, "mbid": track_mbid, "artist": artist, - "position": track_number, + "position": position, "disc_number": data.get("disc_number"), "fid": track_fid, "from_activity_id": from_activity_id, + "attributed_to": data.get("attributed_to", attributed_to), "license": licenses.match(data.get("license"), data.get("copyright")), "copyright": data.get("copyright"), } @@ -562,3 +584,46 @@ def clean_transcoding_cache(): .order_by("id") ) return candidates.delete() + + +def get_prunable_tracks( + exclude_favorites=True, exclude_playlists=True, exclude_listenings=True +): + """ + Returns a list of tracks with no associated uploads, + excluding the one that were listened/favorited/included in playlists. + """ + + queryset = models.Track.objects.all() + queryset = queryset.filter(uploads__isnull=True) + if exclude_favorites: + queryset = queryset.filter(track_favorites__isnull=True) + if exclude_playlists: + queryset = queryset.filter(playlist_tracks__isnull=True) + if exclude_listenings: + queryset = queryset.filter(listenings__isnull=True) + + return queryset + + +def get_prunable_albums(): + return models.Album.objects.filter(tracks__isnull=True) + + +def get_prunable_artists(): + return models.Artist.objects.filter(tracks__isnull=True, albums__isnull=True) + + +def update_library_entity(obj, data): + """ + Given an obj and some updated fields, will persist the changes on the obj + and also check if the entity need to be aliased with existing objs (i.e + if a mbid was added on the obj, and match another entity with the same mbid) + """ + for key, value in data.items(): + setattr(obj, key, value) + + # Todo: handle integrity error on unique fields (such as MBID) + obj.save(update_fields=list(data.keys())) + + return obj diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py index 574f4c8e152fd7e6dd7bc87ae0d5183490a54c67..5031e69ba8030f3db1ec4bffa22bd01af1151c5f 100644 --- a/api/funkwhale_api/music/utils.py +++ b/api/funkwhale_api/music/utils.py @@ -39,6 +39,10 @@ AUDIO_EXTENSIONS_AND_MIMETYPE = [ EXTENSION_TO_MIMETYPE = {ext: mt for ext, mt in AUDIO_EXTENSIONS_AND_MIMETYPE} MIMETYPE_TO_EXTENSION = {mt: ext for ext, mt in AUDIO_EXTENSIONS_AND_MIMETYPE} +SUPPORTED_EXTENSIONS = list( + sorted(set([ext for ext, _ in AUDIO_EXTENSIONS_AND_MIMETYPE])) +) + def get_ext_from_type(mimetype): return MIMETYPE_TO_EXTENSION.get(mimetype) diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 5de07ca947edb23ac3a7334c6f94c294e1b64a15..391a4b333fe45628626383c4479d4a952c9df4b5 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -8,19 +8,23 @@ from django.db.models.functions import Length from django.utils import timezone from rest_framework import mixins -from rest_framework import permissions from rest_framework import settings as rest_settings from rest_framework import views, viewsets from rest_framework.decorators import action from rest_framework.response import Response from taggit.models import Tag +from funkwhale_api.common import decorators as common_decorators from funkwhale_api.common import permissions as common_permissions from funkwhale_api.common import preferences from funkwhale_api.common import utils as common_utils +from funkwhale_api.common import views as common_views from funkwhale_api.federation.authentication import SignatureAuthentication +from funkwhale_api.federation import actors from funkwhale_api.federation import api_serializers as federation_api_serializers +from funkwhale_api.federation import decorators as federation_decorators from funkwhale_api.federation import routes +from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters, licenses, models, serializers, tasks, utils @@ -58,13 +62,18 @@ class TagViewSetMixin(object): return queryset -class ArtistViewSet(viewsets.ReadOnlyModelViewSet): +class ArtistViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelViewSet): queryset = models.Artist.objects.all() serializer_class = serializers.ArtistWithAlbumsSerializer - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" filterset_class = filters.ArtistFilter ordering_fields = ("id", "name", "creation_date") + fetches = federation_decorators.fetches_route() + mutations = common_decorators.mutations_route(types=["update"]) + def get_queryset(self): queryset = super().get_queryset() albums = models.Album.objects.with_tracks_count() @@ -82,15 +91,20 @@ class ArtistViewSet(viewsets.ReadOnlyModelViewSet): ) -class AlbumViewSet(viewsets.ReadOnlyModelViewSet): +class AlbumViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelViewSet): queryset = ( models.Album.objects.all().order_by("artist", "release_date").select_related() ) serializer_class = serializers.AlbumSerializer - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" ordering_fields = ("creation_date", "release_date", "title") filterset_class = filters.AlbumFilter + fetches = federation_decorators.fetches_route() + mutations = common_decorators.mutations_route(types=["update"]) + def get_queryset(self): queryset = super().get_queryset() tracks = ( @@ -123,9 +137,11 @@ class LibraryViewSet( ) serializer_class = serializers.LibraryForOwnerSerializer permission_classes = [ - permissions.IsAuthenticated, + oauth_permissions.ScopePermission, common_permissions.OwnerPermission, ] + required_scope = "libraries" + anonymous_policy = "setting" owner_field = "actor.user" owner_checks = ["read", "write"] @@ -166,22 +182,30 @@ class LibraryViewSet( return Response(serializer.data) -class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet): +class TrackViewSet( + common_views.SkipFilterForGetObject, TagViewSetMixin, viewsets.ReadOnlyModelViewSet +): """ A simple ViewSet for viewing and editing accounts. """ queryset = models.Track.objects.all().for_nested_serialization() serializer_class = serializers.TrackSerializer - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" filterset_class = filters.TrackFilter ordering_fields = ( "creation_date", "title", "album__release_date", "size", + "position", + "disc_number", "artist__name", ) + fetches = federation_decorators.fetches_route() + mutations = common_decorators.mutations_route(types=["update"]) def get_queryset(self): queryset = super().get_queryset() @@ -195,31 +219,6 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet): ) return queryset - @action(methods=["get"], detail=True) - @transaction.non_atomic_requests - def lyrics(self, request, *args, **kwargs): - try: - track = models.Track.objects.get(pk=kwargs["pk"]) - except models.Track.DoesNotExist: - return Response(status=404) - - work = track.work - if not work: - work = track.get_work() - - if not work: - return Response({"error": "unavailable work "}, status=404) - - lyrics = work.fetch_lyrics() - try: - if not lyrics.content: - tasks.fetch_content(lyrics_id=lyrics.pk) - lyrics.refresh_from_db() - except AttributeError: - return Response({"error": "unavailable lyrics"}, status=404) - serializer = serializers.LyricsSerializer(lyrics) - return Response(serializer.data) - libraries = action(methods=["get"], detail=True)( get_libraries(filter_uploads=lambda o, uploads: uploads.filter(track=o)) ) @@ -241,6 +240,8 @@ def get_file_path(audio_file): "MUSIC_DIRECTORY_PATH to serve in-place imported files" ) path = "/music" + audio_file.replace(prefix, "", 1) + if path.startswith("http://") or path.startswith("https://"): + return (settings.PROTECT_FILES_PATH + "/media/" + path).encode("utf-8") return (settings.PROTECT_FILES_PATH + path).encode("utf-8") if t == "apache2": try: @@ -256,25 +257,35 @@ def get_file_path(audio_file): return path.encode("utf-8") -def should_transcode(upload, format): +def should_transcode(upload, format, max_bitrate=None): if not preferences.get("music__transcoding_enabled"): return False + format_need_transcoding = True + bitrate_need_transcoding = True if format is None: - return False - if format not in utils.EXTENSION_TO_MIMETYPE: + format_need_transcoding = False + elif format not in utils.EXTENSION_TO_MIMETYPE: # format should match supported formats - return False - if upload.mimetype is None: + format_need_transcoding = False + elif upload.mimetype is None: # upload should have a mimetype, otherwise we cannot transcode - return False - if upload.mimetype == utils.EXTENSION_TO_MIMETYPE[format]: + format_need_transcoding = False + elif upload.mimetype == utils.EXTENSION_TO_MIMETYPE[format]: # requested format sould be different than upload mimetype, otherwise # there is no need to transcode - return False - return True + format_need_transcoding = False + + if max_bitrate is None: + bitrate_need_transcoding = False + elif not upload.bitrate: + bitrate_need_transcoding = False + elif upload.bitrate <= max_bitrate: + bitrate_need_transcoding = False + + return format_need_transcoding or bitrate_need_transcoding -def handle_serve(upload, user, format=None): +def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True): f = upload # we update the accessed_date now = timezone.now() @@ -295,7 +306,11 @@ def handle_serve(upload, user, format=None): # thus resulting in multiple downloads from the remote qs = f.__class__.objects.select_for_update() f = qs.get(pk=f.pk) - f.download_audio_from_remote(user=user) + if user.is_authenticated: + actor = user.actor + else: + actor = actors.get_service_actor() + f.download_audio_from_remote(actor=actor) data = f.get_audio_data() if data: f.duration = data["duration"] @@ -307,13 +322,18 @@ def handle_serve(upload, user, format=None): file_path = get_file_path(f.source.replace("file://", "", 1)) mt = f.mimetype - if should_transcode(f, format): - transcoded_version = upload.get_transcoded_version(format) + if should_transcode(f, format, max_bitrate=max_bitrate): + transcoded_version = f.get_transcoded_version(format, max_bitrate=max_bitrate) transcoded_version.accessed_date = now transcoded_version.save(update_fields=["accessed_date"]) f = transcoded_version file_path = get_file_path(f.audio_file) mt = f.mimetype + if not proxy_media: + # we simply issue a 302 redirect to the real URL + response = Response(status=302) + response["Location"] = f.audio_file.url + return response if mt: response = Response(content_type=mt) else: @@ -337,7 +357,9 @@ class ListenViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): rest_settings.api_settings.DEFAULT_AUTHENTICATION_CLASSES + [SignatureAuthentication] ) - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" lookup_field = "uuid" def retrieve(self, request, *args, **kwargs): @@ -354,7 +376,21 @@ class ListenViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): return Response(status=404) format = request.GET.get("to") - return handle_serve(upload, user=request.user, format=format) + max_bitrate = request.GET.get("max_bitrate") + try: + max_bitrate = min(max(int(max_bitrate), 0), 320) or None + except (TypeError, ValueError): + max_bitrate = None + + if max_bitrate: + max_bitrate = max_bitrate * 1000 + return handle_serve( + upload, + user=request.user, + format=format, + max_bitrate=max_bitrate, + proxy_media=settings.PROXY_MEDIA, + ) class UploadViewSet( @@ -372,9 +408,11 @@ class UploadViewSet( ) serializer_class = serializers.UploadForOwnerSerializer permission_classes = [ - permissions.IsAuthenticated, + oauth_permissions.ScopePermission, common_permissions.OwnerPermission, ] + required_scope = "libraries" + anonymous_policy = "setting" owner_field = "library.actor.user" owner_checks = ["read", "write"] filterset_class = filters.UploadFilter @@ -419,12 +457,16 @@ class UploadViewSet( class TagViewSet(viewsets.ReadOnlyModelViewSet): queryset = Tag.objects.all().order_by("name") serializer_class = serializers.TagSerializer - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" class Search(views.APIView): max_results = 3 - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" def get(self, request, *args, **kwargs): query = request.GET["query"] @@ -489,10 +531,13 @@ class Search(views.APIView): class LicenseViewSet(viewsets.ReadOnlyModelViewSet): - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" serializer_class = serializers.LicenseSerializer queryset = models.License.objects.all().order_by("code") lookup_value_regex = ".*" + max_page_size = 1000 def get_queryset(self): # ensure our licenses are up to date in DB @@ -514,7 +559,9 @@ class LicenseViewSet(viewsets.ReadOnlyModelViewSet): class OembedView(views.APIView): - permission_classes = [common_permissions.ConditionalAuthentication] + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" def get(self, request, *args, **kwargs): serializer = serializers.OembedSerializer(data=request.GET) diff --git a/api/funkwhale_api/musicbrainz/client.py b/api/funkwhale_api/musicbrainz/client.py index 1355da9438ea6b1a67a4fa659dccd83ddb8e30ce..ae038f9001d9ac896f528d19fe2d49280418c9ca 100644 --- a/api/funkwhale_api/musicbrainz/client.py +++ b/api/funkwhale_api/musicbrainz/client.py @@ -40,10 +40,6 @@ class API(object): _api.get_recording_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION ) - class works(object): - search = memo(_api.search_works, max_age=settings.MUSICBRAINZ_CACHE_DURATION) - get = memo(_api.get_work_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION) - class releases(object): search = memo(_api.search_releases, max_age=settings.MUSICBRAINZ_CACHE_DURATION) get = memo(_api.get_release_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION) diff --git a/api/funkwhale_api/playlists/models.py b/api/funkwhale_api/playlists/models.py index 1d33388015a80c618628b4923311cab49f15298c..15332c75a2d8ec01d0d8bac3bafa41361bd83c24 100644 --- a/api/funkwhale_api/playlists/models.py +++ b/api/funkwhale_api/playlists/models.py @@ -17,7 +17,7 @@ class PlaylistQuerySet(models.QuerySet): def with_covers(self): album_prefetch = models.Prefetch( - "album", queryset=music_models.Album.objects.only("cover") + "album", queryset=music_models.Album.objects.only("cover", "artist_id") ) track_prefetch = models.Prefetch( "track", @@ -70,7 +70,7 @@ class Playlist(models.Model): return self.name @transaction.atomic - def insert(self, plt, index=None): + def insert(self, plt, index=None, allow_duplicates=True): """ Given a PlaylistTrack, insert it at the correct index in the playlist, and update other tracks index if necessary. @@ -96,6 +96,10 @@ class Playlist(models.Model): if index < 0: raise exceptions.ValidationError("Index must be zero or positive") + if not allow_duplicates: + existing_without_current_plt = existing.exclude(pk=plt.pk) + self._check_duplicate_add(existing_without_current_plt, [plt.track]) + if move: # we remove the index temporarily, to avoid integrity errors plt.index = None @@ -125,7 +129,7 @@ class Playlist(models.Model): return to_update.update(index=models.F("index") - 1) @transaction.atomic - def insert_many(self, tracks): + def insert_many(self, tracks, allow_duplicates=True): existing = self.playlist_tracks.select_for_update() now = timezone.now() total = existing.filter(index__isnull=False).count() @@ -134,6 +138,10 @@ class Playlist(models.Model): raise exceptions.ValidationError( "Playlist would reach the maximum of {} tracks".format(max_tracks) ) + + if not allow_duplicates: + self._check_duplicate_add(existing, tracks) + self.save(update_fields=["modification_date"]) start = total plts = [ @@ -144,6 +152,26 @@ class Playlist(models.Model): ] return PlaylistTrack.objects.bulk_create(plts) + def _check_duplicate_add(self, existing_playlist_tracks, tracks_to_add): + track_ids = [t.pk for t in tracks_to_add] + + duplicates = existing_playlist_tracks.filter( + track__pk__in=track_ids + ).values_list("track__pk", flat=True) + if duplicates: + duplicate_tracks = [t for t in tracks_to_add if t.pk in duplicates] + raise exceptions.ValidationError( + { + "non_field_errors": [ + { + "tracks": duplicate_tracks, + "playlist_name": self.name, + "code": "tracks_already_exist_in_playlist", + } + ] + } + ) + class PlaylistTrackQuerySet(models.QuerySet): def for_nested_serialization(self, actor=None): diff --git a/api/funkwhale_api/playlists/serializers.py b/api/funkwhale_api/playlists/serializers.py index b64996640259c03a6394c1353bea641afb31dcf5..ccdf82f4b94fcb11ce5b3d16f1aa96db47a4e7db 100644 --- a/api/funkwhale_api/playlists/serializers.py +++ b/api/funkwhale_api/playlists/serializers.py @@ -24,10 +24,11 @@ class PlaylistTrackSerializer(serializers.ModelSerializer): class PlaylistTrackWriteSerializer(serializers.ModelSerializer): index = serializers.IntegerField(required=False, min_value=0, allow_null=True) + allow_duplicates = serializers.BooleanField(required=False) class Meta: model = models.PlaylistTrack - fields = ("id", "track", "playlist", "index") + fields = ("id", "track", "playlist", "index", "allow_duplicates") def validate_playlist(self, value): if self.context.get("request"): @@ -47,17 +48,21 @@ class PlaylistTrackWriteSerializer(serializers.ModelSerializer): @transaction.atomic def create(self, validated_data): index = validated_data.pop("index", None) + allow_duplicates = validated_data.pop("allow_duplicates", True) instance = super().create(validated_data) - instance.playlist.insert(instance, index) + + instance.playlist.insert(instance, index, allow_duplicates) return instance @transaction.atomic def update(self, instance, validated_data): update_index = "index" in validated_data index = validated_data.pop("index", None) + allow_duplicates = validated_data.pop("allow_duplicates", True) super().update(instance, validated_data) if update_index: - instance.playlist.insert(instance, index) + instance.playlist.insert(instance, index, allow_duplicates) + return instance def get_unique_together_validators(self): @@ -117,9 +122,21 @@ class PlaylistSerializer(serializers.ModelSerializer): except AttributeError: return [] + excluded_artists = [] + try: + user = self.context["request"].user + except (KeyError, AttributeError): + user = None + if user and user.is_authenticated: + excluded_artists = list( + user.content_filters.values_list("target_artist", flat=True) + ) + covers = [] max_covers = 5 for plt in plts: + if plt.track.album.artist_id in excluded_artists: + continue url = plt.track.album.cover.crop["200x200"].url if url in covers: continue @@ -139,3 +156,7 @@ class PlaylistAddManySerializer(serializers.Serializer): tracks = serializers.PrimaryKeyRelatedField( many=True, queryset=Track.objects.for_nested_serialization() ) + allow_duplicates = serializers.BooleanField(required=False) + + class Meta: + fields = "allow_duplicates" diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py index 2f536d7a712f47515dccc55a01d7ddc9fba55a47..861dc81755f6007ade9f47df442c92d110646363 100644 --- a/api/funkwhale_api/playlists/views.py +++ b/api/funkwhale_api/playlists/views.py @@ -2,11 +2,12 @@ from django.db import transaction from django.db.models import Count from rest_framework import exceptions, mixins, viewsets from rest_framework.decorators import action -from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response from funkwhale_api.common import fields, permissions from funkwhale_api.music import utils as music_utils +from funkwhale_api.users.oauth import permissions as oauth_permissions + from . import filters, models, serializers @@ -28,10 +29,11 @@ class PlaylistViewSet( .with_duration() ) permission_classes = [ - permissions.ConditionalAuthentication, + oauth_permissions.ScopePermission, permissions.OwnerPermission, - IsAuthenticatedOrReadOnly, ] + required_scope = "playlists" + anonymous_policy = "setting" owner_checks = ["write"] filterset_class = filters.PlaylistFilter ordering_fields = ("id", "name", "creation_date", "modification_date") @@ -53,7 +55,10 @@ class PlaylistViewSet( serializer = serializers.PlaylistAddManySerializer(data=request.data) serializer.is_valid(raise_exception=True) try: - plts = playlist.insert_many(serializer.validated_data["tracks"]) + plts = playlist.insert_many( + serializer.validated_data["tracks"], + serializer.validated_data["allow_duplicates"], + ) except exceptions.ValidationError as e: payload = {"playlist": e.detail} return Response(payload, status=400) @@ -101,10 +106,11 @@ class PlaylistTrackViewSet( serializer_class = serializers.PlaylistTrackSerializer queryset = models.PlaylistTrack.objects.all() permission_classes = [ - permissions.ConditionalAuthentication, + oauth_permissions.ScopePermission, permissions.OwnerPermission, - IsAuthenticatedOrReadOnly, ] + required_scope = "playlists" + anonymous_policy = "setting" owner_field = "playlist.user" owner_checks = ["write"] diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py index 8d9eb816a192e6f6d5d3b72f04e1c14e4abb4da1..a0dc36b62adf8d76eb4c65f472ec04f440f56fc2 100644 --- a/api/funkwhale_api/radios/radios.py +++ b/api/funkwhale_api/radios/radios.py @@ -1,10 +1,11 @@ import random from django.core.exceptions import ValidationError -from django.db.models import Count +from django.db import connection from rest_framework import serializers from taggit.models import Tag +from funkwhale_api.moderation import filters as moderation_filters from funkwhale_api.music.models import Artist, Track from funkwhale_api.users.models import User @@ -43,8 +44,16 @@ class SessionRadio(SimpleRadio): return self.session def get_queryset(self, **kwargs): - qs = Track.objects.annotate(uploads_count=Count("uploads")) - return qs.filter(uploads_count__gt=0) + qs = Track.objects.all() + if not self.session: + return qs + if not self.session.user: + return qs + query = moderation_filters.get_filtered_content_query( + config=moderation_filters.USER_FILTER_CONFIG["TRACK"], + user=self.session.user, + ) + return qs.exclude(query) def get_queryset_kwargs(self): return {} @@ -55,7 +64,13 @@ class SessionRadio(SimpleRadio): if self.session: queryset = self.filter_from_session(queryset) if kwargs.pop("filter_playable", True): - queryset = queryset.playable_by(self.session.user.actor) + queryset = queryset.playable_by( + self.session.user.actor if self.session.user else None + ) + queryset = self.filter_queryset(queryset) + return queryset + + def filter_queryset(self, queryset): return queryset def filter_from_session(self, queryset): @@ -118,7 +133,7 @@ class CustomRadio(SessionRadio): try: user = data["user"] except KeyError: - user = context["user"] + user = context.get("user") try: assert data["custom_radio"].user == user or data["custom_radio"].is_public except KeyError: @@ -153,6 +168,74 @@ class TagRadio(RelatedObjectRadio): return qs.filter(tags__in=[self.session.related_object]) +def weighted_choice(choices): + total = sum(w for c, w in choices) + r = random.uniform(0, total) + upto = 0 + for c, w in choices: + if upto + w >= r: + return c + upto += w + assert False, "Shouldn't get here" + + +class NextNotFound(Exception): + pass + + +@registry.register(name="similar") +class SimilarRadio(RelatedObjectRadio): + model = Track + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + seeds = list( + self.session.session_tracks.all() + .values_list("track_id", flat=True) + .order_by("-id")[:3] + ) + [self.session.related_object.pk] + for seed in seeds: + try: + return queryset.filter(pk=self.find_next_id(queryset, seed)) + except NextNotFound: + continue + + return queryset.none() + + def find_next_id(self, queryset, seed): + with connection.cursor() as cursor: + query = """ + SELECT next, count(next) AS c + FROM ( + SELECT + track_id, + creation_date, + LEAD(track_id) OVER ( + PARTITION by user_id order by creation_date asc + ) AS next + FROM history_listening + INNER JOIN users_user ON (users_user.id = user_id) + WHERE users_user.privacy_level = 'instance' OR users_user.privacy_level = 'everyone' OR user_id = %s + ORDER BY creation_date ASC + ) t WHERE track_id = %s AND next != %s GROUP BY next ORDER BY c DESC; + """ + cursor.execute(query, [self.session.user_id, seed, seed]) + next_candidates = list(cursor.fetchall()) + + if not next_candidates: + raise NextNotFound() + + matching_tracks = list( + queryset.filter(pk__in=[c[0] for c in next_candidates]).values_list( + "id", flat=True + ) + ) + next_candidates = [n for n in next_candidates if n[0] in matching_tracks] + if not next_candidates: + raise NextNotFound() + return random.choice([c[0] for c in next_candidates]) + + @registry.register(name="artist") class ArtistRadio(RelatedObjectRadio): model = Artist diff --git a/api/funkwhale_api/radios/serializers.py b/api/funkwhale_api/radios/serializers.py index 9bffbf5b9cd3ce0c04100dcf3a68de008964b416..397452ecc0e5c6646a82ea663fdc6bb099e2f803 100644 --- a/api/funkwhale_api/radios/serializers.py +++ b/api/funkwhale_api/radios/serializers.py @@ -70,7 +70,7 @@ class RadioSessionSerializer(serializers.ModelSerializer): return data def create(self, validated_data): - validated_data["user"] = self.context["user"] + validated_data["user"] = self.context.get("user") if validated_data.get("related_object_id"): radio = registry[validated_data["radio_type"]]() validated_data["related_object"] = radio.get_related_object( diff --git a/api/funkwhale_api/radios/views.py b/api/funkwhale_api/radios/views.py index 5df0fe287a28f69513f97640f3790bb0cc031018..3c8f41c91fabdadf759a29587efd8fed77c016f2 100644 --- a/api/funkwhale_api/radios/views.py +++ b/api/funkwhale_api/radios/views.py @@ -1,10 +1,12 @@ from django.db.models import Q -from rest_framework import mixins, permissions, status, viewsets +from rest_framework import mixins, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from funkwhale_api.common import permissions as common_permissions from funkwhale_api.music.serializers import TrackSerializer +from funkwhale_api.music import utils as music_utils +from funkwhale_api.users.oauth import permissions as oauth_permissions from . import filters, filtersets, models, serializers @@ -20,12 +22,14 @@ class RadioViewSet( serializer_class = serializers.RadioSerializer permission_classes = [ - permissions.IsAuthenticated, + oauth_permissions.ScopePermission, common_permissions.OwnerPermission, ] filterset_class = filtersets.RadioFilter + required_scope = "radios" owner_field = "user" owner_checks = ["write"] + anonymous_policy = "setting" def get_queryset(self): queryset = models.Radio.objects.all() @@ -44,7 +48,9 @@ class RadioViewSet( def tracks(self, request, *args, **kwargs): radio = self.get_object() tracks = radio.get_candidates().for_nested_serialization() - + actor = music_utils.get_actor_from_request(self.request) + tracks = tracks.with_playable_uploads(actor) + tracks = tracks.playable_by(actor) page = self.paginate_queryset(tracks) if page is not None: serializer = TrackSerializer(page, many=True) @@ -80,29 +86,55 @@ class RadioSessionViewSet( serializer_class = serializers.RadioSessionSerializer queryset = models.RadioSession.objects.all() - permission_classes = [permissions.IsAuthenticated] + permission_classes = [] def get_queryset(self): queryset = super().get_queryset() - return queryset.filter(user=self.request.user) + if self.request.user.is_authenticated: + return queryset.filter( + Q(user=self.request.user) + | Q(session_key=self.request.session.session_key) + ) + + return queryset.filter(session_key=self.request.session.session_key).exclude( + session_key=None + ) + + def perform_create(self, serializer): + if ( + not self.request.user.is_authenticated + and not self.request.session.session_key + ): + self.request.session.create() + return serializer.save( + user=self.request.user if self.request.user.is_authenticated else None, + session_key=self.request.session.session_key, + ) def get_serializer_context(self): context = super().get_serializer_context() - context["user"] = self.request.user + context["user"] = ( + self.request.user if self.request.user.is_authenticated else None + ) return context class RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): serializer_class = serializers.RadioSessionTrackSerializer queryset = models.RadioSessionTrack.objects.all() - permission_classes = [permissions.IsAuthenticated] + permission_classes = [] def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) session = serializer.validated_data["session"] + if not request.user.is_authenticated and not request.session.session_key: + self.request.session.create() try: - assert request.user == session.user + assert (request.user == session.user) or ( + request.session.session_key == session.session_key + and session.session_key + ) except AssertionError: return Response(status=status.HTTP_403_FORBIDDEN) session.radio.pick() diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index f7926d1fdc09250f049045ea8f72ac9542e1a0f8..88d7ece7c575d29bdcec0e7bbb8a056292ffd7d2 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -13,6 +13,7 @@ import funkwhale_api from funkwhale_api.activity import record from funkwhale_api.common import fields, preferences, utils as common_utils from funkwhale_api.favorites.models import TrackFavorite +from funkwhale_api.moderation import filters as moderation_filters from funkwhale_api.music import models as music_models from funkwhale_api.music import utils from funkwhale_api.music import views as music_views @@ -91,7 +92,7 @@ def get_playlist_qs(request): class SubsonicViewSet(viewsets.GenericViewSet): content_negotiation_class = negotiation.SubsonicContentNegociation authentication_classes = [authentication.SubsonicAuthentication] - permissions_classes = [rest_permissions.IsAuthenticated] + permission_classes = [rest_permissions.IsAuthenticated] def dispatch(self, request, *args, **kwargs): if not preferences.get("subsonic__enabled"): @@ -127,7 +128,7 @@ class SubsonicViewSet(viewsets.GenericViewSet): detail=False, methods=["get", "post"], url_name="get_license", - permissions_classes=[], + permission_classes=[], url_path="getLicense", ) def get_license(self, request, *args, **kwargs): @@ -152,8 +153,14 @@ class SubsonicViewSet(viewsets.GenericViewSet): url_path="getArtists", ) def get_artists(self, request, *args, **kwargs): - artists = music_models.Artist.objects.all().playable_by( - utils.get_actor_from_request(request) + artists = ( + music_models.Artist.objects.all() + .exclude( + moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["ARTIST"], request.user + ) + ) + .playable_by(utils.get_actor_from_request(request)) ) data = serializers.GetArtistsSerializer(artists).data payload = {"artists": data} @@ -167,8 +174,14 @@ class SubsonicViewSet(viewsets.GenericViewSet): url_path="getIndexes", ) def get_indexes(self, request, *args, **kwargs): - artists = music_models.Artist.objects.all().playable_by( - utils.get_actor_from_request(request) + artists = ( + music_models.Artist.objects.all() + .exclude( + moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["ARTIST"], request.user + ) + ) + .playable_by(utils.get_actor_from_request(request)) ) data = serializers.GetArtistsSerializer(artists).data payload = {"indexes": data} @@ -237,7 +250,24 @@ class SubsonicViewSet(viewsets.GenericViewSet): format = data.get("format", "raw") if format == "raw": format = None - return music_views.handle_serve(upload=upload, user=request.user, format=format) + + max_bitrate = data.get("maxBitRate") + try: + max_bitrate = min(max(int(max_bitrate), 0), 320) or None + except (TypeError, ValueError): + max_bitrate = None + + if max_bitrate: + max_bitrate = max_bitrate * 1000 + return music_views.handle_serve( + upload=upload, + user=request.user, + format=format, + max_bitrate=max_bitrate, + # Subsonic clients don't expect 302 redirection unfortunately, + # So we have to proxy media files + proxy_media=True, + ) @action(detail=False, methods=["get", "post"], url_name="star", url_path="star") @find_object(music_models.Track.objects.all()) @@ -273,7 +303,11 @@ class SubsonicViewSet(viewsets.GenericViewSet): def get_random_songs(self, request, *args, **kwargs): data = request.GET or request.POST actor = utils.get_actor_from_request(request) - queryset = music_models.Track.objects.all() + queryset = music_models.Track.objects.all().exclude( + moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["TRACK"], request.user + ) + ) queryset = queryset.playable_by(actor) try: size = int(data["size"]) @@ -308,8 +342,14 @@ class SubsonicViewSet(viewsets.GenericViewSet): url_path="getAlbumList2", ) def get_album_list2(self, request, *args, **kwargs): - queryset = music_models.Album.objects.with_tracks_count().order_by( - "artist__name" + queryset = ( + music_models.Album.objects.exclude( + moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["ALBUM"], request.user + ) + ) + .with_tracks_count() + .order_by("artist__name") ) data = request.GET or request.POST filterset = filters.AlbumList2FilterSet(data, queryset=queryset) diff --git a/api/funkwhale_api/templates/account/email/email_confirmation_message.txt b/api/funkwhale_api/templates/account/email/email_confirmation_message.txt index 8aec540fe15c602e99505231e2292cb09839930c..8464e057d4f497b4c0af8b7f5c6a6472f1d38116 100644 --- a/api/funkwhale_api/templates/account/email/email_confirmation_message.txt +++ b/api/funkwhale_api/templates/account/email/email_confirmation_message.txt @@ -1,8 +1,8 @@ -{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}! +{% load account %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with site_name=funkwhale_site_name site_domain=funkwhale_site_domain %}Hello from {{ site_name }}! You're receiving this e-mail because user {{ user_display }} at {{ site_domain }} has given yours as an e-mail address to connect their account. To confirm this is correct, go to {{ funkwhale_url }}/auth/email/confirm?key={{ key }} {% endblocktrans %}{% endautoescape %} -{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you from {{ site_name }}! +{% blocktrans with site_name=funkwhale_site_name site_domain=funkwhale_site_domain %}Thank you from {{ site_name }}! {{ site_domain }}{% endblocktrans %} diff --git a/api/funkwhale_api/templates/registration/password_reset_email.html b/api/funkwhale_api/templates/registration/password_reset_email.html index 7a587d7204b1ae31eecc995bcca3d9a7e68a0e4d..0b6b1384d89036f8a3eb3f8b708559e721c8cca4 100644 --- a/api/funkwhale_api/templates/registration/password_reset_email.html +++ b/api/funkwhale_api/templates/registration/password_reset_email.html @@ -1,5 +1,5 @@ {% load i18n %}{% autoescape off %} -{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} +{% blocktrans with site_name=funkwhale_site_name %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page and choose a new password:" %} {{ funkwhale_url }}/auth/password/reset/confirm?uid={{ uid }}&token={{ token }} @@ -7,6 +7,6 @@ {% trans "Thanks for using our site!" %} -{% blocktrans %}The {{ site_name }} team{% endblocktrans %} +{% blocktrans with site_name=funkwhale_site_name %}The {{ site_name }} team{% endblocktrans %} {% endautoescape %} diff --git a/api/funkwhale_api/users/adapters.py b/api/funkwhale_api/users/adapters.py index 6d8c365d52be08db4551dc05044dd6cbb63cfa06..77171ff1b03f3c466673db9dfd9d809f6dd9da16 100644 --- a/api/funkwhale_api/users/adapters.py +++ b/api/funkwhale_api/users/adapters.py @@ -3,11 +3,22 @@ from django.conf import settings from dynamic_preferences.registries import global_preferences_registry +def get_email_context(): + context = {} + context["funkwhale_url"] = settings.FUNKWHALE_URL + manager = global_preferences_registry.manager() + context["funkwhale_site_name"] = ( + manager["instance__name"] or settings.FUNKWHALE_HOSTNAME + ) + context["funkwhale_site_domain"] = settings.FUNKWHALE_HOSTNAME + return context + + class FunkwhaleAccountAdapter(DefaultAccountAdapter): def is_open_for_signup(self, request): manager = global_preferences_registry.manager() return manager["users__registration_enabled"] def send_mail(self, template_prefix, email, context): - context["funkwhale_url"] = settings.FUNKWHALE_URL + context.update(get_email_context()) return super().send_mail(template_prefix, email, context) diff --git a/api/funkwhale_api/users/admin.py b/api/funkwhale_api/users/admin.py index 303f4f9c3d4d575320ad53fcc2813e8c2ae7f456..c11c33bb66ce606de08777e7ac209fca923658d6 100644 --- a/api/funkwhale_api/users/admin.py +++ b/api/funkwhale_api/users/admin.py @@ -33,6 +33,20 @@ class MyUserCreationForm(UserCreationForm): raise forms.ValidationError(self.error_messages["duplicate_username"]) +def disable(modeladmin, request, queryset): + queryset.exclude(pk=request.user.pk).update(is_active=False) + + +disable.short_description = "Disable login" + + +def enable(modeladmin, request, queryset): + queryset.update(is_active=True) + + +enable.short_description = "Enable login" + + @admin.register(models.User) class UserAdmin(AuthUserAdmin): form = MyUserChangeForm @@ -40,6 +54,7 @@ class UserAdmin(AuthUserAdmin): list_display = [ "username", "email", + "is_active", "date_joined", "last_login", "is_staff", @@ -53,7 +68,7 @@ class UserAdmin(AuthUserAdmin): "permission_library", "permission_moderation", ] - + actions = [disable, enable] fieldsets = ( (None, {"fields": ("username", "password", "privacy_level")}), ( diff --git a/api/funkwhale_api/users/factories.py b/api/funkwhale_api/users/factories.py index a5b113cf4506f0a8c80074f38f5cf98c98669ce6..e7f046ef3fa21f786038d302355b59bbab494ef9 100644 --- a/api/funkwhale_api/users/factories.py +++ b/api/funkwhale_api/users/factories.py @@ -1,7 +1,7 @@ +import pytz import factory from django.contrib.auth.models import Permission from django.utils import timezone - from funkwhale_api.factories import ManyToManyFromList, registry, NoUpdateOnCreate from . import models @@ -87,3 +87,49 @@ class UserFactory(factory.django.DjangoModelFactory): class SuperUserFactory(UserFactory): is_staff = True is_superuser = True + + +@registry.register +class ApplicationFactory(factory.django.DjangoModelFactory): + name = factory.Faker("name") + redirect_uris = factory.Faker("url") + client_type = models.Application.CLIENT_CONFIDENTIAL + authorization_grant_type = models.Application.GRANT_AUTHORIZATION_CODE + scope = "read" + + class Meta: + model = "users.Application" + + +@registry.register +class GrantFactory(factory.django.DjangoModelFactory): + application = factory.SubFactory(ApplicationFactory) + scope = factory.SelfAttribute(".application.scope") + redirect_uri = factory.SelfAttribute(".application.redirect_uris") + user = factory.SubFactory(UserFactory) + expires = factory.Faker("future_datetime", end_date="+15m") + code = factory.Faker("uuid4") + + class Meta: + model = "users.Grant" + + +@registry.register +class AccessTokenFactory(factory.django.DjangoModelFactory): + application = factory.SubFactory(ApplicationFactory) + user = factory.SubFactory(UserFactory) + expires = factory.Faker("future_datetime", tzinfo=pytz.UTC) + token = factory.Faker("uuid4") + + class Meta: + model = "users.AccessToken" + + +@registry.register +class RefreshTokenFactory(factory.django.DjangoModelFactory): + application = factory.SubFactory(ApplicationFactory) + user = factory.SubFactory(UserFactory) + token = factory.Faker("uuid4") + + class Meta: + model = "users.RefreshToken" diff --git a/api/funkwhale_api/users/migrations/0014_oauth.py b/api/funkwhale_api/users/migrations/0014_oauth.py new file mode 100644 index 0000000000000000000000000000000000000000..696867f68340129c66712450c6dfe36d5c405464 --- /dev/null +++ b/api/funkwhale_api/users/migrations/0014_oauth.py @@ -0,0 +1,195 @@ +# Generated by Django 2.0.9 on 2018-12-06 10:08 + +from django.db import migrations, models +import django.db.models.deletion +from django.conf import settings +import oauth2_provider.generators + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0013_auto_20181206_1008"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="AccessToken", + fields=[ + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ("expires", models.DateTimeField()), + ("scope", models.TextField(blank=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ("token", models.CharField(max_length=255, unique=True)), + ], + options={"abstract": False}, + ), + migrations.CreateModel( + name="Application", + fields=[ + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ( + "client_id", + models.CharField( + db_index=True, + default=oauth2_provider.generators.generate_client_id, + max_length=100, + unique=True, + ), + ), + ( + "redirect_uris", + models.TextField( + blank=True, help_text="Allowed URIs list, space separated" + ), + ), + ( + "client_type", + models.CharField( + choices=[ + ("confidential", "Confidential"), + ("public", "Public"), + ], + max_length=32, + ), + ), + ( + "authorization_grant_type", + models.CharField( + choices=[ + ("authorization-code", "Authorization code"), + ("implicit", "Implicit"), + ("password", "Resource owner password-based"), + ("client-credentials", "Client credentials"), + ], + max_length=32, + ), + ), + ( + "client_secret", + models.CharField( + blank=True, + db_index=True, + default=oauth2_provider.generators.generate_client_secret, + max_length=255, + ), + ), + ("name", models.CharField(blank=True, max_length=255)), + ("skip_authorization", models.BooleanField(default=False)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="users_application", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={"abstract": False}, + ), + migrations.CreateModel( + name="Grant", + fields=[ + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ("code", models.CharField(max_length=255, unique=True)), + ("expires", models.DateTimeField()), + ("redirect_uri", models.CharField(max_length=255)), + ("scope", models.TextField(blank=True)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "application", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="users.Application", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="users_grant", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={"abstract": False}, + ), + migrations.CreateModel( + name="RefreshToken", + fields=[ + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ("token", models.CharField(max_length=255)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ("revoked", models.DateTimeField(null=True)), + ( + "access_token", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="refresh_token", + to="users.AccessToken", + ), + ), + ( + "application", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="users.Application", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="users_refreshtoken", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={"abstract": False}, + ), + migrations.AddField( + model_name="accesstoken", + name="application", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="users.Application", + ), + ), + migrations.AddField( + model_name="accesstoken", + name="source_refresh_token", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="refreshed_access_token", + to="users.RefreshToken", + ), + ), + migrations.AddField( + model_name="accesstoken", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="users_accesstoken", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterUniqueTogether( + name="refreshtoken", unique_together={("token", "revoked")} + ), + ] diff --git a/api/funkwhale_api/users/migrations/0015_application_scope.py b/api/funkwhale_api/users/migrations/0015_application_scope.py new file mode 100644 index 0000000000000000000000000000000000000000..5fa8e52d75dd66bfd1676c48a7688eac7255361b --- /dev/null +++ b/api/funkwhale_api/users/migrations/0015_application_scope.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.7 on 2019-03-18 09:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0014_oauth'), + ] + + operations = [ + migrations.AddField( + model_name='application', + name='scope', + field=models.TextField(blank=True), + ), + ] diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index 32e4869a37fbaa1ac313a2e88e7033ac7e9faeca..3748dd634b9abf68ac3ac76810d2be6ea65bf777 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -18,6 +18,8 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from django_auth_ldap.backend import populate_user as ldap_populate_user +from oauth2_provider import models as oauth2_models +from oauth2_provider import validators as oauth2_validators from versatileimagefield.fields import VersatileImageField from versatileimagefield.image_warmer import VersatileImageFieldWarmer @@ -37,12 +39,37 @@ PERMISSIONS_CONFIGURATION = { "moderation": { "label": "Moderation", "help_text": "Block/mute/remove domains, users and content", + "scopes": { + "read:instance:policies", + "write:instance:policies", + "read:instance:accounts", + "write:instance:accounts", + "read:instance:domains", + "write:instance:domains", + }, }, "library": { "label": "Manage library", "help_text": "Manage library, delete files, tracks, artists, albums...", + "scopes": { + "read:instance:edits", + "write:instance:edits", + "read:instance:libraries", + "write:instance:libraries", + }, + }, + "settings": { + "label": "Manage instance-level settings", + "help_text": "", + "scopes": { + "read:instance:settings", + "write:instance:settings", + "read:instance:users", + "write:instance:users", + "read:instance:invitations", + "write:instance:invitations", + }, }, - "settings": {"label": "Manage instance-level settings", "help_text": ""}, } PERMISSIONS = sorted(PERMISSIONS_CONFIGURATION.keys()) @@ -207,6 +234,16 @@ class User(AbstractUser): def full_username(self): return "{}@{}".format(self.username, settings.FEDERATION_HOSTNAME) + @property + def avatar_path(self): + if not self.avatar: + return None + try: + return self.avatar.path + except NotImplementedError: + # external storage + return self.avatar.name + def generate_code(length=10): return "".join( @@ -245,41 +282,91 @@ class Invitation(models.Model): return super().save(**kwargs) -def get_actor_data(user): - username = federation_utils.slugify_username(user.username) +class Application(oauth2_models.AbstractApplication): + scope = models.TextField(blank=True) + + @property + def normalized_scopes(self): + from .oauth import permissions + + raw_scopes = set(self.scope.split(" ") if self.scope else []) + return permissions.normalize(*raw_scopes) + + +# oob schemes are not supported yet in oauth toolkit +# (https://github.com/jazzband/django-oauth-toolkit/issues/235) +# so in the meantime, we override their validation to add support +OOB_SCHEMES = ["urn:ietf:wg:oauth:2.0:oob", "urn:ietf:wg:oauth:2.0:oob:auto"] + + +class CustomRedirectURIValidator(oauth2_validators.RedirectURIValidator): + def __call__(self, value): + if value in OOB_SCHEMES: + return value + return super().__call__(value) + + +oauth2_models.RedirectURIValidator = CustomRedirectURIValidator + + +class Grant(oauth2_models.AbstractGrant): + pass + + +class AccessToken(oauth2_models.AbstractAccessToken): + pass + + +class RefreshToken(oauth2_models.AbstractRefreshToken): + pass + + +def get_actor_data(username): + slugified_username = federation_utils.slugify_username(username) return { - "preferred_username": username, + "preferred_username": slugified_username, "domain": federation_models.Domain.objects.get_or_create( name=settings.FEDERATION_HOSTNAME )[0], "type": "Person", - "name": user.username, + "name": username, "manually_approves_followers": False, "fid": federation_utils.full_url( - reverse("federation:actors-detail", kwargs={"preferred_username": username}) + reverse( + "federation:actors-detail", + kwargs={"preferred_username": slugified_username}, + ) ), "shared_inbox_url": federation_models.get_shared_inbox_url(), "inbox_url": federation_utils.full_url( - reverse("federation:actors-inbox", kwargs={"preferred_username": username}) + reverse( + "federation:actors-inbox", + kwargs={"preferred_username": slugified_username}, + ) ), "outbox_url": federation_utils.full_url( - reverse("federation:actors-outbox", kwargs={"preferred_username": username}) + reverse( + "federation:actors-outbox", + kwargs={"preferred_username": slugified_username}, + ) ), "followers_url": federation_utils.full_url( reverse( - "federation:actors-followers", kwargs={"preferred_username": username} + "federation:actors-followers", + kwargs={"preferred_username": slugified_username}, ) ), "following_url": federation_utils.full_url( reverse( - "federation:actors-following", kwargs={"preferred_username": username} + "federation:actors-following", + kwargs={"preferred_username": slugified_username}, ) ), } def create_actor(user): - args = get_actor_data(user) + args = get_actor_data(user.username) private, public = keys.get_key_pair() args["private_key"] = private.decode("utf-8") args["public_key"] = public.decode("utf-8") @@ -301,3 +388,10 @@ def warm_user_avatar(sender, instance, **kwargs): instance_or_queryset=instance, rendition_key_set="square", image_attr="avatar" ) num_created, failed_to_create = user_avatar_warmer.warm() + + +@receiver(models.signals.pre_delete, sender=User) +def delete_actor(sender, instance, **kwargs): + if not instance.actor: + return + instance.actor.delete() diff --git a/api/funkwhale_api/users/oauth/__init__.py b/api/funkwhale_api/users/oauth/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/funkwhale_api/users/oauth/permissions.py b/api/funkwhale_api/users/oauth/permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..ebd44a937a6dee21e462d3321dfdf1dfcc7149b8 --- /dev/null +++ b/api/funkwhale_api/users/oauth/permissions.py @@ -0,0 +1,123 @@ +from rest_framework import permissions +from django.core.exceptions import ImproperlyConfigured + +from funkwhale_api.common import preferences + +from .. import models +from . import scopes + + +def normalize(*scope_ids): + """ + Given an iterable containing scopes ids such as {read, write:playlists} + will return a set containing all the leaf scopes (and no parent scopes) + """ + final = set() + for scope_id in scope_ids: + try: + scope_obj = scopes.SCOPES_BY_ID[scope_id] + except KeyError: + continue + + if scope_obj.children: + final = final | {s.id for s in scope_obj.children} + else: + final.add(scope_obj.id) + return final + + +def should_allow(required_scope, request_scopes): + if not required_scope: + return True + + if not request_scopes: + return False + + return required_scope in normalize(*request_scopes) + + +METHOD_SCOPE_MAPPING = { + "get": "read", + "post": "write", + "patch": "write", + "put": "write", + "delete": "write", +} + + +class ScopePermission(permissions.BasePermission): + def has_permission(self, request, view): + + if request.method.lower() in ["options", "head"]: + return True + + try: + scope_config = getattr(view, "required_scope") + except AttributeError: + raise ImproperlyConfigured( + "ScopePermission requires the view to define the required_scope attribute" + ) + anonymous_policy = getattr(view, "anonymous_policy", False) + if anonymous_policy not in [True, False, "setting"]: + raise ImproperlyConfigured( + "{} is not a valid value for anonymous_policy".format(anonymous_policy) + ) + if isinstance(scope_config, str): + scope_config = { + "read": "read:{}".format(scope_config), + "write": "write:{}".format(scope_config), + } + action = METHOD_SCOPE_MAPPING[request.method.lower()] + required_scope = scope_config[action] + else: + # we have a dict with explicit viewset actions / scopes + required_scope = scope_config[view.action] + + token = request.auth + + if isinstance(token, models.AccessToken): + return self.has_permission_token(token, required_scope) + elif request.user.is_authenticated: + user_scopes = scopes.get_from_permissions(**request.user.get_permissions()) + return should_allow( + required_scope=required_scope, request_scopes=user_scopes + ) + elif hasattr(request, "actor") and request.actor: + # we use default anonymous scopes + user_scopes = scopes.FEDERATION_REQUEST_SCOPES + return should_allow( + required_scope=required_scope, request_scopes=user_scopes + ) + else: + if anonymous_policy is False: + return False + if anonymous_policy == "setting" and preferences.get( + "common__api_authentication_required" + ): + return False + + # we use default anonymous scopes + user_scopes = scopes.ANONYMOUS_SCOPES + return should_allow( + required_scope=required_scope, request_scopes=user_scopes + ) + + def has_permission_token(self, token, required_scope): + + if token.is_expired(): + return False + + if not token.user: + return False + + user = token.user + user_scopes = scopes.get_from_permissions(**user.get_permissions()) + token_scopes = set(token.scopes.keys()) + final_scopes = ( + user_scopes + & normalize(*token_scopes) + & token.application.normalized_scopes + & scopes.OAUTH_APP_SCOPES + ) + + return should_allow(required_scope=required_scope, request_scopes=final_scopes) diff --git a/api/funkwhale_api/users/oauth/scopes.py b/api/funkwhale_api/users/oauth/scopes.py new file mode 100644 index 0000000000000000000000000000000000000000..61b07098383e832dcacd87f5da11fa843f821b29 --- /dev/null +++ b/api/funkwhale_api/users/oauth/scopes.py @@ -0,0 +1,93 @@ +class Scope: + def __init__(self, id, label="", children=None): + self.id = id + self.label = "" + self.children = children or [] + + def copy(self, prefix): + return Scope("{}:{}".format(prefix, self.id)) + + +BASE_SCOPES = [ + Scope( + "profile", "Access profile data (email, username, avatar, subsonic password…)" + ), + Scope("libraries", "Access uploads, libraries, and audio metadata"), + Scope("edits", "Browse and submit edits on audio metadata"), + Scope("follows", "Access library follows"), + Scope("favorites", "Access favorites"), + Scope("filters", "Access content filters"), + Scope("listenings", "Access listening history"), + Scope("radios", "Access radios"), + Scope("playlists", "Access playlists"), + Scope("notifications", "Access personal notifications"), + Scope("security", "Access security settings"), + # Privileged scopes that require specific user permissions + Scope("instance:settings", "Access instance settings"), + Scope("instance:users", "Access local user accounts"), + Scope("instance:invitations", "Access invitations"), + Scope("instance:edits", "Access instance metadata edits"), + Scope( + "instance:libraries", "Access instance uploads, libraries and audio metadata" + ), + Scope("instance:accounts", "Access instance federated accounts"), + Scope("instance:domains", "Access instance domains"), + Scope("instance:policies", "Access instance moderation policies"), +] +SCOPES = [ + Scope("read", children=[s.copy("read") for s in BASE_SCOPES]), + Scope("write", children=[s.copy("write") for s in BASE_SCOPES]), +] + + +def flatten(*scopes): + for scope in scopes: + yield scope + yield from flatten(*scope.children) + + +SCOPES_BY_ID = {s.id: s for s in flatten(*SCOPES)} + +FEDERATION_REQUEST_SCOPES = {"read:libraries"} +ANONYMOUS_SCOPES = { + "read:libraries", + "read:playlists", + "read:listenings", + "read:favorites", + "read:radios", + "read:edits", +} + +COMMON_SCOPES = ANONYMOUS_SCOPES | { + "read:profile", + "write:profile", + "write:libraries", + "write:playlists", + "read:follows", + "write:follows", + "write:favorites", + "read:notifications", + "write:notifications", + "write:radios", + "write:edits", + "read:filters", + "write:filters", + "write:listenings", +} + +LOGGED_IN_SCOPES = COMMON_SCOPES | {"read:security", "write:security"} + +# We don't allow admin access for oauth apps yet +OAUTH_APP_SCOPES = COMMON_SCOPES + + +def get_from_permissions(**permissions): + from funkwhale_api.users import models + + final = LOGGED_IN_SCOPES + for permission_name, value in permissions.items(): + if value is False: + continue + config = models.PERMISSIONS_CONFIGURATION[permission_name] + final = final | config["scopes"] + return final diff --git a/api/funkwhale_api/users/oauth/serializers.py b/api/funkwhale_api/users/oauth/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..4788ba220a8077d2fd3c84fd555216c446cd8581 --- /dev/null +++ b/api/funkwhale_api/users/oauth/serializers.py @@ -0,0 +1,29 @@ +from rest_framework import serializers + +from .. import models + + +class ApplicationSerializer(serializers.ModelSerializer): + scopes = serializers.CharField(source="scope") + + class Meta: + model = models.Application + fields = ["client_id", "name", "scopes", "created", "updated"] + + +class CreateApplicationSerializer(serializers.ModelSerializer): + name = serializers.CharField(required=True, max_length=255) + scopes = serializers.CharField(source="scope", default="read") + + class Meta: + model = models.Application + fields = [ + "client_id", + "name", + "scopes", + "client_secret", + "created", + "updated", + "redirect_uris", + ] + read_only_fields = ["client_id", "client_secret", "created", "updated"] diff --git a/api/funkwhale_api/users/oauth/server.py b/api/funkwhale_api/users/oauth/server.py new file mode 100644 index 0000000000000000000000000000000000000000..f62ebf48a93c438e1f762dc95c2b2d0e85013111 --- /dev/null +++ b/api/funkwhale_api/users/oauth/server.py @@ -0,0 +1,25 @@ +import urllib.parse +import oauthlib.oauth2 + + +class OAuth2Server(oauthlib.oauth2.Server): + def verify_request(self, uri, *args, **kwargs): + valid, request = super().verify_request(uri, *args, **kwargs) + if valid: + return valid, request + + # maybe the token was given in the querystring? + query = urllib.parse.urlparse(request.uri).query + token = None + if query: + parsed_qs = urllib.parse.parse_qs(query) + token = parsed_qs.get("token", []) + if len(token) > 0: + token = token[0] + + if token: + valid = self.request_validator.validate_bearer_token( + token, request.scopes, request + ) + + return valid, request diff --git a/api/funkwhale_api/users/oauth/tasks.py b/api/funkwhale_api/users/oauth/tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..2aaba353a793ccaa563e3ca18af8308f30cafaca --- /dev/null +++ b/api/funkwhale_api/users/oauth/tasks.py @@ -0,0 +1,8 @@ +from funkwhale_api.taskapp import celery + +from oauth2_provider import models as oauth2_models + + +@celery.app.task(name="oauth.clear_expired_tokens") +def clear_expired_tokens(): + oauth2_models.clear_expired() diff --git a/api/funkwhale_api/users/oauth/urls.py b/api/funkwhale_api/users/oauth/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..832f9ca1ba6cfd64a673213961ed35942aef3c98 --- /dev/null +++ b/api/funkwhale_api/users/oauth/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls import url +from django.views.decorators.csrf import csrf_exempt + +from rest_framework import routers + +from . import views + +router = routers.SimpleRouter() +router.register(r"apps", views.ApplicationViewSet, "apps") +router.register(r"grants", views.GrantViewSet, "grants") + +urlpatterns = router.urls + [ + url("^authorize/$", csrf_exempt(views.AuthorizeView.as_view()), name="authorize"), + url("^token/$", views.TokenView.as_view(), name="token"), + url("^revoke/$", views.RevokeTokenView.as_view(), name="revoke"), +] diff --git a/api/funkwhale_api/users/oauth/views.py b/api/funkwhale_api/users/oauth/views.py new file mode 100644 index 0000000000000000000000000000000000000000..a8bbb239ca50a22be070904fc73b2a5536e51fa4 --- /dev/null +++ b/api/funkwhale_api/users/oauth/views.py @@ -0,0 +1,182 @@ +import json +import urllib.parse + +from django import http +from django.utils import timezone +from django.db.models import Q +from rest_framework import mixins, permissions, views, viewsets + +from oauth2_provider import exceptions as oauth2_exceptions +from oauth2_provider import views as oauth_views +from oauth2_provider.settings import oauth2_settings + +from .. import models +from .permissions import ScopePermission +from . import serializers + + +class ApplicationViewSet( + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, +): + anonymous_policy = True + required_scope = { + "retrieve": None, + "create": None, + "destroy": "write:security", + "update": "write:security", + "partial_update": "write:security", + "list": "read:security", + } + lookup_field = "client_id" + queryset = models.Application.objects.all().order_by("-created") + serializer_class = serializers.ApplicationSerializer + + def get_serializer_class(self): + if self.request.method.lower() == "post": + return serializers.CreateApplicationSerializer + return super().get_serializer_class() + + def perform_create(self, serializer): + return serializer.save( + client_type=models.Application.CLIENT_CONFIDENTIAL, + authorization_grant_type=models.Application.GRANT_AUTHORIZATION_CODE, + user=self.request.user if self.request.user.is_authenticated else None, + ) + + def get_serializer(self, *args, **kwargs): + serializer_class = self.get_serializer_class() + try: + owned = args[0].user == self.request.user + except (IndexError, AttributeError): + owned = False + if owned: + serializer_class = serializers.CreateApplicationSerializer + + kwargs["context"] = self.get_serializer_context() + return serializer_class(*args, **kwargs) + + def get_queryset(self): + qs = super().get_queryset() + if self.action in ["list", "destroy", "update", "partial_update"]: + qs = qs.filter(user=self.request.user) + return qs + + +class GrantViewSet( + mixins.RetrieveModelMixin, + mixins.DestroyModelMixin, + mixins.ListModelMixin, + viewsets.GenericViewSet, +): + """ + This is a viewset that list applications that have access to the request user + account, to allow revoking tokens easily. + """ + + permission_classes = [permissions.IsAuthenticated, ScopePermission] + required_scope = "security" + lookup_field = "client_id" + queryset = models.Application.objects.all().order_by("-created") + serializer_class = serializers.ApplicationSerializer + pagination_class = None + + def get_queryset(self): + now = timezone.now() + queryset = super().get_queryset() + grants = models.Grant.objects.filter(user=self.request.user, expires__gt=now) + access_tokens = models.AccessToken.objects.filter(user=self.request.user) + refresh_tokens = models.RefreshToken.objects.filter( + user=self.request.user, revoked=None + ) + + return queryset.filter( + Q(pk__in=access_tokens.values("application")) + | Q(pk__in=refresh_tokens.values("application")) + | Q(pk__in=grants.values("application")) + ).distinct() + + def perform_create(self, serializer): + return serializer.save( + client_type=models.Application.CLIENT_CONFIDENTIAL, + authorization_grant_type=models.Application.GRANT_AUTHORIZATION_CODE, + ) + + def perform_destroy(self, instance): + application = instance + + access_tokens = application.accesstoken_set.filter(user=self.request.user) + for token in access_tokens: + token.revoke() + + refresh_tokens = application.refreshtoken_set.filter(user=self.request.user) + for token in refresh_tokens: + try: + token.revoke() + except models.AccessToken.DoesNotExist: + token.access_token = None + token.revoked = timezone.now() + token.save(update_fields=["access_token", "revoked"]) + grants = application.grant_set.filter(user=self.request.user) + grants.delete() + + +class AuthorizeView(views.APIView, oauth_views.AuthorizationView): + permission_classes = [permissions.IsAuthenticated] + server_class = oauth2_settings.OAUTH2_SERVER_CLASS + validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS + oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS + skip_authorization_completely = False + oauth2_data = {} + + def form_invalid(self, form): + """ + Return a JSON response instead of a template one + """ + errors = form.errors + + return self.json_payload(errors, status_code=400) + + def form_valid(self, form): + try: + response = super().form_valid(form) + + except models.Application.DoesNotExist: + return self.json_payload({"non_field_errors": ["Invalid application"]}, 400) + + if self.request.is_ajax() and response.status_code == 302: + # Web client need this to be able to redirect the user + query = urllib.parse.urlparse(response["Location"]).query + code = urllib.parse.parse_qs(query)["code"][0] + return self.json_payload( + {"redirect_uri": response["Location"], "code": code}, status_code=200 + ) + + return response + + def error_response(self, error, application): + if isinstance(error, oauth2_exceptions.FatalClientError): + return self.json_payload({"detail": error.oauthlib_error.description}, 400) + return super().error_response(error, application) + + def json_payload(self, payload, status_code): + return http.HttpResponse( + json.dumps(payload), status=status_code, content_type="application/json" + ) + + def handle_no_permission(self): + return self.json_payload( + {"detail": "Authentication credentials were not provided."}, 401 + ) + + +class TokenView(oauth_views.TokenView): + pass + + +class RevokeTokenView(oauth_views.RevokeTokenView): + pass diff --git a/api/funkwhale_api/users/permissions.py b/api/funkwhale_api/users/permissions.py deleted file mode 100644 index 02c1198e8cb208a201bd257e910bce3c2d53174b..0000000000000000000000000000000000000000 --- a/api/funkwhale_api/users/permissions.py +++ /dev/null @@ -1,23 +0,0 @@ -from rest_framework.permissions import BasePermission - - -class HasUserPermission(BasePermission): - """ - Ensure the request user has the proper permissions. - - Usage: - - class MyView(APIView): - permission_classes = [HasUserPermission] - required_permissions = ['federation'] - """ - - def has_permission(self, request, view): - if not hasattr(request, "user") or not request.user: - return False - if request.user.is_anonymous: - return False - operator = getattr(view, "permission_operator", "and") - return request.user.has_permissions( - *view.required_permissions, operator=operator - ) diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index c75604f6eb94ea97b26247604b13bf2c01fa6dfe..a45d414b4d9b6eaad6308aff70710df1f3e825c2 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -1,6 +1,5 @@ import re -from django.conf import settings from django.core import validators from django.utils.deconstruct import deconstructible from django.utils.translation import gettext_lazy as _ @@ -12,6 +11,7 @@ from versatileimagefield.serializers import VersatileImageFieldSerializer from funkwhale_api.activity import serializers as activity_serializers from funkwhale_api.common import serializers as common_serializers +from . import adapters from . import models @@ -94,6 +94,7 @@ class UserWriteSerializer(serializers.ModelSerializer): class UserReadSerializer(serializers.ModelSerializer): permissions = serializers.SerializerMethodField() + full_username = serializers.SerializerMethodField() avatar = avatar_field class Meta: @@ -101,6 +102,7 @@ class UserReadSerializer(serializers.ModelSerializer): fields = [ "id", "username", + "full_username", "name", "email", "is_staff", @@ -114,6 +116,10 @@ class UserReadSerializer(serializers.ModelSerializer): def get_permissions(self, o): return o.get_permissions() + def get_full_username(self, o): + if o.actor: + return o.actor.full_username + class MeSerializer(UserReadSerializer): quota_status = serializers.SerializerMethodField() @@ -127,4 +133,4 @@ class MeSerializer(UserReadSerializer): class PasswordResetSerializer(PRS): def get_email_options(self): - return {"extra_email_context": {"funkwhale_url": settings.FUNKWHALE_URL}} + return {"extra_email_context": adapters.get_email_context()} diff --git a/api/funkwhale_api/users/views.py b/api/funkwhale_api/users/views.py index 2393882e725ada35c6d1890c82e04a685f60221f..8cbb23bd11fbc445feda2782ac77da8242d4e8c8 100644 --- a/api/funkwhale_api/users/views.py +++ b/api/funkwhale_api/users/views.py @@ -11,6 +11,7 @@ from . import models, serializers class RegisterView(BaseRegisterView): serializer_class = serializers.RegisterSerializer + permission_classes = [] def create(self, request, *args, **kwargs): invitation_code = request.data.get("invitation") @@ -27,6 +28,8 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet): queryset = models.User.objects.all() serializer_class = serializers.UserWriteSerializer lookup_field = "username" + lookup_value_regex = r"[a-zA-Z0-9-_.]+" + required_scope = "profile" @action(methods=["get"], detail=False) def me(self, request, *args, **kwargs): @@ -34,7 +37,12 @@ class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet): serializer = serializers.MeSerializer(request.user) return Response(serializer.data) - @action(methods=["get", "post", "delete"], url_path="subsonic-token", detail=True) + @action( + methods=["get", "post", "delete"], + required_scope="security", + url_path="subsonic-token", + detail=True, + ) def subsonic_token(self, request, *args, **kwargs): if not self.request.user.username == kwargs.get("username"): return Response(status=403) diff --git a/api/requirements/base.txt b/api/requirements/base.txt index c2fc95a6f3371772e9371bda64a5fdd941cb9f05..c72c20374ecbdb1f72ed0a3d0f4371339e0a90b7 100644 --- a/api/requirements/base.txt +++ b/api/requirements/base.txt @@ -1,5 +1,5 @@ # Bleeding edge Django -django>=2.1,<2.2 +django>=2.2.1,<2.3 # Configuration django-environ>=0.4,<0.5 @@ -9,46 +9,43 @@ Pillow>=5.4,<5.5 # For user registration, either via email or social # Well-built with regular release cycles! -django-allauth>=0.36,<0.37 +django-allauth>=0.39,<0.40 # Python-PostgreSQL Database Adapter -psycopg2-binary>=2.7,<=2.8 +psycopg2-binary>=2.8,<=2.9 # Time zones support -pytz==2018.9 +pytz==2019.1 # Redis support django-redis>=4.10,<4.11 -redis>=3.0,<3.1 -kombu>=4.2.2,<4.3 +redis>=3.2,<3.3 +kombu>=4.5,<4.6 -celery>=4.2,<4.3 +celery>=4.3,<4.4 # Your custom requirements go here -django-cors-headers>=2.1,<2.2 +django-cors-headers>=2.5.3,<2.6 musicbrainzngs==0.6 djangorestframework>=3.9,<3.10 djangorestframework-jwt>=1.11,<1.12 pendulum>=2,<3 persisting-theory>=0.2,<0.3 -django-versatileimagefield>=1.9,<1.10 -django-filter>=2.0,<2.1 +django-versatileimagefield>=1.10,<1.11 +django-filter>=2.1,<2.2 django-rest-auth>=0.9,<0.10 -beautifulsoup4>=4.6,<4.7 -Markdown>=2.6,<2.7 -ipython>=6,<7 +ipython>=7,<8 mutagen>=1.42,<1.43 -django-taggit>=0.23,<0.24 +django-taggit>=0.24,<0.25 pymemoize==1.0.3 django-dynamic-preferences>=1.7,<1.8 -raven>=6.5,<7 +raven>=6.10,<7 python-magic==0.4.15 -ffmpeg-python==0.1.10 # XXX: until https://github.com/django/channels/issues/1240 is fixed channels==2.1.6 channels_redis>=2.3,<2.4 @@ -58,9 +55,17 @@ cryptography>=2,<3 # requests-http-signature==0.0.3 # clone until the branch is merged and released upstream git+https://github.com/EliotBerriot/requests-http-signature.git@signature-header-support -django-cleanup==2.1.0 +django-cleanup==3.2.0 # for LDAP authentication -python-ldap==3.1.0 +python-ldap==3.2.0 django-auth-ldap==1.7.0 -pydub==0.23.0 +pydub==0.23.1 + +pyld==1.0.4 +aiohttp==3.5.4 +autobahn>=19.3.3 + +django-oauth-toolkit==1.2 +django-storages==1.7.1 +boto3<3 diff --git a/api/requirements/local.txt b/api/requirements/local.txt index 60724fc959a0b666d8b15cb39d333e6d61f89866..dcedb43e75c0073424c31608d3510f7000ae9acc 100644 --- a/api/requirements/local.txt +++ b/api/requirements/local.txt @@ -11,3 +11,6 @@ django-debug-toolbar>=1.11,<1.12 ipdb==0.11 black profiling + +asynctest==0.12.2 +aioresponses==0.6.0 diff --git a/api/setup.cfg b/api/setup.cfg index a3c0e746864b1eb9bd92d7d9077c63490cebe2f6..431c4f1ee58f6ad5c7ac70cba9dd34081700c4ad 100644 --- a/api/setup.cfg +++ b/api/setup.cfg @@ -24,3 +24,4 @@ env = WEAK_PASSWORDS=True CREATE_IMAGE_THUMBNAILS=False FORCE_HTTPS_URLS=False + FUNKWHALE_SPA_HTML_ROOT=http://noop/ diff --git a/api/tests/common/test_decorators.py b/api/tests/common/test_decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..f8fef79045f744f965f16723dc071503148d7283 --- /dev/null +++ b/api/tests/common/test_decorators.py @@ -0,0 +1,122 @@ +import pytest + +from rest_framework import viewsets + +from funkwhale_api.common import decorators +from funkwhale_api.common import models +from funkwhale_api.common import mutations +from funkwhale_api.common import serializers +from funkwhale_api.common import signals +from funkwhale_api.common import tasks +from funkwhale_api.music import models as music_models +from funkwhale_api.music import licenses + + +class V(viewsets.ModelViewSet): + queryset = music_models.Track.objects.all() + mutations = decorators.mutations_route(types=["update"]) + permission_classes = [] + + +def test_mutations_route_list(factories, api_request): + track = factories["music.Track"]() + mutation = factories["common.Mutation"](target=track, type="update", payload="") + factories["common.Mutation"](target=track, type="noop", payload="") + + view = V.as_view({"get": "mutations"}) + expected = { + "next": None, + "previous": None, + "count": 1, + "results": [serializers.APIMutationSerializer(mutation).data], + } + + request = api_request.get("/") + response = view(request, pk=track.pk) + + assert response.status_code == 200 + assert response.data == expected + + +@pytest.mark.parametrize("is_approved", [False, True]) +def test_mutations_route_create_success(factories, api_request, is_approved, mocker): + licenses.load(licenses.LICENSES) + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") + user = factories["users.User"](permission_library=True) + actor = user.create_actor() + track = factories["music.Track"](title="foo", local=True) + view = V.as_view({"post": "mutations"}) + + request = api_request.post( + "/", + { + "type": "update", + "payload": {"title": "bar", "unknown": "test", "license": "cc-by-nc-4.0"}, + "summary": "hello", + "is_approved": is_approved, + }, + format="json", + ) + setattr(request, "user", user) + setattr(request, "session", {}) + response = view(request, pk=track.pk) + + assert response.status_code == 201 + + mutation = models.Mutation.objects.get_for_target(track).latest("id") + + assert mutation.type == "update" + assert mutation.payload == {"title": "bar", "license": "cc-by-nc-4.0"} + assert mutation.created_by == actor + assert mutation.is_approved is is_approved + assert mutation.is_applied is None + assert mutation.target == track + assert mutation.summary == "hello" + + if is_approved: + on_commit.assert_any_call(tasks.apply_mutation.delay, mutation_id=mutation.pk) + expected = serializers.APIMutationSerializer(mutation).data + assert response.data == expected + on_commit.assert_any_call( + signals.mutation_created.send, mutation=mutation, sender=None + ) + + +def test_mutations_route_create_no_auth(factories, api_request): + track = factories["music.Track"](title="foo") + view = V.as_view({"post": "mutations"}) + + request = api_request.post("/", {}, format="json") + response = view(request, pk=track.pk) + + assert response.status_code == 401 + + +@pytest.mark.parametrize("is_approved", [False, True]) +def test_mutations_route_create_no_perm(factories, api_request, mocker, is_approved): + track = factories["music.Track"](title="foo") + view = V.as_view({"post": "mutations"}) + user = factories["users.User"]() + actor = user.create_actor() + has_perm = mocker.patch.object(mutations.registry, "has_perm", return_value=False) + request = api_request.post( + "/", + { + "type": "update", + "payload": {"title": "bar", "unknown": "test"}, + "summary": "hello", + "is_approved": is_approved, + }, + format="json", + ) + setattr(request, "user", user) + setattr(request, "session", {}) + response = view(request, pk=track.pk) + + assert response.status_code == 403 + has_perm.assert_called_once_with( + actor=actor, + obj=track, + type="update", + perm="approve" if is_approved else "suggest", + ) diff --git a/api/tests/common/test_filters.py b/api/tests/common/test_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..2e89dfa37c1dc2800569aca928b9452e0b2d29fe --- /dev/null +++ b/api/tests/common/test_filters.py @@ -0,0 +1,38 @@ +import pytest + +from funkwhale_api.common import filters + + +@pytest.mark.parametrize( + "value, expected", + [ + (True, True), + ("True", True), + ("true", True), + ("1", True), + ("yes", True), + (False, False), + ("False", False), + ("false", False), + ("0", False), + ("no", False), + ("None", None), + ("none", None), + ("Null", None), + ("null", None), + ], +) +def test_mutation_filter_is_approved(value, expected, factories): + mutations = { + True: factories["common.Mutation"](is_approved=True, payload={}), + False: factories["common.Mutation"](is_approved=False, payload={}), + None: factories["common.Mutation"](is_approved=None, payload={}), + } + + qs = mutations[True].__class__.objects.all() + + filterset = filters.MutationFilter( + {"q": "is_approved:{}".format(value)}, queryset=qs + ) + + assert list(filterset.qs) == [mutations[expected]] diff --git a/api/tests/common/test_models.py b/api/tests/common/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..25c9befda809de508864b39f7bdc551f6600b14b --- /dev/null +++ b/api/tests/common/test_models.py @@ -0,0 +1,17 @@ +import pytest + +from django.urls import reverse + +from funkwhale_api.federation import utils as federation_utils + + +@pytest.mark.parametrize( + "model,factory_args,namespace", + [("common.Mutation", {"created_by__local": True}, "federation:edits-detail")], +) +def test_mutation_fid_is_populated(factories, model, factory_args, namespace): + instance = factories[model](**factory_args, fid=None, payload={}) + + assert instance.fid == federation_utils.full_url( + reverse(namespace, kwargs={"uuid": instance.uuid}) + ) diff --git a/api/tests/common/test_mutations.py b/api/tests/common/test_mutations.py new file mode 100644 index 0000000000000000000000000000000000000000..3c0d869a116fd832b9927e1b4c7c1da51b4a88e2 --- /dev/null +++ b/api/tests/common/test_mutations.py @@ -0,0 +1,141 @@ +import pytest + +from funkwhale_api.common import mutations + +from rest_framework import serializers + + +@pytest.fixture +def mutations_registry(): + return mutations.Registry() + + +def test_apply_mutation(mutations_registry, db): + class Obj: + pass + + obj = Obj() + + @mutations_registry.connect("foo", Obj) + class S(mutations.MutationSerializer): + foo = serializers.ChoiceField(choices=["bar", "baz"]) + + def apply(self, obj, validated_data): + setattr(obj, "foo", validated_data["foo"]) + + with pytest.raises(mutations.ConfNotFound): + mutations_registry.apply("foo", object(), payload={"foo": "nope"}) + + with pytest.raises(serializers.ValidationError): + mutations_registry.apply("foo", obj, payload={"foo": "nope"}) + + mutations_registry.apply("foo", obj, payload={"foo": "bar"}) + + assert obj.foo == "bar" + + +def test_apply_update_mutation(factories, mutations_registry, mocker): + user = factories["users.User"](email="hello@test.email") + get_update_previous_state = mocker.patch.object( + mutations, "get_update_previous_state" + ) + + @mutations_registry.connect("update", user.__class__) + class S(mutations.UpdateMutationSerializer): + class Meta: + model = user.__class__ + fields = ["username", "email"] + + previous_state = mutations_registry.apply( + "update", user, payload={"username": "foo"} + ) + assert previous_state == get_update_previous_state.return_value + get_update_previous_state.assert_called_once_with( + user, "username", serialized_relations={} + ) + user.refresh_from_db() + + assert user.username == "foo" + assert user.email == "hello@test.email" + + +def test_db_serialize_update_mutation(factories, mutations_registry, mocker): + user = factories["users.User"](email="hello@test.email", with_actor=True) + + class S(mutations.UpdateMutationSerializer): + serialized_relations = {"actor": "full_username"} + + class Meta: + model = user.__class__ + fields = ["actor"] + + expected = {"actor": user.actor.full_username} + assert S().db_serialize({"actor": user.actor}) == expected + + +def test_is_valid_mutation(factories, mutations_registry): + user = factories["users.User"].build() + + @mutations_registry.connect("update", user.__class__) + class S(mutations.UpdateMutationSerializer): + class Meta: + model = user.__class__ + fields = ["email"] + + with pytest.raises(serializers.ValidationError): + mutations_registry.is_valid("update", user, payload={"email": "foo"}) + mutations_registry.is_valid("update", user, payload={"email": "foo@bar.com"}) + + +@pytest.mark.parametrize("perm", ["approve", "suggest"]) +def test_permissions(perm, factories, mutations_registry, mocker): + actor = factories["federation.Actor"].build() + user = factories["users.User"].build() + + class S(mutations.UpdateMutationSerializer): + class Meta: + model = user.__class__ + fields = ["email"] + + mutations_registry.connect("update", user.__class__)(S) + + assert mutations_registry.has_perm(perm, "update", obj=user, actor=actor) is False + + checker = mocker.Mock(return_value=True) + mutations_registry.connect("update", user.__class__, perm_checkers={perm: checker})( + S + ) + + assert mutations_registry.has_perm(perm, "update", obj=user, actor=actor) is True + checker.assert_called_once_with(obj=user, actor=actor) + + +def test_model_apply(factories, mocker, now): + target = factories["music.Artist"]() + mutation = factories["common.Mutation"](type="noop", target=target, payload="hello") + + apply = mocker.patch.object( + mutations.registry, "apply", return_value={"previous": "state"} + ) + + mutation.apply() + apply.assert_called_once_with(type="noop", obj=target, payload="hello") + mutation.refresh_from_db() + + assert mutation.is_applied is True + assert mutation.previous_state == {"previous": "state"} + assert mutation.applied_date == now + + +def test_get_previous_state(factories): + obj = factories["music.Track"]() + expected = { + "title": {"value": obj.title}, + "album": {"value": obj.album.pk, "repr": str(obj.album)}, + } + assert ( + mutations.get_update_previous_state( + obj, "title", "album", serialized_relations={"album": "pk"} + ) + == expected + ) diff --git a/api/tests/common/test_pagination.py b/api/tests/common/test_pagination.py new file mode 100644 index 0000000000000000000000000000000000000000..cacbe740c7ddae374ae502ca1ae1a2867edab27b --- /dev/null +++ b/api/tests/common/test_pagination.py @@ -0,0 +1,29 @@ +import pytest + +from funkwhale_api.common import pagination + + +@pytest.mark.parametrize( + "view_max_page_size, view_default_page_size, request_page_size, expected", + [ + (50, 50, None, 50), + (50, 25, None, 25), + (25, None, None, 25), + (50, 25, 100, 50), + (50, None, 100, 50), + (50, 25, 33, 33), + ], +) +def test_funkwhale_pagination_uses_view_page_size( + view_max_page_size, view_default_page_size, request_page_size, expected, mocker +): + p = pagination.FunkwhalePagination() + + p.view = mocker.Mock( + max_page_size=view_max_page_size, default_page_size=view_default_page_size + ) + query = {} + if request_page_size: + query["page_size"] = request_page_size + request = mocker.Mock(query_params=query) + assert p.get_page_size(request) == expected diff --git a/api/tests/common/test_search.py b/api/tests/common/test_search.py index e5be7bc900f0d215f27909603390dd115fbb68d8..8872298025658d27ec174470ffd193ca6f64c321 100644 --- a/api/tests/common/test_search.py +++ b/api/tests/common/test_search.py @@ -1,6 +1,7 @@ import pytest from django.db.models import Q +from django import forms from funkwhale_api.common import search from funkwhale_api.music import models as music_models @@ -45,6 +46,24 @@ def test_search_config_query(query, expected): assert cleaned["search_query"] == expected +def test_search_config_query_filter_field_handler(): + s = search.SearchConfig( + filter_fields={"account": {"handler": lambda v: Q(hello="world")}} + ) + + cleaned = s.clean("account:noop") + assert cleaned["filter_query"] == Q(hello="world") + + +def test_search_config_query_filter_field(): + s = search.SearchConfig( + filter_fields={"account": {"to": "noop", "field": forms.BooleanField()}} + ) + + cleaned = s.clean("account:true") + assert cleaned["filter_query"] == Q(noop=True) + + @pytest.mark.parametrize( "query,expected", [ diff --git a/api/tests/common/test_tasks.py b/api/tests/common/test_tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..f097c44231af27de220df3889cf7f1db2fe48ea0 --- /dev/null +++ b/api/tests/common/test_tasks.py @@ -0,0 +1,65 @@ +import pytest + +from funkwhale_api.common import serializers +from funkwhale_api.common import signals +from funkwhale_api.common import tasks + + +def test_apply_migration(factories, mocker): + mutation = factories["common.Mutation"](payload={}) + apply = mocker.patch.object(mutation.__class__, "apply") + tasks.apply_mutation(mutation_id=mutation.pk) + + apply.assert_called_once_with() + + +def test_broadcast_mutation_created(factories, mocker): + mutation = factories["common.Mutation"](payload={}) + factories["common.Mutation"](payload={}, is_approved=True) + group_send = mocker.patch("funkwhale_api.common.channels.group_send") + expected = serializers.APIMutationSerializer(mutation).data + + signals.mutation_created.send(sender=None, mutation=mutation) + group_send.assert_called_with( + "instance_activity", + { + "type": "event.send", + "text": "", + "data": { + "type": "mutation.created", + "mutation": expected, + "pending_review_count": 1, + }, + }, + ) + + +def test_broadcast_mutation_updated(factories, mocker): + mutation = factories["common.Mutation"](payload={}, is_approved=True) + factories["common.Mutation"](payload={}) + group_send = mocker.patch("funkwhale_api.common.channels.group_send") + expected = serializers.APIMutationSerializer(mutation).data + + signals.mutation_updated.send( + sender=None, mutation=mutation, old_is_approved=False, new_is_approved=True + ) + group_send.assert_called_with( + "instance_activity", + { + "type": "event.send", + "text": "", + "data": { + "type": "mutation.updated", + "mutation": expected, + "old_is_approved": False, + "new_is_approved": True, + "pending_review_count": 1, + }, + }, + ) + + +def test_cannot_apply_already_applied_migration(factories): + mutation = factories["common.Mutation"](payload={}, is_applied=True) + with pytest.raises(mutation.__class__.DoesNotExist): + tasks.apply_mutation(mutation_id=mutation.pk) diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py index c5c4a8e239b9e24f1699b3d29b36e2d0b720e0f8..ea64ed9d2834f8d97aee7425fb223548ffab1b52 100644 --- a/api/tests/common/test_utils.py +++ b/api/tests/common/test_utils.py @@ -1,3 +1,5 @@ +import pytest + from funkwhale_api.common import utils @@ -42,3 +44,44 @@ def test_update_prefix(factories): old = n.fid n.refresh_from_db() assert n.fid == old.replace("http://", "https://") + + +@pytest.mark.parametrize( + "conf, mock_args, data, expected", + [ + ( + ["field1", "field2"], + {"field1": "foo", "field2": "test"}, + {"field1": "bar"}, + {"field1": "bar"}, + ), + ( + ["field1", "field2"], + {"field1": "foo", "field2": "test"}, + {"field1": "foo"}, + {}, + ), + ( + ["field1", "field2"], + {"field1": "foo", "field2": "test"}, + {"field1": "foo", "field2": "test"}, + {}, + ), + ( + ["field1", "field2"], + {"field1": "foo", "field2": "test"}, + {"field1": "bar", "field2": "test1"}, + {"field1": "bar", "field2": "test1"}, + ), + ( + [("field1", "Hello"), ("field2", "World")], + {"Hello": "foo", "World": "test"}, + {"field1": "bar", "field2": "test1"}, + {"Hello": "bar", "World": "test1"}, + ), + ], +) +def test_get_updated_fields(conf, mock_args, data, expected, mocker): + obj = mocker.Mock(**mock_args) + + assert utils.get_updated_fields(conf, data, obj) == expected diff --git a/api/tests/common/test_views.py b/api/tests/common/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..d2b53b41f620ac80caa5d5835ef3afdb02c1bd60 --- /dev/null +++ b/api/tests/common/test_views.py @@ -0,0 +1,165 @@ +import pytest +from django.urls import reverse + +from funkwhale_api.common import serializers +from funkwhale_api.common import signals +from funkwhale_api.common import tasks + + +def test_can_detail_mutation(logged_in_api_client, factories): + mutation = factories["common.Mutation"]( + payload={}, target=factories["music.Artist"]() + ) + url = reverse("api:v1:mutations-detail", kwargs={"uuid": mutation.uuid}) + + response = logged_in_api_client.get(url) + + expected = serializers.APIMutationSerializer(mutation).data + + assert response.status_code == 200 + assert response.data == expected + + +def test_can_list_mutations(logged_in_api_client, factories): + mutation = factories["common.Mutation"]( + payload={}, target=factories["music.Artist"]() + ) + url = reverse("api:v1:mutations-list") + + response = logged_in_api_client.get(url) + + expected = serializers.APIMutationSerializer(mutation).data + + assert response.status_code == 200 + assert response.data["results"] == [expected] + + +def test_can_destroy_mutation_creator(logged_in_api_client, factories): + actor = logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"]( + target=track, type="update", payload={}, created_by=actor + ) + url = reverse("api:v1:mutations-detail", kwargs={"uuid": mutation.uuid}) + + response = logged_in_api_client.delete(url) + + assert response.status_code == 204 + + +def test_can_destroy_mutation_not_creator(logged_in_api_client, factories): + logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"](type="update", target=track, payload={}) + url = reverse("api:v1:mutations-detail", kwargs={"uuid": mutation.uuid}) + + response = logged_in_api_client.delete(url) + + assert response.status_code == 403 + + mutation.refresh_from_db() + + +def test_can_destroy_mutation_has_perm(logged_in_api_client, factories, mocker): + actor = logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"](target=track, type="update", payload={}) + has_perm = mocker.patch( + "funkwhale_api.common.mutations.registry.has_perm", return_value=True + ) + url = reverse("api:v1:mutations-detail", kwargs={"uuid": mutation.uuid}) + + response = logged_in_api_client.delete(url) + + assert response.status_code == 204 + has_perm.assert_called_once_with( + obj=mutation.target, type=mutation.type, perm="approve", actor=actor + ) + + +@pytest.mark.parametrize("endpoint, expected", [("approve", True), ("reject", False)]) +def test_can_approve_reject_mutation_with_perm( + endpoint, expected, logged_in_api_client, factories, mocker +): + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") + actor = logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"](target=track, type="update", payload={}) + has_perm = mocker.patch( + "funkwhale_api.common.mutations.registry.has_perm", return_value=True + ) + url = reverse( + "api:v1:mutations-{}".format(endpoint), kwargs={"uuid": mutation.uuid} + ) + + response = logged_in_api_client.post(url) + + assert response.status_code == 200 + has_perm.assert_called_once_with( + obj=mutation.target, type=mutation.type, perm="approve", actor=actor + ) + + if expected: + on_commit.assert_any_call(tasks.apply_mutation.delay, mutation_id=mutation.id) + mutation.refresh_from_db() + + assert mutation.is_approved == expected + assert mutation.approved_by == actor + + on_commit.assert_any_call( + signals.mutation_updated.send, + mutation=mutation, + sender=None, + new_is_approved=expected, + old_is_approved=None, + ) + + +@pytest.mark.parametrize("endpoint, expected", [("approve", True), ("reject", False)]) +def test_cannot_approve_reject_applied_mutation( + endpoint, expected, logged_in_api_client, factories, mocker +): + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") + logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"]( + target=track, type="update", payload={}, is_applied=True + ) + mocker.patch("funkwhale_api.common.mutations.registry.has_perm", return_value=True) + url = reverse( + "api:v1:mutations-{}".format(endpoint), kwargs={"uuid": mutation.uuid} + ) + + response = logged_in_api_client.post(url) + + assert response.status_code == 403 + on_commit.assert_not_called() + + mutation.refresh_from_db() + + assert mutation.is_approved is None + assert mutation.approved_by is None + + +@pytest.mark.parametrize("endpoint, expected", [("approve", True), ("reject", False)]) +def test_cannot_approve_reject_without_perm( + endpoint, expected, logged_in_api_client, factories, mocker +): + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") + logged_in_api_client.user.create_actor() + track = factories["music.Track"]() + mutation = factories["common.Mutation"](target=track, type="update", payload={}) + mocker.patch("funkwhale_api.common.mutations.registry.has_perm", return_value=False) + url = reverse( + "api:v1:mutations-{}".format(endpoint), kwargs={"uuid": mutation.uuid} + ) + + response = logged_in_api_client.post(url) + + assert response.status_code == 403 + on_commit.assert_not_called() + + mutation.refresh_from_db() + + assert mutation.is_approved is None + assert mutation.approved_by is None diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 03dbdfa4e53caf36cf40e37c639378ab4517516e..a1baedcc66b47b8518d89a12f11706f69306b03b 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -22,12 +22,16 @@ from django.db import connection from django.db.migrations.executor import MigrationExecutor from django.db.models import QuerySet +from aioresponses import aioresponses from dynamic_preferences.registries import global_preferences_registry from rest_framework import fields as rest_fields from rest_framework.test import APIClient, APIRequestFactory from funkwhale_api.activity import record -from funkwhale_api.users.permissions import HasUserPermission +from funkwhale_api.federation import actors + + +pytest_plugins = "aiohttp.pytest_plugin" class FunkwhaleProvider(internet_provider.Provider): @@ -312,16 +316,6 @@ def authenticated_actor(factories, mocker): yield actor -@pytest.fixture -def assert_user_permission(): - def inner(view, permissions, operator="and"): - assert HasUserPermission in view.permission_classes - assert getattr(view, "permission_operator", "and") == operator - assert set(view.required_permissions) == set(permissions) - - return inner - - @pytest.fixture def to_api_date(): def inner(value): @@ -396,6 +390,7 @@ def stdout(): @pytest.fixture def spa_html(r_mock, settings): + settings.FUNKWHALE_SPA_HTML_ROOT = "http://noop/" yield r_mock.get( settings.FUNKWHALE_SPA_HTML_ROOT + "index.html", text="<head></head>" ) @@ -416,3 +411,14 @@ def migrator(transactional_db): def rsa_small_key(settings): # smaller size for faster generation, since it's CPU hungry settings.RSA_KEY_SIZE = 512 + + +@pytest.fixture(autouse=True) +def a_responses(): + with aioresponses() as m: + yield m + + +@pytest.fixture +def service_actor(db): + return actors.get_service_actor() diff --git a/api/tests/favorites/test_favorites.py b/api/tests/favorites/test_favorites.py index 7e8d1d3fdd14f5e082329873bfe25a6c7c550bfc..190c7918439f4acd73dce5b4e9d599ad3d979673 100644 --- a/api/tests/favorites/test_favorites.py +++ b/api/tests/favorites/test_favorites.py @@ -17,12 +17,14 @@ def test_user_can_add_favorite(factories): assert f.user == user -def test_user_can_get_his_favorites(api_request, factories, logged_in_client, client): +def test_user_can_get_his_favorites( + api_request, factories, logged_in_api_client, client +): r = api_request.get("/") - favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user) + favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) factories["favorites.TrackFavorite"]() url = reverse("api:v1:favorites:tracks-list") - response = logged_in_client.get(url, {"user": logged_in_client.user.pk}) + response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk}) expected = [ { "user": users_serializers.UserBasicSerializer( @@ -40,21 +42,21 @@ def test_user_can_get_his_favorites(api_request, factories, logged_in_client, cl def test_user_can_retrieve_all_favorites_at_once( - api_request, factories, logged_in_client, client + api_request, factories, logged_in_api_client, client ): - favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user) + favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) factories["favorites.TrackFavorite"]() url = reverse("api:v1:favorites:tracks-all") - response = logged_in_client.get(url, {"user": logged_in_client.user.pk}) + response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk}) expected = [{"track": favorite.track.id, "id": favorite.id}] assert response.status_code == 200 assert response.data["results"] == expected -def test_user_can_add_favorite_via_api(factories, logged_in_client, activity_muted): +def test_user_can_add_favorite_via_api(factories, logged_in_api_client, activity_muted): track = factories["music.Track"]() url = reverse("api:v1:favorites:tracks-list") - response = logged_in_client.post(url, {"track": track.pk}) + response = logged_in_api_client.post(url, {"track": track.pk}) favorite = TrackFavorite.objects.latest("id") expected = { @@ -66,15 +68,15 @@ def test_user_can_add_favorite_via_api(factories, logged_in_client, activity_mut assert expected == parsed_json assert favorite.track == track - assert favorite.user == logged_in_client.user + assert favorite.user == logged_in_api_client.user def test_adding_favorites_calls_activity_record( - factories, logged_in_client, activity_muted + factories, logged_in_api_client, activity_muted ): track = factories["music.Track"]() url = reverse("api:v1:favorites:tracks-list") - response = logged_in_client.post(url, {"track": track.pk}) + response = logged_in_api_client.post(url, {"track": track.pk}) favorite = TrackFavorite.objects.latest("id") expected = { @@ -86,27 +88,27 @@ def test_adding_favorites_calls_activity_record( assert expected == parsed_json assert favorite.track == track - assert favorite.user == logged_in_client.user + assert favorite.user == logged_in_api_client.user activity_muted.assert_called_once_with(favorite) -def test_user_can_remove_favorite_via_api(logged_in_client, factories, client): - favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user) +def test_user_can_remove_favorite_via_api(logged_in_api_client, factories): + favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) url = reverse("api:v1:favorites:tracks-detail", kwargs={"pk": favorite.pk}) - response = client.delete(url, {"track": favorite.track.pk}) + response = logged_in_api_client.delete(url, {"track": favorite.track.pk}) assert response.status_code == 204 assert TrackFavorite.objects.count() == 0 @pytest.mark.parametrize("method", ["delete", "post"]) def test_user_can_remove_favorite_via_api_using_track_id( - method, factories, logged_in_client + method, factories, logged_in_api_client ): - favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user) + favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) url = reverse("api:v1:favorites:tracks-remove") - response = getattr(logged_in_client, method)( + response = getattr(logged_in_api_client, method)( url, json.dumps({"track": favorite.track.pk}), content_type="application/json" ) @@ -122,11 +124,11 @@ def test_url_require_auth(url, method, db, preferences, client): assert response.status_code == 401 -def test_can_filter_tracks_by_favorites(factories, logged_in_client): - favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user) +def test_can_filter_tracks_by_favorites(factories, logged_in_api_client): + favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user) url = reverse("api:v1:tracks-list") - response = logged_in_client.get(url, data={"favorites": True}) + response = logged_in_api_client.get(url, data={"favorites": True}) parsed_json = json.loads(response.content.decode("utf-8")) assert parsed_json["count"] == 1 diff --git a/api/tests/favorites/test_filters.py b/api/tests/favorites/test_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..e5eaca39ebcf9c265cff93dbed9466c5e93b4ca5 --- /dev/null +++ b/api/tests/favorites/test_filters.py @@ -0,0 +1,30 @@ +from funkwhale_api.favorites import filters +from funkwhale_api.favorites import models + + +def test_track_favorite_filter_track_artist(factories, mocker, queryset_equal_list): + factories["favorites.TrackFavorite"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_fav = factories["favorites.TrackFavorite"](track__artist=cf.target_artist) + qs = models.TrackFavorite.objects.all() + filterset = filters.TrackFavoriteFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_fav] + + +def test_track_favorite_filter_track_album_artist( + factories, mocker, queryset_equal_list +): + factories["favorites.TrackFavorite"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_fav = factories["favorites.TrackFavorite"]( + track__album__artist=cf.target_artist + ) + qs = models.TrackFavorite.objects.all() + filterset = filters.TrackFavoriteFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_fav] diff --git a/api/tests/federation/test_activity.py b/api/tests/federation/test_activity.py index a1eedeb4930b400a7d2ab03ddc8848225290a484..aaeebbb87c594084bc404df077fb83b2e4a5bc2f 100644 --- a/api/tests/federation/test_activity.py +++ b/api/tests/federation/test_activity.py @@ -14,6 +14,9 @@ from funkwhale_api.federation import ( def test_receive_validates_basic_attributes_and_stores_activity(factories, now, mocker): + mocker.patch.object( + activity.InboxRouter, "get_matching_handlers", return_value=True + ) mocked_dispatch = mocker.patch("funkwhale_api.common.utils.on_commit") local_to_actor = factories["users.User"]().create_actor() local_cc_actor = factories["users.User"]().create_actor() @@ -48,6 +51,9 @@ def test_receive_validates_basic_attributes_and_stores_activity(factories, now, def test_receive_calls_should_reject(factories, now, mocker): should_reject = mocker.patch.object(activity, "should_reject", return_value=True) + mocker.patch.object( + activity.InboxRouter, "get_matching_handlers", return_value=True + ) local_to_actor = factories["users.User"]().create_actor() remote_actor = factories["federation.Actor"]() a = { @@ -60,30 +66,61 @@ def test_receive_calls_should_reject(factories, now, mocker): copy = activity.receive(activity=a, on_behalf_of=remote_actor) should_reject.assert_called_once_with( - id=a["id"], actor_id=remote_actor.fid, payload=a + fid=a["id"], actor_id=remote_actor.fid, payload=a ) assert copy is None +def test_receive_skips_if_no_matching_route(factories, now, mocker): + get_matching_handlers = mocker.patch.object( + activity.InboxRouter, "get_matching_handlers", return_value=[] + ) + local_to_actor = factories["users.User"]().create_actor() + remote_actor = factories["federation.Actor"]() + a = { + "@context": [], + "actor": remote_actor.fid, + "type": "Noop", + "id": "https://test.activity", + "to": [local_to_actor.fid, remote_actor.fid], + } + + copy = activity.receive(activity=a, on_behalf_of=remote_actor) + get_matching_handlers.assert_called_once_with(a) + assert copy is None + assert models.Activity.objects.count() == 0 + + +def test_match_route_ignore_payload_issues(): + payload = {"object": "http://hello"} + assert activity.match_route({"object.type": "Test"}, payload) is False + + @pytest.mark.parametrize( "params, policy_kwargs, expected", [ - ({"id": "https://ok.test"}, {"target_domain__name": "notok.test"}, False), + ({"fid": "https://ok.test"}, {"target_domain__name": "notok.test"}, False), ( - {"id": "https://ok.test"}, + {"fid": "https://ok.test"}, {"target_domain__name": "ok.test", "is_active": False}, False, ), ( - {"id": "https://ok.test"}, + {"fid": "https://ok.test"}, {"target_domain__name": "ok.test", "block_all": False}, False, ), # id match blocked domain - ({"id": "http://notok.test"}, {"target_domain__name": "notok.test"}, True), + ({"fid": "http://notok.test"}, {"target_domain__name": "notok.test"}, True), + # actor id match blocked domain + ( + {"fid": "http://ok.test", "actor_id": "https://notok.test"}, + {"target_domain__name": "notok.test"}, + True, + ), # actor id match blocked domain ( - {"id": "http://ok.test", "actor_id": "https://notok.test"}, + {"fid": None, "actor_id": "https://notok.test"}, {"target_domain__name": "notok.test"}, True, ), @@ -91,7 +128,7 @@ def test_receive_calls_should_reject(factories, now, mocker): ( { "payload": {"type": "Library"}, - "id": "http://ok.test", + "fid": "http://ok.test", "actor_id": "http://notok.test", }, { @@ -399,6 +436,53 @@ def test_prepare_deliveries_and_inbox_items(factories): assert inbox_item.type == "to" +def test_prepare_deliveries_and_inbox_items_instances_with_followers(factories): + + domain1 = factories["federation.Domain"](with_service_actor=True) + domain2 = factories["federation.Domain"](with_service_actor=True) + library = factories["music.Library"](actor__local=True) + + factories["federation.LibraryFollow"]( + target=library, actor__local=True, approved=True + ).actor + library_follower_remote = factories["federation.LibraryFollow"]( + target=library, actor__domain=domain1, approved=True + ).actor + + followed_actor = factories["federation.Actor"](local=True) + factories["federation.Follow"]( + target=followed_actor, actor__local=True, approved=True + ).actor + actor_follower_remote = factories["federation.Follow"]( + target=followed_actor, actor__domain=domain2, approved=True + ).actor + + recipients = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}] + + inbox_items, deliveries, urls = activity.prepare_deliveries_and_inbox_items( + recipients, "to" + ) + + expected_deliveries = sorted( + [ + models.Delivery( + inbox_url=library_follower_remote.domain.service_actor.inbox_url + ), + models.Delivery( + inbox_url=actor_follower_remote.domain.service_actor.inbox_url + ), + ], + key=lambda v: v.inbox_url, + ) + assert inbox_items == [] + assert len(expected_deliveries) == len(deliveries) + + for delivery, expected_delivery in zip( + sorted(deliveries, key=lambda v: v.inbox_url), expected_deliveries + ): + assert delivery.inbox_url == expected_delivery.inbox_url + + def test_should_rotate_actor_key(settings, cache, now): actor_id = 42 settings.ACTOR_KEY_ROTATION_DELAY = 10 diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py index a416cd78f3289e5f48acb3466eb3145777d34467..6e5cf93225ad7de0bd55ccb11395c4d9f910065f 100644 --- a/api/tests/federation/test_actors.py +++ b/api/tests/federation/test_actors.py @@ -14,7 +14,10 @@ def test_actor_fetching(r_mock): assert r == payload -def test_get_actor(factories, r_mock): +def test_get_actor(factories, r_mock, mocker): + update_domain_nodeinfo = mocker.patch( + "funkwhale_api.federation.tasks.update_domain_nodeinfo" + ) actor = factories["federation.Actor"].build() payload = serializers.ActorSerializer(actor).data r_mock.get(actor.fid, json=payload) @@ -22,6 +25,7 @@ def test_get_actor(factories, r_mock): assert new_actor.pk is not None assert serializers.ActorSerializer(new_actor).data == payload + update_domain_nodeinfo.assert_called_once_with(domain_name=new_actor.domain_id) def test_get_actor_use_existing(factories, preferences, mocker): @@ -46,3 +50,15 @@ def test_get_actor_refresh(factories, preferences, mocker): assert new_actor == actor assert new_actor.last_fetch_date > actor.last_fetch_date assert new_actor.preferred_username == "New me" + + +def test_get_service_actor(db, settings): + settings.FEDERATION_HOSTNAME = "test.hello" + settings.FEDERATION_SERVICE_ACTOR_USERNAME = "bob" + actor = actors.get_service_actor() + + assert actor.preferred_username == "bob" + assert actor.domain.name == "test.hello" + assert actor.private_key is not None + assert actor.type == "Service" + assert actor.public_key is not None diff --git a/api/tests/federation/test_api_filters.py b/api/tests/federation/test_api_filters.py index c6e70b6178cd35bf8ad3752f0c8aa1204de20e7f..4cbf4293a3c3a82bda4fb42d3936dc8b41ea0e8c 100644 --- a/api/tests/federation/test_api_filters.py +++ b/api/tests/federation/test_api_filters.py @@ -1,3 +1,4 @@ +from funkwhale_api.federation import fields from funkwhale_api.federation import filters from funkwhale_api.federation import models @@ -7,3 +8,17 @@ def test_inbox_item_filter_before(factories): f = filters.InboxItemFilter({"before": 12}, queryset=models.InboxItem.objects.all()) assert str(f.qs.query) == str(expected.query) + + +def test_domain_from_url_filter(factories): + found = [ + factories["music.Artist"](fid="http://domain/test1"), + factories["music.Artist"](fid="https://domain/test2"), + ] + factories["music.Artist"](fid="http://domain2/test1") + factories["music.Artist"](fid="https://otherdomain/test2") + + queryset = found[0].__class__.objects.all().order_by("id") + field = fields.DomainFromURLFilter() + result = field.filter(queryset, "domain") + assert list(result) == found diff --git a/api/tests/federation/test_api_views.py b/api/tests/federation/test_api_views.py index 75579d39a2d11d4ac2a1c4207ab30efdb477d470..c34c5e99aa1ec74ee2bbdd32bb33142297f54ff9 100644 --- a/api/tests/federation/test_api_views.py +++ b/api/tests/federation/test_api_views.py @@ -167,3 +167,15 @@ def test_user_can_update_read_status_of_inbox_item(factories, logged_in_api_clie ii.refresh_from_db() assert ii.is_read is True + + +def test_can_detail_fetch(logged_in_api_client, factories): + fetch = factories["federation.Fetch"](url="http://test.object") + url = reverse("api:v1:federation:fetches-detail", kwargs={"pk": fetch.pk}) + + response = logged_in_api_client.get(url) + + expected = api_serializers.FetchSerializer(fetch).data + + assert response.status_code == 200 + assert response.data == expected diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py index 7af7089f66c19bcadbb77a98e3c4eac253cf4382..4e837e64177be9919881d0b5fe6adef94bbdaf6f 100644 --- a/api/tests/federation/test_authentication.py +++ b/api/tests/federation/test_authentication.py @@ -1,14 +1,16 @@ import pytest -from funkwhale_api.federation import authentication, exceptions, keys +from funkwhale_api.federation import authentication, exceptions, keys, jsonld def test_authenticate(factories, mocker, api_request): private, public = keys.get_key_pair() + factories["federation.Domain"](name="test.federation", nodeinfo_fetch_date=None) actor_url = "https://test.federation/actor" mocker.patch( "funkwhale_api.federation.actors.get_actor_data", return_value={ + "@context": jsonld.get_default_context(), "id": actor_url, "type": "Person", "outbox": "https://test.com", @@ -22,6 +24,10 @@ def test_authenticate(factories, mocker, api_request): }, }, ) + update_domain_nodeinfo = mocker.patch( + "funkwhale_api.federation.tasks.update_domain_nodeinfo" + ) + signed_request = factories["federation.SignedRequest"]( auth__key=private, auth__key_id=actor_url + "#main-key", auth__headers=["date"] ) @@ -40,6 +46,7 @@ def test_authenticate(factories, mocker, api_request): assert user.is_anonymous is True assert actor.public_key == public.decode("utf-8") assert actor.fid == actor_url + update_domain_nodeinfo.assert_called_once_with(domain_name="test.federation") def test_authenticate_skips_blocked_domain(factories, api_request): @@ -99,6 +106,7 @@ def test_authenticate_ignore_inactive_policy(factories, api_request, mocker): mocker.patch( "funkwhale_api.federation.actors.get_actor_data", return_value={ + "@context": jsonld.get_default_context(), "id": actor_url, "type": "Person", "outbox": "https://test.com", @@ -136,6 +144,7 @@ def test_autenthicate_supports_blind_key_rotation(factories, mocker, api_request mocker.patch( "funkwhale_api.federation.actors.get_actor_data", return_value={ + "@context": jsonld.get_default_context(), "id": actor_url, "type": "Person", "outbox": "https://test.com", diff --git a/api/tests/federation/test_contexts.py b/api/tests/federation/test_contexts.py new file mode 100644 index 0000000000000000000000000000000000000000..a0134d909527a7eef987ae7daf7b2b9fbe3f4133 --- /dev/null +++ b/api/tests/federation/test_contexts.py @@ -0,0 +1,32 @@ +import pytest + +from funkwhale_api.federation import contexts + + +@pytest.mark.parametrize( + "ns, property, expected", + [ + ("AS", "followers", "https://www.w3.org/ns/activitystreams#followers"), + ("AS", "following", "https://www.w3.org/ns/activitystreams#following"), + ("SEC", "owner", "https://w3id.org/security#owner"), + ("SEC", "publicKey", "https://w3id.org/security#publicKey"), + ], +) +def test_context_ns(ns, property, expected): + ns = getattr(contexts, ns) + id = getattr(ns, property) + assert id == expected + + +def test_raise_on_wrong_attr(): + ns = contexts.AS + with pytest.raises(AttributeError): + ns.noop + + +@pytest.mark.parametrize( + "property, expected", + [("publicKey", "_:publicKey"), ("cover", "_:cover"), ("hello", "_:hello")], +) +def test_noop_context(property, expected): + assert getattr(contexts.NOOP, property) == expected diff --git a/api/tests/federation/test_decorators.py b/api/tests/federation/test_decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..fa50f5674a699378cb879e2764759e063bd29823 --- /dev/null +++ b/api/tests/federation/test_decorators.py @@ -0,0 +1,83 @@ +from rest_framework import viewsets + +from funkwhale_api.music import models as music_models + +from funkwhale_api.federation import api_serializers +from funkwhale_api.federation import decorators +from funkwhale_api.federation import models +from funkwhale_api.federation import tasks + + +class V(viewsets.ModelViewSet): + queryset = music_models.Track.objects.all() + fetches = decorators.fetches_route() + permission_classes = [] + + +def test_fetches_route_create(factories, api_request, mocker): + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") + user = factories["users.User"]() + actor = user.create_actor() + track = factories["music.Track"]() + view = V.as_view({"post": "fetches"}) + + request = api_request.post("/", format="json") + setattr(request, "user", user) + setattr(request, "session", {}) + response = view(request, pk=track.pk) + + assert response.status_code == 201 + + fetch = models.Fetch.objects.get_for_object(track).latest("id") + on_commit.assert_called_once_with(tasks.fetch.delay, fetch_id=fetch.pk) + + assert fetch.url == track.fid + assert fetch.object == track + assert fetch.status == "pending" + assert fetch.actor == actor + + expected = api_serializers.FetchSerializer(fetch).data + assert response.data == expected + + +def test_fetches_route_create_local(factories, api_request, mocker, settings): + user = factories["users.User"]() + user.create_actor() + track = factories["music.Track"]( + fid="https://{}/test".format(settings.FEDERATION_HOSTNAME) + ) + view = V.as_view({"post": "fetches"}) + + request = api_request.post("/", format="json") + setattr(request, "user", user) + setattr(request, "session", {}) + response = view(request, pk=track.pk) + + assert response.status_code == 400 + + +def test_fetches_route_list(factories, api_request, mocker): + user = factories["users.User"]() + user.create_actor() + track = factories["music.Track"]() + fetches = [ + factories["federation.Fetch"](object=track), + factories["federation.Fetch"](object=track), + ] + view = V.as_view({"get": "fetches"}) + + request = api_request.get("/", format="json") + setattr(request, "user", user) + setattr(request, "session", {}) + expected = { + "next": None, + "previous": None, + "count": 2, + "results": api_serializers.FetchSerializer(reversed(fetches), many=True).data, + } + + request = api_request.get("/") + response = view(request, pk=track.pk) + + assert response.status_code == 200 + assert response.data == expected diff --git a/api/tests/federation/test_jsonld.py b/api/tests/federation/test_jsonld.py new file mode 100644 index 0000000000000000000000000000000000000000..ad201b7486e9c10e9d93f486c5f7876f52dd392d --- /dev/null +++ b/api/tests/federation/test_jsonld.py @@ -0,0 +1,361 @@ +import pytest + +from rest_framework import serializers + +from funkwhale_api.federation import contexts +from funkwhale_api.federation import jsonld + + +def test_expand_no_external_request(): + payload = { + "id": "https://noop/federation/actors/demo", + "outbox": "https://noop/federation/actors/demo/outbox", + "inbox": "https://noop/federation/actors/demo/inbox", + "preferredUsername": "demo", + "type": "Person", + "name": "demo", + "followers": "https://noop/federation/actors/demo/followers", + "following": "https://noop/federation/actors/demo/following", + "manuallyApprovesFollowers": False, + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {}, + ], + "publicKey": { + "owner": "https://noop/federation/actors/demo", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n", # noqa + "id": "https://noop/federation/actors/demo#main-key", + }, + "endpoints": {"sharedInbox": "https://noop/federation/shared/inbox"}, + } + + expected = { + contexts.AS.endpoints: [ + {contexts.AS.sharedInbox: [{"@id": "https://noop/federation/shared/inbox"}]} + ], + contexts.AS.followers: [ + {"@id": "https://noop/federation/actors/demo/followers"} + ], + contexts.AS.following: [ + {"@id": "https://noop/federation/actors/demo/following"} + ], + "@id": "https://noop/federation/actors/demo", + "http://www.w3.org/ns/ldp#inbox": [ + {"@id": "https://noop/federation/actors/demo/inbox"} + ], + contexts.AS.manuallyApprovesFollowers: [{"@value": False}], + contexts.AS.name: [{"@value": "demo"}], + contexts.AS.outbox: [{"@id": "https://noop/federation/actors/demo/outbox"}], + contexts.AS.preferredUsername: [{"@value": "demo"}], + contexts.SEC.publicKey: [ + { + "@id": "https://noop/federation/actors/demo#main-key", + contexts.SEC.owner: [{"@id": "https://noop/federation/actors/demo"}], + contexts.SEC.publicKeyPem: [ + { + "@value": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n" # noqa + } + ], + } + ], + "@type": [contexts.AS.Person], + } + + doc = jsonld.expand(payload) + + assert doc == expected + + +def test_expand_remote_doc(r_mock): + url = "https://noop/federation/actors/demo" + payload = { + "id": url, + "outbox": "https://noop/federation/actors/demo/outbox", + "inbox": "https://noop/federation/actors/demo/inbox", + "preferredUsername": "demo", + "type": "Person", + "name": "demo", + "followers": "https://noop/federation/actors/demo/followers", + "following": "https://noop/federation/actors/demo/following", + "manuallyApprovesFollowers": False, + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {}, + ], + "publicKey": { + "owner": "https://noop/federation/actors/demo", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n", # noqa + "id": "https://noop/federation/actors/demo#main-key", + }, + "endpoints": {"sharedInbox": "https://noop/federation/shared/inbox"}, + } + r_mock.get(url, json=payload) + + expected = { + contexts.AS.endpoints: [ + {contexts.AS.sharedInbox: [{"@id": "https://noop/federation/shared/inbox"}]} + ], + contexts.AS.followers: [ + {"@id": "https://noop/federation/actors/demo/followers"} + ], + contexts.AS.following: [ + {"@id": "https://noop/federation/actors/demo/following"} + ], + "@id": "https://noop/federation/actors/demo", + "http://www.w3.org/ns/ldp#inbox": [ + {"@id": "https://noop/federation/actors/demo/inbox"} + ], + contexts.AS.manuallyApprovesFollowers: [{"@value": False}], + contexts.AS.name: [{"@value": "demo"}], + contexts.AS.outbox: [{"@id": "https://noop/federation/actors/demo/outbox"}], + contexts.AS.preferredUsername: [{"@value": "demo"}], + contexts.SEC.publicKey: [ + { + "@id": "https://noop/federation/actors/demo#main-key", + contexts.SEC.owner: [{"@id": "https://noop/federation/actors/demo"}], + contexts.SEC.publicKeyPem: [ + { + "@value": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n" # noqa + } + ], + } + ], + "@type": [contexts.AS.Person], + } + + doc = jsonld.expand(url) + + assert doc == expected + + +async def test_fetch_many(a_responses): + doc = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop/federation/actors/demo", + "type": "Person", + "followers": "https://noop/federation/actors/demo/followers", + } + followers_doc = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop/federation/actors/demo/followers", + "type": "Collection", + } + + a_responses.get(doc["id"], payload=doc) + a_responses.get(followers_doc["id"], payload=followers_doc) + fetched = await jsonld.fetch_many(doc["id"], followers_doc["id"]) + assert fetched == {followers_doc["id"]: followers_doc, doc["id"]: doc} + + +def test_dereference(): + + followers_doc = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop/federation/actors/demo/followers", + "type": "Collection", + } + + actor_doc = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop/federation/actors/demo", + "type": "Person", + "followers": "https://noop/federation/actors/demo/followers", + } + + store = {followers_doc["id"]: followers_doc, actor_doc["id"]: actor_doc} + + payload = { + "followers": {"@id": followers_doc["id"]}, + "actor": [ + {"@id": actor_doc["id"], "hello": "world"}, + {"somethingElse": [{"@id": actor_doc["id"]}]}, + ], + } + expected = { + "followers": followers_doc, + "actor": [actor_doc, {"somethingElse": [actor_doc]}], + } + + assert jsonld.dereference(payload, store) == expected + + +def test_prepare_for_serializer(): + config = { + "followers": { + "property": contexts.AS.followers, + "keep": "first", + "attr": "@id", + }, + "name": {"property": contexts.AS.name, "keep": "first", "attr": "@value"}, + "keys": {"property": contexts.SEC.publicKey, "type": "raw"}, + } + + payload = { + "@id": "https://noop/federation/actors/demo", + "@type": [contexts.AS.Person], + contexts.AS.followers: [ + {"@id": "https://noop/federation/actors/demo/followers"} + ], + contexts.AS.name: [{"@value": "demo"}], + contexts.SEC.publicKey: [ + {"@id": "https://noop/federation/actors/demo#main-key1"}, + {"@id": "https://noop/federation/actors/demo#main-key2"}, + ], + } + + expected = { + "id": "https://noop/federation/actors/demo", + "type": contexts.AS.Person, + "followers": "https://noop/federation/actors/demo/followers", + "name": "demo", + "keys": [ + {"@id": "https://noop/federation/actors/demo#main-key1"}, + {"@id": "https://noop/federation/actors/demo#main-key2"}, + ], + } + + assert jsonld.prepare_for_serializer(payload, config) == expected + + +def test_prepare_for_serializer_fallback(): + config = { + "name": {"property": contexts.AS.name, "keep": "first", "attr": "@value"}, + "album": {"property": contexts.FW.Album, "keep": "first"}, + "noop_album": {"property": contexts.NOOP.Album, "keep": "first"}, + } + fallbacks = {"album": ["noop_album"]} + + payload = { + "@id": "https://noop/federation/actors/demo", + "@type": [contexts.AS.Person], + contexts.AS.name: [{"@value": "demo"}], + contexts.NOOP.Album: [{"@id": "https://noop/federation/album/demo"}], + } + + expected = { + "id": "https://noop/federation/actors/demo", + "type": contexts.AS.Person, + "name": "demo", + "album": {"@id": "https://noop/federation/album/demo"}, + "noop_album": {"@id": "https://noop/federation/album/demo"}, + } + + assert ( + jsonld.prepare_for_serializer(payload, config, fallbacks=fallbacks) == expected + ) + + +def test_jsonld_serializer_fallback(): + class TestSerializer(jsonld.JsonLdSerializer): + id = serializers.URLField() + type = serializers.CharField() + name = serializers.CharField() + username = serializers.CharField() + total = serializers.IntegerField() + + class Meta: + jsonld_fallbacks = {"total": ["total_fallback"]} + jsonld_mapping = { + "name": { + "property": contexts.AS.name, + "keep": "first", + "attr": "@value", + }, + "username": { + "property": contexts.AS.preferredUsername, + "keep": "first", + "attr": "@value", + }, + "total": { + "property": contexts.AS.totalItems, + "keep": "first", + "attr": "@value", + }, + "total_fallback": { + "property": contexts.NOOP.count, + "keep": "first", + "attr": "@value", + }, + } + + payload = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop.url/federation/actors/demo", + "type": "Person", + "name": "Hello", + "preferredUsername": "World", + "count": 42, + } + + serializer = TestSerializer(data=payload) + assert serializer.is_valid(raise_exception=True) + + assert serializer.validated_data == { + "type": contexts.AS.Person, + "id": payload["id"], + "name": payload["name"], + "username": payload["preferredUsername"], + "total": 42, + } + + +def test_jsonld_serializer_dereference(a_responses): + class TestSerializer(jsonld.JsonLdSerializer): + id = serializers.URLField() + type = serializers.CharField() + followers = serializers.JSONField() + + class Meta: + jsonld_mapping = { + "followers": {"property": contexts.AS.followers, "dereference": True} + } + + payload = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop.url/federation/actors/demo", + "type": "Person", + "followers": "https://noop.url/federation/actors/demo/followers", + } + + followers_doc = { + "@context": ["https://www.w3.org/ns/activitystreams", {}], + "id": "https://noop.url/federation/actors/demo/followers", + "type": "Collection", + } + + a_responses.get(followers_doc["id"], payload=followers_doc) + serializer = TestSerializer(data=payload) + + assert serializer.is_valid(raise_exception=True) + assert serializer.validated_data == { + "type": contexts.AS.Person, + "id": payload["id"], + "followers": [followers_doc], + } + + +@pytest.mark.parametrize( + "doc, ctx, expected", + [ + ( + {"@context": [{}], "hello": "world"}, + "http://test", + {"@context": [{}, "http://test"], "hello": "world"}, + ), + ( + {"@context": {"key": "value"}, "hello": "world"}, + "http://test", + {"@context": [{"key": "value"}, "http://test"], "hello": "world"}, + ), + ( + {"@context": "http://as", "hello": "world"}, + "http://test", + {"@context": ["http://as", "http://test"], "hello": "world"}, + ), + ], +) +def test_insert_context(doc, ctx, expected): + jsonld.insert_context(ctx, doc) + assert doc == expected diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py index 6eeebd660ad12db40f7882ae753bd72acead92b2..d6f862bb356300e9d93fb02c0300f266eef0a6bf 100644 --- a/api/tests/federation/test_models.py +++ b/api/tests/federation/test_models.py @@ -134,3 +134,42 @@ def test_actor_stats(factories): actor = factories["federation.Actor"]() assert actor.get_stats() == expected + + +def test_actor_can_manage_false(mocker, factories): + obj = mocker.Mock() + actor = factories["federation.Actor"]() + + assert actor.can_manage(obj) is False + + +def test_actor_can_manage_attributed_to(mocker, factories): + actor = factories["federation.Actor"]() + obj = mocker.Mock(attributed_to_id=actor.pk) + + assert actor.can_manage(obj) is True + + +def test_actor_can_manage_domain_not_service_actor(mocker, factories): + actor = factories["federation.Actor"]() + obj = mocker.Mock(fid="https://{}/hello".format(actor.domain_id)) + + assert actor.can_manage(obj) is False + + +def test_actor_can_manage_domain_service_actor(mocker, factories): + actor = factories["federation.Actor"]() + actor.domain.service_actor = actor + actor.domain.save() + obj = mocker.Mock(fid="https://{}/hello".format(actor.domain_id)) + + assert actor.can_manage(obj) is True + + +def test_can_create_fetch_for_object(factories): + track = factories["music.Track"](fid="http://test.domain") + fetch = factories["federation.Fetch"](object=track) + assert fetch.url == "http://test.domain" + assert fetch.status == "pending" + assert fetch.detail == {} + assert fetch.object == track diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py index 7232b746cfa0768992ced9ca0bec818c58246bfa..56834d55f472a5ce9e3d49ee6e96486bf59bd673 100644 --- a/api/tests/federation/test_routes.py +++ b/api/tests/federation/test_routes.py @@ -1,6 +1,6 @@ import pytest -from funkwhale_api.federation import routes, serializers +from funkwhale_api.federation import actors, contexts, jsonld, routes, serializers @pytest.mark.parametrize( @@ -13,6 +13,9 @@ from funkwhale_api.federation import routes, serializers ({"type": "Delete", "object.type": "Library"}, routes.inbox_delete_library), ({"type": "Delete", "object.type": "Audio"}, routes.inbox_delete_audio), ({"type": "Undo", "object.type": "Follow"}, routes.inbox_undo_follow), + ({"type": "Update", "object.type": "Artist"}, routes.inbox_update_artist), + ({"type": "Update", "object.type": "Album"}, routes.inbox_update_album), + ({"type": "Update", "object.type": "Track"}, routes.inbox_update_track), ], ) def test_inbox_routes(route, handler): @@ -34,6 +37,7 @@ def test_inbox_routes(route, handler): ({"type": "Delete", "object.type": "Library"}, routes.outbox_delete_library), ({"type": "Delete", "object.type": "Audio"}, routes.outbox_delete_audio), ({"type": "Undo", "object.type": "Follow"}, routes.outbox_undo_follow), + ({"type": "Update", "object.type": "Track"}, routes.outbox_update_track), ], ) def test_outbox_routes(route, handler): @@ -113,6 +117,44 @@ def test_inbox_follow_library_manual_approve(factories, mocker): mocked_outbox_dispatch.assert_not_called() +def test_inbox_follow_library_already_approved(factories, mocker): + """Cf #830, out of sync follows""" + mocked_outbox_dispatch = mocker.patch( + "funkwhale_api.federation.activity.OutboxRouter.dispatch" + ) + + local_actor = factories["users.User"]().create_actor() + remote_actor = factories["federation.Actor"]() + library = factories["music.Library"](actor=local_actor, privacy_level="me") + ii = factories["federation.InboxItem"](actor=local_actor) + existing_follow = factories["federation.LibraryFollow"]( + target=library, actor=remote_actor, approved=True + ) + payload = { + "type": "Follow", + "id": "https://test.follow", + "actor": remote_actor.fid, + "object": library.fid, + } + + result = routes.inbox_follow( + payload, + context={"actor": remote_actor, "inbox_items": [ii], "raise_exception": True}, + ) + follow = library.received_follows.latest("id") + + assert result["object"] == library + assert result["related_object"] == follow + + assert follow.fid == payload["id"] + assert follow.actor == remote_actor + assert follow.approved is True + assert follow.uuid != existing_follow.uuid + mocked_outbox_dispatch.assert_called_once_with( + {"type": "Accept"}, context={"follow": follow} + ) + + def test_outbox_accept(factories, mocker): remote_actor = factories["federation.Actor"]() follow = factories["federation.LibraryFollow"](actor=remote_actor) @@ -190,6 +232,7 @@ def test_inbox_create_audio(factories, mocker): activity = factories["federation.Activity"]() upload = factories["music.Upload"](bitrate=42, duration=55) payload = { + "@context": jsonld.get_default_context(), "type": "Create", "actor": upload.library.actor.fid, "object": serializers.UploadSerializer(upload).data, @@ -404,3 +447,115 @@ def test_outbox_delete_follow_library(factories): assert activity["actor"] == follow.actor assert activity["object"] == follow assert activity["related_object"] == follow.target + + +def test_handle_library_entry_update_can_manage(factories, mocker): + update_library_entity = mocker.patch( + "funkwhale_api.music.tasks.update_library_entity" + ) + activity = factories["federation.Activity"]() + obj = factories["music.Artist"]() + actor = factories["federation.Actor"]() + mocker.patch.object(actor, "can_manage", return_value=False) + data = serializers.ArtistSerializer(obj).data + data["name"] = "New name" + payload = {"type": "Update", "actor": actor, "object": data} + + routes.inbox_update_artist( + payload, context={"actor": actor, "raise_exception": True, "activity": activity} + ) + + update_library_entity.assert_not_called() + + +def test_inbox_update_artist(factories, mocker): + update_library_entity = mocker.patch( + "funkwhale_api.music.tasks.update_library_entity" + ) + activity = factories["federation.Activity"]() + obj = factories["music.Artist"](attributed=True) + actor = obj.attributed_to + data = serializers.ArtistSerializer(obj).data + data["name"] = "New name" + payload = {"type": "Update", "actor": actor, "object": data} + + routes.inbox_update_artist( + payload, context={"actor": actor, "raise_exception": True, "activity": activity} + ) + + update_library_entity.assert_called_once_with(obj, {"name": "New name"}) + + +def test_outbox_update_artist(factories): + artist = factories["music.Artist"]() + activity = list(routes.outbox_update_artist({"artist": artist}))[0] + expected = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.ArtistSerializer(artist).data} + ).data + + expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}] + + assert dict(activity["payload"]) == dict(expected) + assert activity["actor"] == actors.get_service_actor() + + +def test_inbox_update_album(factories, mocker): + update_library_entity = mocker.patch( + "funkwhale_api.music.tasks.update_library_entity" + ) + activity = factories["federation.Activity"]() + obj = factories["music.Album"](attributed=True) + actor = obj.attributed_to + data = serializers.AlbumSerializer(obj).data + data["name"] = "New title" + payload = {"type": "Update", "actor": actor, "object": data} + + routes.inbox_update_album( + payload, context={"actor": actor, "raise_exception": True, "activity": activity} + ) + + update_library_entity.assert_called_once_with(obj, {"title": "New title"}) + + +def test_outbox_update_album(factories): + album = factories["music.Album"]() + activity = list(routes.outbox_update_album({"album": album}))[0] + expected = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.AlbumSerializer(album).data} + ).data + + expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}] + + assert dict(activity["payload"]) == dict(expected) + assert activity["actor"] == actors.get_service_actor() + + +def test_inbox_update_track(factories, mocker): + update_library_entity = mocker.patch( + "funkwhale_api.music.tasks.update_library_entity" + ) + activity = factories["federation.Activity"]() + obj = factories["music.Track"](attributed=True) + actor = obj.attributed_to + data = serializers.TrackSerializer(obj).data + data["name"] = "New title" + payload = {"type": "Update", "actor": actor, "object": data} + + routes.inbox_update_track( + payload, context={"actor": actor, "raise_exception": True, "activity": activity} + ) + + update_library_entity.assert_called_once_with(obj, {"title": "New title"}) + + +def test_outbox_update_track(factories): + track = factories["music.Track"]() + activity = list(routes.outbox_update_track({"track": track}))[0] + expected = serializers.ActivitySerializer( + {"type": "Update", "object": serializers.TrackSerializer(track).data} + ).data + + expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}] + + assert dict(activity["payload"]) == dict(expected) + assert activity["actor"] == actors.get_service_actor() diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index 207a8fbe5e21f8e0fd29f53b41350bc7a91d18e3..5e3259e18facd0a57c30bc765492697f39642c71 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -5,52 +5,63 @@ import uuid from django.core.paginator import Paginator from django.utils import timezone -from funkwhale_api.federation import models, serializers, utils +from funkwhale_api.federation import keys +from funkwhale_api.federation import jsonld +from funkwhale_api.federation import models +from funkwhale_api.federation import serializers +from funkwhale_api.federation import utils +from funkwhale_api.music import licenses def test_actor_serializer_from_ap(db): + private, public = keys.get_key_pair() + actor_url = "https://test.federation/actor" payload = { - "id": "https://test.federation/user", + "@context": jsonld.get_default_context_fw(), + "id": actor_url, "type": "Person", - "following": "https://test.federation/user/following", - "followers": "https://test.federation/user/followers", - "inbox": "https://test.federation/user/inbox", - "outbox": "https://test.federation/user/outbox", - "preferredUsername": "user", - "name": "Real User", + "outbox": "https://test.com/outbox", + "inbox": "https://test.com/inbox", + "following": "https://test.com/following", + "followers": "https://test.com/followers", + "preferredUsername": "test", + "name": "Test", "summary": "Hello world", - "url": "https://test.federation/@user", - "manuallyApprovesFollowers": False, + "manuallyApprovesFollowers": True, "publicKey": { - "id": "https://test.federation/user#main-key", - "owner": "https://test.federation/user", - "publicKeyPem": "yolo", + "publicKeyPem": public.decode("utf-8"), + "owner": actor_url, + "id": actor_url + "#main-key", }, - "endpoints": {"sharedInbox": "https://test.federation/inbox"}, + "endpoints": {"sharedInbox": "https://noop.url/federation/shared/inbox"}, } serializer = serializers.ActorSerializer(data=payload) assert serializer.is_valid(raise_exception=True) + actor = serializer.save() - actor = serializer.build() - - assert actor.fid == payload["id"] + assert actor.fid == actor_url + assert actor.url is None assert actor.inbox_url == payload["inbox"] - assert actor.outbox_url == payload["outbox"] assert actor.shared_inbox_url == payload["endpoints"]["sharedInbox"] - assert actor.followers_url == payload["followers"] + assert actor.outbox_url == payload["outbox"] assert actor.following_url == payload["following"] - assert actor.public_key == payload["publicKey"]["publicKeyPem"] + assert actor.followers_url == payload["followers"] + assert actor.followers_url == payload["followers"] + assert actor.type == "Person" assert actor.preferred_username == payload["preferredUsername"] assert actor.name == payload["name"] - assert actor.domain.pk == "test.federation" assert actor.summary == payload["summary"] - assert actor.type == "Person" - assert actor.manually_approves_followers == payload["manuallyApprovesFollowers"] + assert actor.fid == actor_url + assert actor.manually_approves_followers is True + assert actor.private_key is None + assert actor.public_key == payload["publicKey"]["publicKeyPem"] + assert actor.domain_id == "test.federation" def test_actor_serializer_only_mandatory_field_from_ap(db): payload = { + "@context": jsonld.get_default_context(), "id": "https://test.federation/user", "type": "Person", "following": "https://test.federation/user/following", @@ -78,11 +89,7 @@ def test_actor_serializer_only_mandatory_field_from_ap(db): def test_actor_serializer_to_ap(): expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "id": "https://test.federation/user", "type": "Person", "following": "https://test.federation/user/following", @@ -147,11 +154,7 @@ def test_follow_serializer_to_ap(factories): serializer = serializers.FollowSerializer(follow) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "id": follow.get_federation_id(), "type": "Follow", "actor": follow.actor.fid, @@ -208,11 +211,7 @@ def test_accept_follow_serializer_representation(factories): follow = factories["federation.Follow"](approved=None) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "id": follow.get_federation_id() + "/accept", "type": "Accept", "actor": follow.target.fid, @@ -228,11 +227,7 @@ def test_accept_follow_serializer_save(factories): follow = factories["federation.Follow"](approved=None) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context_fw(), "id": follow.get_federation_id() + "/accept", "type": "Accept", "actor": follow.target.fid, @@ -252,11 +247,7 @@ def test_accept_follow_serializer_validates_on_context(factories): follow = factories["federation.Follow"](approved=None) impostor = factories["federation.Actor"]() data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context_fw(), "id": follow.get_federation_id() + "/accept", "type": "Accept", "actor": impostor.url, @@ -276,11 +267,7 @@ def test_undo_follow_serializer_representation(factories): follow = factories["federation.Follow"](approved=True) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "id": follow.get_federation_id() + "/undo", "type": "Undo", "actor": follow.actor.fid, @@ -296,11 +283,7 @@ def test_undo_follow_serializer_save(factories): follow = factories["federation.Follow"](approved=True) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context_fw(), "id": follow.get_federation_id() + "/undo", "type": "Undo", "actor": follow.actor.fid, @@ -319,11 +302,7 @@ def test_undo_follow_serializer_validates_on_context(factories): follow = factories["federation.Follow"](approved=True) impostor = factories["federation.Actor"]() data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context_fw(), "id": follow.get_federation_id() + "/undo", "type": "Undo", "actor": impostor.url, @@ -351,14 +330,11 @@ def test_paginated_collection_serializer(factories): "page_size": 2, } expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "type": "Collection", "id": conf["id"], "actor": actor.fid, + "attributedTo": actor.fid, "totalItems": len(uploads), "current": conf["id"] + "?page=1", "last": conf["id"] + "?page=3", @@ -372,10 +348,12 @@ def test_paginated_collection_serializer(factories): def test_paginated_collection_serializer_validation(): data = { + "@context": jsonld.get_default_context_fw(), "type": "Collection", "id": "https://test.federation/test", "totalItems": 5, "actor": "http://test.actor", + "attributedTo": "http://test.actor", "first": "https://test.federation/test?page=1", "last": "https://test.federation/test?page=1", "items": [], @@ -386,16 +364,18 @@ def test_paginated_collection_serializer_validation(): assert serializer.is_valid(raise_exception=True) is True assert serializer.validated_data["totalItems"] == 5 assert serializer.validated_data["id"] == data["id"] - assert serializer.validated_data["actor"] == data["actor"] + assert serializer.validated_data["attributedTo"] == data["actor"] def test_collection_page_serializer_validation(): base = "https://test.federation/test" data = { + "@context": jsonld.get_default_context(), "type": "CollectionPage", "id": base + "?page=2", "totalItems": 5, "actor": "https://test.actor", + "attributedTo": "https://test.actor", "items": [], "first": "https://test.federation/test?page=1", "last": "https://test.federation/test?page=3", @@ -409,7 +389,7 @@ def test_collection_page_serializer_validation(): assert serializer.is_valid(raise_exception=True) is True assert serializer.validated_data["totalItems"] == 5 assert serializer.validated_data["id"] == data["id"] - assert serializer.validated_data["actor"] == data["actor"] + assert serializer.validated_data["attributedTo"] == data["actor"] assert serializer.validated_data["items"] == [] assert serializer.validated_data["prev"] == data["prev"] assert serializer.validated_data["next"] == data["next"] @@ -418,9 +398,10 @@ def test_collection_page_serializer_validation(): def test_collection_page_serializer_can_validate_child(): data = { + "@context": jsonld.get_default_context(), "type": "CollectionPage", "id": "https://test.page?page=2", - "actor": "https://test.actor", + "attributedTo": "https://test.actor", "first": "https://test.page?page=1", "last": "https://test.page?page=3", "partOf": "https://test.page", @@ -448,14 +429,11 @@ def test_collection_page_serializer(factories): "page": Paginator(uploads, 2).page(2), } expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "type": "CollectionPage", "id": conf["id"] + "?page=2", "actor": actor.fid, + "attributedTo": actor.fid, "totalItems": len(uploads), "partOf": conf["id"], "prev": conf["id"] + "?page=1", @@ -483,17 +461,14 @@ def test_music_library_serializer_to_ap(factories): factories["music.Upload"](import_status="finished") serializer = serializers.LibrarySerializer(library) expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "audience": "https://www.w3.org/ns/activitystreams#Public", "type": "Library", "id": library.fid, "name": library.name, "summary": library.description, "actor": library.actor.fid, + "attributedTo": library.actor.fid, "totalItems": 0, "current": library.fid + "?page=1", "last": library.fid + "?page=1", @@ -510,18 +485,14 @@ def test_music_library_serializer_from_public(factories, mocker): "funkwhale_api.federation.utils.retrieve_ap_object", return_value=actor ) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "audience": "https://www.w3.org/ns/activitystreams#Public", "name": "Hello", "summary": "World", "type": "Library", "id": "https://library.id", "followers": "https://library.id/followers", - "actor": actor.fid, + "attributedTo": actor.fid, "totalItems": 12, "first": "https://library.id?page=1", "last": "https://library.id?page=2", @@ -542,6 +513,7 @@ def test_music_library_serializer_from_public(factories, mocker): retrieve.assert_called_once_with( actor.fid, + actor=None, queryset=actor.__class__, serializer_class=serializers.ActorSerializer, ) @@ -553,18 +525,14 @@ def test_music_library_serializer_from_private(factories, mocker): "funkwhale_api.federation.utils.retrieve_ap_object", return_value=actor ) data = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context_fw(), "audience": "", "name": "Hello", "summary": "World", "type": "Library", "id": "https://library.id", "followers": "https://library.id/followers", - "actor": actor.fid, + "attributedTo": actor.fid, "totalItems": 12, "first": "https://library.id?page=1", "last": "https://library.id?page=2", @@ -584,13 +552,14 @@ def test_music_library_serializer_from_private(factories, mocker): assert library.followers_url == data["followers"] retrieve.assert_called_once_with( actor.fid, + actor=None, queryset=actor.__class__, serializer_class=serializers.ActorSerializer, ) def test_activity_pub_artist_serializer_to_ap(factories): - artist = factories["music.Artist"]() + artist = factories["music.Artist"](attributed=True) expected = { "@context": serializers.AP_CONTEXT, "type": "Artist", @@ -598,6 +567,7 @@ def test_activity_pub_artist_serializer_to_ap(factories): "name": artist.name, "musicbrainzId": artist.mbid, "published": artist.creation_date.isoformat(), + "attributedTo": artist.attributed_to.fid, } serializer = serializers.ArtistSerializer(artist) @@ -605,7 +575,7 @@ def test_activity_pub_artist_serializer_to_ap(factories): def test_activity_pub_album_serializer_to_ap(factories): - album = factories["music.Album"]() + album = factories["music.Album"](attributed=True) expected = { "@context": serializers.AP_CONTEXT, @@ -625,6 +595,7 @@ def test_activity_pub_album_serializer_to_ap(factories): album.artist, context={"include_ap_context": False} ).data ], + "attributedTo": album.attributed_to.fid, } serializer = serializers.AlbumSerializer(album) @@ -633,7 +604,7 @@ def test_activity_pub_album_serializer_to_ap(factories): def test_activity_pub_track_serializer_to_ap(factories): track = factories["music.Track"]( - license="cc-by-4.0", copyright="test", disc_number=3 + license="cc-by-4.0", copyright="test", disc_number=3, attributed=True ) expected = { "@context": serializers.AP_CONTEXT, @@ -654,17 +625,24 @@ def test_activity_pub_track_serializer_to_ap(factories): "album": serializers.AlbumSerializer( track.album, context={"include_ap_context": False} ).data, + "attributedTo": track.attributed_to.fid, } serializer = serializers.TrackSerializer(track) assert serializer.data == expected -def test_activity_pub_track_serializer_from_ap(factories, r_mock): +def test_activity_pub_track_serializer_from_ap(factories, r_mock, mocker): + track_attributed_to = factories["federation.Actor"]() + album_attributed_to = factories["federation.Actor"]() + album_artist_attributed_to = factories["federation.Actor"]() + artist_attributed_to = factories["federation.Actor"]() + activity = factories["federation.Activity"]() published = timezone.now() released = timezone.now().date() data = { + "@context": jsonld.get_default_context(), "type": "Track", "id": "http://hello.track", "published": published.isoformat(), @@ -672,6 +650,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock): "name": "Black in back", "position": 5, "disc": 1, + "attributedTo": track_attributed_to.fid, "album": { "type": "Album", "id": "http://hello.album", @@ -679,6 +658,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock): "musicbrainzId": str(uuid.uuid4()), "published": published.isoformat(), "released": released.isoformat(), + "attributedTo": album_attributed_to.fid, "cover": { "type": "Link", "href": "https://cover.image/test.png", @@ -691,6 +671,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock): "name": "John Smith", "musicbrainzId": str(uuid.uuid4()), "published": published.isoformat(), + "attributedTo": album_artist_attributed_to.fid, } ], }, @@ -700,6 +681,7 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock): "id": "http://hello.trackartist", "name": "Bob Smith", "musicbrainzId": str(uuid.uuid4()), + "attributedTo": artist_attributed_to.fid, "published": published.isoformat(), } ], @@ -719,28 +701,32 @@ def test_activity_pub_track_serializer_from_ap(factories, r_mock): assert track.position == data["position"] assert track.disc_number == data["disc"] assert track.creation_date == published + assert track.attributed_to == track_attributed_to assert str(track.mbid) == data["musicbrainzId"] assert album.from_activity == activity assert album.cover.read() == b"coucou" - assert album.cover.path.endswith(".png") + assert album.cover_path.endswith(".png") assert album.title == data["album"]["name"] assert album.fid == data["album"]["id"] assert str(album.mbid) == data["album"]["musicbrainzId"] assert album.creation_date == published assert album.release_date == released + assert album.attributed_to == album_attributed_to assert artist.from_activity == activity assert artist.name == data["artists"][0]["name"] assert artist.fid == data["artists"][0]["id"] assert str(artist.mbid) == data["artists"][0]["musicbrainzId"] assert artist.creation_date == published + assert artist.attributed_to == artist_attributed_to assert album_artist.from_activity == activity assert album_artist.name == data["album"]["artists"][0]["name"] assert album_artist.fid == data["album"]["artists"][0]["id"] assert str(album_artist.mbid) == data["album"]["artists"][0]["musicbrainzId"] assert album_artist.creation_date == published + assert album_artist.attributed_to == album_artist_attributed_to def test_activity_pub_upload_serializer_from_ap(factories, mocker, r_mock): @@ -866,11 +852,7 @@ def test_activity_pub_audio_serializer_to_ap(factories): def test_local_actor_serializer_to_ap(factories): expected = { - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - {}, - ], + "@context": jsonld.get_default_context(), "id": "https://test.federation/user", "type": "Person", "following": "https://test.federation/user/following", @@ -928,3 +910,16 @@ def test_activity_serializer_validate_recipients_empty(db): with pytest.raises(serializers.serializers.ValidationError): s.validate_recipients({"cc": []}) + + +def test_track_serializer_update_license(factories): + licenses.load(licenses.LICENSES) + + obj = factories["music.Track"](license=None) + + serializer = serializers.TrackSerializer() + serializer.update(obj, {"license": "http://creativecommons.org/licenses/by/2.0/"}) + + obj.refresh_from_db() + + assert obj.license_id == "cc-by-2.0" diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py index 21aa181f8730489baf4bfd14be786ab4ba1e6b8d..7c29d4698ebbbbc8cd0ccef6a4046603dadf6c76 100644 --- a/api/tests/federation/test_tasks.py +++ b/api/tests/federation/test_tasks.py @@ -5,7 +5,11 @@ import pytest from django.utils import timezone +from funkwhale_api.federation import jsonld +from funkwhale_api.federation import models +from funkwhale_api.federation import serializers from funkwhale_api.federation import tasks +from funkwhale_api.federation import utils def test_clean_federation_music_cache_if_no_listen(preferences, factories): @@ -22,10 +26,10 @@ def test_clean_federation_music_cache_if_no_listen(preferences, factories): # local upload, should not be cleaned upload4 = factories["music.Upload"](library__actor__local=True, accessed_date=None) - path1 = upload1.audio_file.path - path2 = upload2.audio_file.path - path3 = upload3.audio_file.path - path4 = upload4.audio_file.path + path1 = upload1.audio_file_path + path2 = upload2.audio_file_path + path3 = upload3.audio_file_path + path4 = upload4.audio_file_path tasks.clean_music_cache() @@ -62,7 +66,7 @@ def test_clean_federation_music_cache_orphaned(settings, preferences, factories) upload.refresh_from_db() assert bool(upload.audio_file) is True - assert os.path.exists(upload.audio_file.path) is True + assert os.path.exists(upload.audio_file_path) is True assert os.path.exists(remove_path) is False @@ -83,16 +87,21 @@ def test_handle_in(factories, mocker, now, queryset_equal_list): ) -def test_dispatch_outbox(factories, mocker): +@pytest.mark.parametrize( + "type, call_handlers", [("Noop", False), ("Update", False), ("Follow", True)] +) +def test_dispatch_outbox(factories, mocker, type, call_handlers): mocked_inbox = mocker.patch("funkwhale_api.federation.tasks.dispatch_inbox.delay") mocked_deliver_to_remote = mocker.patch( "funkwhale_api.federation.tasks.deliver_to_remote.delay" ) - activity = factories["federation.Activity"](actor__local=True) + activity = factories["federation.Activity"](actor__local=True, type=type) factories["federation.InboxItem"](activity=activity) delivery = factories["federation.Delivery"](activity=activity) tasks.dispatch_outbox(activity_id=activity.pk) - mocked_inbox.assert_called_once_with(activity_id=activity.pk, call_handlers=False) + mocked_inbox.assert_called_once_with( + activity_id=activity.pk, call_handlers=call_handlers + ) mocked_deliver_to_remote.assert_called_once_with(delivery_id=delivery.pk) @@ -162,23 +171,42 @@ def test_fetch_nodeinfo(factories, r_mock, now): assert tasks.fetch_nodeinfo("test.test") == {"hello": "world"} -def test_update_domain_nodeinfo(factories, mocker, now): - domain = factories["federation.Domain"]() - mocker.patch.object(tasks, "fetch_nodeinfo", return_value={"hello": "world"}) +def test_update_domain_nodeinfo(factories, mocker, now, service_actor): + domain = factories["federation.Domain"](nodeinfo_fetch_date=None) + actor = factories["federation.Actor"](fid="https://actor.id") + retrieve_ap_object = mocker.spy(utils, "retrieve_ap_object") + + mocker.patch.object( + tasks, + "fetch_nodeinfo", + return_value={"hello": "world", "metadata": {"actorId": "https://actor.id"}}, + ) assert domain.nodeinfo == {} assert domain.nodeinfo_fetch_date is None + assert domain.service_actor is None tasks.update_domain_nodeinfo(domain_name=domain.name) domain.refresh_from_db() assert domain.nodeinfo_fetch_date == now - assert domain.nodeinfo == {"status": "ok", "payload": {"hello": "world"}} + assert domain.nodeinfo == { + "status": "ok", + "payload": {"hello": "world", "metadata": {"actorId": "https://actor.id"}}, + } + assert domain.service_actor == actor + + retrieve_ap_object.assert_called_once_with( + "https://actor.id", + actor=service_actor, + queryset=models.Actor, + serializer_class=serializers.ActorSerializer, + ) def test_update_domain_nodeinfo_error(factories, r_mock, now): - domain = factories["federation.Domain"]() + domain = factories["federation.Domain"](nodeinfo_fetch_date=None) wellknown_url = "https://{}/.well-known/nodeinfo".format(domain.name) r_mock.get(wellknown_url, status_code=500) @@ -194,6 +222,31 @@ def test_update_domain_nodeinfo_error(factories, r_mock, now): } +def test_refresh_nodeinfo_known_nodes(settings, factories, mocker, now): + settings.NODEINFO_REFRESH_DELAY = 666 + + refreshed = [ + factories["federation.Domain"](nodeinfo_fetch_date=None), + factories["federation.Domain"]( + nodeinfo_fetch_date=now + - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY + 1) + ), + ] + factories["federation.Domain"]( + nodeinfo_fetch_date=now + - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY - 1) + ) + + update_domain_nodeinfo = mocker.patch.object(tasks.update_domain_nodeinfo, "delay") + + tasks.refresh_nodeinfo_known_nodes() + + assert update_domain_nodeinfo.call_count == len(refreshed) + + for d in refreshed: + update_domain_nodeinfo.assert_any_call(domain_name=d.name) + + def test_handle_purge_actors(factories, mocker): to_purge = factories["federation.Actor"]() keeped = [ @@ -285,3 +338,60 @@ def test_rotate_actor_key(factories, settings, mocker): assert actor.public_key == "public" assert actor.private_key == "private" + + +def test_fetch_skipped(factories, r_mock): + url = "https://fetch.object" + fetch = factories["federation.Fetch"](url=url) + payload = {"@context": jsonld.get_default_context(), "type": "Unhandled"} + r_mock.get(url, json=payload) + + tasks.fetch(fetch_id=fetch.pk) + + fetch.refresh_from_db() + + assert fetch.status == "skipped" + assert fetch.detail["reason"] == "unhandled_type" + + +@pytest.mark.parametrize( + "r_mock_args, expected_error_code", + [ + ({"json": {"type": "Unhandled"}}, "invalid_jsonld"), + ({"json": {"@context": jsonld.get_default_context()}}, "invalid_jsonld"), + ({"text": "invalidjson"}, "invalid_json"), + ({"status_code": 404}, "http"), + ({"status_code": 500}, "http"), + ], +) +def test_fetch_errored(factories, r_mock_args, expected_error_code, r_mock): + url = "https://fetch.object" + fetch = factories["federation.Fetch"](url=url) + r_mock.get(url, **r_mock_args) + + tasks.fetch(fetch_id=fetch.pk) + + fetch.refresh_from_db() + + assert fetch.status == "errored" + assert fetch.detail["error_code"] == expected_error_code + + +def test_fetch_success(factories, r_mock, mocker): + artist = factories["music.Artist"]() + fetch = factories["federation.Fetch"](url=artist.fid) + payload = serializers.ArtistSerializer(artist).data + init = mocker.spy(serializers.ArtistSerializer, "__init__") + save = mocker.spy(serializers.ArtistSerializer, "save") + + r_mock.get(artist.fid, json=payload) + + tasks.fetch(fetch_id=fetch.pk) + + fetch.refresh_from_db() + payload["@context"].append("https://funkwhale.audio/ns") + assert fetch.status == "finished" + assert init.call_count == 1 + assert init.call_args[0][1] == artist + assert init.call_args[1]["data"] == payload + assert save.call_count == 1 diff --git a/api/tests/federation/test_utils.py b/api/tests/federation/test_utils.py index a8d02cdb9cc30ae7468a01bbc5b787ce1da73b2e..9aa850728ef969ee8a7e764050e3846650d21358 100644 --- a/api/tests/federation/test_utils.py +++ b/api/tests/federation/test_utils.py @@ -56,7 +56,7 @@ def test_extract_headers_from_meta(): def test_retrieve_ap_object(db, r_mock): fid = "https://some.url" m = r_mock.get(fid, json={"hello": "world"}) - result = utils.retrieve_ap_object(fid) + result = utils.retrieve_ap_object(fid, actor=None) assert result == {"hello": "world"} assert m.request_history[-1].headers["Accept"] == "application/activity+json" @@ -69,7 +69,7 @@ def test_retrieve_ap_object_honor_instance_policy_domain(factories): fid = "https://{}/test".format(domain.name) with pytest.raises(exceptions.BlockedActorOrDomain): - utils.retrieve_ap_object(fid) + utils.retrieve_ap_object(fid, actor=None) def test_retrieve_ap_object_honor_instance_policy_different_url_and_id( @@ -82,7 +82,7 @@ def test_retrieve_ap_object_honor_instance_policy_different_url_and_id( r_mock.get(fid, json={"id": "http://{}/test".format(domain.name)}) with pytest.raises(exceptions.BlockedActorOrDomain): - utils.retrieve_ap_object(fid) + utils.retrieve_ap_object(fid, actor=None) def test_retrieve_with_actor(r_mock, factories): @@ -99,7 +99,7 @@ def test_retrieve_with_actor(r_mock, factories): def test_retrieve_with_queryset(factories): actor = factories["federation.Actor"]() - assert utils.retrieve_ap_object(actor.fid, queryset=actor.__class__) + assert utils.retrieve_ap_object(actor.fid, actor=None, queryset=actor.__class__) def test_retrieve_with_serializer(db, r_mock): @@ -109,6 +109,6 @@ def test_retrieve_with_serializer(db, r_mock): fid = "https://some.url" r_mock.get(fid, json={"hello": "world"}) - result = utils.retrieve_ap_object(fid, serializer_class=S) + result = utils.retrieve_ap_object(fid, actor=None, serializer_class=S) assert result == {"persisted": "object"} diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index 2caa7856a10c81ba13aaf024a7f155d0bb074c9e..93ce05b8ebde3b88cbd1c5528ce4db6579256d7f 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -2,7 +2,7 @@ import pytest from django.core.paginator import Paginator from django.urls import reverse -from funkwhale_api.federation import serializers, webfinger +from funkwhale_api.federation import actors, serializers, webfinger def test_wellknown_webfinger_validates_resource(db, api_client, settings, mocker): @@ -54,6 +54,19 @@ def test_local_actor_detail(factories, api_client): assert response.data == serializer.data +def test_service_actor_detail(factories, api_client): + actor = actors.get_service_actor() + url = reverse( + "federation:actors-detail", + kwargs={"preferred_username": actor.preferred_username}, + ) + serializer = serializers.ActorSerializer(actor) + response = api_client.get(url) + + assert response.status_code == 200 + assert response.data == serializer.data + + def test_local_actor_inbox_post_requires_auth(factories, api_client): user = factories["users.User"](with_actor=True) url = reverse( @@ -80,6 +93,35 @@ def test_local_actor_inbox_post(factories, api_client, mocker, authenticated_act ) +def test_local_actor_inbox_post_receive( + factories, api_client, mocker, authenticated_actor +): + payload = { + "to": [ + "https://test.server/federation/music/libraries/956af6c9-1eb9-4117-8d17-b15e7b34afeb/followers" + ], + "type": "Create", + "actor": authenticated_actor.fid, + "object": { + "id": "https://test.server/federation/music/uploads/fe564a47-b1d4-4596-bf96-008ccf407672", + "type": "Audio", + }, + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {}, + ], + } + user = factories["users.User"](with_actor=True) + url = reverse( + "federation:actors-inbox", + kwargs={"preferred_username": user.actor.preferred_username}, + ) + response = api_client.post(url, payload, format="json") + + assert response.status_code == 200 + + def test_shared_inbox_post(factories, api_client, mocker, authenticated_actor): patched_receive = mocker.patch("funkwhale_api.federation.activity.receive") url = reverse("federation:shared-inbox") @@ -161,3 +203,75 @@ def test_music_library_retrieve_page_follow( response = api_client.get(url, {"page": 1}) assert response.status_code == expected + + +@pytest.mark.parametrize( + "factory, serializer_class, namespace", + [ + ("music.Artist", serializers.ArtistSerializer, "artists"), + ("music.Album", serializers.AlbumSerializer, "albums"), + ("music.Track", serializers.TrackSerializer, "tracks"), + ], +) +def test_music_local_entity_detail( + factories, api_client, factory, serializer_class, namespace, settings +): + obj = factories[factory](fid="http://{}/1".format(settings.FEDERATION_HOSTNAME)) + url = reverse( + "federation:music:{}-detail".format(namespace), kwargs={"uuid": obj.uuid} + ) + response = api_client.get(url) + + assert response.status_code == 200 + assert response.data == serializer_class(obj).data + + +@pytest.mark.parametrize( + "factory, namespace", + [("music.Artist", "artists"), ("music.Album", "albums"), ("music.Track", "tracks")], +) +def test_music_non_local_entity_detail( + factories, api_client, factory, namespace, settings +): + obj = factories[factory](fid="http://wrong-domain/1") + url = reverse( + "federation:music:{}-detail".format(namespace), kwargs={"uuid": obj.uuid} + ) + response = api_client.get(url) + + assert response.status_code == 404 + + +@pytest.mark.parametrize( + "privacy_level, expected", [("me", 404), ("instance", 404), ("everyone", 200)] +) +def test_music_upload_detail(factories, api_client, privacy_level, expected): + upload = factories["music.Upload"]( + library__privacy_level=privacy_level, + library__actor__local=True, + import_status="finished", + ) + url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid}) + response = api_client.get(url) + + assert response.status_code == expected + if expected == 200: + assert response.data == serializers.UploadSerializer(upload).data + + +@pytest.mark.parametrize("privacy_level", ["me", "instance"]) +def test_music_upload_detail_private_approved_follow( + factories, api_client, authenticated_actor, privacy_level +): + upload = factories["music.Upload"]( + library__privacy_level=privacy_level, + library__actor__local=True, + import_status="finished", + ) + factories["federation.LibraryFollow"]( + actor=authenticated_actor, target=upload.library, approved=True + ) + url = reverse("federation:music:uploads-detail", kwargs={"uuid": upload.uuid}) + response = api_client.get(url) + + assert response.status_code == 200 diff --git a/api/tests/history/test_filters.py b/api/tests/history/test_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..257d46bf0c0b311ff57c0b71f90d32b0beea38c1 --- /dev/null +++ b/api/tests/history/test_filters.py @@ -0,0 +1,28 @@ +from funkwhale_api.history import filters +from funkwhale_api.history import models + + +def test_listening_filter_track_artist(factories, mocker, queryset_equal_list): + factories["history.Listening"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_listening = factories["history.Listening"](track__artist=cf.target_artist) + qs = models.Listening.objects.all() + filterset = filters.ListeningFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_listening] + + +def test_listening_filter_track_album_artist(factories, mocker, queryset_equal_list): + factories["history.Listening"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_listening = factories["history.Listening"]( + track__album__artist=cf.target_artist + ) + qs = models.Listening.objects.all() + filterset = filters.ListeningFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_listening] diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py index a5bdc70933c696faae1297540532b594453d110b..233b388d43895ed3138c22126d48210ef520a47a 100644 --- a/api/tests/instance/test_nodeinfo.py +++ b/api/tests/instance/test_nodeinfo.py @@ -1,5 +1,7 @@ import funkwhale_api from funkwhale_api.instance import nodeinfo +from funkwhale_api.federation import actors +from funkwhale_api.music import utils as music_utils def test_nodeinfo_dump(preferences, mocker): @@ -23,6 +25,7 @@ def test_nodeinfo_dump(preferences, mocker): "openRegistrations": preferences["users__registration_enabled"], "usage": {"users": {"total": 1, "activeHalfyear": 12, "activeMonth": 13}}, "metadata": { + "actorId": actors.get_service_actor().fid, "private": preferences["instance__nodeinfo_private"], "shortDescription": preferences["instance__short_description"], "longDescription": preferences["instance__long_description"], @@ -44,6 +47,7 @@ def test_nodeinfo_dump(preferences, mocker): "favorites": {"tracks": {"total": stats["track_favorites"]}}, "listenings": {"total": stats["listenings"]}, }, + "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, }, } assert nodeinfo.get() == expected @@ -60,6 +64,7 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker): "openRegistrations": preferences["users__registration_enabled"], "usage": {"users": {"total": 0, "activeHalfyear": 0, "activeMonth": 0}}, "metadata": { + "actorId": actors.get_service_actor().fid, "private": preferences["instance__nodeinfo_private"], "shortDescription": preferences["instance__short_description"], "longDescription": preferences["instance__long_description"], @@ -73,6 +78,7 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker): "common__api_authentication_required" ], }, + "supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS, }, } assert nodeinfo.get() == expected diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py index 6dd4f6345afebaf91dc001d0b408fb31ae83bbda..0b3b7b79ad2e57716159a5fd12396068ef04bdd8 100644 --- a/api/tests/instance/test_views.py +++ b/api/tests/instance/test_views.py @@ -1,13 +1,5 @@ -import pytest from django.urls import reverse -from funkwhale_api.instance import views - - -@pytest.mark.parametrize("view,permissions", [(views.AdminSettings, ["settings"])]) -def test_permissions(assert_user_permission, view, permissions): - assert_user_permission(view, permissions) - def test_nodeinfo_endpoint(db, api_client, mocker): payload = {"test": "test"} diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index 53bc2504b2487e018448755f4cd25b9438108efe..65c75c2c331cc013761d3ee00c22a985e21c2b21 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -40,7 +40,7 @@ def test_user_update_permission(factories): def test_manage_domain_serializer(factories, now): - domain = factories["federation.Domain"]() + domain = factories["federation.Domain"](nodeinfo_fetch_date=None) setattr(domain, "actors_count", 42) setattr(domain, "outbox_activities_count", 23) expected = { @@ -257,3 +257,221 @@ def test_instance_policy_serializer_purges_target_actor( assert getattr(policy, param) is False assert on_commit.call_count == 0 + + +def test_manage_artist_serializer(factories, now): + artist = factories["music.Artist"](attributed=True) + track = factories["music.Track"](artist=artist) + album = factories["music.Album"](artist=artist) + expected = { + "id": artist.id, + "domain": artist.domain_name, + "is_local": artist.is_local, + "fid": artist.fid, + "name": artist.name, + "mbid": artist.mbid, + "creation_date": artist.creation_date.isoformat().split("+")[0] + "Z", + "albums": [serializers.ManageNestedAlbumSerializer(album).data], + "tracks": [serializers.ManageNestedTrackSerializer(track).data], + "attributed_to": serializers.ManageBaseActorSerializer( + artist.attributed_to + ).data, + } + s = serializers.ManageArtistSerializer(artist) + + assert s.data == expected + + +def test_manage_nested_track_serializer(factories, now): + track = factories["music.Track"]() + expected = { + "id": track.id, + "domain": track.domain_name, + "is_local": track.is_local, + "fid": track.fid, + "title": track.title, + "mbid": track.mbid, + "creation_date": track.creation_date.isoformat().split("+")[0] + "Z", + "position": track.position, + "disc_number": track.disc_number, + "copyright": track.copyright, + "license": track.license, + } + s = serializers.ManageNestedTrackSerializer(track) + + assert s.data == expected + + +def test_manage_nested_album_serializer(factories, now): + album = factories["music.Album"]() + setattr(album, "tracks_count", 44) + expected = { + "id": album.id, + "domain": album.domain_name, + "is_local": album.is_local, + "fid": album.fid, + "title": album.title, + "mbid": album.mbid, + "creation_date": album.creation_date.isoformat().split("+")[0] + "Z", + "release_date": album.release_date.isoformat(), + "cover": { + "original": album.cover.url, + "square_crop": album.cover.crop["400x400"].url, + "medium_square_crop": album.cover.crop["200x200"].url, + "small_square_crop": album.cover.crop["50x50"].url, + }, + "tracks_count": 44, + } + s = serializers.ManageNestedAlbumSerializer(album) + + assert s.data == expected + + +def test_manage_nested_artist_serializer(factories, now): + artist = factories["music.Artist"]() + expected = { + "id": artist.id, + "domain": artist.domain_name, + "is_local": artist.is_local, + "fid": artist.fid, + "name": artist.name, + "mbid": artist.mbid, + "creation_date": artist.creation_date.isoformat().split("+")[0] + "Z", + } + s = serializers.ManageNestedArtistSerializer(artist) + + assert s.data == expected + + +def test_manage_album_serializer(factories, now): + album = factories["music.Album"](attributed=True) + track = factories["music.Track"](album=album) + expected = { + "id": album.id, + "domain": album.domain_name, + "is_local": album.is_local, + "fid": album.fid, + "title": album.title, + "mbid": album.mbid, + "creation_date": album.creation_date.isoformat().split("+")[0] + "Z", + "release_date": album.release_date.isoformat(), + "cover": { + "original": album.cover.url, + "square_crop": album.cover.crop["400x400"].url, + "medium_square_crop": album.cover.crop["200x200"].url, + "small_square_crop": album.cover.crop["50x50"].url, + }, + "artist": serializers.ManageNestedArtistSerializer(album.artist).data, + "tracks": [serializers.ManageNestedTrackSerializer(track).data], + "attributed_to": serializers.ManageBaseActorSerializer( + album.attributed_to + ).data, + } + s = serializers.ManageAlbumSerializer(album) + + assert s.data == expected + + +def test_manage_track_serializer(factories, now): + track = factories["music.Track"](attributed=True) + setattr(track, "uploads_count", 44) + expected = { + "id": track.id, + "domain": track.domain_name, + "is_local": track.is_local, + "fid": track.fid, + "title": track.title, + "mbid": track.mbid, + "disc_number": track.disc_number, + "position": track.position, + "copyright": track.copyright, + "license": track.license, + "creation_date": track.creation_date.isoformat().split("+")[0] + "Z", + "artist": serializers.ManageNestedArtistSerializer(track.artist).data, + "album": serializers.ManageTrackAlbumSerializer(track.album).data, + "attributed_to": serializers.ManageBaseActorSerializer( + track.attributed_to + ).data, + "uploads_count": 44, + } + s = serializers.ManageTrackSerializer(track) + + assert s.data == expected + + +def test_manage_library_serializer(factories, now): + library = factories["music.Library"]() + setattr(library, "followers_count", 42) + setattr(library, "_uploads_count", 44) + expected = { + "id": library.id, + "fid": library.fid, + "url": library.url, + "uuid": str(library.uuid), + "followers_url": library.followers_url, + "domain": library.domain_name, + "is_local": library.is_local, + "name": library.name, + "description": library.description, + "privacy_level": library.privacy_level, + "creation_date": library.creation_date.isoformat().split("+")[0] + "Z", + "actor": serializers.ManageBaseActorSerializer(library.actor).data, + "uploads_count": 44, + "followers_count": 42, + } + s = serializers.ManageLibrarySerializer(library) + + assert s.data == expected + + +def test_manage_upload_serializer(factories, now): + upload = factories["music.Upload"]() + + expected = { + "id": upload.id, + "fid": upload.fid, + "audio_file": upload.audio_file.url, + "listen_url": upload.listen_url, + "uuid": str(upload.uuid), + "domain": upload.domain_name, + "is_local": upload.is_local, + "duration": upload.duration, + "size": upload.size, + "bitrate": upload.bitrate, + "mimetype": upload.mimetype, + "source": upload.source, + "filename": upload.filename, + "metadata": upload.metadata, + "creation_date": upload.creation_date.isoformat().split("+")[0] + "Z", + "modification_date": upload.modification_date.isoformat().split("+")[0] + "Z", + "accessed_date": None, + "import_date": None, + "import_metadata": upload.import_metadata, + "import_status": upload.import_status, + "import_reference": upload.import_reference, + "import_details": upload.import_details, + "library": serializers.ManageNestedLibrarySerializer(upload.library).data, + "track": serializers.ManageNestedTrackSerializer(upload.track).data, + } + s = serializers.ManageUploadSerializer(upload) + + assert s.data == expected + + +@pytest.mark.parametrize( + "factory, serializer_class", + [ + ("music.Track", serializers.ManageTrackActionSerializer), + ("music.Album", serializers.ManageAlbumActionSerializer), + ("music.Artist", serializers.ManageArtistActionSerializer), + ("music.Library", serializers.ManageLibraryActionSerializer), + ("music.Upload", serializers.ManageUploadActionSerializer), + ], +) +def test_action_serializer_delete(factory, serializer_class, factories): + objects = factories[factory].create_batch(size=5) + s = serializer_class(queryset=None) + + s.handle_delete(objects[0].__class__.objects.all()) + + assert objects[0].__class__.objects.count() == 0 diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py index 6402fb6505cdd1ff66d833631a55903ab4924687..e3d136a0e841ab5e49c9dd782083d5f3b9c5cf2a 100644 --- a/api/tests/manage/test_views.py +++ b/api/tests/manage/test_views.py @@ -1,39 +1,8 @@ -import pytest from django.urls import reverse from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import tasks as federation_tasks -from funkwhale_api.manage import serializers, views - - -@pytest.mark.parametrize( - "view,permissions,operator", - [ - (views.ManageUploadViewSet, ["library"], "and"), - (views.ManageUserViewSet, ["settings"], "and"), - (views.ManageInvitationViewSet, ["settings"], "and"), - (views.ManageDomainViewSet, ["moderation"], "and"), - (views.ManageActorViewSet, ["moderation"], "and"), - (views.ManageInstancePolicyViewSet, ["moderation"], "and"), - ], -) -def test_permissions(assert_user_permission, view, permissions, operator): - assert_user_permission(view, permissions, operator) - - -@pytest.mark.skip(reason="Refactoring in progress") -def test_upload_view(factories, superuser_api_client): - uploads = factories["music.Upload"].create_batch(size=5) - qs = uploads[0].__class__.objects.order_by("-creation_date") - url = reverse("api:v1:manage:library:uploads-list") - - response = superuser_api_client.get(url, {"sort": "-creation_date"}) - expected = serializers.ManageUploadSerializer( - qs, many=True, context={"request": response.wsgi_request} - ).data - - assert response.data["count"] == len(uploads) - assert response.data["results"] == expected +from funkwhale_api.manage import serializers def test_user_view(factories, superuser_api_client, mocker): @@ -92,12 +61,16 @@ def test_domain_detail(factories, superuser_api_client): assert response.data["name"] == d.pk -def test_domain_create(superuser_api_client): +def test_domain_create(superuser_api_client, mocker): + update_domain_nodeinfo = mocker.patch( + "funkwhale_api.federation.tasks.update_domain_nodeinfo" + ) url = reverse("api:v1:manage:federation:domains-list") response = superuser_api_client.post(url, {"name": "test.federation"}) assert response.status_code == 201 assert federation_models.Domain.objects.filter(pk="test.federation").exists() + update_domain_nodeinfo.assert_called_once_with(domain_name="test.federation") def test_domain_nodeinfo(factories, superuser_api_client, mocker): @@ -159,3 +132,223 @@ def test_instance_policy_create(superuser_api_client, factories): policy = domain.instance_policy assert policy.actor == actor + + +def test_artist_list(factories, superuser_api_client, settings): + artist = factories["music.Artist"]() + url = reverse("api:v1:manage:library:artists-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == artist.id + + +def test_artist_detail(factories, superuser_api_client): + artist = factories["music.Artist"]() + url = reverse("api:v1:manage:library:artists-detail", kwargs={"pk": artist.pk}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == artist.id + + +def test_artist_detail_stats(factories, superuser_api_client): + artist = factories["music.Artist"]() + url = reverse("api:v1:manage:library:artists-stats", kwargs={"pk": artist.pk}) + response = superuser_api_client.get(url) + expected = { + "libraries": 0, + "uploads": 0, + "listenings": 0, + "playlists": 0, + "mutations": 0, + "track_favorites": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + assert response.status_code == 200 + assert response.data == expected + + +def test_artist_delete(factories, superuser_api_client): + artist = factories["music.Artist"]() + url = reverse("api:v1:manage:library:artists-detail", kwargs={"pk": artist.pk}) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 + + +def test_album_list(factories, superuser_api_client, settings): + album = factories["music.Album"]() + factories["music.Album"]() + url = reverse("api:v1:manage:library:albums-list") + response = superuser_api_client.get( + url, {"q": 'artist:"{}"'.format(album.artist.name)} + ) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == album.id + + +def test_album_detail(factories, superuser_api_client): + album = factories["music.Album"]() + url = reverse("api:v1:manage:library:albums-detail", kwargs={"pk": album.pk}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == album.id + + +def test_album_detail_stats(factories, superuser_api_client): + album = factories["music.Album"]() + url = reverse("api:v1:manage:library:albums-stats", kwargs={"pk": album.pk}) + response = superuser_api_client.get(url) + expected = { + "libraries": 0, + "uploads": 0, + "listenings": 0, + "playlists": 0, + "mutations": 0, + "track_favorites": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + assert response.status_code == 200 + assert response.data == expected + + +def test_album_delete(factories, superuser_api_client): + album = factories["music.Album"]() + url = reverse("api:v1:manage:library:albums-detail", kwargs={"pk": album.pk}) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 + + +def test_track_list(factories, superuser_api_client, settings): + track = factories["music.Track"]() + url = reverse("api:v1:manage:library:tracks-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == track.id + + +def test_track_detail(factories, superuser_api_client): + track = factories["music.Track"]() + url = reverse("api:v1:manage:library:tracks-detail", kwargs={"pk": track.pk}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == track.id + + +def test_track_detail_stats(factories, superuser_api_client): + track = factories["music.Track"]() + url = reverse("api:v1:manage:library:tracks-stats", kwargs={"pk": track.pk}) + response = superuser_api_client.get(url) + expected = { + "libraries": 0, + "uploads": 0, + "listenings": 0, + "playlists": 0, + "mutations": 0, + "track_favorites": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + assert response.status_code == 200 + assert response.data == expected + + +def test_track_delete(factories, superuser_api_client): + track = factories["music.Track"]() + url = reverse("api:v1:manage:library:tracks-detail", kwargs={"pk": track.pk}) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 + + +def test_library_list(factories, superuser_api_client, settings): + library = factories["music.Library"]() + url = reverse("api:v1:manage:library:libraries-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == library.id + + +def test_library_detail(factories, superuser_api_client): + library = factories["music.Library"]() + url = reverse( + "api:v1:manage:library:libraries-detail", kwargs={"uuid": library.uuid} + ) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == library.id + + +def test_library_detail_stats(factories, superuser_api_client): + library = factories["music.Library"]() + url = reverse( + "api:v1:manage:library:libraries-stats", kwargs={"uuid": library.uuid} + ) + response = superuser_api_client.get(url) + expected = { + "uploads": 0, + "followers": 0, + "tracks": 0, + "albums": 0, + "artists": 0, + "media_total_size": 0, + "media_downloaded_size": 0, + } + assert response.status_code == 200 + assert response.data == expected + + +def test_library_delete(factories, superuser_api_client): + library = factories["music.Library"]() + url = reverse( + "api:v1:manage:library:libraries-detail", kwargs={"uuid": library.uuid} + ) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 + + +def test_upload_list(factories, superuser_api_client, settings): + upload = factories["music.Upload"]() + url = reverse("api:v1:manage:library:uploads-list") + response = superuser_api_client.get(url) + + assert response.status_code == 200 + + assert response.data["count"] == 1 + assert response.data["results"][0]["id"] == upload.id + + +def test_upload_detail(factories, superuser_api_client): + upload = factories["music.Upload"]() + url = reverse("api:v1:manage:library:uploads-detail", kwargs={"uuid": upload.uuid}) + response = superuser_api_client.get(url) + + assert response.status_code == 200 + assert response.data["id"] == upload.id + + +def test_upload_delete(factories, superuser_api_client): + upload = factories["music.Upload"]() + url = reverse("api:v1:manage:library:uploads-detail", kwargs={"uuid": upload.uuid}) + response = superuser_api_client.delete(url) + + assert response.status_code == 204 diff --git a/api/tests/moderation/__init__.py b/api/tests/moderation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/tests/moderation/test_filters.py b/api/tests/moderation/test_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..cb1dab95a309cf5a8c001989e1a164ed30d9c2a2 --- /dev/null +++ b/api/tests/moderation/test_filters.py @@ -0,0 +1,68 @@ +from funkwhale_api.moderation import filters +from funkwhale_api.music import models as music_models + + +def test_hidden_defaults_to_true(factories, queryset_equal_list, mocker): + user = factories["users.User"]() + artist = factories["music.Artist"]() + hidden_artist = factories["music.Artist"]() + factories["moderation.UserFilter"](target_artist=hidden_artist, user=user) + + class FS(filters.HiddenContentFilterSet): + class Meta: + hidden_content_fields_mapping = {"target_artist": ["pk"]} + + filterset = FS( + data={}, + queryset=music_models.Artist.objects.all(), + request=mocker.Mock(user=user), + ) + assert filterset.data["hidden"] is False + queryset = filterset.filter_hidden_content( + music_models.Artist.objects.all(), "", False + ) + + assert queryset == [artist] + + +def test_hidden_false(factories, queryset_equal_list, mocker): + user = factories["users.User"]() + factories["music.Artist"]() + hidden_artist = factories["music.Artist"]() + factories["moderation.UserFilter"](target_artist=hidden_artist, user=user) + + class FS(filters.HiddenContentFilterSet): + class Meta: + hidden_content_fields_mapping = {"target_artist": ["pk"]} + + filterset = FS( + data={}, + queryset=music_models.Artist.objects.all(), + request=mocker.Mock(user=user), + ) + + queryset = filterset.filter_hidden_content( + music_models.Artist.objects.all(), "", True + ) + + assert queryset == [hidden_artist] + + +def test_hidden_anonymous(factories, queryset_equal_list, mocker, anonymous_user): + artist = factories["music.Artist"]() + + class FS(filters.HiddenContentFilterSet): + class Meta: + hidden_content_fields_mapping = {"target_artist": ["pk"]} + + filterset = FS( + data={}, + queryset=music_models.Artist.objects.all(), + request=mocker.Mock(user=anonymous_user), + ) + + queryset = filterset.filter_hidden_content( + music_models.Artist.objects.all(), "", True + ) + + assert queryset == [artist] diff --git a/api/tests/moderation/test_serializers.py b/api/tests/moderation/test_serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..a38214143db36f97aa349f712681a3dab2fcbf1b --- /dev/null +++ b/api/tests/moderation/test_serializers.py @@ -0,0 +1,30 @@ +from funkwhale_api.moderation import serializers + + +def test_user_filter_serializer_repr(factories): + artist = factories["music.Artist"]() + content_filter = factories["moderation.UserFilter"](target_artist=artist) + + expected = { + "uuid": str(content_filter.uuid), + "target": {"type": "artist", "id": artist.pk, "name": artist.name}, + "creation_date": content_filter.creation_date.isoformat().replace( + "+00:00", "Z" + ), + } + + serializer = serializers.UserFilterSerializer(content_filter) + + assert serializer.data == expected + + +def test_user_filter_serializer_save(factories): + artist = factories["music.Artist"]() + user = factories["users.User"]() + data = {"target": {"type": "artist", "id": artist.pk}} + + serializer = serializers.UserFilterSerializer(data=data) + serializer.is_valid(raise_exception=True) + content_filter = serializer.save(user=user) + + assert content_filter.target_artist == artist diff --git a/api/tests/moderation/test_views.py b/api/tests/moderation/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..3d53f4565a315b84c40fccd7d8b6c226a6883a24 --- /dev/null +++ b/api/tests/moderation/test_views.py @@ -0,0 +1,24 @@ +from django.urls import reverse + + +def test_restrict_to_own_filters(factories, logged_in_api_client): + cf = factories["moderation.UserFilter"]( + for_artist=True, user=logged_in_api_client.user + ) + factories["moderation.UserFilter"](for_artist=True) + url = reverse("api:v1:moderation:content-filters-list") + response = logged_in_api_client.get(url) + assert response.status_code == 200 + assert response.data["count"] == 1 + assert response.data["results"][0]["uuid"] == str(cf.uuid) + + +def test_create_filter(factories, logged_in_api_client): + artist = factories["music.Artist"]() + url = reverse("api:v1:moderation:content-filters-list") + data = {"target": {"type": "artist", "id": artist.pk}} + response = logged_in_api_client.post(url, data, format="json") + + cf = logged_in_api_client.user.content_filters.latest("id") + assert cf.target_artist == artist + assert response.status_code == 201 diff --git a/api/tests/music/test_commands.py b/api/tests/music/test_commands.py index 38186dd7e92095715668f56ecf88bfc5c6659ad0..a08f1b10b8f9f90d0f3d8474ce7380bcf45594b7 100644 --- a/api/tests/music/test_commands.py +++ b/api/tests/music/test_commands.py @@ -1,6 +1,9 @@ import os +import pytest +from funkwhale_api.music.management.commands import check_inplace_files from funkwhale_api.music.management.commands import fix_uploads +from funkwhale_api.music.management.commands import prune_library DATA_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -73,3 +76,110 @@ def test_fix_uploads_mimetype(factories, mocker): assert upload1.mimetype == "audio/mpeg" assert upload2.mimetype == "audio/something" + + +def test_prune_library_dry_run(factories): + prunable = factories["music.Track"]() + not_prunable = factories["music.Track"]() + c = prune_library.Command() + options = { + "prune_artists": True, + "prune_albums": True, + "prune_tracks": True, + "exclude_favorites": False, + "exclude_listenings": False, + "exclude_playlists": False, + "dry_run": True, + } + c.handle(**options) + + for t in [prunable, not_prunable]: + # nothing pruned, because dry run + t.refresh_from_db() + + +def test_prune_library(factories, mocker): + prunable_track = factories["music.Track"]() + not_prunable_track = factories["music.Track"]() + prunable_tracks = prunable_track.__class__.objects.filter(pk=prunable_track.pk) + get_prunable_tracks = mocker.patch( + "funkwhale_api.music.tasks.get_prunable_tracks", return_value=prunable_tracks + ) + + prunable_album = factories["music.Album"]() + not_prunable_album = factories["music.Album"]() + prunable_albums = prunable_album.__class__.objects.filter(pk=prunable_album.pk) + get_prunable_albums = mocker.patch( + "funkwhale_api.music.tasks.get_prunable_albums", return_value=prunable_albums + ) + + prunable_artist = factories["music.Artist"]() + not_prunable_artist = factories["music.Artist"]() + prunable_artists = prunable_artist.__class__.objects.filter(pk=prunable_artist.pk) + get_prunable_artists = mocker.patch( + "funkwhale_api.music.tasks.get_prunable_artists", return_value=prunable_artists + ) + + c = prune_library.Command() + options = { + "exclude_favorites": mocker.Mock(), + "exclude_listenings": mocker.Mock(), + "exclude_playlists": mocker.Mock(), + "prune_artists": True, + "prune_albums": True, + "prune_tracks": True, + "dry_run": False, + } + c.handle(**options) + + get_prunable_tracks.assert_called_once_with( + exclude_favorites=options["exclude_favorites"], + exclude_listenings=options["exclude_listenings"], + exclude_playlists=options["exclude_playlists"], + ) + get_prunable_albums.assert_called_once() + get_prunable_artists.assert_called_once() + + with pytest.raises(prunable_track.DoesNotExist): + prunable_track.refresh_from_db() + + with pytest.raises(prunable_album.DoesNotExist): + prunable_album.refresh_from_db() + + with pytest.raises(prunable_artist.DoesNotExist): + prunable_artist.refresh_from_db() + + for o in [not_prunable_track, not_prunable_album, not_prunable_artist]: + o.refresh_from_db() + + +def test_check_inplace_files_dry_run(factories, tmpfile): + prunable = factories["music.Upload"](source="file:///notfound", audio_file=None) + not_prunable = factories["music.Upload"]( + source="file://{}".format(tmpfile.name), audio_file=None + ) + c = check_inplace_files.Command() + c.handle(dry_run=True) + + for u in [prunable, not_prunable]: + # nothing pruned, because dry run + u.refresh_from_db() + + +def test_check_inplace_files_no_dry_run(factories, tmpfile): + prunable = factories["music.Upload"](source="file:///notfound", audio_file=None) + not_prunable = [ + factories["music.Upload"]( + source="file://{}".format(tmpfile.name), audio_file=None + ), + factories["music.Upload"](source="upload://"), + factories["music.Upload"](source="https://"), + ] + c = check_inplace_files.Command() + c.handle(dry_run=False) + + with pytest.raises(prunable.DoesNotExist): + prunable.refresh_from_db() + + for u in not_prunable: + u.refresh_from_db() diff --git a/api/tests/music/test_filters.py b/api/tests/music/test_filters.py new file mode 100644 index 0000000000000000000000000000000000000000..f9abc4b2156b6a537471bb83ec583ecfb477faee --- /dev/null +++ b/api/tests/music/test_filters.py @@ -0,0 +1,54 @@ +from funkwhale_api.music import filters +from funkwhale_api.music import models + + +def test_album_filter_hidden(factories, mocker, queryset_equal_list): + factories["music.Album"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_album = factories["music.Album"](artist=cf.target_artist) + + qs = models.Album.objects.all() + filterset = filters.AlbumFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_album] + + +def test_artist_filter_hidden(factories, mocker, queryset_equal_list): + factories["music.Artist"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_artist = cf.target_artist + + qs = models.Artist.objects.all() + filterset = filters.ArtistFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_artist] + + +def test_artist_filter_track_artist(factories, mocker, queryset_equal_list): + factories["music.Track"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_track = factories["music.Track"](artist=cf.target_artist) + + qs = models.Track.objects.all() + filterset = filters.TrackFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_track] + + +def test_artist_filter_track_album_artist(factories, mocker, queryset_equal_list): + factories["music.Track"]() + cf = factories["moderation.UserFilter"](for_artist=True) + hidden_track = factories["music.Track"](album__artist=cf.target_artist) + + qs = models.Track.objects.all() + filterset = filters.TrackFilter( + {"hidden": "true"}, request=mocker.Mock(user=cf.user), queryset=qs + ) + + assert filterset.qs == [hidden_track] diff --git a/api/tests/music/test_lyrics.py b/api/tests/music/test_lyrics.py deleted file mode 100644 index c8ce92b6a26418e9600e725b93bd1daa53ee72a4..0000000000000000000000000000000000000000 --- a/api/tests/music/test_lyrics.py +++ /dev/null @@ -1,69 +0,0 @@ -from django.urls import reverse - -from funkwhale_api.music import lyrics as lyrics_utils -from funkwhale_api.music import models, tasks - - -def test_lyrics_tasks(lyricswiki_content, mocker, factories): - mocker.patch( - "funkwhale_api.music.lyrics._get_html", return_value=lyricswiki_content - ) - lyrics = factories["music.Lyrics"]( - url="http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!" - ) - - tasks.fetch_content(lyrics_id=lyrics.pk) - lyrics.refresh_from_db() - assert "Grab a brush and put on a little makeup" in lyrics.content - - -def test_clean_content(): - c = """<div class="lyricbox">Hello<br /><script>alert('hello');</script>Is it me you're looking for?<br /></div>""" - d = lyrics_utils.extract_content(c) - d = lyrics_utils.clean_content(d) - - expected = """Hello -Is it me you're looking for? -""" - assert d == expected - - -def test_markdown_rendering(factories): - content = """Hello -Is it me you're looking for?""" - - lyrics = factories["music.Lyrics"](content=content) - - expected = "<p>Hello<br />\nIs it me you're looking for?</p>" - assert expected == lyrics.content_rendered - - -def test_works_import_lyrics_if_any( - lyricswiki_content, works, tracks, mocker, factories, logged_in_client -): - mocker.patch( - "funkwhale_api.musicbrainz.api.works.get", - return_value=works["get"]["chop_suey"], - ) - mocker.patch( - "funkwhale_api.musicbrainz.api.recordings.get", - return_value=tracks["get"]["chop_suey"], - ) - mocker.patch( - "funkwhale_api.music.lyrics._get_html", return_value=lyricswiki_content - ) - track = factories["music.Track"]( - work=None, mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448" - ) - - url = reverse("api:v1:tracks-lyrics", kwargs={"pk": track.pk}) - response = logged_in_client.get(url) - - assert response.status_code == 200 - - track.refresh_from_db() - lyrics = models.Lyrics.objects.latest("id") - work = models.Work.objects.latest("id") - - assert track.work == work - assert lyrics.work == work diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py index f91f7545b89195a99b0072552cfa8ff164d6ca25..1656ece49701159c4215e82c95d384b052925d35 100644 --- a/api/tests/music/test_metadata.py +++ b/api/tests/music/test_metadata.py @@ -11,45 +11,22 @@ from funkwhale_api.music import metadata DATA_DIR = os.path.dirname(os.path.abspath(__file__)) -def test_get_all_metadata_at_once(): - path = os.path.join(DATA_DIR, "test.ogg") - data = metadata.Metadata(path) - - expected = { - "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", - "artist": "Edvard Grieg", - "album_artist": "Edvard Grieg", - "album": "Peer Gynt Suite no. 1, op. 46", - "date": datetime.date(2012, 8, 15), - "track_number": 1, - "disc_number": 1, - "musicbrainz_albumid": uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75"), - "musicbrainz_recordingid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"), - "musicbrainz_artistid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), - "musicbrainz_albumartistid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), - "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", - "copyright": "Someone", - } - - assert data.all() == expected - - @pytest.mark.parametrize( "field,value", [ ("title", "Peer Gynt Suite no. 1, op. 46: I. Morning"), ("artist", "Edvard Grieg"), - ("album_artist", "Edvard Grieg"), + ("album_artist", "Edvard Grieg; Musopen Symphony Orchestra"), ("album", "Peer Gynt Suite no. 1, op. 46"), - ("date", datetime.date(2012, 8, 15)), - ("track_number", 1), - ("disc_number", 1), - ("musicbrainz_albumid", uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75")), - ("musicbrainz_recordingid", uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656")), - ("musicbrainz_artistid", uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823")), + ("date", "2012-08-15"), + ("position", "1"), + ("disc_number", "1"), + ("musicbrainz_albumid", "a766da8b-8336-47aa-a3ee-371cc41ccc75"), + ("mbid", "bd21ac48-46d8-4e78-925f-d9cc2a294656"), + ("musicbrainz_artistid", "013c8e5b-d72a-4cd3-8dee-6c64d6125823"), ( "musicbrainz_albumartistid", - uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f", ), ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"), ("copyright", "Someone"), @@ -62,22 +39,44 @@ def test_can_get_metadata_from_ogg_file(field, value): assert data.get(field) == value +def test_can_get_metadata_all(): + path = os.path.join(DATA_DIR, "test.ogg") + data = metadata.Metadata(path) + + expected = { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artist": "Edvard Grieg", + "album_artist": "Edvard Grieg; Musopen Symphony Orchestra", + "album": "Peer Gynt Suite no. 1, op. 46", + "date": "2012-08-15", + "position": "1", + "disc_number": "1", + "musicbrainz_albumid": "a766da8b-8336-47aa-a3ee-371cc41ccc75", + "mbid": "bd21ac48-46d8-4e78-925f-d9cc2a294656", + "musicbrainz_artistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823", + "musicbrainz_albumartistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f", + "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", + "copyright": "Someone", + } + assert data.all() == expected + + @pytest.mark.parametrize( "field,value", [ ("title", "Peer Gynt Suite no. 1, op. 46: I. Morning"), ("artist", "Edvard Grieg"), - ("album_artist", "Edvard Grieg"), + ("album_artist", "Edvard Grieg; Musopen Symphony Orchestra"), ("album", "Peer Gynt Suite no. 1, op. 46"), - ("date", datetime.date(2012, 8, 15)), - ("track_number", 1), - ("disc_number", 1), - ("musicbrainz_albumid", uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75")), - ("musicbrainz_recordingid", uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656")), - ("musicbrainz_artistid", uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823")), + ("date", "2012-08-15"), + ("position", "1"), + ("disc_number", "1"), + ("musicbrainz_albumid", "a766da8b-8336-47aa-a3ee-371cc41ccc75"), + ("mbid", "bd21ac48-46d8-4e78-925f-d9cc2a294656"), + ("musicbrainz_artistid", "013c8e5b-d72a-4cd3-8dee-6c64d6125823"), ( "musicbrainz_albumartistid", - uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f", ), ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"), ("copyright", "Someone"), @@ -97,16 +96,13 @@ def test_can_get_metadata_from_opus_file(field, value): ("artist", "Die Toten Hosen"), ("album_artist", "Die Toten Hosen"), ("album", "Ballast der Republik"), - ("date", datetime.date(2012, 5, 4)), - ("track_number", 1), - ("disc_number", 1), - ("musicbrainz_albumid", uuid.UUID("1f0441ad-e609-446d-b355-809c445773cf")), - ("musicbrainz_recordingid", uuid.UUID("124d0150-8627-46bc-bc14-789a3bc960c8")), - ("musicbrainz_artistid", uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1")), - ( - "musicbrainz_albumartistid", - uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"), - ), + ("date", "2012-05-04"), + ("position", "1/16"), + ("disc_number", "1/2"), + ("musicbrainz_albumid", "1f0441ad-e609-446d-b355-809c445773cf"), + ("mbid", "124d0150-8627-46bc-bc14-789a3bc960c8"), + ("musicbrainz_artistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"), + ("musicbrainz_albumartistid", "c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"), # somehow, I cannot successfully create an ogg theora file # with the proper license field # ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"), @@ -126,16 +122,13 @@ def test_can_get_metadata_from_ogg_theora_file(field, value): ("artist", "Binärpilot"), ("album_artist", "Binärpilot"), ("album", "You Can't Stop Da Funk"), - ("date", datetime.date(2006, 2, 7)), - ("track_number", 2), - ("disc_number", 1), - ("musicbrainz_albumid", uuid.UUID("ce40cdb1-a562-4fd8-a269-9269f98d4124")), - ("musicbrainz_recordingid", uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb")), - ("musicbrainz_artistid", uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13")), - ( - "musicbrainz_albumartistid", - uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"), - ), + ("date", "2006-02-07"), + ("position", "2/4"), + ("disc_number", "1/1"), + ("musicbrainz_albumid", "ce40cdb1-a562-4fd8-a269-9269f98d4124"), + ("mbid", "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb"), + ("musicbrainz_artistid", "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"), + ("musicbrainz_albumartistid", "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"), ("license", "https://creativecommons.org/licenses/by-nc-nd/2.5/"), ("copyright", "Someone"), ], @@ -144,7 +137,7 @@ def test_can_get_metadata_from_id3_mp3_file(field, value): path = os.path.join(DATA_DIR, "test.mp3") data = metadata.Metadata(path) - assert data.get(field) == value + assert str(data.get(field)) == value @pytest.mark.parametrize( @@ -170,16 +163,13 @@ def test_can_get_pictures(name): ("artist", "Nine Inch Nails"), ("album_artist", "Nine Inch Nails"), ("album", "The Slip"), - ("date", datetime.date(2008, 5, 5)), - ("track_number", 1), - ("disc_number", 1), - ("musicbrainz_albumid", uuid.UUID("12b57d46-a192-499e-a91f-7da66790a1c1")), - ("musicbrainz_recordingid", uuid.UUID("30f3f33e-8d0c-4e69-8539-cbd701d18f28")), - ("musicbrainz_artistid", uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da")), - ( - "musicbrainz_albumartistid", - uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da"), - ), + ("date", "2008-05-05"), + ("position", "1"), + ("disc_number", "1"), + ("musicbrainz_albumid", "12b57d46-a192-499e-a91f-7da66790a1c1"), + ("mbid", "30f3f33e-8d0c-4e69-8539-cbd701d18f28"), + ("musicbrainz_artistid", "b7ffd2af-418f-4be2-bdd1-22f8b48613da"), + ("musicbrainz_albumartistid", "b7ffd2af-418f-4be2-bdd1-22f8b48613da"), ("license", "http://creativecommons.org/licenses/by-nc-sa/3.0/us/"), ("copyright", "2008 nin"), ], @@ -199,54 +189,18 @@ def test_can_get_metadata_from_flac_file_not_crash_if_empty(): data.get("test") -@pytest.mark.parametrize( - "field_name", - [ - "musicbrainz_artistid", - "musicbrainz_albumid", - "musicbrainz_recordingid", - "musicbrainz_albumartistid", - ], -) -def test_mbid_clean_keeps_only_first(field_name): - u1 = str(uuid.uuid4()) - u2 = str(uuid.uuid4()) - field = metadata.VALIDATION[field_name] - result = field.to_python("/".join([u1, u2])) - - assert str(result) == u1 - - @pytest.mark.parametrize( "raw,expected", [ ("2017", datetime.date(2017, 1, 1)), ("2017-12-31", datetime.date(2017, 12, 31)), ("2017-14-01 01:32", datetime.date(2017, 1, 14)), # deezer format + ("2017-02", datetime.date(2017, 1, 1)), # weird format that exists + ("nonsense", None), ], ) def test_date_parsing(raw, expected): - assert metadata.get_date(raw) == expected - - -def test_date_parsing_failure(): - with pytest.raises(metadata.ParseError): - metadata.get_date("noop") - - -def test_metadata_all_ignore_parse_errors_true(mocker): - path = os.path.join(DATA_DIR, "sample.flac") - data = metadata.Metadata(path) - mocker.patch.object(data, "get", side_effect=metadata.ParseError("Failure")) - assert data.all()["date"] is None - - -def test_metadata_all_ignore_parse_errors_false(mocker): - path = os.path.join(DATA_DIR, "sample.flac") - data = metadata.Metadata(path) - mocker.patch.object(data, "get", side_effect=metadata.ParseError("Failure")) - with pytest.raises(metadata.ParseError): - data.all(ignore_parse_errors=False) + assert metadata.PermissiveDateField().to_internal_value(raw) == expected def test_metadata_fallback_ogg_theora(mocker): @@ -264,3 +218,337 @@ def test_metadata_fallback_ogg_theora(mocker): assert data.get("pictures", "default") == expected_result fallback_get.assert_called_once_with("pictures", "default") + + +@pytest.mark.parametrize( + "path, expected", + [ + ( + "test.mp3", + { + "title": "Bend", + "artists": [ + { + "name": "Binärpilot", + "mbid": uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"), + } + ], + "album": { + "title": "You Can't Stop Da Funk", + "mbid": uuid.UUID("ce40cdb1-a562-4fd8-a269-9269f98d4124"), + "release_date": datetime.date(2006, 2, 7), + "artists": [ + { + "name": "Binärpilot", + "mbid": uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"), + } + ], + }, + "position": 2, + "disc_number": 1, + "mbid": uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb"), + "license": "https://creativecommons.org/licenses/by-nc-nd/2.5/", + "copyright": "Someone", + }, + ), + ( + "test.ogg", + { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + } + ], + "album": { + "title": "Peer Gynt Suite no. 1, op. 46", + "mbid": uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75"), + "release_date": datetime.date(2012, 8, 15), + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + }, + { + "name": "Musopen Symphony Orchestra", + "mbid": uuid.UUID("5b4d7d2d-36df-4b38-95e3-a964234f520f"), + }, + ], + }, + "position": 1, + "disc_number": 1, + "mbid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"), + "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", + "copyright": "Someone", + }, + ), + ( + "test.opus", + { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + } + ], + "album": { + "title": "Peer Gynt Suite no. 1, op. 46", + "mbid": uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75"), + "release_date": datetime.date(2012, 8, 15), + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + }, + { + "name": "Musopen Symphony Orchestra", + "mbid": uuid.UUID("5b4d7d2d-36df-4b38-95e3-a964234f520f"), + }, + ], + }, + "position": 1, + "disc_number": 1, + "mbid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"), + "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", + "copyright": "Someone", + }, + ), + ( + "test_theora.ogg", + { + "title": "Drei Kreuze (dass wir hier sind)", + "artists": [ + { + "name": "Die Toten Hosen", + "mbid": uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"), + } + ], + "album": { + "title": "Ballast der Republik", + "mbid": uuid.UUID("1f0441ad-e609-446d-b355-809c445773cf"), + "release_date": datetime.date(2012, 5, 4), + "artists": [ + { + "name": "Die Toten Hosen", + "mbid": uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"), + } + ], + }, + "position": 1, + "disc_number": 1, + "mbid": uuid.UUID("124d0150-8627-46bc-bc14-789a3bc960c8"), + # somehow, I cannot successfully create an ogg theora file + # with the proper license field + # ("license", "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/"), + "copyright": "â„— 2012 JKP GmbH & Co. KG", + }, + ), + ( + "sample.flac", + { + "title": "999,999", + "artists": [ + { + "name": "Nine Inch Nails", + "mbid": uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da"), + } + ], + "album": { + "title": "The Slip", + "mbid": uuid.UUID("12b57d46-a192-499e-a91f-7da66790a1c1"), + "release_date": datetime.date(2008, 5, 5), + "artists": [ + { + "name": "Nine Inch Nails", + "mbid": uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da"), + } + ], + }, + "position": 1, + "disc_number": 1, + "mbid": uuid.UUID("30f3f33e-8d0c-4e69-8539-cbd701d18f28"), + "license": "http://creativecommons.org/licenses/by-nc-sa/3.0/us/", + "copyright": "2008 nin", + }, + ), + ], +) +def test_track_metadata_serializer(path, expected, mocker): + path = os.path.join(DATA_DIR, path) + data = metadata.Metadata(path) + get_picture = mocker.patch.object(data, "get_picture") + expected["cover_data"] = get_picture.return_value + + serializer = metadata.TrackMetadataSerializer(data=data) + assert serializer.is_valid(raise_exception=True) is True + assert serializer.validated_data == expected + + get_picture.assert_called_once_with("cover_front", "other") + + +@pytest.mark.parametrize( + "raw, expected", + [ + ( + { + "names": "Hello; World", + "mbids": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb; f269d497-1cc0-4ae4-a0c4-157ec7d73fcd ", + }, + [ + { + "name": "Hello", + "mbid": uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb"), + }, + { + "name": "World", + "mbid": uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcd"), + }, + ], + ), + ( + { + "names": "Hello; World; Foo", + "mbids": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb; f269d497-1cc0-4ae4-a0c4-157ec7d73fcd ", + }, + [ + { + "name": "Hello", + "mbid": uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb"), + }, + { + "name": "World", + "mbid": uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcd"), + }, + {"name": "Foo", "mbid": None}, + ], + ), + ], +) +def test_artists_cleaning(raw, expected): + field = metadata.ArtistField() + assert field.to_internal_value(raw) == expected + + +@pytest.mark.parametrize( + "data, errored_field", + [ + ({"name": "Hello", "mbid": "wrong-uuid"}, "mbid"), # wrong uuid + ({"name": "", "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcd"}, "name"), + ], +) +def test_artist_serializer_validation(data, errored_field): + serializer = metadata.ArtistSerializer(data=data) + assert serializer.is_valid() is False + + assert len(serializer.errors) == 1 + assert errored_field in serializer.errors + + +@pytest.mark.parametrize( + "data, errored_field", + [ + ({"title": "Hello", "mbid": "wrong"}, "mbid"), # wrong uuid + ( + {"title": "", "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcd"}, + "title", + ), # empty title + ], +) +def test_album_serializer_validation(data, errored_field): + serializer = metadata.AlbumSerializer(data=data) + assert serializer.is_valid() is False + + assert len(serializer.errors) == 1 + assert errored_field in serializer.errors + + +def test_fake_metadata_with_serializer(): + data = { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artist": "Edvard Grieg", + "album_artist": "Edvard Grieg; Musopen Symphony Orchestra", + "album": "Peer Gynt Suite no. 1, op. 46", + "date": "2012-08-15", + "position": "1", + "disc_number": "1", + "musicbrainz_albumid": "a766da8b-8336-47aa-a3ee-371cc41ccc75", + "mbid": "bd21ac48-46d8-4e78-925f-d9cc2a294656", + "musicbrainz_artistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823", + "musicbrainz_albumartistid": "013c8e5b-d72a-4cd3-8dee-6c64d6125823;5b4d7d2d-36df-4b38-95e3-a964234f520f", + "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", + "copyright": "Someone", + } + + expected = { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + } + ], + "album": { + "title": "Peer Gynt Suite no. 1, op. 46", + "mbid": uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75"), + "release_date": datetime.date(2012, 8, 15), + "artists": [ + { + "name": "Edvard Grieg", + "mbid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"), + }, + { + "name": "Musopen Symphony Orchestra", + "mbid": uuid.UUID("5b4d7d2d-36df-4b38-95e3-a964234f520f"), + }, + ], + }, + "position": 1, + "disc_number": 1, + "mbid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"), + "license": "Dummy license: http://creativecommons.org/licenses/by-sa/4.0/", + "copyright": "Someone", + "cover_data": None, + } + serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data)) + assert serializer.is_valid(raise_exception=True) is True + assert serializer.validated_data == expected + + +def test_serializer_album_artist_missing(): + data = { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artist": "Edvard Grieg", + "album": "Peer Gynt Suite no. 1, op. 46", + } + + expected = { + "title": "Peer Gynt Suite no. 1, op. 46: I. Morning", + "artists": [{"name": "Edvard Grieg", "mbid": None}], + "album": { + "title": "Peer Gynt Suite no. 1, op. 46", + "mbid": None, + "release_date": None, + "artists": [], + }, + "cover_data": None, + } + serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data)) + assert serializer.is_valid(raise_exception=True) is True + assert serializer.validated_data == expected + + +def test_artist_field_featuring(): + data = { + "artist": "Santana feat. Chris Cornell", + # here is the tricky bit, note the slash + "musicbrainz_artistid": "9a3bf45c-347d-4630-894d-7cf3e8e0b632/cbf9738d-8f81-4a92-bc64-ede09341652d", + } + + expected = [{"name": "Santana feat. Chris Cornell", "mbid": None}] + + field = metadata.ArtistField() + value = field.get_value(data) + + assert field.to_internal_value(value) == expected diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py index ab32579b7e0db29c5a77843161715a919e9a45c3..5aa29b3cc4519d0c5158301d521a3550e2d56e62 100644 --- a/api/tests/music/test_models.py +++ b/api/tests/music/test_models.py @@ -522,3 +522,35 @@ def test_track_order_for_album(factories): t4 = factories["music.Track"](album=album, position=2, disc_number=2) assert list(models.Track.objects.order_for_album()) == [t1, t3, t2, t4] + + +@pytest.mark.parametrize("factory", ["music.Artist", "music.Album", "music.Track"]) +def test_queryset_local_entities(factories, settings, factory): + settings.FEDERATION_HOSTNAME = "test.com" + obj1 = factories[factory](fid="http://test.com/1") + obj2 = factories[factory](fid="https://test.com/2") + factories[factory](fid="https://test.coma/3") + factories[factory](fid="https://noope/3") + + assert list(obj1.__class__.objects.local().order_by("id")) == [obj1, obj2] + + +@pytest.mark.parametrize( + "federation_hostname, fid, expected", + [ + ("test.domain", "http://test.domain/", True), + ("test.domain", None, True), + ("test.domain", "https://test.domain/", True), + ("test.otherdomain", "http://test.domain/", False), + ], +) +def test_api_model_mixin_is_local(federation_hostname, fid, expected, settings): + settings.FEDERATION_HOSTNAME = federation_hostname + obj = models.Track(fid=fid) + assert obj.is_local is expected + + +def test_api_model_mixin_domain_name(): + obj = models.Track(fid="https://test.domain:543/something") + + assert obj.domain_name == "test.domain" diff --git a/api/tests/music/test_music.py b/api/tests/music/test_music.py index ab5853f14d2262c97db146da29c5c0b8406f33a2..10d281071a8c885cb52031f68b96b6b6120ce5c4 100644 --- a/api/tests/music/test_music.py +++ b/api/tests/music/test_music.py @@ -1,7 +1,5 @@ import datetime -import pytest - from funkwhale_api.federation import utils as federation_utils from funkwhale_api.music import models @@ -40,8 +38,6 @@ def test_can_create_album_from_api(artists, albums, mocker, db): assert album.mbid, data["id"] assert album.title, "Hypnotize" - with pytest.raises(ValueError): - assert album.cover.path is not None assert album.release_date, datetime.date(2005, 1, 1) assert album.artist.name, "System of a Down" assert album.artist.mbid, data["artist-credit"][0]["artist"]["id"] diff --git a/api/tests/music/test_mutations.py b/api/tests/music/test_mutations.py new file mode 100644 index 0000000000000000000000000000000000000000..be3fb0d76cf831d026e414131b82258e9b1a11db --- /dev/null +++ b/api/tests/music/test_mutations.py @@ -0,0 +1,119 @@ +import datetime +import pytest + +from funkwhale_api.music import licenses + + +@pytest.mark.parametrize( + "field, old_value, new_value, expected", [("name", "foo", "bar", "bar")] +) +def test_artist_mutation(field, old_value, new_value, expected, factories, now, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + artist = factories["music.Artist"](**{field: old_value}) + mutation = factories["common.Mutation"]( + type="update", target=artist, payload={field: new_value} + ) + mutation.apply() + artist.refresh_from_db() + + assert getattr(artist, field) == expected + dispatch.assert_called_once_with( + {"type": "Update", "object": {"type": "Artist"}}, context={"artist": artist} + ) + + +@pytest.mark.parametrize( + "field, old_value, new_value, expected", + [ + ("title", "foo", "bar", "bar"), + ( + "release_date", + datetime.date(2016, 1, 1), + "2018-02-01", + datetime.date(2018, 2, 1), + ), + ], +) +def test_album_mutation(field, old_value, new_value, expected, factories, now, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + album = factories["music.Album"](**{field: old_value}) + mutation = factories["common.Mutation"]( + type="update", target=album, payload={field: new_value} + ) + mutation.apply() + album.refresh_from_db() + + assert getattr(album, field) == expected + dispatch.assert_called_once_with( + {"type": "Update", "object": {"type": "Album"}}, context={"album": album} + ) + + +def test_track_license_mutation(factories, now): + track = factories["music.Track"](license=None) + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"license": "cc-by-sa-4.0"} + ) + licenses.load(licenses.LICENSES) + mutation.apply() + track.refresh_from_db() + + assert track.license.code == "cc-by-sa-4.0" + + +def test_track_null_license_mutation(factories, now): + track = factories["music.Track"](license="cc-by-sa-4.0") + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"license": None} + ) + licenses.load(licenses.LICENSES) + mutation.apply() + track.refresh_from_db() + + assert track.license is None + + +def test_track_title_mutation(factories, now): + track = factories["music.Track"](title="foo") + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"title": "bar"} + ) + mutation.apply() + track.refresh_from_db() + + assert track.title == "bar" + + +def test_track_copyright_mutation(factories, now): + track = factories["music.Track"](copyright="foo") + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"copyright": "bar"} + ) + mutation.apply() + track.refresh_from_db() + + assert track.copyright == "bar" + + +def test_track_position_mutation(factories): + track = factories["music.Track"](position=4) + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"position": 12} + ) + mutation.apply() + track.refresh_from_db() + + assert track.position == 12 + + +def test_track_mutation_apply_outbox(factories, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + track = factories["music.Track"](position=4) + mutation = factories["common.Mutation"]( + type="update", target=track, payload={"position": 12} + ) + mutation.apply() + + dispatch.assert_called_once_with( + {"type": "Update", "object": {"type": "Track"}}, context={"track": track} + ) diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py index 155f998904dccadcf2915ab3890767d34eb33be0..5f9b7d9d151035a78396ac3e4e3d91d30884c298 100644 --- a/api/tests/music/test_serializers.py +++ b/api/tests/music/test_serializers.py @@ -34,6 +34,7 @@ def test_artist_album_serializer(factories, to_api_date): album = album.__class__.objects.with_tracks_count().get(pk=album.pk) expected = { "id": album.id, + "fid": album.fid, "mbid": str(album.mbid), "title": album.title, "artist": album.artist.id, @@ -47,6 +48,7 @@ def test_artist_album_serializer(factories, to_api_date): "small_square_crop": album.cover.crop["50x50"].url, }, "release_date": to_api_date(album.release_date), + "is_local": album.is_local, } serializer = serializers.ArtistAlbumSerializer(album) @@ -61,8 +63,10 @@ def test_artist_with_albums_serializer(factories, to_api_date): expected = { "id": artist.id, + "fid": artist.fid, "mbid": str(artist.mbid), "name": artist.name, + "is_local": artist.is_local, "creation_date": to_api_date(artist.creation_date), "albums": [serializers.ArtistAlbumSerializer(album).data], } @@ -79,6 +83,7 @@ def test_album_track_serializer(factories, to_api_date): expected = { "id": track.id, + "fid": track.fid, "artist": serializers.ArtistSimpleSerializer(track.artist).data, "album": track.album.id, "mbid": str(track.mbid), @@ -91,6 +96,7 @@ def test_album_track_serializer(factories, to_api_date): "duration": None, "license": track.license.code, "copyright": track.copyright, + "is_local": track.is_local, } serializer = serializers.AlbumTrackSerializer(track) assert serializer.data == expected @@ -154,6 +160,7 @@ def test_album_serializer(factories, to_api_date): album = track1.album expected = { "id": album.id, + "fid": album.fid, "mbid": str(album.mbid), "title": album.title, "artist": serializers.ArtistSimpleSerializer(album.artist).data, @@ -167,6 +174,7 @@ def test_album_serializer(factories, to_api_date): }, "release_date": to_api_date(album.release_date), "tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data, + "is_local": album.is_local, } serializer = serializers.AlbumSerializer(album) @@ -181,6 +189,7 @@ def test_track_serializer(factories, to_api_date): setattr(track, "playable_uploads", [upload]) expected = { "id": track.id, + "fid": track.fid, "artist": serializers.ArtistSimpleSerializer(track.artist).data, "album": serializers.TrackAlbumSerializer(track.album).data, "mbid": str(track.mbid), @@ -189,10 +198,10 @@ def test_track_serializer(factories, to_api_date): "disc_number": track.disc_number, "uploads": [serializers.TrackUploadSerializer(upload).data], "creation_date": to_api_date(track.creation_date), - "lyrics": track.get_lyrics_url(), "listen_url": track.listen_url, "license": upload.track.license.code, "copyright": upload.track.copyright, + "is_local": upload.track.is_local, } serializer = serializers.TrackSerializer(track) assert serializer.data == expected diff --git a/api/tests/music/test_spa_views.py b/api/tests/music/test_spa_views.py index 7761e313f33747c0c30f5ad62ed1992b2d376566..b9397009c669575c749f2605f3f3096ee0342ee8 100644 --- a/api/tests/music/test_spa_views.py +++ b/api/tests/music/test_spa_views.py @@ -3,6 +3,7 @@ import urllib.parse from django.urls import reverse from funkwhale_api.common import utils +from funkwhale_api.music import serializers def test_library_track(spa_html, no_api_auth, client, factories, settings): @@ -68,6 +69,14 @@ def test_library_track(spa_html, no_api_auth, client, factories, settings): ) ), }, + {"tag": "meta", "property": "twitter:card", "content": "player"}, + { + "tag": "meta", + "property": "twitter:player", + "content": serializers.get_embed_url("track", id=track.id), + }, + {"tag": "meta", "property": "twitter:player:width", "content": "600"}, + {"tag": "meta", "property": "twitter:player:height", "content": "400"}, ] metas = utils.parse_meta(response.content.decode()) @@ -122,6 +131,14 @@ def test_library_album(spa_html, no_api_auth, client, factories, settings): ) ), }, + {"tag": "meta", "property": "twitter:card", "content": "player"}, + { + "tag": "meta", + "property": "twitter:player", + "content": serializers.get_embed_url("album", id=album.id), + }, + {"tag": "meta", "property": "twitter:player:width", "content": "600"}, + {"tag": "meta", "property": "twitter:player:height", "content": "400"}, ] metas = utils.parse_meta(response.content.decode()) @@ -132,6 +149,7 @@ def test_library_album(spa_html, no_api_auth, client, factories, settings): def test_library_artist(spa_html, no_api_auth, client, factories, settings): album = factories["music.Album"]() + factories["music.Upload"](playable=True, track__album=album) artist = album.artist url = "/library/artists/{}".format(artist.pk) @@ -152,6 +170,25 @@ def test_library_artist(spa_html, no_api_auth, client, factories, settings): settings.FUNKWHALE_URL, album.cover.crop["400x400"].url ), }, + { + "tag": "link", + "rel": "alternate", + "type": "application/json+oembed", + "href": ( + utils.join_url(settings.FUNKWHALE_URL, reverse("api:v1:oembed")) + + "?format=json&url={}".format( + urllib.parse.quote_plus(utils.join_url(settings.FUNKWHALE_URL, url)) + ) + ), + }, + {"tag": "meta", "property": "twitter:card", "content": "player"}, + { + "tag": "meta", + "property": "twitter:player", + "content": serializers.get_embed_url("artist", id=artist.id), + }, + {"tag": "meta", "property": "twitter:player:width", "content": "600"}, + {"tag": "meta", "property": "twitter:player:height", "content": "400"}, ] metas = utils.parse_meta(response.content.decode()) diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py index fcb464f3686e44b05f3733830441e5d073a85a51..78f4622babb9b31cfde6f02f04b67eb2c90319ee 100644 --- a/api/tests/music/test_tasks.py +++ b/api/tests/music/test_tasks.py @@ -8,6 +8,7 @@ from django.core.paginator import Paginator from django.utils import timezone from funkwhale_api.federation import serializers as federation_serializers +from funkwhale_api.federation import jsonld from funkwhale_api.music import licenses, metadata, signals, tasks DATA_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -19,15 +20,13 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__)) def test_can_create_track_from_file_metadata_no_mbid(db, mocker): metadata = { "title": "Test track", - "artist": "Test artist", - "album": "Test album", - "date": datetime.date(2012, 8, 15), - "track_number": 4, + "artists": [{"name": "Test artist"}], + "album": {"title": "Test album", "release_date": datetime.date(2012, 8, 15)}, + "position": 4, "disc_number": 2, "license": "Hello world: http://creativecommons.org/licenses/by-sa/4.0/", "copyright": "2018 Someone", } - mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata) match_license = mocker.spy(licenses, "match") track = tasks.get_track_from_import_metadata(metadata) @@ -38,44 +37,100 @@ def test_can_create_track_from_file_metadata_no_mbid(db, mocker): assert track.disc_number == 2 assert track.license.code == "cc-by-sa-4.0" assert track.copyright == metadata["copyright"] - assert track.album.title == metadata["album"] + assert track.album.title == metadata["album"]["title"] assert track.album.mbid is None assert track.album.release_date == datetime.date(2012, 8, 15) - assert track.artist.name == metadata["artist"] + assert track.artist.name == metadata["artists"][0]["name"] assert track.artist.mbid is None + assert track.artist.attributed_to is None match_license.assert_called_once_with(metadata["license"], metadata["copyright"]) +def test_can_create_track_from_file_metadata_attributed_to(factories, mocker): + actor = factories["federation.Actor"]() + metadata = { + "title": "Test track", + "artists": [{"name": "Test artist"}], + "album": {"title": "Test album", "release_date": datetime.date(2012, 8, 15)}, + "position": 4, + "disc_number": 2, + "copyright": "2018 Someone", + } + + track = tasks.get_track_from_import_metadata(metadata, attributed_to=actor) + + assert track.title == metadata["title"] + assert track.mbid is None + assert track.position == 4 + assert track.disc_number == 2 + assert track.copyright == metadata["copyright"] + assert track.attributed_to == actor + assert track.album.title == metadata["album"]["title"] + assert track.album.mbid is None + assert track.album.release_date == datetime.date(2012, 8, 15) + assert track.album.attributed_to == actor + assert track.artist.name == metadata["artists"][0]["name"] + assert track.artist.mbid is None + assert track.artist.attributed_to == actor + + +def test_can_create_track_from_file_metadata_featuring(factories): + metadata = { + "title": "Whole Lotta Love", + "position": 1, + "disc_number": 1, + "mbid": "508704c0-81d4-4c94-ba58-3fc0b7da23eb", + "album": { + "title": "Guitar Heaven: The Greatest Guitar Classics of All Time", + "mbid": "d06f2072-4148-488d-af6f-69ab6539ddb8", + "release_date": datetime.date(2010, 9, 17), + "artists": [ + {"name": "Santana", "mbid": "9a3bf45c-347d-4630-894d-7cf3e8e0b632"} + ], + }, + "artists": [{"name": "Santana feat. Chris Cornell", "mbid": None}], + } + track = tasks.get_track_from_import_metadata(metadata) + + assert track.album.artist.name == "Santana" + assert track.artist.name == "Santana feat. Chris Cornell" + + def test_can_create_track_from_file_metadata_mbid(factories, mocker): metadata = { "title": "Test track", - "artist": "Test artist", - "album_artist": "Test album artist", - "album": "Test album", - "date": datetime.date(2012, 8, 15), - "track_number": 4, - "musicbrainz_albumid": "ce40cdb1-a562-4fd8-a269-9269f98d4124", - "musicbrainz_recordingid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb", - "musicbrainz_artistid": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13", - "musicbrainz_albumartistid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e13", + "artists": [ + {"name": "Test artist", "mbid": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"} + ], + "album": { + "title": "Test album", + "release_date": datetime.date(2012, 8, 15), + "mbid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e15", + "artists": [ + { + "name": "Test album artist", + "mbid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e13", + } + ], + }, + "position": 4, + "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb", "cover_data": {"content": b"image_content", "mimetype": "image/png"}, } - mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata) - track = tasks.get_track_from_import_metadata(metadata) assert track.title == metadata["title"] - assert track.mbid == metadata["musicbrainz_recordingid"] + assert track.mbid == metadata["mbid"] assert track.position == 4 assert track.disc_number is None - assert track.album.title == metadata["album"] - assert track.album.mbid == metadata["musicbrainz_albumid"] - assert track.album.artist.mbid == metadata["musicbrainz_albumartistid"] - assert track.album.artist.name == metadata["album_artist"] + assert track.album.title == metadata["album"]["title"] + assert track.album.mbid == metadata["album"]["mbid"] + assert track.album.artist.mbid == metadata["album"]["artists"][0]["mbid"] + assert track.album.artist.name == metadata["album"]["artists"][0]["name"] assert track.album.release_date == datetime.date(2012, 8, 15) - assert track.artist.name == metadata["artist"] - assert track.artist.mbid == metadata["musicbrainz_artistid"] + assert track.artist.name == metadata["artists"][0]["name"] + assert track.artist.mbid == metadata["artists"][0]["mbid"] def test_can_create_track_from_file_metadata_mbid_existing_album_artist( @@ -84,22 +139,21 @@ def test_can_create_track_from_file_metadata_mbid_existing_album_artist( artist = factories["music.Artist"]() album = factories["music.Album"]() metadata = { - "artist": "", - "album": "", + "album": { + "mbid": album.mbid, + "title": "", + "artists": [{"name": "", "mbid": album.artist.mbid}], + }, "title": "Hello", - "track_number": 4, - "musicbrainz_albumid": album.mbid, - "musicbrainz_recordingid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb", - "musicbrainz_artistid": artist.mbid, - "musicbrainz_albumartistid": album.artist.mbid, + "position": 4, + "artists": [{"mbid": artist.mbid, "name": ""}], + "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb", } - mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata) - track = tasks.get_track_from_import_metadata(metadata) assert track.title == metadata["title"] - assert track.mbid == metadata["musicbrainz_recordingid"] + assert track.mbid == metadata["mbid"] assert track.position == 4 assert track.album == album assert track.artist == artist @@ -111,18 +165,17 @@ def test_can_create_track_from_file_metadata_fid_existing_album_artist( artist = factories["music.Artist"]() album = factories["music.Album"]() metadata = { - "artist": "", - "album": "", + "artists": [{"name": "", "fid": artist.fid}], + "album": { + "title": "", + "fid": album.fid, + "artists": [{"name": "", "fid": album.artist.fid}], + }, "title": "Hello", - "track_number": 4, + "position": 4, "fid": "https://hello", - "album_fid": album.fid, - "artist_fid": artist.fid, - "album_artist_fid": album.artist.fid, } - mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata) - track = tasks.get_track_from_import_metadata(metadata) assert track.title == metadata["title"] @@ -132,25 +185,68 @@ def test_can_create_track_from_file_metadata_fid_existing_album_artist( assert track.artist == artist +def test_can_create_track_from_file_metadata_distinct_release_mbid(factories): + """Cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/772""" + artist = factories["music.Artist"]() + album = factories["music.Album"](artist=artist) + track = factories["music.Track"](album=album, artist=artist) + metadata = { + "artists": [{"name": artist.name, "mbid": artist.mbid}], + "album": {"title": album.title, "mbid": str(uuid.uuid4())}, + "title": track.title, + "position": 4, + "fid": "https://hello", + } + + new_track = tasks.get_track_from_import_metadata(metadata) + + # the returned track should be different from the existing one, and mapped + # to a new album, because the albumid is different + assert new_track.album != album + assert new_track != track + + +def test_can_create_track_from_file_metadata_distinct_position(factories): + """Cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/740""" + artist = factories["music.Artist"]() + album = factories["music.Album"](artist=artist) + track = factories["music.Track"](album=album, artist=artist) + metadata = { + "artists": [{"name": artist.name, "mbid": artist.mbid}], + "album": {"title": album.title, "mbid": album.mbid}, + "title": track.title, + "position": track.position + 1, + } + + new_track = tasks.get_track_from_import_metadata(metadata) + + assert new_track != track + + def test_can_create_track_from_file_metadata_federation(factories, mocker, r_mock): metadata = { - "artist": "Artist", - "album": "Album", - "album_artist": "Album artist", + "artists": [ + {"name": "Artist", "fid": "https://artist.fid", "fdate": timezone.now()} + ], + "album": { + "title": "Album", + "fid": "https://album.fid", + "fdate": timezone.now(), + "artists": [ + { + "name": "Album artist", + "fid": "https://album.artist.fid", + "fdate": timezone.now(), + } + ], + }, "title": "Hello", - "track_number": 4, + "position": 4, "fid": "https://hello", - "album_fid": "https://album.fid", - "artist_fid": "https://artist.fid", - "album_artist_fid": "https://album.artist.fid", "fdate": timezone.now(), - "album_fdate": timezone.now(), - "album_artist_fdate": timezone.now(), - "artist_fdate": timezone.now(), "cover_data": {"url": "https://cover/hello.png", "mimetype": "image/png"}, } r_mock.get(metadata["cover_data"]["url"], body=io.BytesIO(b"coucou")) - mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata) track = tasks.get_track_from_import_metadata(metadata, update_cover=True) @@ -159,16 +255,16 @@ def test_can_create_track_from_file_metadata_federation(factories, mocker, r_moc assert track.creation_date == metadata["fdate"] assert track.position == 4 assert track.album.cover.read() == b"coucou" - assert track.album.cover.path.endswith(".png") - assert track.album.fid == metadata["album_fid"] - assert track.album.title == metadata["album"] - assert track.album.creation_date == metadata["album_fdate"] - assert track.album.artist.fid == metadata["album_artist_fid"] - assert track.album.artist.name == metadata["album_artist"] - assert track.album.artist.creation_date == metadata["album_artist_fdate"] - assert track.artist.fid == metadata["artist_fid"] - assert track.artist.name == metadata["artist"] - assert track.artist.creation_date == metadata["artist_fdate"] + assert track.album.cover_path.endswith(".png") + assert track.album.fid == metadata["album"]["fid"] + assert track.album.title == metadata["album"]["title"] + assert track.album.creation_date == metadata["album"]["fdate"] + assert track.album.artist.fid == metadata["album"]["artists"][0]["fid"] + assert track.album.artist.name == metadata["album"]["artists"][0]["name"] + assert track.album.artist.creation_date == metadata["album"]["artists"][0]["fdate"] + assert track.artist.fid == metadata["artists"][0]["fid"] + assert track.artist.name == metadata["artists"][0]["name"] + assert track.artist.creation_date == metadata["artists"][0]["fdate"] def test_sort_candidates(factories): @@ -184,6 +280,7 @@ def test_upload_import(now, factories, temp_signal, mocker): outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") update_album_cover = mocker.patch("funkwhale_api.music.tasks.update_album_cover") get_picture = mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture") + get_track_from_import_metadata = mocker.spy(tasks, "get_track_from_import_metadata") track = factories["music.Track"](album__cover="") upload = factories["music.Upload"]( track=None, import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}} @@ -201,6 +298,10 @@ def test_upload_import(now, factories, temp_signal, mocker): update_album_cover.assert_called_once_with( upload.track.album, cover_data=get_picture.return_value, source=upload.source ) + assert ( + get_track_from_import_metadata.call_args[-1]["attributed_to"] + == upload.library.actor + ) handler.assert_called_once_with( upload=upload, old_status="pending", @@ -348,7 +449,38 @@ def test_upload_import_error(factories, now, temp_signal): assert upload.import_status == "errored" assert upload.import_date == now - assert upload.import_details == {"error_code": "track_uuid_not_found"} + assert upload.import_details == { + "error_code": "track_uuid_not_found", + "detail": None, + } + handler.assert_called_once_with( + upload=upload, + old_status="pending", + new_status="errored", + sender=None, + signal=signals.upload_import_status_updated, + ) + + +def test_upload_import_error_metadata(factories, now, temp_signal, mocker): + path = os.path.join(DATA_DIR, "test.ogg") + upload = factories["music.Upload"](audio_file__frompath=path) + mocker.patch.object( + metadata.AlbumField, + "to_internal_value", + side_effect=metadata.serializers.ValidationError("Hello"), + ) + with temp_signal(signals.upload_import_status_updated) as handler: + tasks.process_upload(upload_id=upload.pk) + upload.refresh_from_db() + + assert upload.import_status == "errored" + assert upload.import_date == now + assert upload.import_details == { + "error_code": "invalid_metadata", + "detail": {"album": ["Hello"]}, + "file_metadata": metadata.Metadata(path).all(), + } handler.assert_called_once_with( upload=upload, old_status="pending", @@ -402,10 +534,17 @@ def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, m ) -def test_federation_audio_track_to_metadata(now): +def test_federation_audio_track_to_metadata(now, mocker): published = now released = now.date() + references = { + "http://track.attributed": mocker.Mock(), + "http://album.attributed": mocker.Mock(), + "http://album-artist.attributed": mocker.Mock(), + "http://artist.attributed": mocker.Mock(), + } payload = { + "@context": jsonld.get_default_context(), "type": "Track", "id": "http://hello.track", "musicbrainzId": str(uuid.uuid4()), @@ -415,6 +554,7 @@ def test_federation_audio_track_to_metadata(now): "published": published.isoformat(), "license": "http://creativecommons.org/licenses/by-sa/4.0/", "copyright": "2018 Someone", + "attributedTo": "http://track.attributed", "album": { "published": published.isoformat(), "type": "Album", @@ -422,6 +562,7 @@ def test_federation_audio_track_to_metadata(now): "name": "Purple album", "musicbrainzId": str(uuid.uuid4()), "released": released.isoformat(), + "attributedTo": "http://album.attributed", "artists": [ { "type": "Artist", @@ -429,8 +570,14 @@ def test_federation_audio_track_to_metadata(now): "id": "http://hello.artist", "name": "John Smith", "musicbrainzId": str(uuid.uuid4()), + "attributedTo": "http://album-artist.attributed", } ], + "cover": { + "type": "Link", + "href": "http://cover.test", + "mediaType": "image/png", + }, }, "artists": [ { @@ -439,46 +586,65 @@ def test_federation_audio_track_to_metadata(now): "id": "http://hello.trackartist", "name": "Bob Smith", "musicbrainzId": str(uuid.uuid4()), + "attributedTo": "http://artist.attributed", } ], } serializer = federation_serializers.TrackSerializer(data=payload) serializer.is_valid(raise_exception=True) expected = { - "artist": payload["artists"][0]["name"], - "album": payload["album"]["name"], - "album_artist": payload["album"]["artists"][0]["name"], "title": payload["name"], - "date": released, - "track_number": payload["position"], + "position": payload["position"], "disc_number": payload["disc"], "license": "http://creativecommons.org/licenses/by-sa/4.0/", "copyright": "2018 Someone", + "mbid": payload["musicbrainzId"], + "fdate": serializer.validated_data["published"], + "fid": payload["id"], + "attributed_to": references["http://track.attributed"], + "album": { + "title": payload["album"]["name"], + "attributed_to": references["http://album.attributed"], + "release_date": released, + "mbid": payload["album"]["musicbrainzId"], + "fid": payload["album"]["id"], + "fdate": serializer.validated_data["album"]["published"], + "artists": [ + { + "name": a["name"], + "mbid": a["musicbrainzId"], + "fid": a["id"], + "attributed_to": references["http://album-artist.attributed"], + "fdate": serializer.validated_data["album"]["artists"][i][ + "published" + ], + } + for i, a in enumerate(payload["album"]["artists"]) + ], + }, # musicbrainz - "musicbrainz_albumid": payload["album"]["musicbrainzId"], - "musicbrainz_recordingid": payload["musicbrainzId"], - "musicbrainz_artistid": payload["artists"][0]["musicbrainzId"], - "musicbrainz_albumartistid": payload["album"]["artists"][0]["musicbrainzId"], # federation - "fid": payload["id"], - "album_fid": payload["album"]["id"], - "artist_fid": payload["artists"][0]["id"], - "album_artist_fid": payload["album"]["artists"][0]["id"], - "fdate": serializer.validated_data["published"], - "artist_fdate": serializer.validated_data["artists"][0]["published"], - "album_artist_fdate": serializer.validated_data["album"]["artists"][0][ - "published" + "artists": [ + { + "name": a["name"], + "mbid": a["musicbrainzId"], + "fid": a["id"], + "fdate": serializer.validated_data["artists"][i]["published"], + "attributed_to": references["http://artist.attributed"], + } + for i, a in enumerate(payload["artists"]) ], - "album_fdate": serializer.validated_data["album"]["published"], + "cover_data": { + "mimetype": serializer.validated_data["album"]["cover"]["mediaType"], + "url": serializer.validated_data["album"]["cover"]["href"], + }, } - result = tasks.federation_audio_track_to_metadata(serializer.validated_data) + result = tasks.federation_audio_track_to_metadata( + serializer.validated_data, references + ) assert result == expected - # ensure we never forget to test a mandatory field - for k in metadata.ALL_FIELDS: - assert k in result - def test_scan_library_fetches_page_and_calls_scan_page(now, mocker, factories, r_mock): scan = factories["music.LibraryScan"]() @@ -584,3 +750,83 @@ def test_clean_transcoding_cache(preferences, now, factories): with pytest.raises(u1.__class__.DoesNotExist): u1.refresh_from_db() + + +def test_get_prunable_tracks(factories): + prunable_track = factories["music.Track"]() + # non prunable tracks + factories["music.Upload"]() + factories["favorites.TrackFavorite"]() + factories["history.Listening"]() + factories["playlists.PlaylistTrack"]() + + assert list(tasks.get_prunable_tracks()) == [prunable_track] + + +def test_get_prunable_tracks_include_favorites(factories): + prunable_track = factories["music.Track"]() + favorited = factories["favorites.TrackFavorite"]().track + # non prunable tracks + factories["favorites.TrackFavorite"](track__playable=True) + factories["music.Upload"]() + factories["history.Listening"]() + factories["playlists.PlaylistTrack"]() + + qs = tasks.get_prunable_tracks(exclude_favorites=False).order_by("id") + assert list(qs) == [prunable_track, favorited] + + +def test_get_prunable_tracks_include_playlists(factories): + prunable_track = factories["music.Track"]() + in_playlist = factories["playlists.PlaylistTrack"]().track + # non prunable tracks + factories["favorites.TrackFavorite"]() + factories["music.Upload"]() + factories["history.Listening"]() + factories["playlists.PlaylistTrack"](track__playable=True) + + qs = tasks.get_prunable_tracks(exclude_playlists=False).order_by("id") + assert list(qs) == [prunable_track, in_playlist] + + +def test_get_prunable_tracks_include_listenings(factories): + prunable_track = factories["music.Track"]() + listened = factories["history.Listening"]().track + # non prunable tracks + factories["favorites.TrackFavorite"]() + factories["music.Upload"]() + factories["history.Listening"](track__playable=True) + factories["playlists.PlaylistTrack"]() + + qs = tasks.get_prunable_tracks(exclude_listenings=False).order_by("id") + assert list(qs) == [prunable_track, listened] + + +def test_get_prunable_albums(factories): + prunable_album = factories["music.Album"]() + # non prunable album + factories["music.Track"]().album + + assert list(tasks.get_prunable_albums()) == [prunable_album] + + +def test_get_prunable_artists(factories): + prunable_artist = factories["music.Artist"]() + # non prunable artist + non_prunable_artist = factories["music.Artist"]() + non_prunable_album_artist = factories["music.Artist"]() + factories["music.Track"](artist=non_prunable_artist) + factories["music.Track"](album__artist=non_prunable_album_artist) + + assert list(tasks.get_prunable_artists()) == [prunable_artist] + + +def test_update_library_entity(factories, mocker): + artist = factories["music.Artist"]() + save = mocker.spy(artist, "save") + + tasks.update_library_entity(artist, {"name": "Hello"}) + save.assert_called_once_with(update_fields=["name"]) + + artist.refresh_from_db() + assert artist.name == "Hello" diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 85ba2955a141f200fb1f0e81143d82cee2c78dcb..102b5a790a82d8d6fc174df72b414d0bf64033bf 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -70,6 +70,19 @@ def test_track_list_serializer(api_request, factories, logged_in_api_client): assert response.data == expected +def test_track_list_filter_id(api_request, factories, logged_in_api_client): + track1 = factories["music.Track"]() + track2 = factories["music.Track"]() + factories["music.Track"]() + url = reverse("api:v1:tracks-list") + response = logged_in_api_client.get(url, {"id[]": [track1.id, track2.id]}) + + assert response.status_code == 200 + assert response.data["count"] == 2 + assert response.data["results"][0]["id"] == track2.id + assert response.data["results"][1]["id"] == track1.id + + @pytest.mark.parametrize("param,expected", [("true", "full"), ("false", "empty")]) def test_artist_view_filter_playable(param, expected, factories, api_request): artists = { @@ -108,6 +121,27 @@ def test_album_view_filter_playable(param, expected, factories, api_request): assert list(queryset) == expected +@pytest.mark.parametrize( + "param", [("I've Got"), ("Français"), ("I've Got Everything : Spoken Word Poetry")] +) +def test_album_view_filter_query(param, factories, api_request): + # Test both partial and full search. + factories["music.Album"](title="I've Got Nothing : Original Soundtrack") + factories["music.Album"](title="I've Got Cake : Remix") + factories["music.Album"](title="Français Et Tu") + factories["music.Album"](title="I've Got Everything : Spoken Word Poetry") + + request = api_request.get("/", {"q": param}) + view = views.AlbumViewSet() + view.action_map = {"get": "list"} + view.request = view.initialize_request(request) + queryset = view.filter_queryset(view.get_queryset()) + + # Loop through our "expected list", and assert some string finds against our param. + for val in list(queryset): + assert val.title.find(param) != -1 + + def test_can_serve_upload_as_remote_library( factories, authenticated_actor, logged_in_api_client, settings, preferences ): @@ -297,7 +331,7 @@ def test_listen_correct_access(factories, logged_in_api_client): assert response.status_code == 200 -def test_listen_explicit_file(factories, logged_in_api_client, mocker): +def test_listen_explicit_file(factories, logged_in_api_client, mocker, settings): mocked_serve = mocker.spy(views, "handle_serve") upload1 = factories["music.Upload"]( library__privacy_level="everyone", import_status="finished" @@ -310,8 +344,24 @@ def test_listen_explicit_file(factories, logged_in_api_client, mocker): assert response.status_code == 200 mocked_serve.assert_called_once_with( - upload2, user=logged_in_api_client.user, format=None + upload2, + user=logged_in_api_client.user, + format=None, + max_bitrate=None, + proxy_media=settings.PROXY_MEDIA, + ) + + +def test_listen_no_proxy(factories, logged_in_api_client, settings): + settings.PROXY_MEDIA = False + upload = factories["music.Upload"]( + library__privacy_level="everyone", import_status="finished" ) + url = reverse("api:v1:listen-detail", kwargs={"uuid": upload.track.uuid}) + response = logged_in_api_client.get(url, {"upload": upload.uuid}) + + assert response.status_code == 302 + assert response["Location"] == upload.audio_file.url @pytest.mark.parametrize( @@ -333,6 +383,22 @@ def test_should_transcode(mimetype, format, expected, factories): assert views.should_transcode(upload, format) is expected +@pytest.mark.parametrize( + "bitrate,max_bitrate,expected", + [ + # already in acceptable bitrate + (192000, 320000, False), + # No max bitrate specified + (192000, None, False), + # requested max below available + (192000, 128000, True), + ], +) +def test_should_transcode_bitrate(bitrate, max_bitrate, expected, factories): + upload = models.Upload(mimetype="audio/mpeg", bitrate=bitrate) + assert views.should_transcode(upload, "mp3", max_bitrate=max_bitrate) is expected + + @pytest.mark.parametrize("value", [True, False]) def test_should_transcode_according_to_preference(value, preferences, factories): upload = models.Upload(mimetype="audio/ogg") @@ -352,14 +418,14 @@ def test_handle_serve_create_mp3_version(factories, now): assert version.mimetype == "audio/mpeg" assert version.accessed_date == now assert version.bitrate == upload.bitrate - assert version.audio_file.path.endswith(".mp3") + assert version.audio_file_path.endswith(".mp3") assert version.size == version.audio_file.size assert magic.from_buffer(version.audio_file.read(), mime=True) == "audio/mpeg" assert response.status_code == 200 -def test_listen_transcode(factories, now, logged_in_api_client, mocker): +def test_listen_transcode(factories, now, logged_in_api_client, mocker, settings): upload = factories["music.Upload"]( import_status="finished", library__actor__user=logged_in_api_client.user ) @@ -370,7 +436,43 @@ def test_listen_transcode(factories, now, logged_in_api_client, mocker): assert response.status_code == 200 handle_serve.assert_called_once_with( - upload, user=logged_in_api_client.user, format="mp3" + upload, + user=logged_in_api_client.user, + format="mp3", + max_bitrate=None, + proxy_media=settings.PROXY_MEDIA, + ) + + +@pytest.mark.parametrize( + "max_bitrate, expected", + [ + ("", None), + ("", None), + ("-1", None), + ("128", 128000), + ("320", 320000), + ("460", 320000), + ], +) +def test_listen_transcode_bitrate( + max_bitrate, expected, factories, now, logged_in_api_client, mocker, settings +): + upload = factories["music.Upload"]( + import_status="finished", library__actor__user=logged_in_api_client.user + ) + url = reverse("api:v1:listen-detail", kwargs={"uuid": upload.track.uuid}) + handle_serve = mocker.spy(views, "handle_serve") + response = logged_in_api_client.get(url, {"max_bitrate": max_bitrate}) + + assert response.status_code == 200 + + handle_serve.assert_called_once_with( + upload, + user=logged_in_api_client.user, + format=None, + max_bitrate=expected, + proxy_media=settings.PROXY_MEDIA, ) @@ -396,7 +498,11 @@ def test_listen_transcode_in_place( assert response.status_code == 200 handle_serve.assert_called_once_with( - upload, user=logged_in_api_client.user, format="mp3" + upload, + user=logged_in_api_client.user, + format="mp3", + max_bitrate=None, + proxy_media=settings.PROXY_MEDIA, ) @@ -578,7 +684,7 @@ def test_list_licenses(api_client, preferences, mocker): expected = [ serializers.LicenseSerializer(l.conf).data - for l in models.License.objects.order_by("code")[:25] + for l in models.License.objects.order_by("code") ] url = reverse("api:v1:licenses-list") @@ -667,3 +773,38 @@ def test_oembed_album(factories, no_api_auth, api_client, settings): response = api_client.get(url, {"url": album_url, "format": "json"}) assert response.data == expected + + +def test_oembed_artist(factories, no_api_auth, api_client, settings): + settings.FUNKWHALE_URL = "http://test" + settings.FUNKWHALE_EMBED_URL = "http://embed" + track = factories["music.Track"]() + album = track.album + artist = track.artist + url = reverse("api:v1:oembed") + artist_url = "https://test.com/library/artists/{}".format(artist.pk) + iframe_src = "http://embed?type=artist&id={}".format(artist.pk) + expected = { + "version": "1.0", + "type": "rich", + "provider_name": settings.APP_NAME, + "provider_url": settings.FUNKWHALE_URL, + "height": 400, + "width": 600, + "title": artist.name, + "description": artist.name, + "thumbnail_url": federation_utils.full_url(album.cover.crop["400x400"].url), + "thumbnail_height": 400, + "thumbnail_width": 400, + "html": '<iframe width="600" height="400" scrolling="no" frameborder="no" src="{}"></iframe>'.format( + iframe_src + ), + "author_name": artist.name, + "author_url": federation_utils.full_url( + utils.spa_reverse("library_artist", kwargs={"pk": artist.pk}) + ), + } + + response = api_client.get(url, {"url": artist_url, "format": "json"}) + + assert response.data == expected diff --git a/api/tests/music/test_works.py b/api/tests/music/test_works.py deleted file mode 100644 index 96b537ca297a5b9e8c81528349b42d5404fbe9f6..0000000000000000000000000000000000000000 --- a/api/tests/music/test_works.py +++ /dev/null @@ -1,61 +0,0 @@ -from funkwhale_api.music import models - - -def test_can_import_work(factories, mocker, works): - mocker.patch( - "funkwhale_api.musicbrainz.api.works.get", - return_value=works["get"]["chop_suey"], - ) - recording = factories["music.Track"](mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448") - mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" - work = models.Work.create_from_api(id=mbid) - - assert work.title == "Chop Suey!" - assert work.nature == "song" - assert work.language == "eng" - assert work.mbid == mbid - - # a imported work should also be linked to corresponding recordings - - recording.refresh_from_db() - assert recording.work == work - - -def test_can_get_work_from_recording(factories, mocker, works, tracks): - mocker.patch( - "funkwhale_api.musicbrainz.api.works.get", - return_value=works["get"]["chop_suey"], - ) - mocker.patch( - "funkwhale_api.musicbrainz.api.recordings.get", - return_value=tracks["get"]["chop_suey"], - ) - recording = factories["music.Track"]( - work=None, mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448" - ) - mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" - - assert recording.work is None - - work = recording.get_work() - - assert work.title == "Chop Suey!" - assert work.nature == "song" - assert work.language == "eng" - assert work.mbid == mbid - - recording.refresh_from_db() - assert recording.work == work - - -def test_works_import_lyrics_if_any(db, mocker, works): - mocker.patch( - "funkwhale_api.musicbrainz.api.works.get", - return_value=works["get"]["chop_suey"], - ) - mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5" - work = models.Work.create_from_api(id=mbid) - - lyrics = models.Lyrics.objects.latest("id") - assert lyrics.work == work - assert lyrics.url == "http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!" diff --git a/api/tests/playlists/test_models.py b/api/tests/playlists/test_models.py index b90f525184b2f4863d8d9f7ab07314ac1e1a1712..92ffbf4edf8a25731b0473458e45f4022282946a 100644 --- a/api/tests/playlists/test_models.py +++ b/api/tests/playlists/test_models.py @@ -124,6 +124,139 @@ def test_insert_many_honor_max_tracks(preferences, factories): playlist.insert_many([track, track, track]) +def test_can_insert_duplicate_by_default(factories): + playlist = factories["playlists.Playlist"]() + track = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track) + + new = factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=track) + playlist.insert(new) + + new.refresh_from_db() + assert new.index == 1 + + +def test_cannot_insert_duplicate(factories): + playlist = factories["playlists.Playlist"](name="playlist") + track = factories["music.Track"]() + + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track) + + with pytest.raises(exceptions.ValidationError) as e: + new = factories["playlists.PlaylistTrack"]( + playlist=playlist, index=1, track=track + ) + playlist.insert(new, allow_duplicates=False) + + errors = e.value.detail["non_field_errors"] + assert len(errors) == 1 + + err = errors[0] + assert err["code"] == "tracks_already_exist_in_playlist" + assert err["playlist_name"] == "playlist" + assert err["tracks"] == [track.title] + + +def test_can_insert_track_to_playlist_with_existing_duplicates(factories): + playlist = factories["playlists.Playlist"]() + existing_duplicate = factories["music.Track"]() + factories["playlists.PlaylistTrack"]( + playlist=playlist, index=0, track=existing_duplicate + ) + factories["playlists.PlaylistTrack"]( + playlist=playlist, index=1, track=existing_duplicate + ) + factories["playlists.PlaylistTrack"]( + playlist=playlist, index=2, track=existing_duplicate + ) + + new_track = factories["music.Track"]() + new_plt = factories["playlists.PlaylistTrack"]( + playlist=playlist, index=3, track=new_track + ) + + # no error + playlist.insert(new_plt, allow_duplicates=False) + + +def test_can_insert_duplicate_with_override(factories): + playlist = factories["playlists.Playlist"]() + track = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=track) + + new = factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=track) + playlist.insert(new, allow_duplicates=True) + + new.refresh_from_db() + assert new.index == 1 + + +def test_can_insert_many_duplicates_by_default(factories): + playlist = factories["playlists.Playlist"]() + + t1 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1) + + t2 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2) + + t4 = factories["music.Track"]() + + tracks = [t1, t4, t2] + + plts = playlist.insert_many(tracks) + + assert len(plts) == 3 + assert plts[0].track == t1 + assert plts[1].track == t4 + assert plts[2].track == t2 + + +def test_cannot_insert_many_duplicates(factories): + playlist = factories["playlists.Playlist"](name="playlist") + + t1 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1) + + t2 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2) + + t4 = factories["music.Track"]() + + with pytest.raises(exceptions.ValidationError) as e: + tracks = [t1, t4, t2] + playlist.insert_many(tracks, allow_duplicates=False) + + errors = e.value.detail["non_field_errors"] + assert len(errors) == 1 + + err = errors[0] + assert err["code"] == "tracks_already_exist_in_playlist" + assert err["playlist_name"] == "playlist" + assert err["tracks"] == [t1.title, t2.title] + + +def test_can_insert_many_duplicates_with_override(factories): + playlist = factories["playlists.Playlist"]() + + t1 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=0, track=t1) + + t2 = factories["music.Track"]() + factories["playlists.PlaylistTrack"](playlist=playlist, index=1, track=t2) + + t4 = factories["music.Track"]() + + tracks = [t1, t4, t2] + + plts = playlist.insert_many(tracks, allow_duplicates=True) + + assert len(plts) == 3 + assert plts[0].track == t1 + assert plts[1].track == t4 + assert plts[2].track == t2 + + @pytest.mark.parametrize( "privacy_level,expected", [("me", False), ("instance", False), ("everyone", True)] ) diff --git a/api/tests/playlists/test_serializers.py b/api/tests/playlists/test_serializers.py index 0afc927ade0fcb58c25578064dcdc9d4c7c5f88a..2500947292142b653e99886144c8020788ad5762 100644 --- a/api/tests/playlists/test_serializers.py +++ b/api/tests/playlists/test_serializers.py @@ -24,7 +24,7 @@ def test_create_insert_is_called_when_index_is_None(factories, mocker): assert serializer.is_valid() is True plt = serializer.save() - insert.assert_called_once_with(playlist, plt, None) + insert.assert_called_once_with(playlist, plt, None, True) assert plt.index == 0 @@ -41,7 +41,7 @@ def test_create_insert_is_called_when_index_is_provided(factories, mocker): plt = serializer.save() first.refresh_from_db() - insert.assert_called_once_with(playlist, plt, 0) + insert.assert_called_once_with(playlist, plt, 0, True) assert plt.index == 0 assert first.index == 1 @@ -60,11 +60,35 @@ def test_update_insert_is_called_when_index_is_provided(factories, mocker): plt = serializer.save() first.refresh_from_db() - insert.assert_called_once_with(playlist, plt, 0) + insert.assert_called_once_with(playlist, plt, 0, True) assert plt.index == 0 assert first.index == 1 +def test_update_insert_is_called_with_duplicate_override_when_duplicates_allowed( + factories, mocker +): + playlist = factories["playlists.Playlist"]() + plt = factories["playlists.PlaylistTrack"](playlist=playlist, index=0) + insert = mocker.spy(models.Playlist, "insert") + factories["playlists.Playlist"]() + factories["music.Track"]() + + serializer = serializers.PlaylistTrackWriteSerializer( + plt, + data={ + "playlist": playlist.pk, + "track": plt.track.pk, + "index": 0, + "allow_duplicates": True, + }, + ) + assert serializer.is_valid() is True + plt = serializer.save() + + insert.assert_called_once_with(playlist, plt, 0, True) + + def test_playlist_serializer_include_covers(factories, api_request): playlist = factories["playlists.Playlist"]() t1 = factories["music.Track"]() diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py index 7e8f260d0338d2ef71b3b51fb11c5acde917789a..640e712117bfb2ffc641af43daaa0eb5268ad0fd 100644 --- a/api/tests/radios/test_radios.py +++ b/api/tests/radios/test_radios.py @@ -237,3 +237,44 @@ def test_can_start_less_listened_radio(factories): for i in range(5): assert radio.pick(filter_playable=False) in good_tracks + + +def test_similar_radio_track(factories): + user = factories["users.User"]() + seed = factories["music.Track"]() + radio = radios.SimilarRadio() + radio.start_session(user, related_object=seed) + + factories["music.Track"].create_batch(5) + + # one user listened to this track + l1 = factories["history.Listening"](track=seed) + + expected_next = factories["music.Track"]() + factories["history.Listening"](track=expected_next, user=l1.user) + + assert radio.pick(filter_playable=False) == expected_next + + +def test_session_radio_get_queryset_ignore_filtered_track_artist( + factories, queryset_equal_list +): + cf = factories["moderation.UserFilter"](for_artist=True) + factories["music.Track"](artist=cf.target_artist) + valid_track = factories["music.Track"]() + radio = radios.RandomRadio() + radio.start_session(user=cf.user) + + assert radio.get_queryset() == [valid_track] + + +def test_session_radio_get_queryset_ignore_filtered_track_album_artist( + factories, queryset_equal_list +): + cf = factories["moderation.UserFilter"](for_artist=True) + factories["music.Track"](album__artist=cf.target_artist) + valid_track = factories["music.Track"]() + radio = radios.RandomRadio() + radio.start_session(user=cf.user) + + assert radio.get_queryset() == [valid_track] diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index 0dbbaf39a2b6018017351dbf51400b3b62ad5d35..73f968ff47130daefa5312d1aa61e10a9a15ddde 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -7,6 +7,7 @@ from django.utils import timezone from rest_framework.response import Response import funkwhale_api +from funkwhale_api.moderation import filters as moderation_filters from funkwhale_api.music import models as music_models from funkwhale_api.music import views as music_views from funkwhale_api.subsonic import renderers, serializers @@ -100,20 +101,31 @@ def test_ping(f, db, api_client): def test_get_artists( f, db, logged_in_api_client, factories, mocker, queryset_equal_queries ): + factories["moderation.UserFilter"]( + user=logged_in_api_client.user, + target_artist=factories["music.Artist"](playable=True), + ) url = reverse("api:subsonic-get_artists") assert url.endswith("getArtists") is True factories["music.Artist"].create_batch(size=3, playable=True) playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by") + exclude_query = moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["ARTIST"], logged_in_api_client.user + ) + assert exclude_query is not None expected = { "artists": serializers.GetArtistsSerializer( - music_models.Artist.objects.all() + music_models.Artist.objects.all().exclude(exclude_query) ).data } response = logged_in_api_client.get(url, {"f": f}) assert response.status_code == 200 assert response.data == expected - playable_by.assert_called_once_with(music_models.Artist.objects.all(), None) + playable_by.assert_called_once_with( + music_models.Artist.objects.all().exclude(exclude_query), + logged_in_api_client.user.actor, + ) @pytest.mark.parametrize("f", ["json"]) @@ -205,7 +217,12 @@ def test_get_song( @pytest.mark.parametrize("f", ["json"]) -def test_stream(f, db, logged_in_api_client, factories, mocker, queryset_equal_queries): +def test_stream( + f, db, logged_in_api_client, factories, mocker, queryset_equal_queries, settings +): + # Even with this settings set to false, we proxy media in the subsonic API + # Because clients don't expect a 302 redirect + settings.PROXY_MEDIA = False url = reverse("api:subsonic-stream") mocked_serve = mocker.spy(music_views, "handle_serve") assert url.endswith("stream") is True @@ -214,7 +231,11 @@ def test_stream(f, db, logged_in_api_client, factories, mocker, queryset_equal_q response = logged_in_api_client.get(url, {"f": f, "id": upload.track.pk}) mocked_serve.assert_called_once_with( - upload=upload, user=logged_in_api_client.user, format=None + upload=upload, + user=logged_in_api_client.user, + format=None, + max_bitrate=None, + proxy_media=True, ) assert response.status_code == 200 playable_by.assert_called_once_with(music_models.Track.objects.all(), None) @@ -230,7 +251,34 @@ def test_stream_format(format, expected, logged_in_api_client, factories, mocker response = logged_in_api_client.get(url, {"id": upload.track.pk, "format": format}) mocked_serve.assert_called_once_with( - upload=upload, user=logged_in_api_client.user, format=expected + upload=upload, + user=logged_in_api_client.user, + format=expected, + max_bitrate=None, + proxy_media=True, + ) + assert response.status_code == 200 + + +@pytest.mark.parametrize( + "max_bitrate,expected", [(0, None), (192, 192000), (2000, 320000)] +) +def test_stream_bitrate(max_bitrate, expected, logged_in_api_client, factories, mocker): + url = reverse("api:subsonic-stream") + mocked_serve = mocker.patch.object( + music_views, "handle_serve", return_value=Response() + ) + upload = factories["music.Upload"](playable=True) + response = logged_in_api_client.get( + url, {"id": upload.track.pk, "maxBitRate": max_bitrate} + ) + + mocked_serve.assert_called_once_with( + upload=upload, + user=logged_in_api_client.user, + format=None, + max_bitrate=expected, + proxy_media=True, ) assert response.status_code == 200 @@ -502,12 +550,20 @@ def test_get_music_folders(f, db, logged_in_api_client, factories): def test_get_indexes( f, db, logged_in_api_client, factories, mocker, queryset_equal_queries ): + factories["moderation.UserFilter"]( + user=logged_in_api_client.user, + target_artist=factories["music.Artist"](playable=True), + ) + exclude_query = moderation_filters.get_filtered_content_query( + moderation_filters.USER_FILTER_CONFIG["ARTIST"], logged_in_api_client.user + ) + url = reverse("api:subsonic-get_indexes") assert url.endswith("getIndexes") is True factories["music.Artist"].create_batch(size=3, playable=True) expected = { "indexes": serializers.GetArtistsSerializer( - music_models.Artist.objects.all() + music_models.Artist.objects.all().exclude(exclude_query) ).data } playable_by = mocker.spy(music_models.ArtistQuerySet, "playable_by") @@ -516,7 +572,10 @@ def test_get_indexes( assert response.status_code == 200 assert response.data == expected - playable_by.assert_called_once_with(music_models.Artist.objects.all(), None) + playable_by.assert_called_once_with( + music_models.Artist.objects.all().exclude(exclude_query), + logged_in_api_client.user.actor, + ) def test_get_cover_art_album(factories, logged_in_api_client): diff --git a/api/tests/test_auth.py b/api/tests/test_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..653110f0944e0b08e13bf40f97460de4d47a83da --- /dev/null +++ b/api/tests/test_auth.py @@ -0,0 +1,36 @@ +from django.urls import reverse +from rest_framework_jwt.settings import api_settings + +jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER +jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER + + +def test_can_authenticate_using_jwt_token_param_in_url(factories, preferences, client): + user = factories["users.User"]() + preferences["common__api_authentication_required"] = True + url = reverse("api:v1:tracks-list") + response = client.get(url) + + assert response.status_code == 401 + + payload = jwt_payload_handler(user) + token = jwt_encode_handler(payload) + response = client.get(url, data={"jwt": token}) + assert response.status_code == 200 + + +def test_can_authenticate_using_oauth_token_param_in_url( + factories, preferences, client, mocker +): + mocker.patch( + "funkwhale_api.users.oauth.permissions.should_allow", return_value=True + ) + token = factories["users.AccessToken"]() + preferences["common__api_authentication_required"] = True + url = reverse("api:v1:tracks-list") + response = client.get(url) + + assert response.status_code == 401 + + response = client.get(url, data={"token": token.token}) + assert response.status_code == 200 diff --git a/api/tests/test_jwt_querystring.py b/api/tests/test_jwt_querystring.py deleted file mode 100644 index 18a673fb480d71fdea41dc60557dfd56b6c34aaa..0000000000000000000000000000000000000000 --- a/api/tests/test_jwt_querystring.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.urls import reverse -from rest_framework_jwt.settings import api_settings - -jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER -jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER - - -def test_can_authenticate_using_token_param_in_url(factories, preferences, client): - user = factories["users.User"]() - preferences["common__api_authentication_required"] = True - url = reverse("api:v1:tracks-list") - response = client.get(url) - - assert response.status_code == 401 - - payload = jwt_payload_handler(user) - token = jwt_encode_handler(payload) - response = client.get(url, data={"jwt": token}) - assert response.status_code == 200 diff --git a/api/tests/users/oauth/__init__.py b/api/tests/users/oauth/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/api/tests/users/oauth/test_api_permissions.py b/api/tests/users/oauth/test_api_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..e73d3a3f91c71b30c2444719ed035f5049d3d4a2 --- /dev/null +++ b/api/tests/users/oauth/test_api_permissions.py @@ -0,0 +1,80 @@ +import pytest +import uuid + +from django.urls import reverse + +from funkwhale_api.users.oauth import scopes + +# mutations + + +@pytest.mark.parametrize( + "name, url_kwargs, scope, method", + [ + ("api:v1:search", {}, "read:libraries", "get"), + ("api:v1:artists-list", {}, "read:libraries", "get"), + ("api:v1:albums-list", {}, "read:libraries", "get"), + ("api:v1:tracks-list", {}, "read:libraries", "get"), + ("api:v1:tracks-mutations", {"pk": 42}, "read:edits", "get"), + ("api:v1:tags-list", {}, "read:libraries", "get"), + ("api:v1:licenses-list", {}, "read:libraries", "get"), + ("api:v1:moderation:content-filters-list", {}, "read:filters", "get"), + ("api:v1:listen-detail", {"uuid": uuid.uuid4()}, "read:libraries", "get"), + ("api:v1:uploads-list", {}, "read:libraries", "get"), + ("api:v1:playlists-list", {}, "read:playlists", "get"), + ("api:v1:playlist-tracks-list", {}, "read:playlists", "get"), + ("api:v1:favorites:tracks-list", {}, "read:favorites", "get"), + ("api:v1:history:listenings-list", {}, "read:listenings", "get"), + ("api:v1:radios:radios-list", {}, "read:radios", "get"), + ("api:v1:oauth:grants-list", {}, "read:security", "get"), + ("api:v1:federation:inbox-list", {}, "read:notifications", "get"), + ( + "api:v1:federation:libraries-detail", + {"uuid": uuid.uuid4()}, + "read:libraries", + "get", + ), + ("api:v1:federation:library-follows-list", {}, "read:follows", "get"), + # admin / privileged stuff + ("api:v1:instance:admin-settings-list", {}, "read:instance:settings", "get"), + ( + "api:v1:manage:users:invitations-list", + {}, + "read:instance:invitations", + "get", + ), + ("api:v1:manage:users:users-list", {}, "read:instance:users", "get"), + ("api:v1:manage:library:uploads-list", {}, "read:instance:libraries", "get"), + ("api:v1:manage:accounts-list", {}, "read:instance:accounts", "get"), + ("api:v1:manage:federation:domains-list", {}, "read:instance:domains", "get"), + ( + "api:v1:manage:moderation:instance-policies-list", + {}, + "read:instance:policies", + "get", + ), + ("api:v1:manage:library:artists-list", {}, "read:instance:libraries", "get"), + ], +) +def test_views_permissions( + name, url_kwargs, scope, method, mocker, logged_in_api_client +): + """ + Smoke tests to ensure viewsets are correctly protected + """ + url = reverse(name, kwargs=url_kwargs) + user_scopes = scopes.get_from_permissions( + **logged_in_api_client.user.get_permissions() + ) + + should_allow = mocker.patch( + "funkwhale_api.users.oauth.permissions.should_allow", return_value=False + ) + handler = getattr(logged_in_api_client, method) + response = handler(url) + should_allow.assert_called_once_with( + required_scope=scope, request_scopes=user_scopes + ) + assert response.status_code == 403, "{} on {} is not protected correctly!".format( + method, url + ) diff --git a/api/tests/users/oauth/test_models.py b/api/tests/users/oauth/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..1b27900ee825568961f4d139128a09ce14d558a2 --- /dev/null +++ b/api/tests/users/oauth/test_models.py @@ -0,0 +1,21 @@ +import pytest + +from django import forms + +from funkwhale_api.users import models + + +@pytest.mark.parametrize( + "uri", + ["urn:ietf:wg:oauth:2.0:oob", "urn:ietf:wg:oauth:2.0:oob:auto", "http://test.com"], +) +def test_redirect_uris_oob(uri, db): + app = models.Application(redirect_uris=uri) + assert app.clean() is None + + +@pytest.mark.parametrize("uri", ["urn:ietf:wg:oauth:2.0:invalid", "noop"]) +def test_redirect_uris_invalid(uri, db): + app = models.Application(redirect_uris=uri) + with pytest.raises(forms.ValidationError): + app.clean() diff --git a/api/tests/users/oauth/test_permissions.py b/api/tests/users/oauth/test_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..65974fbf645f92af478457fadd7138f7067a3a01 --- /dev/null +++ b/api/tests/users/oauth/test_permissions.py @@ -0,0 +1,241 @@ +import pytest + +from funkwhale_api.users.oauth import scopes +from funkwhale_api.users.oauth import permissions + + +@pytest.mark.parametrize( + "required_scope, request_scopes, expected", + [ + (None, {}, True), + ("write:profile", {"write"}, True), + ("write:profile", {"read"}, False), + ("write:profile", {"read:profile"}, False), + ("write:profile", {"write:profile"}, True), + ("read:profile", {"read"}, True), + ("read:profile", {"write"}, False), + ("read:profile", {"read:profile"}, True), + ("read:profile", {"write:profile"}, False), + ("write:profile", {"write"}, True), + ("write:profile", {"read:profile"}, False), + ("write:profile", {"write:profile"}, True), + ("write:profile", {"write"}, True), + ("write:profile", {"read:profile"}, False), + ("write:profile", {"write:profile"}, True), + ("write:profile", {"write"}, True), + ("write:profile", {"read:profile"}, False), + ("write:profile", {"write:profile"}, True), + ], +) +def test_should_allow(required_scope, request_scopes, expected): + assert ( + permissions.should_allow( + required_scope=required_scope, request_scopes=request_scopes + ) + is expected + ) + + +@pytest.mark.parametrize("method", ["OPTIONS", "HEAD"]) +def test_scope_permission_safe_methods(method, mocker, factories): + view = mocker.Mock(required_scope="write:profile", anonymous_policy=False) + request = mocker.Mock(method=method) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is True + + +@pytest.mark.parametrize( + "policy, preference, expected", + [ + (True, False, True), + (False, False, False), + ("setting", True, False), + ("setting", False, True), + ], +) +def test_scope_permission_anonymous_policy( + policy, preference, expected, preferences, mocker, anonymous_user +): + preferences["common__api_authentication_required"] = preference + view = mocker.Mock(required_scope="libraries", anonymous_policy=policy) + request = mocker.Mock(method="GET", user=anonymous_user, actor=None) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is expected + + +def test_scope_permission_dict_no_required(mocker, anonymous_user): + view = mocker.Mock( + required_scope={"read": None, "write": "write:profile"}, + anonymous_policy=True, + action="read", + ) + request = mocker.Mock(method="GET", user=anonymous_user, actor=None) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is True + + +@pytest.mark.parametrize( + "required_scope, method, action, expected_scope", + [ + ("profile", "GET", "read", "read:profile"), + ("profile", "POST", "write", "write:profile"), + ({"read": "read:profile"}, "GET", "read", "read:profile"), + ({"write": "write:profile"}, "POST", "write", "write:profile"), + ], +) +def test_scope_permission_user( + required_scope, method, action, expected_scope, mocker, factories +): + user = factories["users.User"]() + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method=method, user=user, actor=None) + view = mocker.Mock( + required_scope=required_scope, anonymous_policy=False, action=action + ) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope=expected_scope, + request_scopes=scopes.get_from_permissions(**user.get_permissions()), + ) + + +def test_scope_permission_token(mocker, factories): + token = factories["users.AccessToken"]( + scope="write:profile read:playlists", + application__scope="write:profile read:playlists", + ) + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", auth=token) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope="write:profile", + request_scopes={"write:profile", "read:playlists"}, + ) + + +def test_scope_permission_actor(mocker, factories, anonymous_user): + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock( + method="POST", actor=factories["federation.Actor"](), user=anonymous_user + ) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope="write:profile", request_scopes=scopes.FEDERATION_REQUEST_SCOPES + ) + + +def test_scope_permission_token_anonymous_user_auth_required( + mocker, factories, anonymous_user, preferences +): + preferences["common__api_authentication_required"] = True + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", user=anonymous_user, actor=None) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is False + + should_allow.assert_not_called() + + +def test_scope_permission_token_anonymous_user_auth_not_required( + mocker, factories, anonymous_user, preferences +): + preferences["common__api_authentication_required"] = False + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", user=anonymous_user, actor=None) + view = mocker.Mock(required_scope="profile", anonymous_policy="setting") + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope="write:profile", request_scopes=scopes.ANONYMOUS_SCOPES + ) + + +def test_scope_permission_token_expired(mocker, factories, now): + token = factories["users.AccessToken"]( + scope="profile:write playlists:read", expires=now + ) + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", auth=token) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is False + + should_allow.assert_not_called() + + +def test_scope_permission_token_no_user(mocker, factories, now): + token = factories["users.AccessToken"]( + scope="profile:write playlists:read", user=None + ) + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", auth=token) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) is False + + should_allow.assert_not_called() + + +def test_scope_permission_token_honor_app_scopes(mocker, factories, now): + # token contains read access, but app scope only allows profile:write + token = factories["users.AccessToken"]( + scope="write:profile read", application__scope="write:profile" + ) + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", auth=token) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope="write:profile", request_scopes={"write:profile"} + ) + + +def test_scope_permission_token_honor_allowed_app_scopes(mocker, factories, now): + mocker.patch.object(scopes, "OAUTH_APP_SCOPES", {"read:profile"}) + token = factories["users.AccessToken"]( + scope="write:profile read:profile read", + application__scope="write:profile read:profile read", + ) + should_allow = mocker.patch.object(permissions, "should_allow") + request = mocker.Mock(method="POST", auth=token) + view = mocker.Mock(required_scope="profile", anonymous_policy=False) + p = permissions.ScopePermission() + + assert p.has_permission(request, view) == should_allow.return_value + + should_allow.assert_called_once_with( + required_scope="write:profile", request_scopes={"read:profile"} + ) diff --git a/api/tests/users/oauth/test_scopes.py b/api/tests/users/oauth/test_scopes.py new file mode 100644 index 0000000000000000000000000000000000000000..3d12cb664e7dadcdc078706b0e79fae1764fe221 --- /dev/null +++ b/api/tests/users/oauth/test_scopes.py @@ -0,0 +1,156 @@ +import pytest + +from funkwhale_api.users.oauth import scopes + + +@pytest.mark.parametrize( + "user_perms, expected", + [ + ( + # All permissions, so all scopes + {"moderation": True, "library": True, "settings": True}, + { + "read:profile", + "write:profile", + "read:libraries", + "write:libraries", + "read:playlists", + "write:playlists", + "read:favorites", + "write:favorites", + "read:notifications", + "write:notifications", + "read:radios", + "write:radios", + "read:follows", + "write:follows", + "read:edits", + "write:edits", + "read:filters", + "write:filters", + "read:listenings", + "write:listenings", + "read:security", + "write:security", + "read:instance:policies", + "write:instance:policies", + "read:instance:accounts", + "write:instance:accounts", + "read:instance:domains", + "write:instance:domains", + "read:instance:settings", + "write:instance:settings", + "read:instance:users", + "write:instance:users", + "read:instance:invitations", + "write:instance:invitations", + "read:instance:edits", + "write:instance:edits", + "read:instance:libraries", + "write:instance:libraries", + }, + ), + ( + {"moderation": True, "library": False, "settings": True}, + { + "read:profile", + "write:profile", + "read:libraries", + "write:libraries", + "read:playlists", + "write:playlists", + "read:favorites", + "write:favorites", + "read:notifications", + "write:notifications", + "read:radios", + "write:radios", + "read:follows", + "write:follows", + "read:edits", + "write:edits", + "read:filters", + "write:filters", + "read:listenings", + "write:listenings", + "read:security", + "write:security", + "read:instance:policies", + "write:instance:policies", + "read:instance:accounts", + "write:instance:accounts", + "read:instance:domains", + "write:instance:domains", + "read:instance:settings", + "write:instance:settings", + "read:instance:users", + "write:instance:users", + "read:instance:invitations", + "write:instance:invitations", + }, + ), + ( + {"moderation": True, "library": False, "settings": False}, + { + "read:profile", + "write:profile", + "read:libraries", + "write:libraries", + "read:playlists", + "write:playlists", + "read:favorites", + "write:favorites", + "read:notifications", + "write:notifications", + "read:radios", + "write:radios", + "read:follows", + "write:follows", + "read:edits", + "write:edits", + "read:filters", + "write:filters", + "read:listenings", + "write:listenings", + "read:security", + "write:security", + "read:instance:policies", + "write:instance:policies", + "read:instance:accounts", + "write:instance:accounts", + "read:instance:domains", + "write:instance:domains", + }, + ), + ( + {"moderation": False, "library": False, "settings": False}, + { + "read:profile", + "write:profile", + "read:libraries", + "write:libraries", + "read:playlists", + "write:playlists", + "read:favorites", + "write:favorites", + "read:notifications", + "write:notifications", + "read:radios", + "write:radios", + "read:follows", + "write:follows", + "read:edits", + "write:edits", + "read:filters", + "write:filters", + "read:listenings", + "write:listenings", + "read:security", + "write:security", + }, + ), + ], +) +def test_get_scopes_from_user_permissions(user_perms, expected): + + assert scopes.get_from_permissions(**user_perms) == expected diff --git a/api/tests/users/oauth/test_tasks.py b/api/tests/users/oauth/test_tasks.py new file mode 100644 index 0000000000000000000000000000000000000000..c30f2488371f1c38d8525ddee47b8c4dc417185a --- /dev/null +++ b/api/tests/users/oauth/test_tasks.py @@ -0,0 +1,10 @@ +from oauth2_provider import models +from funkwhale_api.users.oauth import tasks + + +def test_clear_expired_tokens(mocker, db): + clear_expired = mocker.spy(models, "clear_expired") + + tasks.clear_expired_tokens() + + clear_expired.assert_called_once() diff --git a/api/tests/users/oauth/test_views.py b/api/tests/users/oauth/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..19d25870974fd0ee756fab765a20db06fd459186 --- /dev/null +++ b/api/tests/users/oauth/test_views.py @@ -0,0 +1,363 @@ +import json +import pytest + +from django.urls import reverse + +from funkwhale_api.users import models +from funkwhale_api.users.oauth import serializers + + +def test_apps_post(api_client, db): + url = reverse("api:v1:oauth:apps-list") + data = { + "name": "Test app", + "redirect_uris": "http://test.app", + "scopes": "read write:profile", + } + response = api_client.post(url, data) + + assert response.status_code == 201 + + app = models.Application.objects.get(name=data["name"]) + + assert app.client_type == models.Application.CLIENT_CONFIDENTIAL + assert app.authorization_grant_type == models.Application.GRANT_AUTHORIZATION_CODE + assert app.redirect_uris == data["redirect_uris"] + assert response.data == serializers.CreateApplicationSerializer(app).data + assert app.scope == "read write:profile" + assert app.user is None + + +def test_apps_post_logged_in_user(logged_in_api_client, db): + url = reverse("api:v1:oauth:apps-list") + data = { + "name": "Test app", + "redirect_uris": "http://test.app", + "scopes": "read write:profile", + } + response = logged_in_api_client.post(url, data) + + assert response.status_code == 201 + + app = models.Application.objects.get(name=data["name"]) + + assert app.client_type == models.Application.CLIENT_CONFIDENTIAL + assert app.authorization_grant_type == models.Application.GRANT_AUTHORIZATION_CODE + assert app.redirect_uris == data["redirect_uris"] + assert response.data == serializers.CreateApplicationSerializer(app).data + assert app.scope == "read write:profile" + assert app.user == logged_in_api_client.user + + +def test_apps_list_anonymous(api_client, db): + url = reverse("api:v1:oauth:apps-list") + response = api_client.get(url) + + assert response.status_code == 401 + + +def test_apps_list_logged_in(factories, logged_in_api_client, db): + app = factories["users.Application"](user=logged_in_api_client.user) + factories["users.Application"]() + url = reverse("api:v1:oauth:apps-list") + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data["results"] == [serializers.ApplicationSerializer(app).data] + + +def test_apps_delete_not_owner(factories, logged_in_api_client, db): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.delete(url) + + assert response.status_code == 404 + + +def test_apps_delete_owner(factories, logged_in_api_client, db): + app = factories["users.Application"](user=logged_in_api_client.user) + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.delete(url) + + assert response.status_code == 204 + + with pytest.raises(app.DoesNotExist): + app.refresh_from_db() + + +def test_apps_update_not_owner(factories, logged_in_api_client, db): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.patch(url, {"name": "Hello"}) + + assert response.status_code == 404 + + +def test_apps_update_owner(factories, logged_in_api_client, db): + app = factories["users.Application"](user=logged_in_api_client.user) + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.patch(url, {"name": "Hello"}) + + assert response.status_code == 200 + app.refresh_from_db() + + assert app.name == "Hello" + + +def test_apps_get(preferences, logged_in_api_client, factories): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data == serializers.ApplicationSerializer(app).data + + +def test_apps_get_owner(preferences, logged_in_api_client, factories): + app = factories["users.Application"](user=logged_in_api_client.user) + url = reverse("api:v1:oauth:apps-detail", kwargs={"client_id": app.client_id}) + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data == serializers.CreateApplicationSerializer(app).data + + +def test_authorize_view_post(logged_in_client, factories): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": app.redirect_uris, + "client_id": app.client_id, + "state": "hello", + "response_type": "code", + "scope": "read", + }, + ) + grant = models.Grant.objects.get(application=app) + assert response.status_code == 302 + assert response["Location"] == "{}?code={}&state={}".format( + app.redirect_uris, grant.code, "hello" + ) + + +def test_authorize_view_post_ajax_no_redirect(logged_in_client, factories): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": app.redirect_uris, + "client_id": app.client_id, + "state": "hello", + "response_type": "code", + "scope": "read", + }, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + assert response.status_code == 200 + grant = models.Grant.objects.get(application=app) + assert json.loads(response.content.decode()) == { + "redirect_uri": "{}?code={}&state={}".format( + app.redirect_uris, grant.code, "hello" + ), + "code": grant.code, + } + + +def test_authorize_view_post_ajax_oob(logged_in_client, factories): + app = factories["users.Application"](redirect_uris="urn:ietf:wg:oauth:2.0:oob") + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": app.redirect_uris, + "client_id": app.client_id, + "state": "hello", + "response_type": "code", + "scope": "read", + }, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + assert response.status_code == 200 + grant = models.Grant.objects.get(application=app) + assert json.loads(response.content.decode()) == { + "redirect_uri": "{}?code={}&state={}".format( + app.redirect_uris, grant.code, "hello" + ), + "code": grant.code, + } + + +def test_authorize_view_invalid_form(logged_in_client, factories): + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": "", + "client_id": "Noop", + "state": "hello", + "response_type": "code", + "scope": "read", + }, + ) + + assert response.status_code == 400 + assert json.loads(response.content.decode()) == { + "redirect_uri": ["This field is required."] + } + + +def test_authorize_view_invalid_redirect_url(logged_in_client, factories): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": "http://wrong.url", + "client_id": app.client_id, + "state": "hello", + "response_type": "code", + "scope": "read", + }, + ) + + assert response.status_code == 400 + assert json.loads(response.content.decode()) == { + "detail": "Mismatching redirect URI." + } + + +def test_authorize_view_invalid_oauth(logged_in_client, factories): + app = factories["users.Application"]() + url = reverse("api:v1:oauth:authorize") + response = logged_in_client.post( + url, + { + "allow": True, + "redirect_uri": app.redirect_uris, + "client_id": "wrong_id", + "state": "hello", + "response_type": "code", + "scope": "read", + }, + ) + + assert response.status_code == 400 + assert json.loads(response.content.decode()) == { + "non_field_errors": ["Invalid application"] + } + + +def test_authorize_view_anonymous(client, factories): + url = reverse("api:v1:oauth:authorize") + response = client.post(url, {}) + + assert response.status_code == 401 + + +def test_token_view_post(api_client, factories): + grant = factories["users.Grant"]() + app = grant.application + url = reverse("api:v1:oauth:token") + + response = api_client.post( + url, + { + "redirect_uri": app.redirect_uris, + "client_id": app.client_id, + "client_secret": app.client_secret, + "grant_type": "authorization_code", + "code": grant.code, + }, + ) + payload = json.loads(response.content.decode()) + + assert "access_token" in payload + assert "refresh_token" in payload + assert payload["expires_in"] == 36000 + assert payload["scope"] == grant.scope + assert payload["token_type"] == "Bearer" + assert response.status_code == 200 + + with pytest.raises(grant.DoesNotExist): + grant.refresh_from_db() + + +def test_revoke_view_post(logged_in_client, factories): + token = factories["users.AccessToken"]() + url = reverse("api:v1:oauth:revoke") + + response = logged_in_client.post( + url, + { + "token": token.token, + "client_id": token.application.client_id, + "client_secret": token.application.client_secret, + }, + ) + assert response.status_code == 200 + + with pytest.raises(token.DoesNotExist): + token.refresh_from_db() + + +def test_grants_list(factories, logged_in_api_client): + token = factories["users.AccessToken"](user=logged_in_api_client.user) + refresh_token = factories["users.RefreshToken"](user=logged_in_api_client.user) + factories["users.AccessToken"]() + url = reverse("api:v1:oauth:grants-list") + expected = [ + serializers.ApplicationSerializer(refresh_token.application).data, + serializers.ApplicationSerializer(token.application).data, + ] + + response = logged_in_api_client.get(url) + + assert response.status_code == 200 + assert response.data == expected + + +def test_grant_delete(factories, logged_in_api_client, mocker, now): + token = factories["users.AccessToken"](user=logged_in_api_client.user) + refresh_token = factories["users.RefreshToken"]( + user=logged_in_api_client.user, application=token.application + ) + grant = factories["users.Grant"]( + user=logged_in_api_client.user, application=token.application + ) + revoke_token = mocker.spy(token.__class__, "revoke") + revoke_refresh = mocker.spy(refresh_token.__class__, "revoke") + to_keep = [ + factories["users.AccessToken"](application=token.application), + factories["users.RefreshToken"](application=token.application), + factories["users.Grant"](application=token.application), + ] + url = reverse( + "api:v1:oauth:grants-detail", kwargs={"client_id": token.application.client_id} + ) + + response = logged_in_api_client.delete(url) + + assert response.status_code == 204 + + revoke_token.assert_called_once() + revoke_refresh.assert_called_once() + + with pytest.raises(token.DoesNotExist): + token.refresh_from_db() + + with pytest.raises(grant.DoesNotExist): + grant.refresh_from_db() + + refresh_token.refresh_from_db() + assert refresh_token.revoked == now + + for t in to_keep: + t.refresh_from_db() diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py index 4b2f71bca02fa754bdb60615fd08d3e496d58013..1b185e55f88844baa9400a82c49dff3d706a24f7 100644 --- a/api/tests/users/test_models.py +++ b/api/tests/users/test_models.py @@ -219,3 +219,13 @@ def test_user_get_quota_status(factories, preferences, mocker): "errored": 3, "finished": 4, } + + +def test_deleting_users_deletes_associated_actor(factories): + actor = factories["federation.Actor"]() + user = factories["users.User"](actor=actor) + + user.delete() + + with pytest.raises(actor.DoesNotExist): + actor.refresh_from_db() diff --git a/api/tests/users/test_permissions.py b/api/tests/users/test_permissions.py deleted file mode 100644 index 0b92f74a58ab7d914a0b15ceeb48b17e5abf95ae..0000000000000000000000000000000000000000 --- a/api/tests/users/test_permissions.py +++ /dev/null @@ -1,92 +0,0 @@ -import pytest -from rest_framework.views import APIView - -from funkwhale_api.users import permissions - - -def test_has_user_permission_no_user(api_request): - view = APIView.as_view() - permission = permissions.HasUserPermission() - request = api_request.get("/") - assert permission.has_permission(request, view) is False - - -def test_has_user_permission_anonymous(anonymous_user, api_request): - view = APIView.as_view() - permission = permissions.HasUserPermission() - request = api_request.get("/") - setattr(request, "user", anonymous_user) - assert permission.has_permission(request, view) is False - - -@pytest.mark.parametrize("value", [True, False]) -def test_has_user_permission_logged_in_single(value, factories, api_request): - user = factories["users.User"](permission_moderation=value) - - class View(APIView): - required_permissions = ["moderation"] - - view = View() - permission = permissions.HasUserPermission() - request = api_request.get("/") - setattr(request, "user", user) - result = permission.has_permission(request, view) - assert result == user.has_permissions("moderation") == value - - -@pytest.mark.parametrize( - "moderation,library,expected", - [ - (True, False, False), - (False, True, False), - (False, False, False), - (True, True, True), - ], -) -def test_has_user_permission_logged_in_multiple_and( - moderation, library, expected, factories, api_request -): - user = factories["users.User"]( - permission_moderation=moderation, permission_library=library - ) - - class View(APIView): - required_permissions = ["moderation", "library"] - permission_operator = "and" - - view = View() - permission = permissions.HasUserPermission() - request = api_request.get("/") - setattr(request, "user", user) - result = permission.has_permission(request, view) - assert result == user.has_permissions("moderation", "library") == expected - - -@pytest.mark.parametrize( - "moderation,library,expected", - [ - (True, False, True), - (False, True, True), - (False, False, False), - (True, True, True), - ], -) -def test_has_user_permission_logged_in_multiple_or( - moderation, library, expected, factories, api_request -): - user = factories["users.User"]( - permission_moderation=moderation, permission_library=library - ) - - class View(APIView): - required_permissions = ["moderation", "library"] - permission_operator = "or" - - view = View() - permission = permissions.HasUserPermission() - request = api_request.get("/") - setattr(request, "user", user) - result = permission.has_permission(request, view) - has_permission_result = user.has_permissions("moderation", "library", operator="or") - - assert result == has_permission_result == expected diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py index 92e9922bf0a6679641e10835a1b22c6eb2160ddb..956a7178cf98641e295cc6369d198cf954fd47e6 100644 --- a/api/tests/users/test_views.py +++ b/api/tests/users/test_views.py @@ -168,15 +168,20 @@ def test_changing_password_updates_secret_key(logged_in_api_client): assert user.password != password -def test_can_request_password_reset(factories, api_client, mailoutbox): +def test_can_request_password_reset( + factories, preferences, settings, api_client, mailoutbox +): user = factories["users.User"]() payload = {"email": user.email} - emails = len(mailoutbox) url = reverse("rest_password_reset") + preferences["instance__name"] = "Hello world" response = api_client.post(url, payload) assert response.status_code == 200 - assert len(mailoutbox) > emails + + confirmation_message = mailoutbox[-1] + assert "Hello world" in confirmation_message.body + assert settings.FUNKWHALE_HOSTNAME in confirmation_message.body def test_user_can_patch_his_own_settings(logged_in_api_client): @@ -225,6 +230,21 @@ def test_user_can_get_subsonic_token(logged_in_api_client): assert response.data == {"subsonic_api_token": "test"} +def test_user_can_request_new_subsonic_token_uncommon_username(logged_in_api_client): + user = logged_in_api_client.user + user.username = "firstname.lastname" + user.subsonic_api_token = "test" + user.save() + + url = reverse( + "api:v1:users:users-subsonic-token", kwargs={"username": user.username} + ) + + response = logged_in_api_client.post(url) + + assert response.status_code == 200 + + def test_user_can_delete_subsonic_token(logged_in_api_client): user = logged_in_api_client.user user.subsonic_api_token = "test" @@ -287,3 +307,24 @@ def test_creating_user_creates_actor_as_well( user = User.objects.get(username="test1") assert user.actor == actor + + +def test_creating_user_sends_confirmation_email( + api_client, db, settings, preferences, mailoutbox +): + url = reverse("rest_register") + data = { + "username": "test1", + "email": "test1@test.com", + "password1": "testtest", + "password2": "testtest", + } + preferences["users__registration_enabled"] = True + preferences["instance__name"] = "Hello world" + response = api_client.post(url, data) + + assert response.status_code == 201 + + confirmation_message = mailoutbox[-1] + assert "Hello world" in confirmation_message.body + assert settings.FUNKWHALE_HOSTNAME in confirmation_message.body diff --git a/changes/changelog.d/252.feature b/changes/changelog.d/252.feature new file mode 100644 index 0000000000000000000000000000000000000000..4bbb2da1e527d95e0546366c92bea1d819337d29 --- /dev/null +++ b/changes/changelog.d/252.feature @@ -0,0 +1 @@ +Improved error handling and display during import (#252, #718, #583, #501, #544) diff --git a/changes/changelog.d/356.bugfix b/changes/changelog.d/356.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..c99d3687076fedd9d25ca8d452756644e638bd18 --- /dev/null +++ b/changes/changelog.d/356.bugfix @@ -0,0 +1 @@ +Fixed issue with querying the albums api endpoint (#356) \ No newline at end of file diff --git a/changes/changelog.d/356.feature b/changes/changelog.d/356.feature new file mode 100644 index 0000000000000000000000000000000000000000..cf0744c18cd5d875d055a6ef9ade8fcf683cb3a4 --- /dev/null +++ b/changes/changelog.d/356.feature @@ -0,0 +1 @@ +Added albums view. Similar to artists view, it's viewable by clicking on the "Albums" link on the top bar. (#356) \ No newline at end of file diff --git a/changes/changelog.d/359.feature b/changes/changelog.d/359.feature new file mode 100644 index 0000000000000000000000000000000000000000..657788c917af76f5698d12dd639f997f33286a46 --- /dev/null +++ b/changes/changelog.d/359.feature @@ -0,0 +1 @@ +Change the document title to display current track information. (#359) diff --git a/changes/changelog.d/385.enhancement b/changes/changelog.d/385.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..aa6e0a676cd62d81f6e7fd589fc745a3350a1189 --- /dev/null +++ b/changes/changelog.d/385.enhancement @@ -0,0 +1 @@ +Improved readability of logo (#385) diff --git a/changes/changelog.d/550.enhancement b/changes/changelog.d/550.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..1ec0243dee3ac42fb3108290d455579ca65357d3 --- /dev/null +++ b/changes/changelog.d/550.enhancement @@ -0,0 +1 @@ +Show remaining storage space during import and prevent file upload if not enough space is remaining (#550) diff --git a/changes/changelog.d/563.bugfix b/changes/changelog.d/563.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..14f49b75e7a714b70554ba7a16b1e570b78fc9cc --- /dev/null +++ b/changes/changelog.d/563.bugfix @@ -0,0 +1 @@ +Fixed unplayable radios for anonymous users (#563) diff --git a/changes/changelog.d/565.feature b/changes/changelog.d/565.feature new file mode 100644 index 0000000000000000000000000000000000000000..9f2a92a719bdd62b24b7f1292a7638bbc2947ac8 --- /dev/null +++ b/changes/changelog.d/565.feature @@ -0,0 +1 @@ +Support S3-compatible storages for media files (#565) diff --git a/changes/changelog.d/572.enhancement b/changes/changelog.d/572.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..cb75911db6bbe484c220c45548dfe73ab8f97231 --- /dev/null +++ b/changes/changelog.d/572.enhancement @@ -0,0 +1 @@ +Preload next track in queue (#572) diff --git a/changes/changelog.d/578.enhancement b/changes/changelog.d/578.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..073de471fd355f06832888baa9a5955d3d813808 --- /dev/null +++ b/changes/changelog.d/578.enhancement @@ -0,0 +1,2 @@ +Added twitter:* meta tags to detect tracks and albums players automatically on more sites (#578) +Improved responsiveness of embedded player \ No newline at end of file diff --git a/changes/changelog.d/619.enhancement b/changes/changelog.d/619.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..16a95a641955503ad7c8325c5a526a3e5f70738c --- /dev/null +++ b/changes/changelog.d/619.enhancement @@ -0,0 +1 @@ +Use attributedTo instead of actor in library ActivityPub payload (#619) diff --git a/changes/changelog.d/662.enhancement b/changes/changelog.d/662.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..cda19e4ed3449e1e8a547e7d17b861d7e8fc6ea4 --- /dev/null +++ b/changes/changelog.d/662.enhancement @@ -0,0 +1 @@ +Added standardized translation context for all strings in the frontend to give accurate hints to translators. diff --git a/changes/changelog.d/689.feature b/changes/changelog.d/689.feature new file mode 100644 index 0000000000000000000000000000000000000000..0b53c4a09032aebdaeab41020807097c4fa472a3 --- /dev/null +++ b/changes/changelog.d/689.feature @@ -0,0 +1 @@ +Support metadata update on tracks, albums and artists and broadcast those on the federation (#689) diff --git a/changes/changelog.d/701.feature b/changes/changelog.d/701.feature new file mode 100644 index 0000000000000000000000000000000000000000..d2a9500d6b1fd40f6d4ec33dd9f034bc307cf37c --- /dev/null +++ b/changes/changelog.d/701.feature @@ -0,0 +1 @@ +Allow artists hiding (#701) diff --git a/changes/changelog.d/702.bugfix b/changes/changelog.d/702.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..3c8090fc43a93ce3c7e7f0000a6c3f12de0e6385 --- /dev/null +++ b/changes/changelog.d/702.bugfix @@ -0,0 +1 @@ +Fixed alignement/size issue with some buttons (#702) \ No newline at end of file diff --git a/changes/changelog.d/715.enhancement b/changes/changelog.d/715.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..faba8a62c1fdcc4aa4b79fe078233a49c24a429e --- /dev/null +++ b/changes/changelog.d/715.enhancement @@ -0,0 +1,3 @@ +Better workflow for connecting to another instance (#715) + +Changing the instance used is now better integrated in the App, and it is checked that the chosen instance and the suggested instances are valid and running Funkwhale servers. diff --git a/changes/changelog.d/718.bugfix b/changes/changelog.d/718.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..4dd1e13fef40808c95d8aeda080ef0e6e6a22cfb --- /dev/null +++ b/changes/changelog.d/718.bugfix @@ -0,0 +1 @@ +Fixed crashing upload processing on invalid date format (#718) diff --git a/changes/changelog.d/719.enhancement b/changes/changelog.d/719.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..e4c5b35bebdbe2d0d98793649ed71639a7ddd071 --- /dev/null +++ b/changes/changelog.d/719.enhancement @@ -0,0 +1 @@ +Added a "load more" button on artist pages to load more tracks/albums (#719) diff --git a/changes/changelog.d/722.bugfix b/changes/changelog.d/722.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..9f230ae615e445ce1f38bc532745b69b1962e4d4 --- /dev/null +++ b/changes/changelog.d/722.bugfix @@ -0,0 +1 @@ +Fixed non-transparent background for volume range on Firefox (#722) \ No newline at end of file diff --git a/changes/changelog.d/725.enhancement b/changes/changelog.d/725.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..4c30ba4274f8212be0954614d7de5021cbee43e6 --- /dev/null +++ b/changes/changelog.d/725.enhancement @@ -0,0 +1 @@ +Merged artist/album buttons with title text on artist and album pages (#725) diff --git a/changes/changelog.d/740.bugfix b/changes/changelog.d/740.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..5cf04c826906b92af522b2ca8850d7747e7f5da8 --- /dev/null +++ b/changes/changelog.d/740.bugfix @@ -0,0 +1 @@ +Do not consider tracks as duplicates during import if they have different positions (#740) diff --git a/changes/changelog.d/747.feature b/changes/changelog.d/747.feature new file mode 100644 index 0000000000000000000000000000000000000000..a278f0a1094c4675453af32fd329d37ad4f583dc --- /dev/null +++ b/changes/changelog.d/747.feature @@ -0,0 +1 @@ +Support embedding full artist discographies (#747) diff --git a/changes/changelog.d/748.enhancement b/changes/changelog.d/748.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..1515b08a27e9d8bb126f24000fd06599cefe92de --- /dev/null +++ b/changes/changelog.d/748.enhancement @@ -0,0 +1 @@ +Labels for privacy levels are now consistently grabbed from a common source instead of being hardcoded everytime they are needed. diff --git a/changes/changelog.d/752.feature b/changes/changelog.d/752.feature new file mode 100644 index 0000000000000000000000000000000000000000..6d33f6faa629eacdf695111e2fa26c5a4929596f --- /dev/null +++ b/changes/changelog.d/752.feature @@ -0,0 +1 @@ +Support OAuth2 authorization for better integration with third-party apps (#752) diff --git a/changes/changelog.d/754.bugfix b/changes/changelog.d/754.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..dd7bc48c6dcc42cee6b59e8f079ae301fe424936 --- /dev/null +++ b/changes/changelog.d/754.bugfix @@ -0,0 +1 @@ +Add missing command from contributing file (#754) diff --git a/changes/changelog.d/758.bugfix b/changes/changelog.d/758.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..3275ddbe1a32255b69fa4bc3ba2c6c7a44559d01 --- /dev/null +++ b/changes/changelog.d/758.bugfix @@ -0,0 +1 @@ +Ensure all our ActivityPub fetches are authenticated (#758) diff --git a/changes/changelog.d/768.enhancement b/changes/changelog.d/768.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..4d0ab83e51af129f139b13e25d0594ce29fa6b84 --- /dev/null +++ b/changes/changelog.d/768.enhancement @@ -0,0 +1 @@ +Descriptions will now be shown underneath user libraries (#768) \ No newline at end of file diff --git a/changes/changelog.d/770.doc b/changes/changelog.d/770.doc new file mode 100644 index 0000000000000000000000000000000000000000..4f419f20906586ac00af7da0916647e7c8854c0e --- /dev/null +++ b/changes/changelog.d/770.doc @@ -0,0 +1 @@ +Document how to use Redis over unix sockets (#770) diff --git a/changes/changelog.d/772.bugfix b/changes/changelog.d/772.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..5c0ddbeae0326003b432ac2fed62fc15c9994a47 --- /dev/null +++ b/changes/changelog.d/772.bugfix @@ -0,0 +1 @@ +Prevent skipping on file import if album_mbid is different (#772) diff --git a/changes/changelog.d/776.enhancement b/changes/changelog.d/776.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..4cdf48815ade07870fb1e29ca05c5df836078d07 --- /dev/null +++ b/changes/changelog.d/776.enhancement @@ -0,0 +1 @@ +Don't store unhandled ActivityPub messages in database (#776) diff --git a/changes/changelog.d/777.enhancement b/changes/changelog.d/777.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..96a46409e89e1fa0ff76ab68ac327ac5afac8cb1 --- /dev/null +++ b/changes/changelog.d/777.enhancement @@ -0,0 +1 @@ +Added a prune_library management command to remove obsolete metadata from the database (#777) diff --git a/changes/changelog.d/781.enhancement b/changes/changelog.d/781.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..e3dd2597acfe16eda30c2b643d7f36267c55c0ec --- /dev/null +++ b/changes/changelog.d/781.enhancement @@ -0,0 +1 @@ +Added a `check_inplace_files` management command to remove purge the database from references to in-place imported files that don't exist on disk anymore (#781) diff --git a/changes/changelog.d/782.bugfix b/changes/changelog.d/782.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..16b0ccdd40e6d062b886dd187d4de54ddcb0763f --- /dev/null +++ b/changes/changelog.d/782.bugfix @@ -0,0 +1 @@ +Better handling of featuring/multi-artist tracks tagged with MusicBrainz (#782) diff --git a/changes/changelog.d/784.feature b/changes/changelog.d/784.feature new file mode 100644 index 0000000000000000000000000000000000000000..8e61fb6e35068e8539958c70854b3a032c394957 --- /dev/null +++ b/changes/changelog.d/784.feature @@ -0,0 +1 @@ +Display a confirmation dialog when adding duplicate songs to a playlist (#784) diff --git a/changes/changelog.d/791.bugfix b/changes/changelog.d/791.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..ef5c5c6966ebb9d8f8affb07693fc148f20580a4 --- /dev/null +++ b/changes/changelog.d/791.bugfix @@ -0,0 +1 @@ +Fixed overflowing input on account detail page (#791) diff --git a/changes/changelog.d/795.bugfix b/changes/changelog.d/795.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..13191c0f12eba16f62c3512a06f233b3cf5a1f8e --- /dev/null +++ b/changes/changelog.d/795.bugfix @@ -0,0 +1 @@ +Fixed cover not showing in queue/player when playing tracks from "albums" tab (#795) diff --git a/changes/changelog.d/798.bugfix b/changes/changelog.d/798.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..d1cd2eb6c34d224e507b3028b47c8019dfc4d279 --- /dev/null +++ b/changes/changelog.d/798.bugfix @@ -0,0 +1 @@ +Allow users with dots in their usernames to request a subsonic password (#798) diff --git a/changes/changelog.d/799.enhancement b/changes/changelog.d/799.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..5b24ee44ce116defe104e4854b168058481fa1c7 --- /dev/null +++ b/changes/changelog.d/799.enhancement @@ -0,0 +1 @@ +Removed broken/instable lyrics feature (#799) diff --git a/changes/changelog.d/802.enhancement b/changes/changelog.d/802.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..25d4f3e5daf3bd3e0e63963ad110bcfe4df0c739 --- /dev/null +++ b/changes/changelog.d/802.enhancement @@ -0,0 +1 @@ +Now honor maxBitrate parameter in Subsonic API (#802) diff --git a/changes/changelog.d/805.enhancement b/changes/changelog.d/805.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..e11e2493bd95909ee6f92e8bcba65efee893a07a --- /dev/null +++ b/changes/changelog.d/805.enhancement @@ -0,0 +1 @@ +Reduced app size for regular users by moving admin-related code in a dedicated chunk (#805) diff --git a/changes/changelog.d/806.bugfix b/changes/changelog.d/806.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..7a2f0b518632387969f79afdcda97f9696172cd9 --- /dev/null +++ b/changes/changelog.d/806.bugfix @@ -0,0 +1 @@ +Use proper site name/domain in emails (#806) diff --git a/changes/changelog.d/808.enhancement b/changes/changelog.d/808.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..8a114ee6d38fa5d4852a8e0a2d6d4f849b26512b --- /dev/null +++ b/changes/changelog.d/808.enhancement @@ -0,0 +1 @@ +Advertise the list of supported upload extensions in the Nodeinfo endpoint (#808) diff --git a/changes/changelog.d/809.enhancement b/changes/changelog.d/809.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..92a4e57bc7793bc64ed1ee8aed7c73eee4ebc39f --- /dev/null +++ b/changes/changelog.d/809.enhancement @@ -0,0 +1 @@ +Added admin options to disable login for users, ensure related content is deleted when deleting a user account (#809) diff --git a/changes/changelog.d/812.bugfix b/changes/changelog.d/812.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..ec604dcec4ceae9f3960618493435abfef594d7d --- /dev/null +++ b/changes/changelog.d/812.bugfix @@ -0,0 +1 @@ +Ensure correct track duration and playable status when browsing radios (#812) diff --git a/changes/changelog.d/815.enhancement b/changes/changelog.d/815.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..5635fb00a2b48d178d8c51f9d35f734465adda73 --- /dev/null +++ b/changes/changelog.d/815.enhancement @@ -0,0 +1 @@ +Bumped dependencies to latest versions (#815) diff --git a/changes/changelog.d/819.bugfix b/changes/changelog.d/819.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..a1977568c3b00c959bbdfb1595baea49f71aeebc --- /dev/null +++ b/changes/changelog.d/819.bugfix @@ -0,0 +1 @@ +Fixed invalid required fields in Upload django's admin (#819) diff --git a/changes/changelog.d/822.bugfix b/changes/changelog.d/822.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..547a6acd2e5de098279db33085e7021c1a7ba3d6 --- /dev/null +++ b/changes/changelog.d/822.bugfix @@ -0,0 +1 @@ +Width of filter menus for radios has been set to stop text from overlapping the borders \ No newline at end of file diff --git a/changes/changelog.d/824.bugfix b/changes/changelog.d/824.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..fa681a4e0e62fd4d00f7ac61227a1967532fe6d9 --- /dev/null +++ b/changes/changelog.d/824.bugfix @@ -0,0 +1 @@ +Fixed invalid OEmbed URL when using a local FUNKWHALE_SPA_HTML_ROOT (#824) diff --git a/changes/changelog.d/826.bugfix b/changes/changelog.d/826.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..6cf7c25cb9dbb7536fc783283de5ff6eeb27bec7 --- /dev/null +++ b/changes/changelog.d/826.bugfix @@ -0,0 +1 @@ +Added env variable to set AWS region and signature version to serve media without proxy (#826) diff --git a/changes/changelog.d/828.bugfix b/changes/changelog.d/828.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..768ebcbd1e32252c4ff5bcf4fc6d9c6ee2374f13 --- /dev/null +++ b/changes/changelog.d/828.bugfix @@ -0,0 +1 @@ +Fixed an encoding issue with instance name on about page (#828) diff --git a/changes/changelog.d/830.enhancement b/changes/changelog.d/830.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..190c1127d79231191c313410f28e95a2eac34bc5 --- /dev/null +++ b/changes/changelog.d/830.enhancement @@ -0,0 +1 @@ +Better handling of follow/accept messages to avoid and recover from desync between instances (#830) diff --git a/changes/changelog.d/buttons.enhancement b/changes/changelog.d/buttons.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..a7a079d249f6e5e862e50c68d4d90f8136decf74 --- /dev/null +++ b/changes/changelog.d/buttons.enhancement @@ -0,0 +1 @@ +The buttons displaying an icon now always show a little divider between the icon and the text. (!620) diff --git a/changes/changelog.d/compose.enhancement b/changes/changelog.d/compose.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..bab1ca5e97a0cd0d3151683d6f61165d69c976a7 --- /dev/null +++ b/changes/changelog.d/compose.enhancement @@ -0,0 +1 @@ +Use network/depends_on instead of links in docker-compose.yml (!716) \ No newline at end of file diff --git a/changes/changelog.d/db.enhancement b/changes/changelog.d/db.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..396d9daa71fc2654462e96f5867475ea32ea2041 --- /dev/null +++ b/changes/changelog.d/db.enhancement @@ -0,0 +1 @@ +Keep persistent connections to the database instead of recreating a new one for each request diff --git a/changes/changelog.d/embed-wizard.enhancement b/changes/changelog.d/embed-wizard.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..7c34913229866a89f5e3219345cfdd378769de27 --- /dev/null +++ b/changes/changelog.d/embed-wizard.enhancement @@ -0,0 +1 @@ +Enhanced the design of the embed wizard. (!619) diff --git a/changes/changelog.d/envvar-spa-root.bugfix b/changes/changelog.d/envvar-spa-root.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..42b66e817c436134cf5a69bc333a7f10e85d904c --- /dev/null +++ b/changes/changelog.d/envvar-spa-root.bugfix @@ -0,0 +1 @@ +Add required envvar for dev environment (!668) diff --git a/changes/changelog.d/factories.bugfix b/changes/changelog.d/factories.bugfix new file mode 100644 index 0000000000000000000000000000000000000000..58fa58074c6a99455734a2b6d59312ed4a99c5a3 --- /dev/null +++ b/changes/changelog.d/factories.bugfix @@ -0,0 +1 @@ +Fixed dev command for fake data creation (!664) diff --git a/changes/changelog.d/footer.enhancement b/changes/changelog.d/footer.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..e2c117ead13d49e985c78812406f4ef7d54f15eb --- /dev/null +++ b/changes/changelog.d/footer.enhancement @@ -0,0 +1 @@ +Ensure the footer always stays at the bottom of the page diff --git a/changes/changelog.d/similar-radio.enhancement b/changes/changelog.d/similar-radio.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..d4c0a58de6adcb2fb789953dc8da9b59d60841a8 --- /dev/null +++ b/changes/changelog.d/similar-radio.enhancement @@ -0,0 +1 @@ +[Experimental] Added a new "Similar" radio based on users history (suggested by @gordon) diff --git a/changes/changelog.d/system-actor.enhancement b/changes/changelog.d/system-actor.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..25c5534a862c73b3f121f5bf08cdc90b69dbe3ef --- /dev/null +++ b/changes/changelog.d/system-actor.enhancement @@ -0,0 +1 @@ +Expose an instance-level actor (service@domain) in nodeinfo endpoint (#689) diff --git a/changes/notes.rst b/changes/notes.rst index 96ac3d7651f92166072a2fb200c0dd57606851e3..40f7e3c59dad0be11ab443d4b7b05349f83df0ea 100644 --- a/changes/notes.rst +++ b/changes/notes.rst @@ -5,3 +5,107 @@ Next release notes Those release notes refer to the current development branch and are reset after each release. + +Edits on tracks, albums and artists +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Funkwhale was a bit annoying when it camed to metadata. Tracks, albums and artists profiles +were created from audio file tags, but basically immutable after that (unless you had +admin access to Django's UI, which wasn't ideal to do this kind of changes). + +With this release, everyone can suggest changes on track, album and artist pages. Users +with the "library" permission can review suggested edits in a dedicated interface +and apply/reject them. + +Approved edits are broadcasted via federation, to ensure other instances get the information +too. + +Not all fields are currently modifiable using this feature. Especially, it's not possible +to suggest a new album cover, or reassign a track to a different album or artist. Those will +be implemented in a future release. + +Admin UI for tracks, albums, artists, libraries and uploads +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As part of our ongoing effort to make Funkwhale easier to manage for instance owners, +this release includes a brand new administration interface to deal with: + +- tracks +- albums +- artists +- libraries +- uploads + +You can use this UI to quickly search for any object, delete objects in batch, understand +where they are coming from etc. This new UI should remove the need to go through Django's +admin in the vast majority of cases (but also includes a link to Django's admin when needed). + + +Artist hiding in the interface +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +It's now possible for users to hide artists they don't want to see. + +Content linked to hidden artists will not show up in the interface anymore. Especially: + +- Hidden artists tracks are removed from the current queue +- Starting a playlist will skip tracks from hidden artists +- Recently favorited, recently listened and recently added widgets on the homepage won't include content from hidden artists +- Radio suggestions will exclude tracks from hidden artists +- Hidden artists won't appear in Subsonic apps + +Results linked to hidden artists will continue to show up in search results and their profile page remains accessible. + +OAuth2 authorization for better integration with third-party apps +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Funkwhale now support the OAuth2 authorization and authentication protocol which will allow +third-party apps to interact with Funkwhale on behalf of users. + +This feature makes it possible to build third-party apps that have the same capabilities +as Funkwhale's Web UI. The only exception at the moment is for actions that requires +special permissions, such as modifying instance settings or moderation (but this will be +enabled in a future release). + +If you want to start building an app on top of Funkwhale's API, please check-out +`https://docs.funkwhale.audio/api.html`_ and `https://docs.funkwhale.audio/developers/authentication.html`_. + +Better error handling and display during import +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Funkwhale should now be more resilient to missing tags in imported files, and give +you more insights when something goes wrong, including the specific tags that were missing +or invalid, and additional debug information to share in your support requests. + +This information is available in all pages that list uploads, when clicking on the button next to the upload status. + +Support for S3-compatible storages to store media files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Storing all media files on the Funkwhale server itself may not be possible or desirable +in all scenarios. You can now configure Funkwhale to store those files in a S3 +bucket instead. + +Check-out `https://docs.funkwhale.audio/admin/external-storages.html`_ if you want to use +this feature. + +Prune library command +^^^^^^^^^^^^^^^^^^^^^ + +Users are often surprised by Funkwhale's tendency to keep track, album and artist +metadata even if no associated files exist. + +To help with that, we now offer a ``prune_library`` management command you can run +to purge your database from obsolete entries. `Please refer to our documentation +for usage instructions <https://docs.funkwhale.audio/admin/commands.html#pruning-library>`_. + +Check in-place files command +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using in-place import with a living audio library, you'll quite often rename or +remove files from the file system. Unfortunately, Funkwhale keeps a reference to those +files in the database, which results in unplayable tracks. + +To help with that, we now offer a ``check_inplace_files`` management command you can run +to purge your database from obsolete files. `Please refer to our documentation +for usage instructions <https://docs.funkwhale.audio/admin/commands.html#remove-obsolete-files-from-database>`_. diff --git a/deploy/Gentoo/.gitkeep b/deploy/Gentoo/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/deploy/Gentoo/README.md b/deploy/Gentoo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b4f90b6609d33cc9317c320a9270c7f796291b2b --- /dev/null +++ b/deploy/Gentoo/README.md @@ -0,0 +1,19 @@ +### Gentoo init scripts + +Copy the files to `/etc/init.d/` + +``` +# cp /path/to/scripts/funkwhale_* /etc/init.d/ +``` + +Make the files executable: + +``` +# chmod +x /etc/init.d/funkwhale_* +``` + +Starting funkwhale_server will automatically start the other two, as well as nginx and redis. + +``` +# rc-service funkwhale_server start +``` \ No newline at end of file diff --git a/deploy/Gentoo/funkwhale_beat b/deploy/Gentoo/funkwhale_beat new file mode 100644 index 0000000000000000000000000000000000000000..49d427437a26c9f0bfe9a74777da48537960ff56 --- /dev/null +++ b/deploy/Gentoo/funkwhale_beat @@ -0,0 +1,27 @@ +#!/sbin/openrc-run +NAME=funkwhalebeat +PIDFILE=/var/run/$NAME.pid +USER=funkwhale +WORKDIR=/srv/funkwhale/api +Celery=/srv/funkwhale/virtualenv/bin/celery +BEAT_ARGS="-A funkwhale_api.taskapp beat -l INFO" +depend() { + need net +} + +start() { + ebegin "Starting Funkwhale Beat" + cd /srv/funkwhale/api + set -a && source /srv/funkwhale/config/.env && set +a + echo ' start beat' + start-stop-daemon --start --user $USER --make-pidfile --pidfile $PIDFILE -d $WORKDIR --exec $Celery -- $BEAT_ARGS >> /var/log/funk/worker.log 2>&1& + echo 'Started Beat' + echo + eend $? +} + +stop() { + ebegin "Stopping Funkwhale Beat" + start-stop-daemon --stop --pidfile $PIDFILE + eend $? +} diff --git a/deploy/Gentoo/funkwhale_server b/deploy/Gentoo/funkwhale_server new file mode 100644 index 0000000000000000000000000000000000000000..926bd01d16610d984c6132ea04c72adf1dbc6131 --- /dev/null +++ b/deploy/Gentoo/funkwhale_server @@ -0,0 +1,29 @@ +#!/sbin/openrc-run + +NAME=funkwhaleserver +PIDFILE=/var/run/$NAME.pid +USER=funkwhale +DAEMON_ARGS="-b 127.0.0.1 -p 5000 config.asgi:application --proxy-headers " +Daphne=/srv/funkwhale/virtualenv/bin/daphne +WORKDIR=/srv/funkwhale/api + +depend() { + need net redis postgresql nginx funkwhale_beat funkwhale_worker +} + +start() { + ebegin "Starting Funkwhale Server" + cd /srv/funkwhale/api + set -a && source /srv/funkwhale/config/.env && set +a + echo 'Starting Funkwhale Server' + start-stop-daemon --start --user $USER --make-pidfile --pidfile $PIDFILE -d $WORKDIR --exec $Daphne -- $DAEMON_ARGS >> /var/log/funk/daphne.log 2>&1& + echo 'Funkwhale Server started' + echo + eend $? +} + +stop() { + ebegin "Stopping Funkwhale" + start-stop-daemon --stop --pidfile $PIDFILE + eend $? +} diff --git a/deploy/Gentoo/funkwhale_worker b/deploy/Gentoo/funkwhale_worker new file mode 100644 index 0000000000000000000000000000000000000000..faa54f292925ac63ec59a7b2d476bec4bc4670e3 --- /dev/null +++ b/deploy/Gentoo/funkwhale_worker @@ -0,0 +1,28 @@ +#!/sbin/openrc-run +NAME=funkwhaleworker +PIDFILE=/var/run/$NAME.pid +USER=funkwhale +WORKDIR=/srv/funkwhale/api +Celery=/srv/funkwhale/virtualenv/bin/celery +WORKER_ARGS=" -A funkwhale_api.taskapp worker -l INFO" + +depend() { + need net +} + +start() { + ebegin "Starting Funkwhale Worker" + cd /srv/funkwhale/api + set -a && source /srv/funkwhale/config/.env && set +a + echo ' start beat' + start-stop-daemon --start --user $USER --make-pidfile --pidfile $PIDFILE -d $WORKDIR --exec $Celery -- $WORKER_ARGS >> /var/log/funk/worker.log 2>&1& + echo 'Started Worker' + echo + eend $? +} + +stop() { + ebegin "Stopping Funkwhale Worker" + start-stop-daemon --stop --pidfile $PIDFILE + eend $? +} diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 89ab61c99721bcf1c2002514e610a1c2173ffe53..bcf24422f9591414c18e5e51f833ed4d1e9bfb73 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -3,6 +3,8 @@ version: "3" services: postgres: restart: unless-stopped + networks: + - default env_file: .env image: postgres:11 volumes: @@ -10,6 +12,8 @@ services: redis: restart: unless-stopped + networks: + - default env_file: .env image: redis:3 volumes: @@ -18,6 +22,11 @@ services: celeryworker: restart: unless-stopped image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest} + networks: + - default + depends_on: + - postgres + - redis env_file: .env # Celery workers handle background tasks (such file imports or federation # messaging). The more processes a worker gets, the more tasks @@ -28,9 +37,6 @@ services: # flag: # celery -A funkwhale_api.taskapp worker -l INFO --concurrency=4 command: celery -A funkwhale_api.taskapp worker -l INFO - links: - - postgres - - redis environment: - C_FORCE_ROOT=true volumes: @@ -40,15 +46,22 @@ services: celerybeat: restart: unless-stopped image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest} - env_file: .env - command: celery -A funkwhale_api.taskapp beat -l INFO - links: + networks: + - default + depends_on: - postgres - redis + env_file: .env + command: celery -A funkwhale_api.taskapp beat -l INFO api: restart: unless-stopped image: funkwhale/funkwhale:${FUNKWHALE_VERSION:-latest} + networks: + - default + depends_on: + - postgres + - redis env_file: .env volumes: - "${MUSIC_DIRECTORY_SERVE_PATH-/srv/funkwhale/data/music}:${MUSIC_DIRECTORY_PATH-/music}:ro" @@ -57,13 +70,14 @@ services: - "${FUNKWHALE_FRONTEND_PATH}:/frontend" ports: - "5000" - links: - - postgres - - redis nginx: restart: unless-stopped image: nginx + networks: + - default + depends_on: + - api env_file: - .env environment: @@ -85,5 +99,6 @@ services: > /etc/nginx/conf.d/default.conf && cat /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'" - links: - - api + +networks: + default: diff --git a/deploy/docker.nginx.template b/deploy/docker.nginx.template index fd99c07050f9b42ed5c8999be2bbdf8118121645..431975629741f4546e18f061987d6fc7ec31b699 100644 --- a/deploy/docker.nginx.template +++ b/deploy/docker.nginx.template @@ -57,13 +57,20 @@ server { alias ${MEDIA_ROOT}/; } + # this is an internal location that is used to serve + # audio files once correct permission / authentication + # has been checked on API side location /_protected/media { - # this is an internal location that is used to serve - # audio files once correct permission / authentication - # has been checked on API side internal; alias ${MEDIA_ROOT}; + } + # Comment the previous location and uncomment this one if you're storing + # media files in a S3 bucket + # location ~ /_protected/media/(.+) { + # internal; + # proxy_pass $1; + # } location /_protected/music { # this is an internal location that is used to serve diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample index a71af5310b0c8768720355e44c898ebf950d8aad..b7b0301dadff33f34d288ac78a9684be73663998 100644 --- a/deploy/env.prod.sample +++ b/deploy/env.prod.sample @@ -75,7 +75,15 @@ REVERSE_PROXY_TYPE=nginx # CACHE_URL=redis://:password@localhost:6379/0 # (the extra semicolon is important) # Use the next one if you followed Debian installation guide +# # CACHE_URL=redis://127.0.0.1:6379/0 +# +# If you want to use Redis over unix sockets, you'll actually need two variables: +# For the cache part: +# CACHE_URL=redis:///run/redis/redis.sock?db=0 +# For the Celery/asynchronous tasks part: +# CELERY_BROKER_URL=redis+socket:///run/redis/redis.sock?virtual_host=0 + # Where media files (such as album covers or audio tracks) should be stored # on your system? @@ -136,3 +144,28 @@ FUNKWHALE_FRONTEND_PATH=/srv/funkwhale/front/dist # Nginx related configuration NGINX_MAX_BODY_SIZE=100M + +## External storages configuration +# Funkwhale can store uploaded files on Amazon S3 and S3-compatible storages (such as Minio) +# Uncomment and fill the variables below + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_STORAGE_BUCKET_NAME= +# An optional bucket subdirectory were you want to store the files. This is especially useful +# if you plan to use share the bucket with other services +# AWS_LOCATION= + +# If you use a S3-compatible storage such as minio, set the following variable +# the full URL to the storage server. Example: +# AWS_S3_ENDPOINT_URL=https://minio.mydomain.com +# AWS_S3_ENDPOINT_URL= + +# If you want to serve media directly from your S3 bucket rather than through a proxy, +# set this to true +# PROXY_MEDIA=false + +# If you are using Amazon S3 to serve media directly, you will need to specify your region +# name in order to access files. Example: +# AWS_S3_REGION_NAME=eu-west-2 +# AWS_S3_REGION_NAME= diff --git a/deploy/nginx.template b/deploy/nginx.template index 7cdee70f48c320bc6e7ef1796c6a13b0d82b53f7..78b8ff3d6cf99e1b732e726ef448de9b65f9d517 100644 --- a/deploy/nginx.template +++ b/deploy/nginx.template @@ -111,6 +111,13 @@ server { internal; alias ${MEDIA_ROOT}; } + + # Comment the previous location and uncomment this one if you're storing + # media files in a S3 bucket + # location ~ /_protected/media/(.+) { + # internal; + # proxy_pass $1; + # } location /_protected/music { # this is an internal location that is used to serve diff --git a/dev.yml b/dev.yml index 51aad05898f4f04be13c594cc86bc04d7128f4f6..7c58b910590fc52a79c57402d814c3cd04db2cde 100644 --- a/dev.yml +++ b/dev.yml @@ -60,8 +60,10 @@ services: - "FUNKWHALE_PROTOCOL=${FUNKWHALE_PROTOCOL-http}" - "DATABASE_URL=postgresql://postgres@postgres/postgres" - "CACHE_URL=redis://redis:6379/0" - links: + + depends_on: - postgres + # - minio - redis networks: - internal @@ -73,8 +75,9 @@ services: - .env.dev - .env build: *backend - links: + depends_on: - postgres + # - minio - redis command: celery -A funkwhale_api.taskapp worker -l debug -B environment: @@ -95,6 +98,8 @@ services: - .env.dev - .env image: nginx + ports: + - "${NGINX_PORTS_MAPPING-8000:80}" environment: - "NGINX_MAX_BODY_SIZE=${NGINX_MAX_BODY_SIZE-100M}" - "FUNKWHALE_API_IP=${FUNKHALE_API_IP-api}" @@ -135,7 +140,7 @@ services: - "8001:8001" api-docs: - image: swaggerapi/swagger-ui + image: swaggerapi/swagger-ui:v3.21.0 environment: - "API_URL=/swagger.yml" ports: @@ -143,6 +148,23 @@ services: volumes: - "./docs/swagger.yml:/usr/share/nginx/html/swagger.yml" + # minio: + # image: minio/minio + # command: server /data + # volumes: + # - "./data/${COMPOSE_PROJECT_NAME-node1}/minio:/data" + # environment: + # - "MINIO_ACCESS_KEY=${AWS_ACCESS_KEY_ID-access_key}" + # - "MINIO_SECRET_KEY=${AWS_SECRET_ACCESS_KEY-secret_key}" + # - "MINIO_HTTP_TRACE: /dev/stdout" + # ports: + # - "9000:9000" + # networks: + # - federation + # - internal + + + networks: ? internal federation: diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev index 50c3cbc2ef47f19b96f0c1716f933c61398e59c3..419bb0dd8975b09df4545d9ec6be4d69c24a2c32 100644 --- a/docker/nginx/conf.dev +++ b/docker/nginx/conf.dev @@ -93,13 +93,22 @@ http { alias /protected/media/; } + # this is an internal location that is used to serve + # audio files once correct permission / authentication + # has been checked on API side location /_protected/media { - # this is an internal location that is used to serve - # audio files once correct permission / authentication - # has been checked on API side internal; alias /protected/media; + } + # Comment the previous location and uncomment this one if you're storing + # media files in a S3 bucket + # location ~ /_protected/media/(.+) { + # internal; + # resolver 127.0.0.11; + # proxy_pass $1; + # add_header X-Remote-URL "$1"; + # } location /_protected/music { # this is an internal location that is used to serve diff --git a/docs/admin/backup.rst b/docs/admin/backup.rst new file mode 100644 index 0000000000000000000000000000000000000000..d474678ae3ba48a5c0a51b9114200673148c894e --- /dev/null +++ b/docs/admin/backup.rst @@ -0,0 +1,79 @@ +Backup your Funkwhale instance +============================== + +.. note:: + + Before upgrading your instance, we strongly advise you to make at least a database backup. Ideally, you should make a full backup, including the database and the media files. + + +Docker setup +------------ + +If you've followed the setup instructions in :doc:`../installation/docker`, here is the backup path: + +Multi-container installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Backup the db +^^^^^^^^^^^^^ + +On docker setups, you have to ``pg_dumpall`` in container ``funkwhale_postgres_1``: + +.. code-block:: shell + + docker exec -t funkwhale_postgres_1 pg_dumpall -c -U postgres > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql + +Backup the media files +^^^^^^^^^^^^^^^^^^^^^^ + +To backup docker data volumes, as the volumes are bound mounted to the host, the ``rsync`` way would go like this: + +.. code-block:: shell + + rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media + rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music + + +Backup the configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On docker setups, the configuration file is located at the root level: + +.. code-block:: shell + + rsync -avzhP /srv/funkwhale/.env /path/to/your/backup/.env + + +Non-docker setup +---------------- + +Backup the db +^^^^^^^^^^^^^ + +On non-docker setups, you have to ``pg_dump`` as user ``postgres``: + +.. code-block:: shell + + sudo -u postgres -H pg_dump funkwhale > /path/to/your/backup/dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql + +Backup the media files +^^^^^^^^^^^^^^^^^^^^^^ + +A simple way to backup your media files is to use ``rsync``: + +.. code-block:: shell + + rsync -avzhP /srv/funkwhale/data/media /path/to/your/backup/media + rsync -avzhP /srv/funkwhale/data/music /path/to/your/backup/music + +Backup the configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + rsync -avzhP /srv/funkwhale/config/.env /path/to/your/backup/.env + +.. note:: + You may also want to backup your proxy configuration file. + + For frequent backups, you may want to use deduplication and compression to keep the backup size low. In this case, a tool like ``borg`` will be more appropriate. diff --git a/docs/admin/commands.rst b/docs/admin/commands.rst new file mode 100644 index 0000000000000000000000000000000000000000..c30a67a99913baa39457a56f88d3377d3293f3a7 --- /dev/null +++ b/docs/admin/commands.rst @@ -0,0 +1,82 @@ +Management commands +=================== + +Pruning library +--------------- + +Because Funkwhale is a multi-user and federated audio server, we don't delete any artist, album +and track objects in the database when you delete the corresponding files. + +This is on purpose, because those objects may be referenced in user playlists, favorites, +listening history or on other instances, or other users could have upload files matching +linked to those entities in their own private libraries. + +Therefore, Funkwhale has a really conservative approach and doesn't delete metadata when +audio files are deleted. + +This behaviour can be problematic in some situations though, e.g. if you imported +a lot of wrongly tagged files, then deleted the files to reimport them later. + +To help with that, we provide a management you can run on the server and that will effectively +prune you library from track, album and artist metadata that is not tied to any file: + +.. code-block:: sh + + # print help + python manage.py prune_library --help + + # prune tracks with no uploads + python manage.py prune_library --tracks + + # prune albums with no tracks + python manage.py prune_library --albums + + # prune artists with no tracks/albums + python manage.py prune_library --artists + + # prune everything (tracks, albums and artists) + python manage.py prune_library --tracks --albums --artists + +The ``prune_library`` command will not delete anything by default, and only gives +you an estimate of how many database objects would be affected by the pruning. + +Once you have reviewed the output and are comfortable with the changes, you should rerun +the command with the ``--no-dry-run`` flag to disable dry run mode and actually apply +the changes on the database. + +.. warning:: + + Running this command with ``--no-dry-run`` is irreversible. Unless you have a backup, + there will be no way to retrieve the deleted data. + +.. note:: + + The command will exclude tracks that are favorited, included in playlists or listening + history by default. If you want to include those in the pruning process as well, + add the corresponding ``--ignore-favorites``, ``--ignore-playlists`` and ``--ignore-listenings`` + flags. + +Remove obsolete files from database +----------------------------------- + +When importing using the :ref:`in-place method <in-place-import>`, if you move or remove +in-place imported files on disk, Funkwhale will still have a reference to those files and won't +be able to serve them properly. + +To help with that, whenever you remove or move files that were previously imported +with the ``--in-place`` flag, you can run the following command:: + + python manage.py check_inplace_files + +This command will loop through all the database objects that reference +an in-place imported file, check that the file is accessible on disk, +or delete the database object if it's not. + +Once you have reviewed the output and are comfortable with the changes, you should rerun +the command with the ``--no-dry-run`` flag to disable dry run mode and actually delete the +database objects. + +.. warning:: + + Running this command with ``--no-dry-run`` is irreversible. Unless you have a backup, + there will be no way to retrieve the deleted data. diff --git a/docs/admin/external-storages.rst b/docs/admin/external-storages.rst new file mode 100644 index 0000000000000000000000000000000000000000..c8dbbe9635f30846ebf97090cda35fa763356c9d --- /dev/null +++ b/docs/admin/external-storages.rst @@ -0,0 +1,117 @@ +Using external storages to store Funkwhale content +================================================== + +By default, Funkwhale will store user-uploaded and related media such as audio files, +transcoded files, avatars and album covers on a server directory. + +However, for bigger instances or more complex deployment scenarios, you may want +to use distributed or external storages. + +S3 and S3-compatible servers +---------------------------- + +.. note:: + + This feature was released in Funkwhale 0.19 and is still considered experimental. + Please let us know if you see anything unusual while using it. + +Funkwhale supports storing media files Amazon S3 and compatible implementations such as Minio or Wasabi. + +In this scenario, the content itself is stored in the S3 bucket. Non-sensitive media such as +album covers or user avatars are served directly from the bucket. However, audio files +are still served by the reverse proxy, to enforce proper authentication. + +To enable S3 on Funkwhale, add the following environment variables:: + + AWS_ACCESS_KEY_ID= + AWS_SECRET_ACCESS_KEY= + AWS_STORAGE_BUCKET_NAME= + # An optional bucket subdirectory were you want to store the files. This is especially useful + # if you plan to use share the bucket with other services + # AWS_LOCATION= + + # If you use a S3-compatible storage such as minio, set the following variable + # the full URL to the storage server. Example: + # AWS_S3_ENDPOINT_URL=https://minio.mydomain.com + # AWS_S3_ENDPOINT_URL= + +Then, edit your nginx configuration. On docker setups, the file is located at ``/srv/funkwhale/nginx/funkwhale.template``, +and at ``/etc/nginx/sites-available/funkwhale.template`` on non-docker setups. + +Replace the ``location /_protected/media`` block with the following:: + + location ~ /_protected/media/(.+) { + internal; + proxy_pass $1; + } + +Then restart Funkwhale and nginx. + +From now on, media files will be stored on the S3 bucket you configured. If you already +had media files before configuring the S3 bucket, you also have to move those on the bucket +by hand (which is outside the scope of this guide). + +.. note:: + + At the moment, we do not support S3 when using Apache as a reverse proxy. + +Serving audio files directly from the bucket +******************************************** + +Depending on your setup, you may want to serve audio fils directly from the S3 bucket +instead of proxying them through Funkwhale, e.g to reduce the bandwidth consumption on your server, +or get better performance. + +You can achieve that by adding ``PROXY_MEDIA=false`` to your ``.env`` file. + +When receiving a request on the stream endpoint, Funkwhale will check for authentication and permissions, +then issue a 302 redirect to the file URL in the bucket. + +This URL is actually be visible by the client, but contains a signature valid only for one hour, to ensure +no one can reuse this URL or share it publicly to distribute unauthorized content. + +.. note:: + + If you are using Amazon S3, you will need to set your ``AWS_S3_REGION_NAME`` in the ``.env`` file to + use this feature. + +.. note:: + + Since some Subsonic clients don't support 302 redirections, Funkwhale will ignore + the ``PROXY_MEDIA`` setting and always proxy file when accessed through the Subsonic API. + + +Securing your S3 bucket +*********************** + +It's important to ensure your the root of your bucket doesn't list its content, +which is the default on many S3 servers. Otherwise, anyone could find out the true +URLs of your audio files and bypass authentication. + +To avoid that, you can set the following policy on your bucket:: + + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Principal": { + "AWS": [ + "*" + ] + }, + "Resource": [ + "arn:aws:s3:::<yourbucketname>/*" + ], + "Sid": "Public" + } + ] + } + +If you are using ``awscli``, you can store this policy in a ``/tmp/policy`` file, and +apply it using the following command:: + + aws s3api put-bucket-policy --bucket <yourbucketname> --policy file:///tmp/policy diff --git a/docs/admin/importing-music.rst b/docs/admin/importing-music.rst index acf025c6c2ad552ee18895771132baf97a4ef051..7c04544e9d5b3b7f0e3f37dfef289b6335b5da6f 100644 --- a/docs/admin/importing-music.rst +++ b/docs/admin/importing-music.rst @@ -105,8 +105,13 @@ And import music from this share with this command:: python api/manage.py import_files $LIBRARY_ID "/srv/funkwhale/data/music/nfsshare/**/*.ogg" --recursive --noinput --in-place On docker setups, it will require a bit more work, because while the ``/srv/funkwhale/data/music`` is mounted -in containers, symlinked directories are not. To fix that, in your ``docker-compose.yml`` file, ensure each symlinked -directory is mounted as a volume as well:: +in containers, symlinked directories are not. + +To fix that, you can use bind mounts instead of symbolic links, as it replicates the source directory tree. With the previous NFS share, it would go this way:: + + mount --bind /media/mynfsshare /srv/funkwhale/data/music/nfsshare + +If you want to go with symlinks, ensure each symlinked directory is mounted as a volume as well in your ``docker-compose.yml`` file:: celeryworker: volumes: diff --git a/docs/admin/index.rst b/docs/admin/index.rst index a385a2e5e90f4d77325d5a5f56044fbfc5a7feb1..8d80e3e0ad47487fd5091f7cc71e2068d0f445fe 100644 --- a/docs/admin/index.rst +++ b/docs/admin/index.rst @@ -14,6 +14,7 @@ Setup Guides ../installation/index configuration importing-music + external-storages Administration -------------- @@ -22,6 +23,7 @@ Administration :maxdepth: 2 django + commands url upgrading diff --git a/docs/admin/upgrading.rst b/docs/admin/upgrading.rst index 5b222ce49f0b159af7c0864654a529fc3ddfd453..23c581cf74ebea233eec97f92cec407fc1935dd8 100644 --- a/docs/admin/upgrading.rst +++ b/docs/admin/upgrading.rst @@ -79,7 +79,7 @@ Multi-container installation source .env # Download newest nginx configuration file curl -L -o nginx/funkwhale.template "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/develop/deploy/docker.nginx.template" - curl -L -o nginx/funkwhale_proxy.conf "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/develop/deploy/funkwhale_proxy.conf" + curl -L -o nginx/funkwhale_proxy.conf "https://dev.funkwhale.audio/funkwhale/funkwhale/raw/develop/deploy/docker.funkwhale_proxy.conf" # Pull the new version containers docker-compose pull # Apply the database migrations diff --git a/docs/conf.py b/docs/conf.py index eb3ae5cdd6d0fa3aa892ab05dd8c31f79c468447..bf1afa0a604b6c922d344f3e2bb9823c13975d7e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -164,7 +164,7 @@ texinfo_documents = [ redirect_files = [ ('importing-music.html', 'admin/importing-music.html'), - ('architecture.html', 'developer/architecture.html'), + ('architecture.html', 'developers/architecture.html'), ('troubleshooting.html', 'admin/troubleshooting.html'), ('configuration.html', 'admin/configuration.html'), ('upgrading/index.html', '../admin/upgrading.html'), diff --git a/docs/developers/authentication.rst b/docs/developers/authentication.rst new file mode 100644 index 0000000000000000000000000000000000000000..0d32139c0ed3eb80b99a7c2ec5a81aee700d1cbb --- /dev/null +++ b/docs/developers/authentication.rst @@ -0,0 +1,97 @@ +API Authentication +================== + +Each Funkwhale API endpoint supports access from: + +- Anonymous users (if the endpoint is configured to do so, for exemple via the ``API Authentication Required`` setting) +- Logged-in users +- Third-party apps (via OAuth2) + +To seamlessly support this range of access modes, we internally use oauth scopes +to describes what permissions are required to perform any given operation. + +OAuth +----- + +Create an app +::::::::::::: + +To connect to Funkwhale API via OAuth, you need to create an application. There are +two ways to do that: + +1. By visiting ``/settings/applications/new`` when logged in on your Funkwhale instance. +2. By sending a ``POST`` request to ``/api/v1/oauth/apps/``, as described in `our API documentation <https://docs.funkwhale.audio/swagger/>`_. + +Both method will give you a client ID and secret. + +Getting an access token +::::::::::::::::::::::: + +Once you have a client ID and secret, you can request access tokens +using the `authorization code grant flow <https://tools.ietf.org/html/rfc6749#section-4.1>`_. + +We support the ``urn:ietf:wg:oauth:2.0:oob`` redirect URI for non-web applications, as well +as traditionnal redirection-based flow. + +Our authorization endpoint is located at ``/authorize``, and our token endpoint at ``/api/v1/oauth/token/``. + +Refreshing tokens +::::::::::::::::: + +When your access token is expired, you can `request a new one as described in the OAuth specification <https://tools.ietf.org/html/rfc6749#section-6>`_. + +Security considerations +::::::::::::::::::::::: + +- Grant codes are valid for a 5 minutes after authorization request is approved by the end user. +- Access codes are valid for 10 hours. When expired, you will need to request a new one using your refresh token. +- We return a new refresh token everytime an access token is requested, and invalidate the old one. Ensure you store the new refresh token in your app. + + +Scopes +:::::: + +Scopes are defined in :file:`funkwhale_api/users/oauth/scopes.py:BASE_SCOPES`, and generally are mapped to a business-logic resources (follows, favorites, etc.). All those base scopes come in two flawours: + +- `read:<base_scope>`: get read-only access to the resource +- `write:<base_scope>`: get write-only access to the ressource + +For example, ``playlists`` is a base scope, and ``write:playlists`` is the actual scope needed to perform write +operations on playlists (via a ``POST``, ``PATCH``, ``PUT`` or ``DELETE``. ``read:playlists`` is used +to perform read operations on playlists such as fetching a given playlist via ``GET``. + +Having the generic ``read`` or ``write`` scope give you the corresponding access on *all* resources. + +This is the list of OAuth scopes that third-party applications can request: + + ++-------------------------------------------+---------------------------------------------------+ +| Scope | Description | ++===========================================+===================================================+ +| ``read`` | Read-only access to all data | +| | (equivalent to all ``read:*`` scopes) | ++-------------------------------------------+---------------------------------------------------+ +| ``write`` | Write-only access to all data | +| | (equivalent to all ``write:*`` scopes) | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:profile`` | Access to profile data (email, username, etc.) | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:libraries`` | Access to library data (uploads, libraries | +| | tracks, albums, artists...) | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:favorites`` | Access to favorites | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:listenings`` | Access to history | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:follows`` | Access to followers | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:playlists`` | Access to playlists | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:radios`` | Access to radios | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:filters`` | Access to content filters | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:notifications`` | Access to notifications | ++-------------------------------------------+---------------------------------------------------+ +| ``<read/write>:edits`` | Access to metadata edits | ++-------------------------------------------+---------------------------------------------------+ diff --git a/docs/developers/index.rst b/docs/developers/index.rst index 69f22f2c6e2417b992da646064b95bb8e76c38ed..966cac3afd0d2867c7899bb34160e2a893ad8f9a 100644 --- a/docs/developers/index.rst +++ b/docs/developers/index.rst @@ -12,5 +12,6 @@ Reference architecture ../api + ./authentication ../federation/index subsonic diff --git a/docs/documentation/identifying.rst b/docs/documentation/identifying.rst index eed1dc2f3f9bb60d99b57b10e149ba41d4f46422..3f7ee97d17878cd254c4463ee2d3e66eef06f46b 100644 --- a/docs/documentation/identifying.rst +++ b/docs/documentation/identifying.rst @@ -24,6 +24,5 @@ If you're not comfortable with writing documents or don't feel like you can, you by requesting a document be written. There are three ways to request new documents: - Open a new issue on `Gitlab <https://dev.funkwhale.audio/funkwhale/funkwhale/issues>`_, providing as much detail as possible -- Start a new thread on `the forum <https://socialhub.network/c/funkwhale>`_ with more details about your requests +- Start a new thread on `the forum <https://governance.funkwhale.audio/g/kQgxNq15/funkwhale>`_ with more details about your requests - Ask somebody on our `chat room <https://riot.im/app/#/room/#funkwhale:matrix.org>`_ - diff --git a/docs/federation/index.rst b/docs/federation/index.rst index e54de3fe4c720998a66e7281d7005843bf14ef14..70f766035fe96b49e179648cbb1989e456bd2953 100644 --- a/docs/federation/index.rst +++ b/docs/federation/index.rst @@ -88,8 +88,7 @@ to posting an activity to an outbox, we create an object, with the proper payloa Receiving an activity from a remote actor in a local inbox is basically the same, but we skip step 2. Funkwhale does not support all activities, and we have a basic routing logic to handle -specific activities, and discard unsupported ones. Unsupported activities are still -received and stored though. +specific activities, and discard unsupported ones. If a delivered activity matches one of our routes, a dedicated handler is called, which can trigger additional logic. For instance, if we receive a :ref:`activity-create` activity @@ -102,6 +101,24 @@ Links: - `Delivery logic for activities <https://dev.funkwhale.audio/funkwhale/funkwhale/blob/develop/api/funkwhale_api/federation/tasks.py>`_ +.. _service-actor: + +Service actor +------------- + +In some situations, we will send messages or authenticate our fetches using what we call +the service actor. A service actor is an ActivityPub actor object that acts on behalf +of a Funkwhale server. + +The actor id usually looks like ``https://yourdomain.com/federation/actors/service``, but +the reliable way to determine it is to query the nodeinfo endpoint and use the value +available in the ``metadata > actorId`` field. + +Funkwhale generally considers that the service actor has authority to send activities +associated with any object on the same domain. For instance, the service actor +could send a :ref:`activity-delete` activity linked to another users' library on the same domain. + + Supported activities -------------------- @@ -305,6 +322,59 @@ the audio library's actor are the same. If no local actor follows the audio's library, the activity will be discarded. +.. _activity-update: + + +Update +^^^^^^ + +Supported on +************ + +- :ref:`object-library` objects +- :ref:`object-track` objects + +Example +******* + +.. code-block:: json + + { + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {} + ], + "to": [ + "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers" + ], + "type": "Update", + "actor": "https://awesome.music/federation/actors/Bob", + "object": {} + } + +.. note:: + + Refer to :ref:`object-library` or :ref:`object-track` to see the structure of the ``object`` attribute. + +Internal logic +************** + +When a :ref:`activity-update` is received with a :ref:`object-library` or :ref:`object-track` object, +Funkwhale will try to update the local copy of the corresponding object in it's database. + + +Checks +****** + +Checks vary depending of the type of object associated with the update. + +For :ref:`object-library` objects, we ensure the actor sending the message is the owner of the library. + +For musical entities such as :ref:`object-track`, we ensure the actor sending the message +matches the :ref:`property-attributedTo` property declared on the local copy on the object, +or the :ref:`service-actor`. + .. _activity-delete: Delete @@ -515,7 +585,7 @@ Example { "type": "Library", "id": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6", - "actor": "https://awesome.music/federation/actors/MyNameIsTroll", + "attributedTo": "https://awesome.music/federation/actors/Alice", "name": "My awesome library", "followers": "https://awesome.music/federation/music/libraries/dc702491-f6ce-441b-9da0-cecbed08bcc6/followers", "summary": "This library is for restricted use only", @@ -613,3 +683,19 @@ For :ref:`object-audio` url objects: - If the audio's library is public, audio file can be accessed without restriction - Otherwise, the HTTP request must be signed by an actor with an approved follow on the audio's library + + +Properties +---------- + +.. _property-attributedTo: + +attributedTo +------------ + +Funkwhale will generally use the ``attributedTo`` property to communicate +who is responsible for a given object. When an object has the ``attributedTo`` attribute, +the associated actor has the permission to :ref:`activity-update`, :ref:`activity-delete` or +more generally apply any kind of activity on the object. + +In addition, Funkwhale consider all the objects of a domain as attributed to its corresponding :ref:`service-actor`. diff --git a/docs/installation/debian.rst b/docs/installation/debian.rst index 06449cf7fc50f730e19fbb7fdf505cea15ade584..40597cbe3fa1c7d19aa581d6c3a30df7f86a4ad3 100644 --- a/docs/installation/debian.rst +++ b/docs/installation/debian.rst @@ -51,7 +51,7 @@ Create the user and the directory: .. code-block:: shell - sudo useradd -r -s /usr/bin/nologin -d /srv/funkwhale -m funkwhale + sudo useradd -r -s /usr/sbin/nologin -d /srv/funkwhale -m funkwhale cd /srv/funkwhale Log in as the newly created user from now on: diff --git a/docs/swagger.yml b/docs/swagger.yml index d83171698e4e71ced2459ae9cd9b42690aa7628d..47fd8d4b521fa447cb37e855be05ca48455547eb 100644 --- a/docs/swagger.yml +++ b/docs/swagger.yml @@ -7,7 +7,7 @@ # /api/v1/radios # /api/v1/history -openapi: "3.0.0" +openapi: "3.0.2" info: description: | Interactive documentation for [Funkwhale](https://funkwhale.audio) API. @@ -43,9 +43,9 @@ info: title: "Funkwhale API" servers: - - url: https://demo.funkwhale.audio/api/v1 + - url: https://demo.funkwhale.audio description: Demo server - - url: https://{domain}/api/v1 + - url: https://{domain} description: Custom server variables: domain: @@ -59,6 +59,38 @@ servers: components: securitySchemes: + oauth2: + type: oauth2 + description: This API uses OAuth 2 with the Authorization Code flow. You can register an app using the /oauth/apps/ endpoint. + flows: + authorizationCode: + # Swagger doesn't support relative URLs yet (cf https://github.com/swagger-api/swagger-ui/pull/5244) + authorizationUrl: /authorize + tokenUrl: /api/v1/oauth/token/ + refreshUrl: /api/v1/oauth/token/ + scopes: + "read": "Read-only access to all user data" + "write": "Write-only access on all user data" + "read:profile": "Read-only access to profile data" + "read:libraries": "Read-only access to library and uploads" + "read:playlists": "Read-only access to playlists" + "read:listenings": "Read-only access to listening history" + "read:favorites": "Read-only access to favorites" + "read:radios": "Read-only access to radios" + "read:edits": "Read-only access to edits" + "read:notifications": "Read-only access to notifications" + "read:follows": "Read-only to follows" + "read:filters": "Read-only to to content filters" + "write:profile": "Write-only access to profile data" + "write:libraries": "Write-only access to libraries" + "write:playlists": "Write-only access to playlists" + "write:follows": "Write-only access to follows" + "write:favorites": "Write-only access to favorits" + "write:notifications": "Write-only access to notifications" + "write:radios": "Write-only access to radios" + "write:edits": "Write-only access to edits" + "write:filters": "Write-only access to content-filters" + "write:listenings": "Write-only access to listening history" jwt: type: http scheme: bearer @@ -67,6 +99,7 @@ components: security: - jwt: [] + - oauth2: [] tags: - name: Auth and security @@ -81,7 +114,41 @@ tags: description: Favorites, playlists, radios paths: - /token/: + /api/v1/oauth/apps/: + post: + tags: + - "auth" + description: + Register an OAuth application + security: [] + responses: + 201: + content: + application/json: + schema: + allOf: + - $ref: "#/definitions/OAuthApplication" + - $ref: "#/definitions/OAuthApplicationCreation" + requestBody: + required: true + content: + application/json: + schema: + type: "object" + properties: + name: + type: "string" + example: "My Awesome Funkwhale Client" + summary: "A human readable name for your app" + redirect_uris: + type: "string" + example: "https://myapp/oauth2/funkwhale" + summary: "A list of redirect uris, separated by spaces" + scopes: + type: "string" + summary: "A list of scopes requested by your app, separated by spaces" + example: "read write:playlists write:favorites" + /api/v1/token/: post: tags: - "Auth and security" @@ -180,11 +247,14 @@ paths: schema: $ref: "#/definitions/Me" - /artists/: + /api/v1/artists/: get: summary: List artists tags: - "Library and metadata" + security: + - oauth2: + - "read:libraries" parameters: - name: "q" in: "query" @@ -221,12 +291,14 @@ paths: type: "array" items: $ref: "#/definitions/Artist" - /artists/{id}/: + /api/v1/artists/{id}/: get: summary: Retrieve a single artist parameters: - $ref: "#/parameters/ObjectId" - + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" responses: @@ -240,9 +312,12 @@ paths: application/json: schema: $ref: "#/definitions/ResourceNotFound" - /artists/{id}/libraries/: + /api/v1/artists/{id}/libraries/: get: summary: List available user libraries containing work from this artist + security: + - oauth2: + - "read:libraries" parameters: - $ref: "#/parameters/ObjectId" - $ref: "#/parameters/PageNumber" @@ -262,11 +337,15 @@ paths: schema: $ref: "#/definitions/ResourceNotFound" - /albums/: + /api/v1/albums/: get: summary: List albums tags: - "Library and metadata" + + security: + - oauth2: + - "read:libraries" parameters: - name: "q" in: "query" @@ -311,12 +390,15 @@ paths: type: "array" items: $ref: "#/definitions/Album" - /albums/{id}/: + /api/v1/albums/{id}/: get: summary: Retrieve a single album parameters: - $ref: "#/parameters/ObjectId" + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" responses: @@ -331,7 +413,7 @@ paths: schema: $ref: "#/definitions/ResourceNotFound" - /albums/{id}/libraries/: + /api/v1/albums/{id}/libraries/: get: summary: List available user libraries containing tracks from this album parameters: @@ -339,6 +421,9 @@ paths: - $ref: "#/parameters/PageNumber" - $ref: "#/parameters/PageSize" + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" responses: @@ -353,11 +438,15 @@ paths: schema: $ref: "#/definitions/ResourceNotFound" - /tracks/: + /api/v1/tracks/: get: summary: List tracks tags: - "Library and metadata" + + security: + - oauth2: + - "read:libraries" parameters: - name: "q" in: "query" @@ -425,12 +514,15 @@ paths: type: "array" items: $ref: "#/definitions/Track" - /tracks/{id}/: + /api/v1/tracks/{id}/: get: parameters: - $ref: "#/parameters/ObjectId" summary: Retrieve a single track + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" responses: @@ -445,14 +537,16 @@ paths: schema: $ref: "#/definitions/ResourceNotFound" - /tracks/{id}/libraries/: + /api/v1/tracks/{id}/libraries/: get: summary: List available user libraries containing given track parameters: - $ref: "#/parameters/ObjectId" - $ref: "#/parameters/PageNumber" - $ref: "#/parameters/PageSize" - + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" responses: @@ -526,9 +620,13 @@ paths: application/json: schema: $ref: "#/definitions/ResourceNotFound" - /licenses/: + + /api/v1/licenses/: get: summary: List licenses + security: + - oauth2: + - "read:libraries" tags: - "Library and metadata" parameters: @@ -548,17 +646,20 @@ paths: items: $ref: "#/definitions/License" - /licenses/{code}/: - parameters: - - name: code - in: path - description: License code - required: true - schema: - type: string - example: cc0-1.0 + /api/v1/licenses/{code}/: get: summary: Retrieve a single license + security: + - oauth2: + - "read:libraries" + parameters: + - name: code + in: path + description: License code + required: true + schema: + type: string + example: cc0-1.0 tags: - "Library and metadata" @@ -937,6 +1038,34 @@ properties: - "mp3" definitions: + OAuthApplication: + type: "object" + properties: + client_id: + type: "string" + example: "VKIZWv7FwBq56UMfUtbCSIgSxzUTv1b6nMyOkJvP" + created: + type: "string" + format: "date-time" + updated: + type: "string" + format: "date-time" + scopes: + type: "string" + description: "Coma-separated list of scopes requested by the app" + + OAuthApplicationCreation: + type: "object" + properties: + client_secret: + type: "string" + example: "qnKDX8zjIfC0BG4tUreKlqk3tNtuCfJdGsaEt5MIWrTv0YLLhGI6SGqCjs9kn12gyXtIg4FWfZqWMEckJmolCi7a6qew4LawPWMfnLDii4mQlY1eQG4BJbwPANOrDiTZ" + redirect_uris: + type: "string" + format: "uri" + description: "Coma-separated list of redirect uris allowed for the app" + + ResultPage: type: "object" properties: @@ -1030,12 +1159,20 @@ definitions: type: "integer" format: "int64" example: 42 + fid: + type: string + format: uri + description: "The artist Federation ID (unique accross federation)" name: type: "string" example: "System of a Down" creation_date: type: "string" format: "date-time" + is_local: + type: "boolean" + description: "Indicates if the object was initally created locally or on another server" + Artist: type: "object" allOf: @@ -1057,6 +1194,10 @@ definitions: type: "integer" format: "int64" example: 16 + fid: + type: string + format: uri + description: "The album Federation ID (unique accross federation)" artist: type: "integer" format: "int64" @@ -1076,6 +1217,9 @@ definitions: type: "boolean" cover: $ref: "#/definitions/Image" + is_local: + type: "boolean" + description: "Indicates if the object was initally created locally or on another server" Album: type: "object" @@ -1187,6 +1331,10 @@ definitions: type: "integer" format: "int64" example: 66 + fid: + type: string + format: uri + description: "The track Federation ID (unique accross federation)" artist: type: "integer" format: "int64" @@ -1221,6 +1369,9 @@ definitions: type: "string" description: "Identifier of the license that is linked to the track" example: "cc-by-nc-nd-4.0" + is_local: + type: "boolean" + description: "Indicates if the object was initally created locally or on another server" AlbumTrack: type: "object" diff --git a/docs/users/apps.rst b/docs/users/apps.rst index 6f346c9015cd78616649ec78ed9ca45b56ab3d75..c8303b5f78848a40bd01250ec8d3fabb21e157c5 100644 --- a/docs/users/apps.rst +++ b/docs/users/apps.rst @@ -40,10 +40,10 @@ Those features as missing: .. note:: - If you know or use some recent, well-maintained, Subsonic clients, + If you know or use some recent, well-maintained Subsonic clients, please get in touch so we can add them to this list. - Especially we're still lacking an iOS client! + In particular we're still lacking an iOS client! Enabling Subsonic on your Funkwhale account @@ -166,7 +166,7 @@ Once installed, add the following to your /etc/mopidy/mopidy.conf:: [subidy] enabled=True url=https://path.to/your/funkwhale/server - username=funkwhale + username=your_funkwhale_username password=your_subsonic_password #legacy_auth=(optional - setting to yes may solve some connection errors) #api_version=(optional - specify which API version to use. Subsonic 6.2 uses 1.14.0) @@ -179,6 +179,10 @@ This will show your artists, albums, and playlists when you start ncmpcpp. [Optional]: enable and start mopidy as a service to start the server at boot. +.. note:: + + This also works with the `mopidy-funkwhale plugin <https://dev.funkwhale.audio/funkwhale/mopidy>`_ + Mobydick (Desktop) ^^^^^^^^^^^^^^^^^^ diff --git a/docs/users/queue.rst b/docs/users/queue.rst index a2790e8fa3324e414256b86ac68a6dc0cd7ae36e..7df5c4d7f7ac6694e2c6bb5e45ce6020ae368926 100644 --- a/docs/users/queue.rst +++ b/docs/users/queue.rst @@ -27,18 +27,17 @@ Add an Album to the Queue To add a whole album to the queue: -- Navigate to ``https://your-instance/library/playlists`` or click on "Browse" in the "Music" library on the left-hand side then select "Playlists" along the top -- Find the playlist you wish to add -- Click on the three dot menu to "Play" and select "Add to Queue", "Play Next", or "Play Now" +- Search for an album, or navigate to its album page in the library +- Click on the down arrow next to "Play" and select "Add to Queue", "Play Next", or "Play Now" Add a Playlist to the Queue ^^^^^^^^^^^^^^^^^^^^^^^^^^^ To add a playlist to the queue: -- Search for an album, or navigate to its album page in the library -- Click on the down arrow next to "Play" and select "Add to Queue", "Play Next", or "Play Now" - +- Navigate to ``https://your-instance/library/playlists`` or click on "Browse" in the "Music" library on the left-hand side then select "Playlists" along the top +- Find the playlist you wish to add +- Click on the three dot menu to "Play" and select "Add to Queue", "Play Next", or "Play Now" Rearrange Tracks in Your Queue ------------------------------ diff --git a/docs/users/tagging.rst b/docs/users/tagging.rst new file mode 100644 index 0000000000000000000000000000000000000000..f5276e50381566d544122b59a8d650348730d728 --- /dev/null +++ b/docs/users/tagging.rst @@ -0,0 +1,59 @@ +Tagging Music With MusicBrainz Picard +===================================== + +In order to get the most out of Funkwhale, it is important to tag files correctly. Good tagging makes managing your library much easier and provides Funkwhale with the information necessary to display album art, metadata, and other useful information. The recommended tool for tagging music is `MusicBrainz Picard <https://picard.musicbrainz.org/>`_. + +Tagging Items +-------------- + +Tagging a File +^^^^^^^^^^^^^^ + +To load a file into MusicBrainz Picard: + +* Click on "Add Files" +* Select the files you want to tag from your computer +* Picard should automatically start scanning the items. If nothing happens, select the item(s) and click on "Scan" +* Picard will start assigning suitable tags +* Hit ctrl+s or click "Save" to save the tags to the file + +Tagging a Directory +^^^^^^^^^^^^^^^^^^^ + +* Click on "Add Folder" +* Select the directory of files you want to tag from your computer +* Picard should automatically start scanning the items. If nothing happens, select the item(s) and click on "Scan" +* Picard will start assigning suitable tags +* Hit ctrl+s or click "Save" to save the tags to the file + + +Alternative Versions +-------------------- + +Picard is generally accurate when it comes to determining the release of an album/track, but sometimes it can fail to get the right version. You can force it to use a particular version of an album or track using the following method: + +Alternative Albums +^^^^^^^^^^^^^^^^^^ + +* Load the directory into Picard +* Once it has tagged the full album, right-click on the album and hover over "other versions" +* Select the collect release from the list +* Do this for any duplicate items until all tracks are under the correct release +* Hit ctrl+s or click "Save" to save the tags to the files + +Alternative Tracks +^^^^^^^^^^^^^^^^^^ + +If a track is not picking up its release, do the following: + +* Right-click on the offending track and select "Search for Similar Tracks..." +* Search for your release using `MusicBrainz's search syntax <https://musicbrainz.org/doc/Indexed_Search_Syntax>`_ +* Select the correct track from the list and click on "Load into Picard" +* The track will now inherit tags from the selected track. Hit ctrl+s or click "Save" to save the tags to the file + +Adding Items to MusicBrainz +--------------------------- + +MusicBrainz is an ever-growing library of music, but it may not yet have an entry for certain items. You can add these yourself by following their `guide <https://musicbrainz.org/doc/How_to_Add_a_Release/>`_. + +Once you've added a new item, Picard should automatically pick up the new details based on the files' metadata. This means that it will tag the music not only for you, but for any other user who tries to tag the same item in the future. diff --git a/docs/users/upload.rst b/docs/users/upload.rst index 1dfa4d76fb73f2ed3bf2a8ee70a3db69f908f4f6..169c72710fa638d2999f70fef0d3a05995a19299 100644 --- a/docs/users/upload.rst +++ b/docs/users/upload.rst @@ -154,3 +154,30 @@ Then select the files you want to delete using the checkboxes on the left ; you Finally, select "Delete" in the "Action" menu and click "Go". This operation does *not* remove metadata, meaning that deleted tracks will remain visible in your library. They just won't be playable anymore. + + +Common errors during import +--------------------------- + +.. _invalid_metadata: + +Invalid metadata +:::::::::::::::: + +This error occurs when the uploaded file miss some mandatory tags, or when some tags have +incorrect values (e.g an empty title or artist name). + +To fix this issue, please retag the file properly as described in :ref:`upload-tagging` +and reupload it. + + +.. _unknown_error: + +Unkwown error +::::::::::::: + +This error can happen for multiple reasons and likely indicates an issue with the Funkwhale +server (e.g. misconfiguration) or with Funkwhale itself. + +If the issue persists when you relaunch the import, get in touch with our instance admin +or open a support thread on our forum. diff --git a/front/locales/app.pot b/front/locales/app.pot index 2472e043af0b110013017cb74fea42f1436584a5..9a5a0e4c996c210eb334c53d577771c6ae0da87a 100644 --- a/front/locales/app.pot +++ b/front/locales/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: front 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 16:04+0100\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -19,1824 +19,3063 @@ msgstr "" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "" msgstr[1] "" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 src/views/content/remote/Card.vue:30 +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "" msgstr[1] "" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "" msgstr[1] "" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "" msgstr[1] "" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "" -msgstr[1] "" - #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "" msgstr[1] "" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "" msgstr[1] "" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "" msgstr[1] "" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "" +#: front/src/components/library/EditForm.vue:145 +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "" #: front/src/components/Footer.vue:45 +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +msgctxt "Content/About/Title" msgid "About this instance" msgstr "" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" msgstr "" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/Home.vue:101 +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +msgctxt "Content/*/*/Noun" msgid "Accessed date" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +msgctxt "*/*/*" +msgid "Account" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "" msgstr[1] "" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "" -#: front/src/components/audio/SearchBar.vue:26 src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: front/src/components/audio/SearchBar.vue:26 src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "" -msgstr[1] "" +#: front/src/views/admin/library/TrackDetail.vue:107 +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +msgctxt "*/*/*/Noun" +msgid "Album artist" msgstr "" -#: front/src/components/library/Track.vue:27 -msgid "Album page" +#: front/src/views/admin/library/AlbumDetail.vue:92 +msgctxt "Content/Moderation/Title" +msgid "Album data" +msgstr "" + +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" msgstr "" #: front/src/components/audio/Search.vue:19 src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +msgctxt "*/*/*" msgid "Albums" msgstr "" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +msgctxt "Content/*/Dropdown" msgid "All" msgstr "" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "" +#: front/src/components/federation/FetchButton.vue:21 +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:41 +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 src/components/auth/Settings.vue:225 +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "" -#: front/src/components/audio/SearchBar.vue:25 src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: front/src/components/audio/SearchBar.vue:25 src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" msgstr "" -#: front/src/components/library/Album.vue:22 src/components/library/Track.vue:33 -msgid "Artist page" +#: front/src/views/admin/library/ArtistDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Artist data" +msgstr "" + +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" msgstr "" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +msgctxt "*/*/*" +msgid "Artists" +msgstr "" + #: front/src/components/audio/Search.vue:10 src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "" -#: front/src/components/favorites/List.vue:33 src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" msgstr "" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "" +#: front/src/components/library/Albums.vue:4 +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "" #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "" -#: front/src/components/Home.vue:64 -msgid "Clean library" +#: front/src/components/library/EditForm.vue:75 +msgctxt "Content/Library/Button.Label" +msgid "Clear" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" msgid "Close" msgstr "" +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" +msgid "Close" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "" + +#: front/src/components/auth/Settings.vue:116 +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" msgstr "" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "" #: front/src/components/auth/Signup.vue:4 +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "" + +#: front/src/components/auth/Settings.vue:220 +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "" +#: front/src/components/auth/ApplicationForm.vue:65 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "" +#: front/src/components/auth/Settings.vue:134 src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "" +#: front/src/components/library/ImportStatusModal.vue:64 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 -#: front/src/views/content/libraries/Form.vue:29 src/views/playlists/Detail.vue:33 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 +#: front/src/views/content/libraries/Form.vue:29 src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "" +#: front/src/components/auth/Settings.vue:254 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "" -#: front/src/components/favorites/List.vue:34 src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:66 +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "" + +#: front/src/components/favorites/List.vue:35 src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" +#: front/src/views/admin/library/LibraryDetail.vue:123 +msgctxt "*/*/*/Noun" +msgid "Description" msgstr "" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "" #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." msgstr "" -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "" msgstr[1] "" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "" -#: front/src/components/audio/track/Table.vue:9 src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "" -#: front/src/views/content/libraries/Detail.vue:9 +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 +#: front/src/views/content/libraries/Detail.vue:9 src/views/playlists/Detail.vue:31 +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "" + +#: front/src/components/auth/Settings.vue:246 +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "" + +#: front/src/components/library/ArtistEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "" + +#: front/src/components/library/TrackEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +msgctxt "*/Admin/*/Noun" +msgid "Edits" msgstr "" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "" + +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "" -#: front/src/components/library/Album.vue:44 src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "" +#: front/src/components/auth/Authorize.vue:6 +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "" +#: front/src/components/moderation/FilterModal.vue:13 +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:73 +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "" -#: front/src/components/library/FileUpload.vue:84 -msgid "Filename" +#: front/src/components/library/TrackDetail.vue:66 +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" msgstr "" -#: front/src/views/admin/library/Base.vue:5 src/views/admin/library/FilesList.vue:21 -msgid "Files" +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" +msgid "Filename" msgstr "" -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +msgctxt "Content/Library/*" msgid "Finished" msgstr "" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" msgid "Followers" msgstr "" -#: front/src/views/content/remote/Card.vue:93 -msgid "Following" +#: front/src/components/manage/library/LibrariesTable.vue:53 +msgctxt "Content/*/*/Noun" +msgid "Followers" +msgstr "" + +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" +msgid "Following" +msgstr "" + +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" msgstr "" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" +#: front/src/components/auth/Authorize.vue:28 +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" msgstr "" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "" -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "" #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "" -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "" #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "" +#: front/src/components/library/ImportStatusModal.vue:45 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "" + #: front/src/components/Footer.vue:37 +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "" +#: front/src/components/auth/Settings.vue:128 +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "" +#: front/src/components/moderation/FilterModal.vue:40 +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "" #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "" -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +msgctxt "Popup/Import/Title" +msgid "Import detail" msgstr "" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "" msgstr[1] "" +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "" + #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" msgstr "" -#: front/src/components/auth/Signup.vue:42 -#: front/src/components/manage/users/InvitationForm.vue:11 -msgid "Invitation code" +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" +#: front/src/components/auth/Signup.vue:44 +#: front/src/components/manage/users/InvitationForm.vue:11 +msgctxt "Content/*/Input.Label" +msgid "Invitation code" msgstr "" -#: front/src/views/admin/users/Base.vue:8 src/views/admin/users/InvitationsList.vue:3 +#: front/src/views/admin/users/Base.vue:8 #: front/src/views/admin/users/InvitationsList.vue:24 +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +msgctxt "*/*/*/Noun" msgid "Libraries" msgstr "" +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +msgctxt "*/*/*" +msgid "Libraries" +msgstr "" + +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "" -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +msgctxt "*/*/*" msgid "Library" msgstr "" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +msgctxt "Content/*/*/Noun" msgid "License" msgstr "" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" msgstr "" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "" -#: front/src/components/audio/Player.vue:353 src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "" -#: front/src/components/audio/Player.vue:356 src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "" -#: front/src/components/audio/Player.vue:359 src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "" -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "" +#: front/src/components/auth/Profile.vue:12 +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "" -#: front/src/components/Sidebar.vue:97 src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/components/Sidebar.vue:96 src/components/manage/users/UsersTable.vue:177 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." msgstr "" -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +msgctxt "*/*/*/Noun" msgid "Music" msgstr "" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "" -#: front/src/components/audio/track/Row.vue:40 src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: front/src/components/audio/track/Row.vue:40 src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +msgctxt "*/*/*" +msgid "Name" +msgstr "" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:9 +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "" -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" +msgstr "" + +#: front/src/components/library/TrackDetail.vue:25 +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" msgstr "" #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "" -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "" + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." msgstr "" #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "" -#: front/src/components/favorites/List.vue:31 src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/favorites/List.vue:32 src/components/library/Radios.vue:41 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "" -#: front/src/components/favorites/List.vue:23 src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "" + +#: front/src/components/Sidebar.vue:226 +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "" + +#: front/src/components/auth/Settings.vue:176 +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "" -#: front/src/components/audio/PlayButton.vue:9 src/components/library/Track.vue:40 +#: front/src/components/audio/PlayButton.vue:9 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "" -#: front/src/components/audio/album/Card.vue:50 -#: front/src/components/audio/artist/Card.vue:44 src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/audio/album/Card.vue:48 +#: front/src/components/audio/artist/Card.vue:44 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "" + +#: front/src/views/playlists/Detail.vue:91 +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "" #: front/src/views/playlists/Detail.vue:12 +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "" msgstr[1] "" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 src/views/playlists/List.vue:106 +msgctxt "*/*/*" +msgid "Playlists" +msgstr "" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "" +#: front/src/views/admin/library/TrackDetail.vue:137 +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." msgstr "" -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1845,1053 +3084,1873 @@ msgstr "" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "" -#: front/src/components/library/Library.vue:10 src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 src/components/library/Radios.vue:142 +msgctxt "*/*/*" msgid "Radios" msgstr "" +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +msgctxt "Content/OAuth Scopes/Label" +msgid "Radios" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "" +#: front/src/components/library/EditForm.vue:27 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" msgstr "" #: front/src/components/auth/Signup.vue:9 +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "" #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "" #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "" +#: front/src/components/library/ArtistDetail.vue:12 +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." msgstr "" #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." msgstr "" #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "" -#: front/src/components/favorites/List.vue:38 src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "" + +#: front/src/components/favorites/List.vue:39 src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 src/components/library/Radios.vue:52 +#: front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "" +#: front/src/components/library/ArtistDetail.vue:9 +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "" -#: front/src/views/content/remote/Card.vue:166 -msgid "Scan skipped (previous scan is too recent)" +#: front/src/views/content/remote/Card.vue:35 +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" msgstr "" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" +msgid "Scan skipped (previous scan is too recent)" msgstr "" -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "" -#: front/src/components/library/Artists.vue:10 src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "" +#: front/src/components/manage/library/EditsCardList.vue:211 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:241 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" msgstr "" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "" - #: front/src/components/manage/users/InvitationsTable.vue:153 +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "" - -#: front/src/components/library/Album.vue:33 src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "" -#: front/src/components/library/Library.vue:32 src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 src/views/admin/users/Base.vue:21 #: front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "" msgstr[1] "" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "" #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." msgstr "" #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "" msgstr[1] "" #: front/src/components/audio/artist/Card.vue:30 +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "" msgstr[1] "" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +msgctxt "Content/Library/*/in MB" +msgid "Size" msgstr "" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "" - -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 -msgid "Size" +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +msgctxt "Content/*/*/Noun" +msgid "Size" msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +msgctxt "Content/Library/*" msgid "Skipped" msgstr "" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "" -#: front/src/components/radios/Button.vue:4 -msgid "Start" +#: front/src/components/audio/PlayButton.vue:23 src/components/radios/Button.vue:4 +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" msgstr "" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" msgstr "" -#: front/src/components/library/FileUpload.vue:86 +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "" + +#: front/src/components/library/FileUpload.vue:95 +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "" + +#: front/src/components/manage/library/EditsCardList.vue:12 +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "" + +#: front/src/components/manage/users/UsersTable.vue:43 +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" +#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" msgstr "" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "" + +#: front/src/components/library/ArtistEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "" + +#: front/src/components/library/TrackEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "" +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." msgstr "" -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." msgstr "" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/library/FileUpload.vue:38 +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "" -#: front/src/components/Home.vue:121 +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "" + +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "" +#: front/src/components/playlists/Form.vue:14 +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "" -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "" -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "" +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "" -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" msgstr "" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." msgstr "" -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "" -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "" #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "" -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" +msgid "This will log you out from existing devices that use the current password." +msgstr "" + +#: front/src/components/auth/Settings.vue:253 +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:36 -msgid "This will log you out from existing devices that use the current password." +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." msgstr "" -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "" -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 src/edits.js:39 +msgctxt "*/*/*/Noun" msgid "Title" msgstr "" +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "" -#: front/src/components/audio/SearchBar.vue:27 src/components/library/Track.vue:262 +#: front/src/components/audio/SearchBar.vue:27 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +msgctxt "*/*/*/Noun" +msgid "Track" +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" msgid "Track" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" msgstr "" -#: front/src/components/library/Track.vue:85 -msgid "Track information" +#: front/src/views/admin/library/TrackDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Track data" msgstr "" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" +msgid "Track information" msgstr "" -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" msgstr "" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" msgstr "" -#: front/src/views/content/remote/Card.vue:100 src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 src/views/content/remote/Card.vue:109 +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" msgstr "" #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "" +#: front/src/components/auth/ApplicationForm.vue:64 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" msgstr "" +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." +msgstr "" + #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." msgstr "" -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "" +#: front/src/components/manage/library/LibrariesTable.vue:52 +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +msgctxt "*/*/*" +msgid "Uploads" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "" -#: front/src/components/library/Album.vue:88 src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 src/views/admin/Settings.vue:81 -#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: front/src/components/mixins/Translations.vue:61 src/views/admin/Settings.vue:81 +#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +msgctxt "*/*/*/Noun" msgid "Users" msgstr "" #: front/src/components/Footer.vue:29 +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "" -#: front/src/components/library/Album.vue:37 src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +msgctxt "*/*/*" +msgid "Visibility" msgstr "" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" +#: front/src/components/federation/FetchButton.vue:69 +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" msgstr "" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" +#: front/src/components/auth/ApplicationForm.vue:3 +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" msgstr "" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "" #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "" -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "" -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "" -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "" #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "" + +#: front/src/components/auth/Settings.vue:261 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "" #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "" -#: front/src/components/Sidebar.vue:158 -msgid "You have a radio playing" +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/audio/Player.vue:71 -msgid "You may have a connectivity issue." +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" +msgid "You have a radio playing" msgstr "" -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "You may have a connectivity issue." msgstr "" #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "" -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "" + +#: front/src/components/auth/Settings.vue:215 +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "" +#: front/src/components/auth/Settings.vue:14 +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "" + +#: front/src/edits.js:47 +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "" + +#: front/src/edits.js:54 +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:183 +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/audio/PlayButton.vue:220 +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "" +msgstr[1] "" diff --git a/front/locales/ar/LC_MESSAGES/app.po b/front/locales/ar/LC_MESSAGES/app.po index 9cf735552565dbe16200bda164ca419c7d617b71..33cae0a29f6c92b7686215ba49f87d894dae1f9b 100644 --- a/front/locales/ar/LC_MESSAGES/app.po +++ b/front/locales/ar/LC_MESSAGES/app.po @@ -1,9 +1,10 @@ +# msgid "" msgstr "" "Project-Id-Version: Arabic (FunkWhale)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-17 08:33+0000\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-05-07 09:29+0000\n" "Last-Translator: ButterflyOfFire <butterflyoffire+funkwhale@protonmail.com>\n" "Language-Team: Arabic <https://translate.funkwhale.audio/projects/funkwhale/" "funkwhale/front/ar/>\n" @@ -16,19 +17,28 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\"ØŒ Ù„ÙÙ€ %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } Ù…ÙÙ† %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(Ùارغ)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "الدخول إلى Øسابك على Ùانك وايل Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "" @@ -38,9 +48,11 @@ msgstr[3] "" msgstr[4] "" msgstr[5] "" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } مقطع" @@ -50,7 +62,9 @@ msgstr[3] "%{ count } مَقطَع" msgstr[4] "%{ count } مَقاطÙع" msgstr[5] "%{ count } مَقاطÙع" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } مقاطع ÙÙŠ %{ albumsCount } ألبومات" @@ -60,27 +74,21 @@ msgstr[3] "%{ count } مَقطَع ÙÙŠ %{ albumsCount } ألبومات" msgstr[4] "%{ count } مَقاطÙع ÙÙŠ %{ albumsCount } ألبومات" msgstr[5] "%{ count } مَقاطÙع ÙÙŠ %{ albumsCount } ألبومات" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" -msgstr[5] "" - -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" -msgstr[5] "" +msgstr[0] "مقطع يناسب عامل التصÙية" +msgstr[1] "مقطع يناسب عامل التصÙية" +msgstr[2] "مقطع يناسب عامل التصÙية" +msgstr[3] "مقطع يناسب عامل التصÙية" +msgstr[4] "مقطع يناسب عامل التصÙية" +msgstr[5] "مقطع يناسب عامل التصÙية" #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} مَقطَع" @@ -91,34 +99,48 @@ msgstr[4] "%{ count } مَقاطÙع" msgstr[5] "%{ count } مَقاطÙع" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } سا %{ minutes } د" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } د" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "" +msgstr "لقد قَبÙÙ„ %{ username } طلبك لمتابعة المكتبة \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" -msgstr "" +msgstr "لقد قام %{ username } بمتابعة مكتبتك \"%{ library }\"" + +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "يريد %{ username } متابعة مكتبتك \"%{ library }\"" #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "المل٠الشخصي Ù„ÙÙ€ %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "ألبوم واØد" @@ -129,6 +151,8 @@ msgstr[4] "%{ count } ألبومات" msgstr[5] "%{ count } ألبومات" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "%{ count } Ù…Ùضّلة" @@ -138,83 +162,200 @@ msgstr[3] "%{ count } Ù…Ùضّلة" msgstr[4] "%{ count } Ù…Ùضّلات" msgstr[5] "%{ count } Ù…Ùضّلات" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "مكتبة موسيقية ذات جودة عالية" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Øدث خطأ ÙÙŠ الشبكة أثناء تØميل هذا الملÙ" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Øدث خطأ أثناء عملية ØÙظ التغييرات" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "عن %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "عن %{instanceName}" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "عن Ùانك وايل Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "صÙØØ© الألبوم" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "عن مثيل الخادوم هذا" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "تم قبوله" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "تم قبوله" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "عÙطّل النÙاذ" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "الوصول إلى الملÙات الصوتية والمكتبات والÙنانين والألبومات والمَقاطÙع" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "إختيار عامل تصÙية" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "عÙطّل النÙاذ" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "إضاÙØ© إلى المÙضلة" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "كتم الإشعارات" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "إضاÙØ© إلى قائمة المقاطع الموسيقية …" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "عÙطّل النÙاذ" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" msgstr "صÙلوا إلى موسيقاكم عبر واجهة نظيÙØ© التصميم تÙركّز Ùعلًا على الأهمّ" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "تاريخ النÙاذ" +msgstr "عÙطّل النÙاذ" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "الØسابات" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "الØسابات" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "بيانات الØساب" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "إعدادات الØساب" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "إعدادات الØساب" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Øالة الØساب" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "البريد الإلكتروني الخاص بالØساب" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +#, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "الØسابات" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "الإجراء" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "" @@ -224,615 +365,1103 @@ msgstr[3] "" msgstr[4] "" msgstr[5] "" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "الإجراءات" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "النشاط" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "النشاط" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "عرض النشاط" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" -msgstr "" +msgstr "إضاÙØ©" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" -msgstr "" +msgstr "إضاÙØ© نطاق" + +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +#, fuzzy +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "إضاÙØ© قاعدة إشرا٠جديدة" #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" -msgstr "" +msgstr "إضاÙØ© قاعدة إشرا٠جديدة" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "إضاÙØ© المØتوى Ùˆ إدارته" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "إضاÙØ© Ù…Øتوى" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "إضاÙØ© عامل تصÙية" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "قم بإضاÙØ© عوامل تصÙية لتخصيص إذاعتك" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "أضÙ٠إلى قائمة الانتظار الØالية" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "إضاÙØ© إلى المÙضلة" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "إضاÙØ© إلى قائمة المقاطع الموسيقية …" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "أضÙ٠إلى قائمة الانتظار" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "أضÙÙ‡ إلى قائمة التشغيل هذه" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "إضاÙØ© مقطع موسيقي" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "المدير" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "الإدارة" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "الألبوم" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" -msgstr[5] "" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "الألبوم" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Ù…ÙÙ† ألبومات هذا الÙنان" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "عنوان الألبوم" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "صÙØØ© الألبوم" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "عنوان الألبوم" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "الألبومات" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Ù…ÙÙ† ألبومات هذا الÙنان" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "الكل" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "ترخيص التطبيق" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Øدث خطأ أثناء عملية ØÙظ التغييرات" +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Øدث خطأ أثناء عملية ØÙظ التغييرات" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Øدث خطأ أثناء عملية ØÙظ التغييرات" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "طرأ هناك خطأ ما، ذلك قد يعني أن السيرÙر غير متصل أو أنّ الإتصال به غير ممكن" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "Øدث خطأ مجهول" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "الإجراء" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "تÙاصيل التطبيق" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "Ù…ÙعرّÙ٠التطبيق" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "قبول" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "قبول" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "أمتأكد من أنك تريد الخروج ØŸ" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "الÙنان" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "الÙنان" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "إسم الÙنان" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "صÙØØ© الÙنان" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "إسم الÙنان" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Ùنان ØŒ ألبوم ØŒ مقطع موسيقي …" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "الÙنانون" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "الÙنانون" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "تصاعدي" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "أطلب إعادة تعيين كلمة المرور" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Ù…Øتوى مسموع" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "اختصارات المÙشغّل الصوتي" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "ترخيص %{ app }" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "التطبيقات المÙرخّصة" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "قوائم المقاطع الموسيقية المتوÙرة" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "الصورة الرمزية" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "العودة إلى صÙØØ© تسجيل الدخول" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "تØديث الإعدادات" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "معدل البت" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Øجب الكل" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" msgstr "" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "تصÙÙ‘Ø" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "تصÙØ Ø§Ù„Ù…ÙƒØªØ¨Ø©" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "تصÙÙ‘Ø Ø§Ù„Ø¥Ø°Ø§Ø¹Ø§Øª" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "استعراض الÙنانين" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "تصÙÙ‘Ø Ù‚ÙˆØ§Ø¦Ù… المَقاطÙع الموسيقية" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "تصÙÙ‘Ø Ø§Ù„Ø¥Ø°Ø§Ø¹Ø§Øª" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "المØرّر" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Øسب %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "إن قمت بإلغاء متابعة هذه المكتبة Ùسو٠لن تتمكن Ù…ÙÙ† الوصول إلى Ù…Øتواها." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "إلغاء" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +#, fuzzy +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" -msgstr "" - -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "لا يمكن تغيير كلمة المرور" +msgstr "تاريخ الإنشاء" -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "لا يمكن تØميل هذا الملÙØŒ تØقق أنّ Øجم المل٠ليس ضخما" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "تغيير اللغة" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" -msgstr "تغيير الكلمة السرية" +msgstr "تغيير كلمتي السرية" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" -msgstr "تغيير كلمة المرور" +msgstr "تغيير الكلمة السرية" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "عدّل كلمتك السرية" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" -msgstr "هل تريد تغيير كلمتك السريّة ØŸ" +msgstr "هل تريد تغيير كلمتك السريّة؟" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "تمت مزامنة التعديلات مع السيرÙر" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "سو٠تتأثر كذلك الكلمة السرية لواجهة برمجة تطبيقات صاب سونيك إن قمت بتعديل كلمتك السرية." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "سو٠ينجرّ ما يلي عند تعديل كلمتك السرية" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "قاعة المØادثة" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "اختر خادومك" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "مكتبة موسيقية ذات جودة عالية" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "امسØ" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "امسØ" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Ù…Ø³Ø Ù‚Ø§Ø¦Ù…Ø© المَقاطع الموسيقية" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Ù…Ø³Ø Ù‚Ø§Ø¦Ù…Ø© الانتظار الخاصة بك" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "إضغط مرة واØدة Ùˆ استمع لساعات Ù…ÙÙ† الموسيقى عبر الإذاعات المÙدمَجة" -#: front/src/components/library/FileUpload.vue:75 -msgid "Click to select files to upload or drag and drop files or directories" +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" msgstr "" +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" +msgid "Click to select files to upload or drag and drop files or directories" +msgstr "اضغط لاختيار ملÙات أو قم بسØب وإلقاء ملÙات أو مجلدات" + #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "إغلاق" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "إغلاق" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "إغلاق وإعادة إنعاش الصÙØØ©" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "الرمز" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "تصغير" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "الإعداد" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "تأكيد" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "أكّد بريدك الإلكتروني" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "رمز التأكيد" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "إختيار عامل تصÙية" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "إختيار عامل تصÙية" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "المساهمة" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "نسخ" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "نسخ المَقاطÙع Ù…ÙÙ† قائمة الإنتظار الØالية إلى قائمة التشغيل" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" -msgstr "" +msgstr "انسخ والصق هذا الرمز ÙÙŠ نص HTML على موقعك" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "الØقوق" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "لم نتمكن Ù…ÙÙ† تأكيد عنوان بريدك الإلكتروني" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "تعذر جلب المكتبة البÙعدية" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "أغلÙÙØ© الألبومات Ùˆ كلمات الأغاني، هدÙنا هو دمجها جميعا ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "إنشاء" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "أنشئ Øسابا على Ùانك وايل" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "أنشئ قائمة مَقاطÙع موسيقية جديدة" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "أنشئ قائمة مَقاطÙع موسيقية جديدة" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "أنشئ مكتبة جديدة" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "أنشئ قائمة مَقاطÙع موسيقية جديدة" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "أنشئ Øسابا" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "أنشئ قائمة مَقاطع موسيقية" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "أنشئ مكتبة" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "أنشئ Øسابي" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "أنشئ قائمة مَقاطع موسيقية" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "أنشئ إذاعتك" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "تاريخ الإنشاء" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "الصورة الرمزية الØالية" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "المكتبة الØالية" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "المَقطَع الØالي" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "الاستعمال الØالي" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "التاريخ" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "معلومات عن المَقطَع" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "تخÙيض الصوت" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "ØØ°Ù" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Øذ٠قائمة الأغاني" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "Øذ٠تطبيق \"%{ application }\"ØŸ" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Øذ٠المكتبة" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Øذ٠قاعدة الإشراÙ" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Øذ٠قائمة الأغاني" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Øذ٠الإذاعة" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "أتريد Øذ٠هذه المكتبة؟" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "أتريد Øذ٠هذه المكتبة؟" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "أتريد Øذ٠هذه المكتبة؟" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "أتريد Øذ٠قاعدة الإشرا٠هذه؟" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "أتريد Øذ٠قاعدة الإشرا٠هذه؟" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "أتريد Øذ٠هذه المكتبة؟" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "تنازليًا" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "الوصÙ" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "التÙاصيل" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "الوصÙ" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "التÙاصيل" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "" #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "تعطيل النÙاذ" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "تعطيل النÙاذ عبر صاب سونيك Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "تعطيل النÙاذ عبر واجهة برمجة التطبيقات صاب سونيك ØŸ" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "معطل" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "القرص رقم" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "إكتش٠كيÙية استخدام Ùانك وايل Funkwhale عبر التطبيقات الأخرى" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "الاسم المعروض" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "إعرضها للعامة" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." msgstr "" -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "هل تودّ Ø¥Ùراغ قائمة المَقاطÙع الموسيقية \"%{ playlist }\" ØŸ" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "هل تؤكّد هذا الإجراء ØŸ" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "متأكّد Ù…ÙÙ† أنك تريد Øذ٠قائمة المَقاطÙع الموسيقية \"%{ playlist }\" ØŸ" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "أتريد Øقا Øذ٠إذاعة \"%{ radio }\" ØŸ" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "أتريد Øقا Øذ٠إذاعة \"%{ radio }\" ØŸ" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "" @@ -842,998 +1471,1715 @@ msgstr[3] "" msgstr[4] "" msgstr[5] "" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "هل تريد استرجاع قائمة الإنتظار السابقة للأغاني ØŸ" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "الدليل" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "النطاق" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "النطاقات" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "تنزيل" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" -msgstr "" +msgstr "اسØب والقي أعمدةً قصد ترتيب المقاطÙع على قائمة التشغيل" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "المدّة" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "عنوان البريد الإلكتروني مؤكَّد" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "سهل للإستخدام" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "تعديل" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "تعديل" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Øدث خطأ أثناء تطبيق الإجراء" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "تعديل معلومات مثيل الخادوم" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "تعديل…" +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "تØديث قاعدة الإشراÙ" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "شغّÙÙ„ هذا المَقطَع" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "شغّÙÙ„ هذا المَقطَع" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "شغّÙÙ„ هذا المَقطَع" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "تعديل" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "تعديل" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "البريد الإلكتروني" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "عنوان البريد الإلكتروني" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "ادمج" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "رمز الإدماج" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" -msgstr "" +msgstr "ادرج هذا الألبوم على موقعك" + +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "ادمج هذا المَقطع على موقعك" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "ادمج هذا المَقطع على موقعك" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 -#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "أدخÙÙ„ عنوان رابط لمكتبة ما" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" -msgstr "" +msgstr "الرسائل المÙرسَلة" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "تم تنشيطه" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "إنهاء التعديل" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "أدخÙÙ„ عنوان رابط لمكتبة ما" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "أدخÙÙ„ إسم إذاعة…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "أدخل اسم ألبوم ما…" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "أدخÙÙ„ إسم Ùنان…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "أدخÙÙ„ إسم قائمة مَقاطÙع٠موسيقية…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +#, fuzzy +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "ادخل عنوان البريد الإلكتروني المÙقترن بØسابك" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "أدخÙÙ„ عنوان بريدك الإلكتروني" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "أدخÙÙ„ رمز الدعوة" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "أدخÙÙ„ طلب بØØ«Ùك…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "أدخÙÙ„ إسم المستخدÙÙ…" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "أدخل إسم المستخدÙÙ… أو البريد الإلكتروني" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "خطأ" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "سÙجÙÙ„ الأخطاء" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "سÙجÙÙ„ الأخطاء" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Ùيه خطأ" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Øدث خطأ أثناء تطبيق الإجراء" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Øدث خطأ أثناء إرسال طلب إعادة تعيين الكلمة السرية" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Øدث خطأ أثناء تطبيق الإجراء" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Øدث خطأ أثناء عملية تعديل الكلمة السرية" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Øدث خطأ أثناء إنشاء النطاق" +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Øدث خطأ أثناء إنشاء القاعدة" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Øدث خطأ أثناء إنشاء الدعوة" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Øدث خطأ أثناء إنشاء القاعدة" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Øدث خطأ أثناء إنشاء الدعوة" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Øدث خطأ أثناء عملية جلب معلومات العقدة" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Øدث خطأ أثناء ØÙظ الإعدادات" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Øدث خطأ أثناء ØÙظ الإعدادات" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Øدث خطأ أثناء ØÙظ الإعدادات" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Ùيه خطأ" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "الملÙات الخاطئة" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "الجميع" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "كل Ù…ÙŽÙ† هم على مثيل الخادوم هذا" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +#, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "كاÙØ© Ù…ÙŽÙ† هم على مثيلات الخوادم" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "إستثني" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "تاريخ نهاية الصلاØية" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "منتهية الصلاØيّة" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "إنتهت صلاØيتها/ أو مستعمَلة" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Ùشل" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "المَقاطع الصوتية المخÙقة:" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "المَقاطع الصوتية المخÙقة:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "المÙضلة" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "المÙضلة" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "الÙديرالية" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "الÙديرالية" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "الØقل" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "إسم الملÙÙ‘" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "الملÙّات" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "إسم عامل التصÙية" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "إكتمل" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "أول زيارة" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "تاريخ أول اكتشاÙ" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "إتبع" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "متابعة المكتبات عن بÙعد" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "طلب متابعة Ù…Ùعلّق ÙÙŠ انتظار القبول" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "المتابÙعون" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "المتابÙعون" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "ÙŠÙتابÙع" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Ù…ÙÙ† ألبوم %{ album } Ù„ÙÙ€ %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "إتبع" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "تعطيل النÙاذ" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Ùانك وايل Funkwhale متواÙÙ‚ مع برمجيات تشغيل الموسيقى التي تدعم واجهة برمجية تطبيقات صاب سونيك." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Ùانك وايل Funkwhale سهلٌ جدًا للإستخدام." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Ø·ÙوّÙر Ùانك وايل Funkwhale لتسهيل الإستماع إلى الموسيقى التي تØبونها Ùˆ لاكتشا٠Ùنّانين جÙدد." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Ùانك وايل Funkwhale مجاني Ùˆ ÙŠÙعيد التØكّم ÙÙŠ موسيقاكم بين أيديكم." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Ùانك وايل Funkwhale ÙŠÙØاÙظ على موسيقاكم" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "الاختصارات العامة" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "تØصّل على دعوة جديدة" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "أنقلني إلى المكتبة" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "اØصلوا على بيانات وصÙية ذات جودة عن موسيقاكم بÙضل <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "إبدأ هنا" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "الØصول على مساعدة" + #: front/src/components/Footer.vue:37 +#, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "الØصول على مساعدة" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "هيا" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "إنتقل إلى الصÙØØ© الرئيسية" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "استعراض الÙنانين" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." +msgstr "إخÙاء الØساب أو Ù…Øتوى النطاق Ù…ÙÙ† الجميع باستثناء المتابÙعين." + +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "إضاÙØ© Ù…Øتوى" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" msgstr "" #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "الرئيسية" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "ساعات Ù…ÙÙ† الموسيقى" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "" #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "" -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "تاريخ الإستيراد" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "استيراد الموسيقى من منصات مختلÙØ©ØŒ مثل يوتيوب أو ساوند كلاود" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Øالة الإستيراد" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "مصدر الإستيراد" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Øالة الإستيراد" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Øالة الإستيراد" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "تم استيراده" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "تاريخ الإستيراد" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "تعذر الاتصال بالخادم البÙعدي" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "تمت إضاÙتها مؤخرا" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "ÙÙŠ المÙضلة" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "ÙÙŠ اقتراØات الإذاعات" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "غير ناشط" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "زيادة Øجم الصوت" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" -msgstr[4] "" -msgstr[5] "" +msgstr[0] "%{ count } مقطع" +msgstr[1] "%{ count } مَقطَع" +msgstr[2] "%{ count } مَقطعين" +msgstr[3] "%{ count } مَقطَع" +msgstr[4] "%{ count } مَقاطÙع" +msgstr[5] "%{ count } مَقاطÙع" + +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "بيانات مثيل الخادم" #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "بيانات مثيل الخادم" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "معلومات عن مثيل الخادوم" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "إذاعات مثيل الخادوم" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "إعدادات مثيل الخادوم" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "بيانات مثيل الخادم" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" msgstr "" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" +msgstr "" + +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "رمز الدعوة" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "رمز الدعوة (اختياري)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "الدعوات" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "متعقّب المشاكل" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "ØاÙظوا على أثر موسيقاكم Ùˆ أغانيكم المÙضّلة" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "اختصارات لوØØ© المÙاتيØ" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "الØسابات المعروÙØ©" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "المكتبات المعروÙØ©" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "آخر نشاط" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "آخÙر ÙØص" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "آخر تعديل" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "آخر زيارة" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "آخÙر زيارة" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "آخÙر تØديث:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "إبدأ" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "إعر٠المزيد عن مثيل الخادوم هذا" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "أتركه Ùارغًا للØصول على رمز عشوائي" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "أتركه Ùارغًا للØصول على ودجات تناسبي" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Libraries" msgstr "المكتبات" +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Libraries" +msgstr "المكتبات" + +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "تم تØديث المكتبة" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "" -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "المكتبة" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "تم انشاء المكتبة" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "تم تØديث المكتبة" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "تم Øذ٠المكتبة" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "ملÙّات المكتبة" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "تم تØديث المكتبة" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" msgstr "الرخصة" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +#, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "جار٠تØميل المتابÙعين…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "جار٠تØميل المتابÙعين…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "جار٠تØميل المكتبات…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "جار٠تØميل بيانات المكتبة…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "عملية تØميل الإشعارات جارية…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "عملية تØميل المكتبات البÙعدية جارية…" +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "جار٠تØميل المكتبات البÙعدية…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "جار٠تØميل بيانات الإستخدام…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "جار٠تØميل Ù…Ùضلاتك…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +#, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Øساب Ù…Øلي" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "الدخول" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "الدخول إلى Øسابك على Ùانك وايل Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "الخروج" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Ù…Ùتّصل كـ %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "الدخول" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Øالة الØساب" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "خروج" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "يبدو أنه ليس لديك أية مكتبة بعد، Øان الأوان لإنشاء واØدة." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "" -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "" -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "" -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "الكلمات" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "القائمة الرئيسية" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "إدارة المكتبة" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "إدارة قوائم المَقاطÙع الموسيقية" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "إدارة المستخدÙمين" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "إدارة القوائم الخاصة الموسيقى" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "تØديد الكل كمقروء" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "تØديد كمقروء" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "تØديد كغير مقروء" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "Ù…Ùشغّل الوسائط" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "عضو منذ %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "تطبيقات الكمبيوتر والأجهزة المØمولة" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +#, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "الإشراÙ" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" +msgstr "قواعد الإشرا٠تساعدكم على التØكم ÙÙŠ كيÙية تÙاعل مثيل خادومكم مع النطاقات Ùˆ الØسابات الأخرى." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "تاريخ التعديل" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "تاريخ التعديل" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "الموسيقى" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "كتم" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "كتم النشاط" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "كتم الإشعارات" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Øسابي" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "وصÙÙŠ الرائع" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "مكتبتي الرائعة" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "قائمتي الرائعة للمَقاطÙع الموسيقية" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "إذاعتي الرائعة" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "مكتباتي" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "غير متوÙر" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "الإسم" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "الإسم" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "الإسم" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "الكلمة السرية الجديدة" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "سو٠يتم إضاÙØ© المَقاطÙع الجديدة هنا آليًا." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "المَقطَع التالي" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "لا" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "لا تØتاج إلى تنصيب إضاÙات٠أو Ù…ÙÙ„Øَقات٠: كل ما تØتاج إليه هي مكتبة موسيقية على الويب" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "لم نتمكّن Ù…ÙÙ† العثور على أي ألبوم يناسب طلب بØثك" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "لم نتمكّن Ù…ÙÙ† العثور على أي Ùنان يناسب طلب بØثك" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "لا تتوÙّر هناك كلمات لهذا المَقطَع." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "ليس هناك أية مكتبة مطابÙقة." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." -msgstr "لم تتلق أي اشعار بعد." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "ليس هناك أي إشعار للعرض." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "لا Ø£Øد غيري" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "لا Ø£Øد يتبع هذه المكتبة" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "غير مستعمَل" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "الإشعارات" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "الإشعارات" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "موقع الويب الرسمي" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" -msgstr "الكلمة السرية الجديدة" +msgstr "الكلمة السرية القديمة" + +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Ù…ÙتوØ" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "تØديث قاعدة الإشراÙ" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "اÙØªØ Ø§Ù„ØµÙØØ© الشخصية" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "إطّلع عليه على ميوزيك براينز" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "اÙØªØ Ø§Ù„ØµÙØØ© الشخصية" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "اÙØªØ Ø§Ù„ØµÙØØ© الشخصية" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "اÙØªØ Ù…ÙˆÙ‚Ø¹ الويب" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "أو قم بتخصيص قاعدتك" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "الترتيب" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "الترتيب" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "اتجاه الترتيب" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "المالك" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "الصÙØØ© غير موجودة" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "الصÙØØ© غير موجودة !" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" -msgstr "" +msgstr "تتابÙع الصÙØات" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "كلمة السر" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "تم تØديث كلمة السر" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "تم تØديث كلمة السر بنجاØ" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "ألبÙØ« المَقطَع" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "إيقاÙ/تشغيل المقطع الØالي" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "تم توقيÙÙ‡ مؤقتا" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "معلّق" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "ÙÙŠ انتظار التسريØ" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "الملÙات المعلّقة" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "طلبات المتابَعة المعلَّقة" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "الملÙات المعلّقة" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "الملÙات المعلّقة" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "الصّلاØيّات" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "الصّلاØيّات" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "غنّي" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "تشغيل الكل" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "إعز٠كاÙØ© الألبومات" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "إعز٠التالي" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "شغّÙÙ„ المَقطَع التالي" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "إعز٠الآن" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "شغّÙÙ„ المَقطَع السابق" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "شغّÙÙ„ هذا المَقطَع" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "إعز٠المَقطَع" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "تشغيل…" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "قائمة المَقاطÙع" #: front/src/views/playlists/Detail.vue:12 +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "" @@ -1844,76 +3190,124 @@ msgstr[4] "" msgstr[5] "" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "تم إنشاء قائمة تشغيل الموسيقى" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Ù…Øرّر قوائم تشغيل الموسيقى" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "إسم قائمة المَقاطÙع" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "تم تØديث قائمة تشغيل الموسيقى" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "مدى رؤية القائمة" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "قوائم المَقاطÙع" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "قوائم المَقاطÙع" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "قوائم تشغيل الموسيقى ØŸ متوÙّرة لدينا" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "يرجى التأكّد Ù…ÙÙ† صØØ© الكلمة السرية" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "الرجاء التأكّد Ù…ÙÙ† صØØ© اسم المستخدÙÙ… Ùˆ الكلمة السرية" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "نسق PNG أو GIF أو JPG. الØجم الأقصى 2 ميغابيت. سيتم تغيير Øجمها إلى 400×400 بكسل." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "تتابÙع الصÙØات" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +#, fuzzy +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" +msgstr "إخÙاء الØساب أو Ù…Øتوى النطاق Ù…ÙÙ† الجميع باستثناء المتابÙعين." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "معاينة" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "المَقطَع السابق" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "خطأ أثناء المسØ" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "واصل" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "المواصلة إلى صÙØØ© تسجيل الدخول" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "جار٠العمل" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "اÙØªØ Ø§Ù„ØµÙØØ© الشخصية" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1922,294 +3316,557 @@ msgstr "جار٠العمل" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" -msgstr "" +msgstr "تÙريغ" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" -msgstr "" +msgstr "تنظي٠الملÙات الخاطئة ÙˆØØ°Ùها؟" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "هل تريد إزالة الملÙات المعلّقة؟" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "هل تريد إزالة الملÙات المتخطاة؟" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "قائمة الإنتظار" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "تم خلط قائمة الإنتظار !" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "الإذاعة" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Ù…ÙنشÙئ الإذاعات Ùˆ الراديو" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "تم إنشاء الإذاعة" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "إسم الإذاعة" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "تم تØديث الإذاعة" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "الإذاعات" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "الإذاعات" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "السبب" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" -msgstr "" +msgstr "الطلبات الواردة لمتابعة المكتبات" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "الرسائل الواردة" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "تمت إضاÙتها مؤخرا" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "تمت إضاÙتها مؤخرا" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "تمت إضاÙتها إلى المÙضلة Øديثا" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "مَقاطÙع أستÙÙ…Ùع إليها مؤخرا" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "إنعاش" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "إنعاش" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" +msgstr "تØديث معلومات العÙقدة" + +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "تØديث معلومات العÙقدة" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "تØديث Ù…Øتوى الجدول" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Ù…Ùسجّل منذ %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "إنّ التسجيلات Ù…Ùغلَقة Øاليًا على هذا الخادوم، يلزمك رمز دعوة للتسجيل Ùيه." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "مستخدÙÙ… عادي" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "رÙض" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "ارÙض الوسائط" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "تم رÙضه" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "إعادة Ù…Øاولة الإستيراد" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "آخÙر زيارة" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "المكتبات البÙعدية" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "" #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "ØØ°Ù" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Øذ٠الصورة الرمزية" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Øذ٠الصورة الرمزية" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "ØØ°Ù Ù…ÙÙ† المÙضلة" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." msgstr "" #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." msgstr "" #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." msgstr "" -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "طلب كلمة سرية جديدة" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "متأكد Ù…ÙÙ† أنك تريد إعادة طلب كلمة سرية جديدة لواجهة برمجة تطبيقات صاب سونيك API ØŸ" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "طلب كلمة سرية" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "إعادة تعيين كلمتك السرية" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "إعادة Ù…Øاولة الإستيراد" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "عدد نتائج البØØ« ÙÙŠ كل صÙØØ©" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "العودة إلى صÙØØ© تسجيل الدخول" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "عرض الملÙات" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "القاعدة" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "اØÙظ" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "بدأ الإستكشاÙ" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "قم Ø¨Ø§Ù„Ù…Ø³Ø Ø§Ù„Ø¢Ù†" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "تصاعدي" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Ø§Ù„Ù…Ø³Ø Ù…Ø¹Ù„Ù‚" - -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "تم مسØها" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "تمت عملية Ø§Ù„Ù…Ø³Ø Ø¨Ø£Ø®Ø·Ø§Ø¡" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "جار٠المسØ… (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "البØØ«" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "البØØ« عن مكتبة بÙعدية" +#: front/src/components/manage/library/EditsCardList.vue:211 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "البØØ« Øسب العنوان أو إسم Ùنان أو نطاق…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "البØØ« عبر اسم نطاق أو مستخدÙÙ… أو نبذة…" + +#: front/src/components/manage/library/UploadsTable.vue:241 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "البØØ« عبر اسم نطاق أو مستخدÙÙ… أو نبذة…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "البØØ« عبر اسم نطاق أو مستخدÙÙ… أو نبذة…" + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "البØØ« Øسب العنوان أو إسم Ùنان أو ألبوم…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "البØØ« Øسب العنوان أو إسم Ùنان أو ألبوم…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" msgstr "البØØ« عبر اسم نطاق أو مستخدÙÙ… أو نبذة…" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" msgstr "البØØ« باستخدام اسم…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "البØØ« Øسب العنوان أو إسم Ùنان أو ألبوم…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "البØØ« Øسب العنوان أو إسم Ùنان أو نطاق…" - #: front/src/components/manage/users/InvitationsTable.vue:153 +#, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "البØØ« باسم مستخدÙÙ… أو عنوان بريد إلكتروني أو رمز…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "البØØ« باسم مستخدÙÙ… أو عنوان بريد إلكتروني أو إسم…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "البØØ« عن Ùنانين أو ألبومات أو مَقاطÙع صوتية…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "البØØ« عن بعض Ù…ÙÙ† الموسيقى" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "البØØ« ÙÙŠ lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "البØØ« ÙÙŠ ويكيبيديا" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "القائمة الثانوية" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "الأقسام" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "إختيار عامل تصÙية" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "" @@ -2219,39 +3876,48 @@ msgstr[3] "" msgstr[4] "" msgstr[5] "" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "تØديد الصÙØØ© الØالية Ùقط" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "الإعدادات" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "تم تØديث الإعدادات" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "تم تØديث الإعدادات بنجاØ." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "رابط المشاركة" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"قم بمشاركة هذا الرابط مع مستخدمين آخرين ليتمكنوا Ù…ÙÙ† طلب الوصول إلى مكتبتك." +msgstr "قم بمشاركة هذا الرابط مع مستخدمين آخرين ليتمكنوا Ù…ÙÙ† طلب الوصول إلى مكتبتك." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "رابط المشاركة" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "اعرض %{ count } مقاطع" @@ -2262,6 +3928,7 @@ msgstr[4] "اعرض %{ count } مَقاطÙع" msgstr[5] "اعرض %{ count } مَقاطÙع" #: front/src/components/audio/artist/Card.vue:30 +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "" @@ -2271,733 +3938,1369 @@ msgstr[3] "" msgstr[4] "" msgstr[5] "" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "عرض اختصارات لوØØ© المÙØ§ØªÙŠØ Ø§Ù„Ù…ØªÙˆÙّرة" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "عرض الإشعارات المقروءة" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "إظهار/إخÙاء الكلمة السرية" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "عرض النتائج %{ start }-%{ end } Ù…ÙÙ† %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "خلط قائمة الإنتظار" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "خلط قائمة الإنتظار" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "التسجيل" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "التسجيل" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "تاريخ التسجيل" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "كتم النشاط" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "كتم الإشعارات" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +#, fuzzy +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "الØجم" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "الØجم" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "تمّ تجاهله" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "الملÙات التي تمّ تجاهلها" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "البرمجيات" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "المعذرة، إنّ الصÙØØ© التي قمت بطلبها غير موجودة :" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Ø´Ùرة المصدر" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "عضو ÙÙŠ الÙريق" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "إبدأ" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "إيقا٠الإذاعة" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "الإØصائيات" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" msgstr "" -#: front/src/components/library/FileUpload.vue:86 +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "الØالة" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "الØالة" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "الØالة" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "الØالة" + #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "الØالة" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "إيقاÙ" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "الØالة" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "إيقا٠الإذاعة" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "إرسال" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "صاب سونيك" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "الكلمة السرية لواجهة برمجة التطبيقات صاب سونيك Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "لا يمكننا تØميل هذا المَقطَع" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "لا يمكننا تØميل هذا المَقطَع" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "لا يمكننا تØميل هذا المَقطَع" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "الخيارات المتاØØ©" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "الملخص" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "المنتدى" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "مزامنة التغييرات مع الخادم…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "تم نسخ النص إلى الØاÙظة!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Øسنًا الأمر سهل : Ø£Øببنا غرو٠شارْك Ùˆ أردنا تصميم مشروع Ø£Øسَن منه بكثير." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "تم تصميم شعار Ùانك وايل funkwhale بÙضل Ùˆ كَرَم Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "سو٠يتم Øذ٠المكتبة Ùˆ كل ما تØتويه Ù…ÙÙ† مقاطÙع. لا يمكن الغاء هذا الإجراء." + +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -"سو٠يتم Øذ٠المكتبة Ùˆ كل ما تØتويه Ù…ÙÙ† مقاطÙع. لا يمكن الغاء هذا الإجراء." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "الملÙات الموسيقية التي هي ÙÙŠ صدد الإرسال موسومة بطريقة صØÙŠØØ©:" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." -msgstr "" +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "سيتم تشغيل المقطَع اللاØÙ‚ تلقائيا خلال بضع ثوان…" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "المنصّة مجانية Ùˆ Ù…ÙتوØØ© المصدر، بإمكانكم تنصيبها Ùˆ تعديلها كما ÙŠØلو لكم دون قيود" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "تم إنشاء قائمة تشغيل الموسيقى" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "لا يمكن الغاء هذا الإجراء." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "واجهة برمجة تطبيقات صاب سونيك غير متوÙرة غلى مثيل خادوم Ùانك وايل الØالي." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "لا يمكننا إضاÙØ© المَقطَع إلى قائمة التشغيل" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "لا يمكن الغاء هذا الإجراء." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "نسق ملÙات الموسيقى المÙرسَلة يجب أن تكون OGG أو Flac أو MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "هناك عدة أساليب لجلب Ù…Øتويات جديدة Ùˆ عرضها هنا." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "لا يمكن الغاء هذا الإجراء." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "هذا الألبوم متوÙر على المكتبات التالية:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "هذا الÙنان متوÙر على المكتبات التالية:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "مثيل الخادوم هذا ÙŠÙØªÙŠØ Ù…Ø³Ø§ØØ© تخرين تÙقدَّر بـ %{quota} لكل مستخدÙÙ…." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "هذا أنت !" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "تØتوي هذه المكتبة على الموسيقى الخاصة بي، أتمنى أنها ستلقَى إعجابك." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" msgstr "" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." msgstr "" -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "" -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "تم ارسال المقطع ولكن لم تتم معالجته بعد على الخادم" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "إنّ المقطع متوÙّر Ù…ÙÙ† قبل ÙÙŠ Ø¥Øدى مكتباتك" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "هذا المقطع متوÙر كذلك على المكتبات التالية:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "سو٠يؤدي ذلك إلى الØذ٠الكÙلّي لقائمة التشغيل هذه Ùˆ لا ÙŠÙمكن إلغاء العملية Ùˆ العودة." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "سو٠يؤدي ذلك إلى الØذ٠الكÙلّي لهذه الإذاعة Ùˆ لا ÙŠÙمكن إلغاء العملية Ùˆ العودة." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "" -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" -"ذلك سو٠يؤدي إلى Øذ٠بياناتك المØلية نهائيا Ùˆ إخراجك. أمتأكد أنك ترغب ÙÙŠ " -"المواصلة؟" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." +msgstr "سيؤدي ذلك إلى إخراجك Ù…ÙÙ† الأجهزة الØالية التي تستخدم هذه الكلمة السرية." + +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "سو٠يؤدي ذلك إلى الØذ٠الكÙلّي لقائمة التشغيل هذه Ùˆ لا ÙŠÙمكن إلغاء العملية Ùˆ العودة." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." msgstr "" -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/playlists/Editor.vue:54 +#, fuzzy +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." -msgstr "" +msgstr "سو٠يؤدي ذلك إلى الØذ٠الكÙلّي لقائمة التشغيل هذه Ùˆ لا ÙŠÙمكن إلغاء العملية Ùˆ العودة." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "العنوان" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Title" msgstr "العنوان" +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "العنوان" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "الØجم الإجمالي" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "الØجم الإجمالي للملÙات المتوÙّرة ÙÙŠ هذه المكتبة" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "العدد الإجمالي للمستخدÙمين" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "المَقطَع" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "إنّ المقطع متوÙّر Ù…ÙÙ† قبل ÙÙŠ Ø¥Øدى مكتباتك" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "المَقطَع" + +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" -#: front/src/components/library/Track.vue:85 +#: front/src/views/admin/library/TrackDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "اسم المَقطَع" + +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "معلومات عن المَقطَع" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "مقطع يناسب عامل التصÙية" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "اسم المَقطَع" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "المَقاطÙع" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "مَقاطÙع" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "المَقاطÙع" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "مَقاطÙع لهذا الÙنان" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "مَقاطÙع تم الإعجاب بها" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "مَقاطÙع أستÙÙ…Ùع إليها" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "مقطع يناسب عامل التصÙية" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "النوع" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "النوع" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "تØديث قاعدة الإشراÙ" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "إلغاء المتابعة" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "أتريد إلغاء متابعة هذه المكتبة؟" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "لسوء الØظ، لم يأخذ أصØاب مثيل الخادوم هذا الوقت الكاÙÙŠ لاستكمال هذه الصÙØØ©." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "موسيقى بلا Øدود" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "إلغاء الكتم" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "تØديث" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "تØديث قائمة المَقاطÙع الموسيقية" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "تØديث الصورة الرمزية" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "تØديث المكتبة" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "تØديث قاعدة الإشراÙ" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "تØديث قائمة المَقاطÙع الموسيقية" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "تØديث الإعدادات" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "قم بتØديث كلمتك السرية" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "أرسل" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "إرسال صورة رمزية جديدة" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "إرسال Ù…Øتوى صوتي" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "تاريخ التØميل" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "تاريخ التØميل" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "تم رÙض الإرسال، تØقق أن Øجم المل٠ليس ضخما Ùˆ أنّ مساØØ© التخرين Ø§Ù„Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ كاÙية" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"تم رÙض الإرسال، تØقق أن Øجم المل٠ليس ضخما Ùˆ أنّ مساØØ© التخرين Ø§Ù„Ù…Ø³Ù…ÙˆØ Ø¨Ù‡Ø§ " -"كاÙية" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." msgstr "" -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "إرسال مقاطع موسيقية جديدة" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Øصة التØميل" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" +msgstr "انتهت مهلة الإرسال، الرجاء إعادة المØاولة" + +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." msgstr "" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "تم تØميلها" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "عملية الإرسال جارية" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "الإرسال جارÙ…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "المÙرسَلة" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "المÙرسَلة" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "المÙرسَلة" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "إستخدم مثيل خادوم آخَر" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "استخدم هذه الإستمارة لطلب إعادة ضبط كلمة المرور. سنرسل بريدا الكترونيا إلى العنوان المعين مرÙوقا بتعليمات لإعادة ضبط كلمتك السرية." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Ù…Ùستخدَم" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "المستخدÙÙ…" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "نشاط المستخدÙÙ…" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "مكتبات المستخدÙÙ…" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "إذاعات المستخدÙمين" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "إسم المستخدÙÙ…" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "إسم المستخدÙÙ… أو عنوان البريد الإلكتروني" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "مستخدÙÙ…" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "المستخدÙمون" #: front/src/components/Footer.vue:29 +#, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "لاستخدام Ùانك وايل" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "الإصدار %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "عرض الملÙات" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "إطّلع عليه على ميوزيك براينز" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "المشاهدة" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "المشاهدة: كل Ù…ÙŽÙ† هم على مثيل الخادوم هذا" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "العرض: للجميع، بما ÙÙŠ ذلك لمثيلات الخوادم الأخرى" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "المشاهدة: لا Ø£Øد غيري" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "المشاهدة" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" -msgstr "" +msgstr "مستوى الصوت %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "لا يمكننا إضاÙØ© المَقطَع إلى قائمة التشغيل" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "لا يمكننا إنشاء قائمة التشغيل" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "لا يمكننا إنشاء Øسابك" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "لا يمكننا تØميل هذا المَقطَع" +#: front/src/components/federation/FetchButton.vue:69 +#, fuzzy +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "جار٠تØميل Ù…Ùضلاتك…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "تعذر علينا تسجيل دخولك" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "تعذّر علينا ØÙظ صورتك الرمزية" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "تعذّر علينا ØÙظ إعداداتك" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "لا يمكننا إنشاء Øسابك" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "لا نتعقّبك Ùˆ لا نزعجك بالإعلانات" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "ليس لدينا أية بيانات عن Øقوق التألي٠لهذا المَقطع" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "ننصØكم باستخدام برنامج Picard لهذا الغرض." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "نعتقد أنّ الاستماع إلى الموسيقى ينبغي أن يكون سهلًا." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "المعذرة، إنّ الصÙØØ© التي قمت بطلبها غير موجودة :" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "مرØبًا" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "أهلا وسهلا بك على Ùانك وايل Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "لماذا Ùانك وايل Funkwhale ØŸ" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "طول الودجات" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "عرض الودجات" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "نعم" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "نعم، أؤكد الخروج !" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "" -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "إنك بصدد إرسال موسيقى إلى مكتبتك الصوتية. قبل المواصلة، ندعوك إلى التØقق من أنّ:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "أنت متّصل Øاليا بصÙØ© %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "" -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "يمكÙنك دعوة أصدقائك Ùˆ عائلتك للإنظمام إلى مثيل خادومك للإستمتاع بموسيقاك" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "بإمكانك الآن استعمال الخدمة Ù…ÙÙ† دون قيود." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "يمكنك إنشاء قناتك الإذاعية الخاصة بك عبر هذه الواجهة Ùˆ تشغيل مقاطعك كيÙما شئت." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." -msgstr "" +msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" #: front/src/views/admin/moderation/DomainsDetail.vue:39 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." +msgstr "ليس لدينا أية بيانات عن رخصة هذا المَقطع" + +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/Sidebar.vue:158 +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "إنك تستمع إلى إذاعة" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "ربما عندك مشكلة ÙÙŠ الاتصال." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "يلزمك اختيار مثيل خادوم قصد المواصلة" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "يتوجب عليك تØديث كلمتك السرية على العملاء الآخرين اللذين يشتغلون بها." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "تم إنشاء قائمة تشغيل الموسيقى" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "إشعاراتك" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Ù…Ùضّلاتك" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "موسيقاك، كما ÙŠØلو لك" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "إشعاراتك" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "تم تØديث كلمتك السرية بنجاØ." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "تم تØديث الإعدادات" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "تتابÙع الصÙØات" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "الØقوق" + +#: front/src/components/library/AlbumBase.vue:183 +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: front/src/components/audio/PlayButton.vue:220 +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" diff --git a/front/locales/de/LC_MESSAGES/app.po b/front/locales/de/LC_MESSAGES/app.po index a65275c31b45618e209eead9635866022d16d9e0..b6a0983b722c591d64657ddca3cfc52628f5da54 100644 --- a/front/locales/de/LC_MESSAGES/app.po +++ b/front/locales/de/LC_MESSAGES/app.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-22 08:45+0000\n" -"Last-Translator: jovuit <jo.vuitton@gmail.com>\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-03-11 09:11+0000\n" +"Last-Translator: gerry_the_hat <gerd-schumann@web.de>\n" "Language-Team: none\n" "Language: de\n" "MIME-Version: 1.0\n" @@ -19,1888 +19,3232 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", von %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } von %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(leer)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Melde Dich bei Deinem Funkwhale-Konto an" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } von %{ total } ausgewählt" msgstr[1] "%{ count } von %{ total } ausgewählt" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" -msgstr[0] "%{ count } Track" -msgstr[1] "%{ count } Tracks" +msgstr[0] "%{ count } Titel" +msgstr[1] "%{ count } Titel" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" -msgstr[0] "%{ count } Track in %{ albumsCount } Alben" -msgstr[1] "%{ count } Tracks in %{ albumsCount } Alben" +msgstr[0] "%{ count } Titel in %{ albumsCount } Alben" +msgstr[1] "%{ count } Titel in %{ albumsCount } Alben" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" -msgstr[0] "%{ count } Track entspricht dem ausgewählten Filter" -msgstr[1] "%{ count } Tracks entsprechen dem ausgewählten Filter" - -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "1 Track wurde zur Wiedergabeliste hinzugefügt" -msgstr[1] "%{ count } Tracks wurden zur Wiedergabeliste hinzugefügt" +msgstr[0] "%{ count } Track entspricht den ausgewählten Filtern" +msgstr[1] "%{ count } Tracks entsprechen den ausgewählten Filtern" #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" -msgstr[0] "%{ count} Track" -msgstr[1] "%{ count} Tracks" +msgstr[0] "%{ count} Titel" +msgstr[1] "%{ count} Titel" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } von %{ max } belegt" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "" -"Das Abonnieren zur Meditahek \"%{ library }\" wurde von %{ username } " -"zugesagt" +msgstr "Die Abonnieren-Anfrage zur Meditahek \"%{ library }\" wurde von %{ username } bestätigt" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } hat deine Mediathek \"%{ library }\" abonniert" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } möchte deine Mediathek \"%{ library }\" abonnieren" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Profil von %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">Ãœber " -"%{instanceName}</translate>" #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 Album" msgstr[1] "%{ count } Alben" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 Favorit" msgstr[1] "%{ count } Favoriten" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Eine übersichtliche Mediathek" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Beim Hochladen ist ein Netzwerkfehler aufgetreten" +#: front/src/components/library/EditForm.vue:145 +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Kurze Beschreibung der Änderungen." + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "Ãœber %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "Ãœber %{ instanceName }" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "Ãœber Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Mehr erfahren" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "Ãœber diese Instanz" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Akzeptieren" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Akzeptiert" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Zugriff deaktiviert" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" -msgstr "Greife auf deine Musik mit einer übersichtliche Oberfläche zu, die sich auf das konzentriert, was wirklich wichtig ist" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Filter auswählen" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Zugriff deaktiviert" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Zu deinen Favoriten hinzufügen" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "In den Favoriten- oder Abspiellisten anderer Nutzer" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Benachrichtigungen stummschalten" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Zu einer Wiedergabeliste hinzufügen…" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Zugriff deaktiviert" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "Greife auf Deine Musik mit einer übersichtlichen Oberfläche zu, die sich auf das beschränkt, was wirklich wichtig ist" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Zugriffsdatum" +msgstr "Zugriff deaktiviert" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Konten" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Konten" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" -msgstr "Kontodaten" +msgstr "Kontoübersicht" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Kontoeinstellungen" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Kontoeinstellungen" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Kontostatus" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" -msgstr "E-Mail-Adresse des Kontos" +msgstr "Konto-E-Mail-Adresse" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +#, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Konten" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Aktion" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "Die Aktion %{ action } wurde erfolgreich für %{ count } Element gestartet" msgstr[1] "Die Aktion %{ action } wurde erfolgreich für %{ count } Elemente gestartet" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Aktionen" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Aktiv" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Aktivität" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" -msgstr "Sichtbarkeit der Aktivität" +msgstr "Sichtbarkeit der Aktivitäten" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Hinzufügen" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Domain hinzufügen" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Moderationsregel hinzufügen" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Moderationsregel hinzufügen" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Inhalte hochladen und verwalten" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" -msgstr "Inhalt hinzufügen" +msgstr "Inhalte hinzufügen" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Filter hinzufügen" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Füge Filter hinzu, um dein Radio zu personalisieren" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" -msgstr "Zur Wiedergabeliste hinzufügen" +msgstr "Zur Warteschlange hinzufügen" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Zu deinen Favoriten hinzufügen" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" -msgstr "Zu einer Playlist hinzufügen…" +msgstr "Zu einer Wiedergabeliste hinzufügen…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" -msgstr "Zur Wiedergabeliste hinzufügen" +msgstr "Zur Warteschlange hinzufügen" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" -msgstr "Zu dieser Playlist hinzufügen" +msgstr "Zur Wiedergabeliste hinzufügen" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" -msgstr "Track hinzufügen" +msgstr "Titel hinzufügen" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" -msgstr "Administration" +msgstr "Verwaltung" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Album" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Album mit einem Track, von %{ artist }" -msgstr[1] "Album mit %{ count } Tracks, von %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Album" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Alben von diesem/-r Künstler/in" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Albumname" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Albumseite" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Albumname" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Alben" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" -msgstr "Alben von diesem Künstler·in" +msgstr "Alben von diesem/-r Künstler/in" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Alles" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } von %{ total } ausgewählt" +msgstr[1] "%{ count } von %{ total } ausgewählt" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" -msgstr "Beim Speichern deiner Änderungen ist ein Fehler aufgetreten" +msgstr "Beim Speichern Deiner Änderungen ist ein Fehler aufgetreten" + +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Beim Speichern Deiner Änderungen ist ein Fehler aufgetreten" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Beim Speichern Deiner Änderungen ist ein Fehler aufgetreten" #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" -msgstr "Ein unbekannter Fehler ist aufgetreten, vielleicht ist der Server ausgeschaltet oder er kann nicht erreicht werden" +msgstr "Ein unbekannter Fehler ist aufgetreten. Eventuell ist der Server ausgeschaltet oder er kann nicht erreicht werden" + +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Aktion" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" +msgstr "Bestätigen" + +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" msgstr "Bestätigt" +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "Bestätigt und verarbeitet" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" -msgstr "Möchtest du dich wirklich ausloggen?" +msgstr "Möchtest du dich wirklich abmelden?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Künstler·in" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Künstler·in" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Künstlername" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Künstlerseite" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Künstlername" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" -msgstr "Künstler, Künstlerin, Album, Track…" +msgstr "Künstler·in, Album, Titel…" + +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Künstler·innen" #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" -msgstr "Künstler und Künstlerinnen" +msgstr "Künstler·innen" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" -msgstr "Zunehmend" +msgstr "Aufsteigend" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" -msgstr "Eine Kennwortzurücksetzung beantragen" - -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +msgstr "Zurücksetzen des Kennworts beantragen" + +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Audio-Inhalt" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Audio-Player-Tastenkombinationen" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" -msgstr "Verfügbare Playlists" +msgstr "Verfügbare Wiedergabelisten" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Profilbild" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Zurück zur Anmeldung" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Einstellungen aktualisieren" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrate" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Alles blockieren" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Alles von diesem Konto bzw. Domain blockieren. Die Verbindung mit dieser " -"Entität wird unterbrochen, und der entsprechende Inhalt (Tracks, " -"Mediatheken, Abonnement, usw.) wird gelöscht" +msgstr "Alles von diesem Konto oder dieser Domain blockieren. Das unterbindet jedwede Interaktion mit dieser Instanz, und alle zugehörigen Inhalte (Titel, Mediatheken, Abonnements, usw.) werden gelöscht" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Durchsuchen" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Die Mediathek durchsuchen" +#: front/src/components/library/Albums.vue:4 +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Alben durchsuchen" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Künstler·innen durchsuchen" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" -msgstr "Playlists durchsuchen" +msgstr "Wiedergabelisten durchsuchen" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Radios durchsuchen" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Editor" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Von %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." -msgstr "" -"Wenn du dieser Mediathek nicht mehr folgst, verlierst du den Zugriff auf " -"ihre Inhalte." - -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +msgstr "Wenn du diese Mediathek nicht mehr folgst, verlierst du den Zugriff auf alle ihre Inhalte." + +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" -msgstr "Cache-Volumen" +msgstr "Cache-Größe" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Abbrechen" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" -msgstr "Kandidaten" - -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Das Kennwort kann nicht geändert werden" +msgstr "Entsprechende Tracks" -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" -msgstr "" -"Die Datei kann nicht hochgeladen werden. Bitte prüfe, dass sie nicht zu groß " -"ist" +msgstr "Die Datei kann nicht hochgeladen werden. Bitte prüfe, dass sie nicht zu groß ist" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Sprache ändern" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" -msgstr "Mein Kennwort wechseln" +msgstr "Mein Kennwort ändern" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" -msgstr "Kennwortwechsel" +msgstr "Kennwort ändern" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" -msgstr "Kennwortwechsel" +msgstr "Kennwort ändern" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" -msgstr "Dein Kennwort wechseln?" +msgstr "Möchtest du dein Kennwort ändern?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Änderungen synchronisiert" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." -msgstr "Mit Änderung deines Kennworts wird das Kennwort für die Subsonic-API zurückgesetzt, sofern du eins erstellt hast." +msgstr "Beim Ändern deines Kennworts wird das Kennwort für die Subsonic-API zurückgesetzt, sofern du eins erstellt hast." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" -msgstr "Wenn du dein Kennwort änderst, hat dies folgende Auswirkungen" +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" +msgstr "Wenn du dein Kennwort änderst, hat dies folgende Auswirkungen:" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Chat-Raum" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Wähle deine Instanz" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Eine übersichtliche Mediathek" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Löschen" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Löschen" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" -msgstr "Playlist leeren" +msgstr "Wiedergabeliste leeren" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" -msgstr "Wiedergabeliste leeren" +msgstr "Warteschlange leeren" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" -msgstr "Einmal clicken, und Musik studenlang dank der Radios anhören" +msgstr "Klicke einmal und höre dank der eingebauten Radios studenlang Musik" + +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" -msgstr "Zum Hochladen klicken oder Dateien und Ordner hier her ziehen" +msgstr "Zum Hochladen klicken oder Dateien und Ordner hierher ziehen und ablegen" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Schließen" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Schließen" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" -msgstr "Kode" +msgstr "Code" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" -msgstr "Minimieren" +msgstr "Zuklappen" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Einstellungen" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Bestätigen" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "E-Mail-Adresse bestätigen" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Bestätigungscode" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "Filter erfolgreich hinzugefügt" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Filter auswählen" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Filter auswählen" + +#: front/src/components/auth/Settings.vue:119 +#, fuzzy +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "Mit Filtern können Inhalte verborgen werden, die nicht angezeigt werdern sollen." + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" -msgstr "Der Inhalt wurde aktualisiert und wird beim Neuladen der Seite gezeigt" +msgstr "Der Inhalt wurde aktualisiert. Um den neuesten Inhalt zu sehen, geh auf Aktualisieren" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Mitmachen" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Kopieren" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" -msgstr "Die Wiedergabeliste zur Playlist hinzufügen" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "Die Warteschlange zur Wiedergabeliste hinzufügen" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" -msgstr "Füge dieses Kode in deine HTML-Webseite ein" +msgstr "Füge diesen Code in Deine HTML-Webseite ein" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" -msgstr "Urheberecht" +msgstr "Urheberrecht" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Deine E-Mail-Adresse konnte nicht bestätigt werden" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" -msgstr "Die Fernmediathek konnte nicht abgerufen werden" - -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Ein Fehler ist bei der Verarbeitung des Tracks aufgetreten. Ãœberprüfe bitte, " -"dass er richtig verschlagwortet ist" +msgstr "Die entfernte Mediathek konnte nicht abgerufen werden" -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" -msgstr "Albumcover, Songtexte, unser Ziel ist es, alle zu haben ;)" +msgstr "Albumcover, Liedtexte - unser Ziel ist es, alle zu haben ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Erstellen" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" -msgstr "Erstelle ein Funkwhale-Konto" +msgstr "Funkwhale-Konto erstellen" + +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Eine neue Wiedergabeliste erstellen" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Eine neue Wiedergabeliste erstellen" #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" -msgstr "Eine neue Mediathek erstellen" +msgstr "Neue Mediathek anlegen" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" -msgstr "Eine neue Playlist erstellen" +msgstr "Eine neue Wiedergabeliste erstellen" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Konto erstellen" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Eine Wiedergabeliste erstellen" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Mediathek erstellen" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Mein Konto erstellen" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" -msgstr "Eine Playlist erstellen" +msgstr "Eine Wiedergabeliste erstellen" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Dein eigenes Radio erstellen" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Erstelldatum" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Aktuelles Profilbild" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Aktuelle Mediathek" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" -msgstr "Aktueller Track" +msgstr "Aktueller Titel" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Aktuelle Nutzung" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Datum" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Titelinformation" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Lautstärke verringern" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Löschen" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Wiedergabeliste löschen" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Mediathek löschen" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Moderationsregel löschen" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" -msgstr "Playlist löschen" +msgstr "Wiedergabeliste löschen" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Radio löschen" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Diese Mediathek löschen?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Diese Mediathek löschen?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Diese Mediathek löschen?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Diese Moderationsregel löschen?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Diese Moderationsregel löschen?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Diese Mediathek löschen?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Absteigend" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Beschreibung" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detail" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Beschreibung" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Details" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." -msgstr "" -"Stelle das Speichervolumen der nutzenden Person fest. Beim Leerlassen wird " -"der Standardwert dieser Instanz verwendet." +msgstr "Lege das Speichervolumen des Nutzers fest. Wird kein Wert angegeben, wird der Standardwert der Instanz verwendet." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" -msgstr "Stelle fest, wer deine Aktivität sehen kann" +msgstr "Lege fest, wer deine Aktivität sehen kann" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Zugriff deaktivieren" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Subsonic-Zugriff deaktivieren" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Subsonic-API-Zugriff deaktivieren?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Deaktiviert" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" -msgstr "Entdecke, wie du Funkwhale von anderen Apps benutzen kannst" +msgstr "Entdecke, wie du Funkwhale von anderen Apps aus benutzen kannst" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Anzeigename" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" -msgstr "Öffentlich zeigen" +msgstr "Öffentlich anzeigen" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Lade bitte keine Datei aus diesem Konto oder Domain (Audio-Inhalt, Album-" -"Cover, Profilbild, usw.). Der existierende Inhalt wird ebenfalls gelöscht." +msgstr "Lade keine Medien (Audio-Inhalt, Album-Cover, Profilbild, usw.) aus diesem Konto oder Domain. Alle existierenden Inhalte werden ebenfalls gelöscht." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" -msgstr "Möchtest du die Playlist \"%{ playlist }\" wirklich leeren?" +msgstr "Möchtest du die Wiedergabeliste \"%{ playlist }\" wirklich leeren?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" -msgstr "Aktion bestätigen?" +msgstr "Vorgang bestätigen?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" -msgstr "Möchtest du die Playlist \"%{ playlist }\" löschen?" +msgstr "Möchtest du die Wiedergabeliste \"%{ playlist }\" löschen?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Möchtest du das Radio \"%{ radio }\" löschen?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Möchtest du Inhalte von \"%{ name }\" verbergen?" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Möchtest du %{ action } auf %{ count } Element ausführen?" msgstr[1] "Möchtest du %{ action } auf %{ count } Elemente ausführen?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" -msgstr "Möchtest du die Wiedergabeliste zurückerstellen?" +msgstr "Möchtest du die vorherige Warteschlange wiederherstellen?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Dokumentation" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Domain" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Domains" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Herunterladen" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" -msgstr "Um die Playlist umzuordnen, klicke und schiebe die Tracks hin und her" +msgstr "Klicke die Titel an und verschiebe sie, um die Wiedergabeliste umzuordnen" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Dauer" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "E-Mail-Adresse bestätigt" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" -msgstr "Leicht zu bedienen" +msgstr "Benutzerfreundlich" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Bearbeiten" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Bearbeiten" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Fehler beim Ausführen des Vorgangs" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" -msgstr "Die Infos dieser Instanz bearbeiten" +msgstr "Instanzdaten bearbeiten" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Moderationsregel ändern" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Diesen Titel ändern" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Diesen Titel ändern" + +#: front/src/components/library/TrackEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Diesen Titel ändern" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Bearbeiten…" +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Bearbeiten" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Bearbeiten" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "E-Mail-Adresse" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "E-Mail-Adresse" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Integrieren" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" -msgstr "Kode integrieren" +msgstr "Code integrieren" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" -msgstr "Integriere dieses Album auf deiner Webseite" +msgstr "Bette dieses Album auf Deiner Webseite ein" + +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Bette diesen Track auf Deiner Webseite ein" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" -msgstr "Integriere diesen Track auf deiner Webseite" +msgstr "Bette diesen Track auf Deiner Webseite ein" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 -#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" -msgstr "Ausgehende Abonnements" +msgstr "Ausgehende Mediatheks-Abonnements" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Verschickte Nachrichten" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Aktiviert" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" -msgstr "Bearbeitung abschließen" +msgstr "Bearbeitung beenden" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" -msgstr "Gebe eine Mediathek-URL ein" +msgstr "Gib eine Mediathek-URL ein" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Name des Radios eingeben…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "Album Titel eingeben..." + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Künstlername eingeben…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" -msgstr "Playlist-Name eingeben…" +msgstr "Name der Wiedergabeliste eingeben…" + +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Gib die E-Mail-Adresse ein, die mit deinem Konto verknüpft ist" -#: front/src/components/auth/Signup.vue:100 +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Gib deine E-Mail-Adresse ein" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" -msgstr "Gib deinen Einladungskode ein (Groß- und Kleinschreibung wird nicht berücksichtigt)" +msgstr "Gib deinen Einladungscode ein (Groß- und Kleinschreibung wird nicht berücksichtigt)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Suche eingeben…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Benutzername eingeben" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Benutzername oder E-Mail-Adresse eingeben" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Fehler" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Fehlerbericht" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Fehlerbericht" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Ein Fehler ist aufgetreten" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" -msgstr "Fehler bei der Ausführung der Aktion" +msgstr "Fehler beim Ausführen des Vorgangs" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" -msgstr "Fehler bei der Zurücksetzung des Kennworts" +msgstr "Fehler beim Zurücksetzen des Kennworts" + +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Fehler beim Ausführen des Vorgangs" #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" -msgstr "Fehler bei der Änderung deines Kennworts" +msgstr "Fehler beim Ändern deines Kennworts" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" -msgstr "Fehler bei der Erstellung des Domains" +msgstr "Fehler beim Erstellen der Domain" + +#: front/src/components/moderation/FilterModal.vue:13 +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Fehler beim Erstellen der Regel" #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Fehler bei der Erstellung der Einladung" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" -msgstr "Fehler bei der Erstellung der Regel" +msgstr "Fehler beim Erstellen der Regel" + +#: front/src/components/auth/Authorize.vue:7 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Fehler bei der Erstellung der Einladung" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" -msgstr "Fehler beim Abrufen des Knoten" +msgstr "Fehler beim Abrufen der Knoten-Information" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Fehler beim Speichern der Einstellungen" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" -msgstr "Fehler bei der Speicherung der Einstellungen" +msgstr "Fehler beim Speichern der Einstellungen" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Fehler beim Speichern der Einstellungen" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Ein Fehler ist aufgetreten" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Fehlgeschlagene Dateien" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Alle" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" -msgstr "Alle auf dieser Instanz" +msgstr "Jerder auf dieser Instanz" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +#, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" -msgstr "Alle, auf alle Instanzen" +msgstr "Jeder, auf allen Instanzen" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Ausschließen" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Ablaufdatum" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Abgelaufen" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Abgelaufen bzw. benutzt" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" -"Begründe hier die Moderationsregel, sodass du es nicht vergisst. Je nach den " -"Einstellungen deiner Instanz können die Nutzenden die Moderationsregeln " -"ansehen, um die Moderation ggf. besser verstehen zu können." +msgstr "Erkläre, warum Du die Regel festlegst. Abhängig von Deiner Instanzkonfiguration hilft Dir das, Dich daran zu erinnern, warum Du bzgl. des Kontos oder dieser Domäne so gehandelt hast. Dies kann öffentlich eingesehen werden damit die Benutzer verstehen, welche Moderationsregeln gelten." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Fehlgeschlagen" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" -msgstr "Fehlgeschlagene Tracks:" +msgstr "Fehlgeschlagene Titel:" + +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Fehlgeschlagene Titel:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favoriten" #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favoriten" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Föderation" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Föderation" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "Feld" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Dateiname" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Dateien" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Filtername" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Beendet" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" -msgstr "Erstmal gesehen" +msgstr "Erstmals gesehen" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 -#, fuzzy +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" -msgstr "Erstmal gesehen" +msgstr "Erstmals gesehen" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Abonnieren" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" -msgstr "Entfernte Mediatheken abonnieren" +msgstr "Fernmediatheken abonnieren" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" -msgstr "Ausstehende Abonnieren-Anfrage" +msgstr "Ausstehende Abonnements-Anfrage" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Abonnenten" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Abonnenten" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Abonniert" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Aus dem Album %{ album } von %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Abonnieren" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Zugriff deaktivieren" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." -msgstr "" -"Funkwhale ist mit anderen Mediaplayer kompatibel, die die Subsonic-API " -"unterstützen." +msgstr "Funkwhale ist kompatibel mit anderen Mediaplayern, die die Subsonic-API unterstützen." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." -msgstr "Funkwhale ist sehr einfach zu benutzen." +msgstr "Funkwhale ist absolut einfach zu benutzen." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." -msgstr "Funkwhale hat es leicht gemacht, deine Lieblingsmusik anzuhören und neue Künstler und Künstlerinnen zu entdecken." +msgstr "Funkwhale macht es Dir leicht, Deine Lieblingsmusik anzuhören und neue Künstler·innen zu entdecken." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." -msgstr "Funkwhale ist kostenlos und ermöglicht eine komplette Steuerung deiner Musik." +msgstr "Funkwhale ist kostenlos und gibt dir die Kontrolle über deine Musik." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale kümmert sich um deine Musik" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Allgemeine Tastenkombinationen" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Eine neue Einladung bekommen" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Bring mich zur Mediathek" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Dank <a href=\"https://musicbrainz.org\" target=\"_blank\">MusicBrainz</a> kannst du deine Musik mit hochwertigen Metadaten verschlagworten" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Loslegen" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Hilfe bekommen" + #: front/src/components/Footer.vue:37 +#, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" -msgstr "Hilfe erhalten" +msgstr "Hilfe bekommen" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Los!" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Zurück zur Startseite" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "verborgene Künstler·innen" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." -msgstr "Den Konto- bzw. Domaininhalt allen außer Abonnenten verstecken." +msgstr "Den Konto- bzw. Domaininhalt vor allen außer Abonnenten verbergen." + +#: front/src/components/moderation/FilterModal.vue:40 +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Inhalte verbergen" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "Inhalte dieses Künsters verbergen" + +#: front/src/components/audio/Player.vue:615 +#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "Inhalte dieses Künsters verbergen" #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Start" #: front/src/components/instance/Stats.vue:36 -#, fuzzy +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" -msgstr "Musikstunden" +msgstr "Stunden Musik" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." -msgstr "" -"Der Zugriff auf Funkwhale von anderen Apps benötigt ein zusätzliches " -"Kennwort. Du kannst dieses Kennwort hier erstellen." +msgstr "Der Zugriff auf Funkwhale von diesen Apps benötigt ein zusätzliches Kennwort. Du kannst dieses Kennwort hier erstellen." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." -msgstr "Wenn die angegebene E-Mail-Adresse mit einem Benutzerkonto verknüpft ist, wirst du in Kürze eine E-Mail mit einer Anleitung zur Rücksetzung deines Passworts bekommen." +msgstr "Ist die angegebene E-Mail-Adresse mit einem Benutzerkonto verknüpft, wirst du in Kürze eine E-Mail mit einer Anleitung zum Rücksetzen deines Passworts bekommen." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Importsdatum" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importiere Musik aus mehreren Plattformen, wie YouTube oder SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Importstatus" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Importreferenz" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Importstatus" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Importstatus" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importiert" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Importsdatum" +#: front/src/components/federation/FetchButton.vue:47 +#, fuzzy +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "Es lässt sich keine Verbindung zur angegebenen Adresse herstellen" + +#: front/src/components/moderation/FilterModal.vue:26 +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Kürzlich hinzugefügt" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "In den Favoriten" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "In den Favoriten- oder Abspiellisten anderer Nutzer" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Inaktiv" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Lautstärke erhöhen" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Gebe die E-Mail-Adresse ein, die zu deinem Konto verknüpft ist" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" -msgstr[0] "Aus der Wiedergabeliste hinzufügen (%{ count } Track)" -msgstr[1] "Aus der Wiedergabeliste hinzufügen (%{ count } Tracks)" +msgstr[0] "Aus der Warteschlange hinzufügen (%{ count } Track)" +msgstr[1] "Aus der Warteschlange hinzufügen (%{ count } Tracks)" + +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Instanzdaten" #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Instanzdaten" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" -msgstr "Infos über diese Instanz" +msgstr "Informationen über diese Instanz" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radios der Instanz" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Instanzeinstellungen" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Instanzdaten" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Das Dateiformat ist ungültig. Stelle bitte sicher, dass du eine Audio-Datei hochladen möchtest. Die folgenden Dateiformate sind unterstützt: %{ extensions }" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -"Das Dateiformat ist ungültig. Stelle bitte sicher, dass du eine Audio-Datei " -"hochladen möchtest. Die folgenden Dateiformaten sind unterstützt: %{ " -"extensions }" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" -msgstr "Einladungskode" - -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Einladungskode (ggf.)" +msgstr "Einladungscode" #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Einladungen" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Bugtracker" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "Es lässt sich keine Verbindung zur angegebenen Adresse herstellen" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" -msgstr "Behalte einen Ãœberblick auf deine Lieblingsmusik" +msgstr "Behalte einen Ãœberblick über deine Lieblingsmusik" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "Tastenkombinationen" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Bekannte Konten" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Bekannte Mediatheken" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Letzte Aktivität" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Letzte Ãœberprüfung" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Letzte Bearbeitung" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Zuletzt gesehen" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Zuletzt gesehen am" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Letzte Aktualisierung:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Starten" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Mehr über diese Instanz erfahren" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" -msgstr "Leerlassen für einen beliebigen Kode" +msgstr "Leerlassen für einen zufälligen Code" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" -msgstr "Leerlassen für einen reaktionsfähiges Grafikobjekt" +msgstr "Leerlassen für ein reaktionsfähiges Widget" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Mediatheken" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Mediatheken" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Mediathek aktualisiert" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." -msgstr "Mediatheken helfen dir deine Musiksammlungen zu verwalten und zu teilen. Du kannst deine eigene Musiksammlung zu Funkwhale hochladen und mit deinen Freunden oder deiner Familie teilen." +msgstr "Bibliotheken helfen Dir deine Musiksammlungen zu organisieren und teilen. Du kannst deine eigene Musiksammlung zu Funkwhale hochladen und mit deinen Freunden und deiner Familie teilen." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Mediathek" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Mediathek erstellt" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Mediathek aktualisiert" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Mediathek gelöscht" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" -msgstr "Dateien aus der Mediathek" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" +msgstr "Mediathek-Dateien" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Mediathek aktualisiert" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Lizenz" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +#, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Abonnenten werden geladen…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Abonnenten werden geladen…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Die Mediatheken werden geladen…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Die Mediathekdaten werden geladen…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Die Benachrichtigungen werden geladen…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "Laden der entfernten Mediatheken…" +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Die Fernmediatheken werden geladen…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Die Nutzungsdaten werden geladen…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Deine Favoriten werden geladen…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +#, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Lokales Konto" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" -msgstr "Einloggen" +msgstr "Anmelden" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" -msgstr "Logge dich bei deinem Funkwhale-Konto ein" +msgstr "Melde Dich bei Deinem Funkwhale-Konto an" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" -msgstr "Ausloggen" +msgstr "Abmelden" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" -msgstr "Als %{ username } angemeldet" +msgstr "Angemeldet als %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" -msgstr "Einloggen" +msgstr "Anmelden" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" -msgstr "Kontostatus" +msgstr "Anmeldestatus" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" -msgstr "Ausloggen" +msgstr "Abmelden" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "" -"Es sieht aus, als hättest du noch keine Mediathek, höchste Zeit eine " -"anzulegen." +msgstr "Es sieht aus, als hättest du noch keine Mediathek, höchste Zeit eine anzulegen." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." -msgstr "Wiederholung deaktiviert. Schalte die Wiederholung des aktuellen Tracks beim Klicken." +msgstr "Wiederholung deaktiviert. Klicke um den aktuellen Track zu wiederholen." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." -msgstr "Wiederholung des aktuellen Tracks. Wiederhole die ganze Wiedergabeliste beim Klicken." +msgstr "Wiederholung des aktuellen Titels. Klicken um die ganze Warteschlange zu wiederholen." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." -msgstr "Wiederholung auf die ganze Wiedergabeliste. Deaktiviere die Wiederholung beim Klicken." +msgstr "Wiederholung der kompletten Warteschlange. Deaktiviere die Wiederholung durch Klicken." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Liedtext" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Hauptmenü" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Mediathek verwalten" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" -msgstr "Playlists verwalten" +msgstr "Wiedergabelisten verwalten" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Nutzende verwalten" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" -msgstr "Verwalte deine Playlists" +msgstr "Verwalte deine Wiedergabelisten" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" -msgstr "Als gelesen markieren" +msgstr "Alles als gelesen markieren" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Als gelesen markieren" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Als ungelesen markieren" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" -msgstr "Audio-Player" +msgstr "Medienspieler" + +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Mitglied seit %{ date }" #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "Mobile und desktopbasierte Anwendungen" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +#, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Moderation" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"Moderationsregeln lassen dich bestimmen, wie deine Instanz mit einem " -"bestimmten Domain oder Konto interagiert." +msgstr "Moderationsregeln lassen Dich festlegen, wie Deine Instanz mit einer bestimmten Domain oder einem bestimmten Konto interagiert." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Änderungsdatum" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Änderungsdatum" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Musik" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Stummschalten" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Aktivität stummschalten" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Benachrichtigungen stummschalten" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Mein Konto" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" -msgstr "Meine super Beschreibung" +msgstr "Meine klasse Beschreibung" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Meine fantastische Mediathek" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" -msgstr "Meine super Playlist" +msgstr "Meine super Wiedergabeliste" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Mein super Radio" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Meine Mediatheken" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "k.A." +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Name" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Name" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Name" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Neues Kennwort" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Neue Tracks werden hier automatisch hinzugefügt." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "Neuer Wert" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Nächster Track" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Nein" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Keine Erweiterungen, keine Plugins: du brauchst nur eine online Mediathek" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" -msgstr "Keine passendes Album gefunden" +msgstr "Kein passendes Album gefunden" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" -msgstr "Kein passende Künstler oder Künstlerin gefunden" +msgstr "Keinen passenden Künstler oder Künstlerin gefunden" + +#: front/src/components/library/TrackDetail.vue:14 +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" +msgstr "Kein Liedtext für diesen Titel verfügbar" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." -msgstr "Kein verfügbarer Songtext für diesen Track." +#: front/src/components/library/TrackDetail.vue:25 +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Keine Lizenzdaten für diesen Titel verfügbar" #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Keine passende Mediathek gefunden." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." -msgstr "Noch keine Benachrichtigungen." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "Keine Benachrichtigungen zum Anzeigen." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "Nichts gefunden." #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Niemand außer mir" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Niemand folgt dieser Mediathek" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Nicht verwendet" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Benachrichtigungen" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Benachrichtigungen" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Offizielle Webseite" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Altes Kennwort" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "Alter Wert" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Frei" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Moderationsregel ändern" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Profil öffnen" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Auf MusicBrainz ansehen" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Profil öffnen" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Profil öffnen" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Webseite öffnen" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "Oder die Moderationsregel anpassen" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Sortierung" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" -msgstr "Sortierung" +msgstr "Reihenfolge" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" -msgstr "Reihenfolge" +msgstr "Sortierreihenfolge" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Besitzer" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Seite nicht gefunden" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Seite nicht gefunden!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "Seitennummerierung" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Kennwort" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Kennwort aktualisiert" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Kennwort erfolgreich aktualisiert" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" -msgstr "Track pausen" +msgstr "Titel pausieren" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" -msgstr "Track pausen bzw. wiedergeben" +msgstr "Titel pausieren bzw. wiedergeben" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "Pausiert" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "Ausstehend" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" -msgstr "Steht auf Bestätigung aus" +msgstr "Bestätigung steht aus" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Ausstehende Dateien" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Ausstehende Abonnieren-Anfrage" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Ausstehende Dateien" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Ausstehende Dateien" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Permissions" msgstr "Berechtigungen" -#: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 -msgid "Play" +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Permissions" +msgstr "Berechtigungen" + +#: front/src/components/audio/PlayButton.vue:9 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Play" msgstr "Abspielen" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Alles abspielen" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Alle Alben abspielen" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Danach abspielen" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Nächster Track abspielen" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Jetzt abspielen" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Vorheriger Track abspielen" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "Spiele ähnliche Stücke" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Diesen Track abspielen" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Abspielen" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Abspielen..." + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" -msgstr "Playlist" +msgstr "Wiedergabeliste" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" -msgstr[0] "Playlist mit %{ count } Track, von %{ username }" -msgstr[1] "Playlist mit %{ count } Tracks, von %{ username }" +msgstr[0] "Wiedergabeliste mit %{ count } Track, von %{ username }" +msgstr[1] "Wiedergabeliste mit %{ count } Tracks, von %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" -msgstr "Playlist erstellt" +msgstr "Wiedergabeliste erstellt" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" -msgstr "Playlisteditor" +msgstr "Wiedergabelisteneditor" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" -msgstr "Playlistname" +msgstr "Name der Wiedergabeliste" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" -msgstr "Playlist aktualisiert" +msgstr "Wiedergabeliste aktualisiert" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" -msgstr "Sichtbarkeit der Playlist" +msgstr "Sichtbarkeit der Wiedergabeliste" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" msgid "Playlists" -msgstr "Playlists" +msgstr "Wiedergabelisten" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Playlists" +msgstr "Wiedergabelisten" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" -msgstr "Playlists? Haben wir auch!" +msgstr "Wiedergabelisten? Haben wir auch!" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" -msgstr "Prüfe bitte, dass dein Kennwort richtig ist" +msgstr "Prüfe bitte genau, ob dein Kennwort stimmt" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" -msgstr "Bitte prüfe, dass dein Benutzername und dein Kennwort miteinander stimmen" +msgstr "Bitte prüfe genau, ob deine Benutzernamen- und Kennwortkombination stimmen" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." -msgstr "PNG, GIF oder JPG. Max. 2 Mb. Das Bild wird ggf. auf 400x400 px verkleinert." +msgstr "PNG, GIF oder JPG. Max. 2 MB. Das Bild wird ggf. auf 400x400 Bildpunkte verkleinert." + +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Seitennummerierung" #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" -"Die Benachrichtigungen von diesem Konto bzw. Domain werden an allen " -"gesperrt, außer Abonnenten." +msgstr "Verhindert, dass Konto oder Domain Benachrichtigungen auslösen. Ausnahme sind Abonnenten." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "Vorschau" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Vorheriger Track" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" -msgstr "Fehler beim Scannen" +msgstr "Fehler beim Durchsuchen" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Fortfahren" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" -msgstr "Jetzt einloggen" +msgstr "Weiter zum Anmelden" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "In Bearbeitung" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Profil öffnen" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1909,1102 +3253,1958 @@ msgstr "In Bearbeitung" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" -msgstr "Säubern" +msgstr "Entfernen" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" -msgstr "Fehlerhafte Dateien entfernen?" +msgstr "Fehlerhafte Dateien löschen?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" -msgstr "Ausstehende Dateien löschen?" +msgstr "Ausstehende Dateien entfernen?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" -msgstr "Ãœbersprungene Dateien löschen?" +msgstr "Ãœbersprungene Dateien entfernen?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" -msgstr "Wiedergabeliste" +msgstr "Warteschlange" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" -msgstr "Wiedergabeliste gemischt!" +msgstr "Warteschlange gemischt!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Radioeditor" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Radio erstellt" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Radioname" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Radio aktualisiert" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "Begründung" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "Empfangene Abonnements" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Empfangene Nachrichten" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Kürzlich hinzugefügt" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" -msgstr "Neulich hinzugefügt" +msgstr "Kürzlich hinzugefügt" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" -msgstr "Neulich zu den Favoriten hinzugefügt" +msgstr "Kürzlich zu den Favoriten hinzugefügt" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" -msgstr "Neulich angehört" +msgstr "Kürzlich angehört" + +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Aktualisieren" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Aktualisieren" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Knoteninformationen aktualisieren" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Knoteninformationen aktualisieren" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "Tabelleninhalt aktualisieren" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Angemeldet seit %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." -msgstr "" -"Die Registrierung neuer Nutzenden ist geschlossen. Die Registrierung erfolgt " -"nur mit einem Einladungscode." +msgstr "Die Anmeldung auf dieser Instanz ist geschlossen. Du brauchst einen Einladungskode, um dich anmelden zu können." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" -msgstr "Standardkonto" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" +msgstr "herkömmlicher Benutzer" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" -msgstr "Ablehnen" +msgstr "Abweisen" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" -msgstr "Media abweisen" +msgstr "Medien abweisen" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" -msgstr "Abgelehnt" +msgstr "Abgewiesen" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Import erneut starten" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Zuletzt gesehen am" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Fernmediatheken" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." -msgstr "" -"Fernmediatheken gehören anderen Nutzenden im Netzwerk. Du kannst auf sie " -"zugreifen solange sie öffentlich sind oder dir Zugang gewährt wurde." +msgstr "Entfernte Mediatheken sind im Besitz anderer Nutzer des Netzwerks. Du kannst darauf zugreifen, wenn diese öffentlich sind, oder du die Erlaubnis hast." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" -msgstr "Löschen" +msgstr "Entfernen" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Profilbild löschen" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Profilbild löschen" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Aus den Favoriten entfernen" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Die importierten Tracks, die noch nicht vom Server verarbeitet wurden, " -"werden vollständig gelöscht. Du erhältst den entsprechenden Speicherplatz " -"zurück." +msgstr "Die importierten Titel, die noch nicht vom Server verarbeitet wurden, werden vollständig entfernt. Du erhältst den entsprechenden Speicherplatz zurück." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" -"Die hochgeladenen Dateien, die beim Import übersprungen wurden, werden " -"vollständig gelöscht. Du erhältst den entsprechenden Speicherplatz zurück." +msgstr "Die hochgeladenen Titel, die beim Import übersprungen wurden, werden vollständig entfernt. Du erhältst den entsprechenden Speicherplatz zurück." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" -"Die hochgeladenen Dateien, die vom Server nicht verarbeitet wurden, werden " -"vollständig gelöscht. Du erhältst den entsprechenden Speicherplatz zurück." +msgstr "Die hochgeladenen Titel, die vom Server nicht verarbeitet wurden, werden vollständig entfernt. Du erhältst den entsprechenden Speicherplatz zurück." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Neues Kennwort beantragen" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Neues Subsonic-API-Kennwort beantragen?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Kennwort beantragen" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Kennwort zurücksetzen" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Import erneut starten" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Ergebnisse pro Seite" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "Begrenze auf unbestätigte Änderungen" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" -msgstr "Zurück zur Anmeldeseite" +msgstr "Zurück zur Anmeldung" + +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Dateien ansehen" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Regel" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Speichern" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Scan gestartet" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Jetzt scannen" -#: front/src/views/content/remote/Card.vue:166 -msgid "Scan skipped (previous scan is too recent)" -msgstr "Scan übersprungen (letzter Scan ist zu jung)" +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Aufsteigend" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Scan steht aus" +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" +msgid "Scan skipped (previous scan is too recent)" +msgstr "Scan ausgelassen (der letzte Scan fand kurz zuvor statt)" -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Scan abgeschlossen" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Scan mit Fehlern abgeschlossen" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Scan läuft… (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Suchen" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Eine Fernmediathek suchen" +#: front/src/components/manage/library/EditsCardList.vue:211 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Nach Titel, Künstler·innen oder Domain suchen…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Suche nach Domain, Benutzernamen, Biografie…" + +#: front/src/components/manage/library/UploadsTable.vue:241 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Suche nach Domain, Benutzernamen, Biografie…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Suche nach Domain, Benutzernamen, Biografie…" + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Nach Titel, Künstler und Album suchen…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Nach Titel, Künstler und Album suchen…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." -msgstr "Domain, Benutzernamen, Biografie suchen…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Suche nach Domain, Benutzernamen, Biografie…" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." -msgstr "Namen suchen…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Suchen nach Name…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" -msgstr "Titel, Künstler, Album suchen…" - -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Titel, Künstler, Künstlerin oder Domain suchen…" +msgstr "Nach Titel, Künstler und Album suchen…" #: front/src/components/manage/users/InvitationsTable.vue:153 +#, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Benutzernamen, E-Mail-Adresse, Kode suchen…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Benutzernamen, E-Mail-Adresse, Namen suchen…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" -msgstr "Künstler, Künstlerinnen, Alben, Tracks suchen…" +msgstr "Nach Künstler·innen, Alben und Titeln suchen…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Musik suchen" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Auf lyrics.wikia.com suchen" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Auf Wikipedia suchen" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" -msgstr "Nebenmenü" +msgstr "Untermenü" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Abschnitte" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Filter auswählen" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "1 Element auswählen" msgstr[1] "Alle %{ total } Elemente auswählen" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Nur die aktuelle Seite auswählen" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Einstellungen" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Einstellungen aktualisiert" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Einstellungen erfolgreich aktualisiert." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Freigabe-Link" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Teile diesen Link mit anderen Nutzenden, damit diese Zugriff auf deine " -"Mediathek anfordern können." +msgstr "Teile diesen Link mit anderen Nutzenden, damit diese Zugriff auf deine Mediathek anfordern können." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Freigabe-Link" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "%{ count } weiteren Track zeigen" msgstr[1] "%{ count } weitere Tracks zeigen" #: front/src/components/audio/artist/Card.vue:30 +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "1 weiteres Album zeigen" msgstr[1] "%{ count } weitere Alben zeigen" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "Zeige alle Änderungen" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "Verfügbare Tastenkombinationen zeigen" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Gelesene Benachrichtigungen zeigen" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Passwort verstecken bzw. zeigen" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Ergebnisse %{ start } bis %{ end } von %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" -msgstr "Wiedergabeliste mischen" +msgstr "Warteschlange mischen" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" -msgstr "Wiedergabeliste mischen" +msgstr "Warteschlange mischen" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Anmeldung" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Anmeldung" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Anmeldedatum" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "Aktivität stummschalten" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Benachrichtigungen stummschalten" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +#, fuzzy +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Größe" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Größe" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" -msgstr "Ausgelassen" +msgstr "Ãœbersprungen" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" -msgstr "Ausgelassene Dateien" +msgstr "Ãœbersprungene Dateien" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Software" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Entschuldigung, die aufgerufene Seite existiert nicht:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Quellcode" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Teammember" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Starten" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Radio einschalten" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Statistik" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" -"Statistiken betrachten ausschließlich die Aktivität und den Inhalt, die von " -"deiner Instanz bekannt sind, und stellen nicht die gesamte Aktivität dieses " -"Kontos dar" +msgstr "Statistiken betrachten ausschließlich die Aktivität und den Inhalt, die von deiner Instanz bekannt sind, und stellen nicht die gesamte Aktivität dieses Kontos dar" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" -"Statistiken betrachten ausschließlich die Aktivität und den Inhalt, die von " -"deiner Instanz bekannt sind, und stellen nicht die gesamte Aktivität dieses " -"Domains dar" +msgstr "Statistiken betrachten ausschließlich die Aktivität und den Inhalt, die von deiner Instanz bekannt sind, und stellen nicht die gesamte Aktivität dieses Domains dar" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "Statistiken betrachten ausschließlich die Aktivität und den Inhalt, die von deiner Instanz bekannt sind, und stellen nicht die gesamte Aktivität dieses Kontos dar" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Status" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Status" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Status" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Status" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Status" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Abbrechen" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Status" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" -msgstr "Radio stoppen" +msgstr "Radio ausschalten" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Abschicken" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "Senden und anwenden" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Subsonic-API-Kennwort" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Dieser Track kann nicht geladen werden" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Dieser Track kann nicht geladen werden" + +#: front/src/components/library/TrackEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Dieser Track kann nicht geladen werden" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Empfehlungen" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Zusammenfassung" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "Zusammenfassung (optional)" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Hilfe-Forum" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Unterstützte Dateierweiterungen: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Synchronisierung der Änderungen auf dem Server…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Text in die Zwischenablage kopiert!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." -msgstr "So einfach ist es: wir liebten Grooveshark, doch wollen wir noch besser machen." +msgstr "So einfach ist es: wir liebten Grooveshark und wollen noch etwas viel besseres aufbauen." + +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." -msgstr "Das Funkwhale-Logo wurde mit Lieb von Francis Gading erbracht." +msgstr "Das Funkwhale-Logo wurde gestaltet und freundlicherweise zur Verfügung gestellt von Francis Gading." + +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "Die angegebene Adresse ist kein Funkwhale-Server" #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "Die Mediathek und all ihre Titel werden gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden." + +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -"Die Mediathek und all ihre Dateien werden gelöscht. Dieser Vorgang ist " -"unwiderruflich." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" -msgstr "Die Musikdateien, die du hochlädst sind richtig verschlagwortet:" +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." +msgstr "Die Musikdateien, die du hochlädst, sind richtig verschlagwortet." -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." -msgstr "Der nächste Track wird in wenigen Sekunden automatisch wiedergegeben…" +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "Der nächste Titel wird automatisch in wenigen Sekunden wiedergegeben…" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" -msgstr "Die Plattform ist kostenlos und Open-Source, du kannst sie herunterladen, installieren und anpassen ohne Sorge" +msgstr "Die Plattform ist kostenlos und Open-Source, du kannst sie installieren und anpassen ohne Beschränkung" + +#: front/src/components/playlists/Form.vue:14 +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Die Wiedergabeliste konnte nicht erstellt werden" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Der Vorschlag wird entgültig gelöscht. Das kann nicht rückgängig gemacht werden." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "Der Server läuft wahrscheinlich nicht" #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "Die Subsonic-API ist auf dieser Instanz nicht verfügbar." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "Der Vorschlag wird entgültig gelöscht. Das kann nicht rückgängig gemacht werden." + +#: front/src/components/playlists/PlaylistModal.vue:34 +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Dieser Titel kann nicht zu einer Wiedergabeliste hinzugefügt werden" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "Der Titel kann nicht geladen werden" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Der Vorschlag wird entgültig gelöscht. Das kann nicht rückgängig gemacht werden." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Die hochgeladenen Musikdateien sind im OGG-, Flac- oder MP3-Format" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "Es gibt zahlreiche Wege, neue Inhalte abzurufen und zu veröffentlichen." +msgstr "Es gibt zahlreiche Wege, neue Inhalte abzurufen und hier zu veröffentlichen." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Dieser Vorgang ist unwiderruflich." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Dieses Album ist in den folgenden Mediatheken enthalten:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" -msgstr "Dieser Künstler ist in den folgenden Mediatheken enthalten:" +msgstr "Dieser Künstler kommt in den folgenden Mediatheken vor:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" -msgstr "Diese Domain wird von besonderen Moderationsregeln verwaltet" +msgstr "Für diese Domain gelten besondere Moderationsregeln" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "Diese Instanz bietet den Nutzenden bis zu %{quota} Speicherplatz." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Das bist du!" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." -msgstr "" -"Diese Mediathek enthält meine persönliche Musik. Ich hoffe sie gefällt euch." +msgstr "Diese Mediathek enthält meine persönliche Musik. Ich hoffe sie gefällt euch." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Diese Mediathek ist privat. Dein Zugriff auf sie braucht die Zusage ihrer " -"besitzenden Person" +msgstr "Diese Mediathek ist privat. Dein Zugriff auf sie benötigt die Genehmigung des Eigentümers / der Eigentümerin" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "Diese Mediathek ist öffentlich. Du kannst dem Inhalt frei zugreifen" +msgstr "Diese Mediathek ist öffentlich. Du kannst auf den Inhalt ohne Einschränkungen zugreifen" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "Durch diese Aktion können mehrere Elemente betroffen sein. Ãœberlege, ob du es wirklich so willst." + +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." msgstr "" -"Durch diese Aktion können mehrere Elemente betroffen werden, prüfe bitte " -"nach, das du es wirklich willst." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Diese Referenz wird verwendet, um importierte Dateien zu gruppieren." -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Bei der Verarbeitung des Titels ist ein Fehler aufgetreten. Ãœberprüfe bitte, dass er richtig verschlagwortet ist" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Der Titel wurde hochgeladen, er ist aber noch nicht vom Server verarbeitet worden" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Eine deiner Mediatheken enthält bereits diesen Titel" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" -msgstr "Dieser Track ist in keiner deiner verfügbaren Mediatheken enthalten" +msgstr "Dieser Titel ist in keiner deiner verfügbaren Mediatheken enthalten" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Dieser Track ist in den folgenden Mediatheken enthalten:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." -msgstr "Die Playlist wird dauerhaft gelöscht und kann nicht zurückerstellt werden." +msgstr "Die Wiedergabeliste wird dauerhaft gelöscht und kann nicht zurückerstellt werden." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." -msgstr "Das Radio wird dauerhaft gelöscht und kann nicht zurückerstellt werden." +msgstr "Das Radio wird dauerhaft gelöscht und kann nicht wiederhergestellt werden." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Der Zugriff zur Subsonic-API von diesem Konto wird deaktiviert." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" -"Deine lokalen Daten werden gelöscht, und du wirst abgemeldet. Möchtest du " -"fortfahren?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." -msgstr "Du wirst von den bestehenden Geräten abgemeldet, die dieses Passwort nutzen." +msgstr "Du wirst von den vorhandenen Geräten abgemeldet, die dieses Passwort nutzen." + +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Die Wiedergabeliste wird dauerhaft gelöscht und kann nicht zurückerstellt werden." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." -msgstr "Alle Tracks dieses Playlists werden dauerhauft gelöscht und können nicht zurückerstellt werden." +msgstr "Damit werden alle Titel von der Playlist gelöscht. Das kann nicht rückgängig gemacht werden." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Title" msgstr "Titel" +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "Titel" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "Titel" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "Wähle bitte nun die Funkhwhale-Instanz aus, zu der Du dich verbinden möchtest. Gib die Adresse direkt an, oder wähle einen der Vorschläge aus." + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" -msgstr "Wiedergabelistewiederholung umschalten" +msgstr "Warteschlangenwiederholung umschalten" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Gesamtvolumen" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" -msgstr "Gesamtvolumen dieser Mediathek" +msgstr "Gesamtgröße der Dateien in dieser Mediathek" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Gesamtanzahl der Nutzenden" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Track" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "Eine deiner Mediatheken enthält bereits diesen Track" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Track" -#: front/src/components/library/Track.vue:85 -msgid "Track information" -msgstr "Trackinformation" +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Passender Track" +#: front/src/views/admin/library/TrackDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Trackname" -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" +msgid "Track information" +msgstr "Titelinformation" + +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Trackname" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "" -"Der Track wurde hochgeladen, er ist aber noch nicht vom Server verarbeitet " -"worden" - -#: front/src/components/instance/Stats.vue:54 -msgid "tracks" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" msgstr "Tracks" -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/instance/Stats.vue:54 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Tracks" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" -msgstr "Tracks von diesem Künstler oder Künstlerin" +msgstr "Titel von diesem Künstler oder Künstlerin" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Tracks in den Favoriten" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "Angehörte Tracks" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Titel Auswahl Filter" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Typ" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Typ" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "Von einer Moderationsregel verwaltet" +msgstr "Unterliegt einer Moderationsregel" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Abonnement beenden" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" -msgstr "Das Abonnement dieser Mediathek beenden?" +msgstr "Das Abonnement an dieser Mediathek beenden?" + +#: front/src/components/About.vue:17 +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." +msgstr "Leider haben die Eintümer dieser Instanz diese Seite noch nicht fertig gestellt." + +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" msgstr "" -"Leider wurde diese Seite von den Verwaltenden dieser Instanz noch nicht " -"ausgefüllt." #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" -msgstr "Unbegrenzte Musik" +msgstr "Unbegrenzt Musik anhören" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Stummschaltung aufheben" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Aktualisieren" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Wiedergabeliste aktualisieren" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Profilbild aktualisieren" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Mediathek aktualisieren" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Moderationsregel aktualisieren" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" -msgstr "Playlist aktualisiert" +msgstr "Wiedergabeliste aktualisieren" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Einstellungen aktualisieren" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Dein Kennwort aktualisieren" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Hochladen" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Neues Profilbild hochladen" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Audio-Inhalte hochladen" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Hochladedatum" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Hochladedatum" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "Hochladen abgelehnt. Bitte prüfe, dass die Datei nicht zu groß ist, und dass du noch über genügenden Speicherplatz verfügst" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"Hochladen abgelehnt. Bitte prüfe, dass die Datei nicht zu groß ist, und dass " -"du noch über genügenden Speicherplatz verfügst" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" -"Lade Musikdatei (MP3, OGG, Flac, usw.) von deiner eigenen Mediathek direkt " -"von deinem Browser hoch, und höre sie hier an." +msgstr "Lade Musikdatei (MP3, OGG, Flac, usw.) von deiner eigenen Mediathek direkt im Browser hoch, und höre sie hier an." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Neue Tracks hochladen" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Speichervolumen" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Hochladezeit abgelaufen. Bitte versuche es erneut" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Hochgeladen" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "Hochladen läuft" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "Hochladen läuft…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "Hochladen" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Hochladen" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "Hochladen" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Eine andere Instanz benutzen" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." -msgstr "Mit diesem Formular kannst du ein neues Kennwort beantragen. Wir schicken dir eine E-Mail an die angegebene Adresse mit den Anleitungen." +msgstr "Mit diesem Formular kannst du ein neues Kennwort beantragen. In Kürze erhältst du von uns eine Nachricht an die angegebene E-Mail-Adresse mit den Anleitungen." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Mit dieser Einstellung kannst du die Moderationsregel temporär umschalten, " -"ohne sie zu löschen." +msgstr "Mit dieser Einstellung kannst du die Moderationsregel temporär umschalten, ohne sie zu löschen." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Verwendet" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" -msgstr "Nutzende" +msgstr "Benutzer" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Aktivität der Nutzenden" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" -msgstr "Mediatheken der nutzenden Person" +msgstr "Mediatheken der Nutzenden" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" -msgstr "Radios der Nutzende" +msgstr "Radios der Nutzenden" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Benutzername" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Benutzername oder E-Mail-Adresse" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "Nutzende" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Nutzende" #: front/src/components/Footer.vue:29 +#, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "Funwhale nutzen" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" -msgstr "Version (%{ version })" +msgstr "Version %{ version }" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Dateien ansehen" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Auf MusicBrainz ansehen" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Sichtbarkeit" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Sichtbarkeit: alle auf dieser Instanz" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Sichbarkeit: alle, auch auf anderen Instanzen" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Sichtbarkeit: niemand außer mir" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Sichtbarkeit" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Band %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Dieser Track kann nicht zu einer Playlist hinzugefügt werden" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Die Playlist kann nicht erstellt werden" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Dein Konto kann nicht erstellt werden" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Dieser Track kann nicht geladen werden" +#: front/src/components/federation/FetchButton.vue:69 +#, fuzzy +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Deine Favoriten werden geladen…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Wir können dich nicht einloggen" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Dein Profilbild kann nicht gespeichert werden" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Deine Einstellungen können nicht gespeichert werden" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Dein Konto kann nicht erstellt werden" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Weder verfolgen wir dich noch stören wir dich mit Werbung" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Es sind keine Urheberechtsdaten für diesen Track verfügbar" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Es sind keine Lizenzdaten für diesen Track" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Zu diesem Zweck wird Picard empfohlen." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." -msgstr "Weil Musik hören sollte leicht sein." - -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Entschuldigung, die aufgerufene Seite existiert nicht:" +msgstr "Wir denken, Musik hören sollte möglichst einfach sein." -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Willkommen" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Willkommen auf Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Warum Funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Grafikobjektshöhe" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "Grafikobjektsbreite" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Ja" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Ja, logge mich aus!" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "Du kannst deine Mediathek mit Anderen teilen, auch wenn sie privat ist." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Die Musik wird bald zu deiner Mediathek hochgeladen. Prüfe bitte vorab, dass:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Du bist als %{ username } angemeldet" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "Die lädst kein urheberrechtlich geschütztes Material in eine öffentliche Mediathek hoch. Wenn doch, missachtest Du das Gesetz" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." -msgstr "Abonniere dich zu Mediatheken und greif neuer Musik zu. Öffentliche Mediatheke können sofort abonniert werden; das Abonnieren einer privaten Mediathek benötigt die Bestätigung des Besitzers." +msgstr "Folge Mediatheken von anderen Hörern, um auf neue Musik zu stoßen. Öffentlichen Mediatheken kann sofort gefolgt werden, wohingegen geschlossene Mediatheken die Erlaubnis von ihrem Eigentümer / von ihrer Eigentümerin bedürfen." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" -msgstr "Du kannst Freunde und Familie auf deiner Instanz einladen, sodass sie deine Musik genießen können" +msgstr "Du kannst Freunde und Deine Familie auf Deine eigene Instanz einladen, sodass sie Deine Musik genießen können" + +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Du kannst jetzt den Service unbegrenzt nutzen." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Dank dieser Schnittstelle kannst du dein eigenes Radio aufbauen, das die entsprechenden Tracks abspielt." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." -msgstr "Damit kannst du deine Playlists und Musik offline genießen, zum Beispiel auf deinem Smartphone bzw. Tablett." +msgstr "Damit kannst du deine Wiedergabelisten und Musik offline genießen, zum Beispiel auf deinem Smartphone bzw. Tablett." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Du hast keine Moderationsregeln in Kraft für dieses Konto." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Du hast keine Moderationsregeln in Kraft für dieses Konto." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Du hast keine Moderationsregeln in Kraft für dieses Konto." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Du hast keine Moderationsregeln in Kraft für diese Domain." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "" + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Du hörst gerade ein Radio an" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "Du kannst Verbindungsproblemen erfahren." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Zum Fortfahren wähle bitte eine Instanz aus" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Du wirst von dieser Sitzung ausgeloggt und du musst dich mit deinem neuen Kennwort einloggen" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." -msgstr "Du musst das Kennwort auf deine verbundenen Geräte anpassen, die dieses Kennwort verwenden." +msgstr "Du musst dann das Kennwort auf allen Deiner verbundenen Geräte aktualisieren." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Dein Avatar kann nicht gespeichert werden" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Deine Benachrichtigungen" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "Dein Avatar kann nicht gespeichert werden" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "Die Änderung wurde erfolgreich übertragen." + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Deine Favoriten" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" -msgstr "Deine Musik, deine Weise" +msgstr "Deine Musik, so wie du willst" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Deine Benachrichtigungen" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "Dein Kennwort kann nicht geändert werden" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Dein Kennwort wurde erfolgreich aktualisiert." +#: front/src/components/auth/Settings.vue:14 +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Einstellungen konnten nicht aktualisiert werden" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" -msgstr "Dein Subsonic-Kennwort wird mit einem neuen beliebigen Kennwort ersetzt, und du wirst auf allen aktuellen verbundenen Geräten ausgeloggt, die das alte Kennwort nutzen" +msgstr "Dein Subsonic-Kennwort wird mit einem neuen zufälligen Kennwort ersetzt. Du wirst auf allen aktuell verbundenen Geräten ausgeloggt, die noch das alte Kennwort nutzen" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Seitennummerierung" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Urheberrecht" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Album mit %{ count } Track, von %{ artist }" +msgstr[1] "Album mit %{ count } Tracks, von %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "1 Titel wurde zur Warteschlange hinzugefügt" +msgstr[1] "%{ count } Titel wurden zur Warteschlange hinzugefügt" diff --git a/front/locales/en_GB/LC_MESSAGES/app.po b/front/locales/en_GB/LC_MESSAGES/app.po new file mode 100644 index 0000000000000000000000000000000000000000..0300026f43472ca639c7546acb08af968ca335d8 --- /dev/null +++ b/front/locales/en_GB/LC_MESSAGES/app.po @@ -0,0 +1,2988 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the front package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: front 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-01-29 10:47+0100\n" +"PO-Revision-Date: 2019-03-07 12:44+0000\n" +"Last-Translator: gerry_the_hat <gerd-schumann@web.de>\n" +"Language-Team: none\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 3.2.2\n" + +#: front/src/components/playlists/PlaylistModal.vue:9 +msgid "\"%{ title }\", by %{ artist }" +msgstr "\"%{ title }\", by %{ artist }" + +#: front/src/components/Sidebar.vue:24 +msgid "(%{ index } of %{ length })" +msgstr "(%{ index } of %{ length })" + +#: front/src/components/Sidebar.vue:22 +msgid "(empty)" +msgstr "(empty)" + +#: front/src/components/common/ActionTable.vue:57 +#: front/src/components/common/ActionTable.vue:66 +msgid "%{ count } on %{ total } selected" +msgid_plural "%{ count } on %{ total } selected" +msgstr[0] "%{ count } on %{ total } selected" +msgstr[1] "%{ count } on %{ total } selected" + +#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 +#: front/src/views/content/libraries/Card.vue:39 src/views/content/remote/Card.vue:26 +msgid "%{ count } track" +msgid_plural "%{ count } tracks" +msgstr[0] "%{ count } track" +msgstr[1] "%{ count } tracks" + +#: front/src/components/library/Artist.vue:13 +msgid "%{ count } track in %{ albumsCount } albums" +msgid_plural "%{ count } tracks in %{ albumsCount } albums" +msgstr[0] "%{ count } track in %{ albumsCount } albums" +msgstr[1] "%{ count } tracks in %{ albumsCount } albums" + +#: front/src/components/library/radios/Builder.vue:80 +msgid "%{ count } track matching combined filters" +msgid_plural "%{ count } tracks matching combined filters" +msgstr[0] "%{ count } track matching combined filters" +msgstr[1] "%{ count } tracks matching combined filters" + +#: front/src/components/audio/PlayButton.vue:180 +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } track was added to your queue" +msgstr[1] "%{ count } tracks were added to your queue" + +#: front/src/components/playlists/Card.vue:18 +msgid "%{ count} track" +msgid_plural "%{ count } tracks" +msgstr[0] "%{ count} track" +msgstr[1] "%{ count } tracks" + +#: front/src/views/content/libraries/Quota.vue:11 +msgid "%{ current } used on %{ max } allowed" +msgstr "%{ current } used on %{ max } allowed" + +#: front/src/components/common/Duration.vue:2 +msgid "%{ hours } h %{ minutes } min" +msgstr "%{ hours } h %{ minutes } min" + +#: front/src/components/common/Duration.vue:5 +msgid "%{ minutes } min" +msgstr "%{ minutes } min" + +#: front/src/components/notifications/NotificationRow.vue:40 +msgid "%{ username } accepted your follow on library \"%{ library }\"" +msgstr "%{ username } accepted your follow on library \"%{ library }\"" + +#: front/src/components/notifications/NotificationRow.vue:39 +msgid "%{ username } followed your library \"%{ library }\"" +msgstr "%{ username } followed your library \"%{ library }\"" + +#: front/src/components/notifications/NotificationRow.vue:41 +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } wants to follow your library \"%{ library }\"" + +#: front/src/components/auth/Profile.vue:46 +msgid "%{ username }'s profile" +msgstr "%{ username }'s profile" + +#: front/src/components/audio/artist/Card.vue:41 +msgid "1 album" +msgid_plural "%{ count } albums" +msgstr[0] "1 album" +msgstr[1] "%{ count } albums" + +#: front/src/components/favorites/List.vue:10 +msgid "1 favorite" +msgid_plural "%{ count } favorites" +msgstr[0] "1 favourite" +msgstr[1] "%{ count } favourites" + +#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/library/FileUpload.vue:227 +msgid "A network error occured while uploading this file" +msgstr "A network error occurred while uploading this file" + +#: front/src/components/About.vue:5 +msgid "About %{ instance }" +msgstr "About %{ instance }" + +#: front/src/components/Footer.vue:6 +msgid "About %{instanceName}" +msgstr "About %{instanceName}" + +#: front/src/components/Footer.vue:45 +msgid "About Funkwhale" +msgstr "About Funkwhale" + +#: front/src/components/Footer.vue:10 +msgid "About page" +msgstr "About page" + +#: front/src/components/About.vue:8 src/components/About.vue:67 +msgid "About this instance" +msgstr "About this instance" + +#: front/src/views/content/libraries/Detail.vue:48 +msgid "Accept" +msgstr "Accept" + +#: front/src/views/content/libraries/Detail.vue:40 +msgid "Accepted" +msgstr "Accepted" + +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgid "Access disabled" +msgstr "Access disabled" + +#: front/src/components/Home.vue:106 +msgid "Access your music from a clean interface that focus on what really matters" +msgstr "" +"Access your music from a clean interface that focuses on what really matters" + +#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:20 +msgid "Accessed date" +msgstr "Access date" + +#: front/src/views/admin/moderation/AccountsDetail.vue:78 +msgid "Account data" +msgstr "Account data" + +#: front/src/components/auth/Settings.vue:5 +msgid "Account settings" +msgstr "Account settings" + +#: front/src/components/auth/Settings.vue:263 +msgid "Account Settings" +msgstr "Account Settings" + +#: front/src/components/manage/users/UsersTable.vue:39 +msgid "Account status" +msgstr "Account status" + +#: front/src/views/auth/PasswordReset.vue:14 +msgid "Account's email" +msgstr "Account's email" + +#: front/src/views/admin/moderation/AccountsList.vue:3 +#: front/src/views/admin/moderation/AccountsList.vue:24 +#: front/src/views/admin/moderation/Base.vue:8 +msgid "Accounts" +msgstr "Accounts" + +#: front/src/views/content/libraries/Detail.vue:29 +msgid "Action" +msgstr "Action" + +#: front/src/components/common/ActionTable.vue:99 +msgid "Action %{ action } was launched successfully on %{ count } element" +msgid_plural "Action %{ action } was launched successfully on %{ count } elements" +msgstr[0] "Action %{ action } was launched successfully on %{ count } element" +msgstr[1] "Action %{ action } was launched successfully on %{ count } elements" + +#: front/src/components/common/ActionTable.vue:21 +#: front/src/components/library/radios/Builder.vue:64 +msgid "Actions" +msgstr "Actions" + +#: front/src/components/manage/users/UsersTable.vue:53 +msgid "Active" +msgstr "Active" + +#: front/src/views/admin/moderation/AccountsDetail.vue:199 +#: front/src/views/admin/moderation/DomainsDetail.vue:144 +msgid "Activity" +msgstr "Activity" + +#: front/src/components/mixins/Translations.vue:7 +#: front/src/components/mixins/Translations.vue:8 +msgid "Activity visibility" +msgstr "Activity visibility" + +#: front/src/views/admin/moderation/DomainsList.vue:18 +msgid "Add" +msgstr "Add" + +#: front/src/views/admin/moderation/DomainsList.vue:13 +msgid "Add a domain" +msgstr "Add a domain" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgid "Add a new moderation rule" +msgstr "Add a new moderation rule" + +#: front/src/views/content/Home.vue:35 +msgid "Add and manage content" +msgstr "Add and manage content" + +#: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgid "Add content" +msgstr "Add content" + +#: front/src/components/library/radios/Builder.vue:50 +msgid "Add filter" +msgstr "Add filter" + +#: front/src/components/library/radios/Builder.vue:40 +msgid "Add filters to customize your radio" +msgstr "Add filters to customise your radio" + +#: front/src/components/audio/PlayButton.vue:64 +msgid "Add to current queue" +msgstr "Add to current queue" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:4 +#: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgid "Add to favorites" +msgstr "Add to favourites" + +#: front/src/components/playlists/TrackPlaylistIcon.vue:6 +#: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgid "Add to playlist…" +msgstr "Add to playlist…" + +#: front/src/components/audio/PlayButton.vue:14 +msgid "Add to queue" +msgstr "Add to queue" + +#: front/src/components/playlists/PlaylistModal.vue:116 +msgid "Add to this playlist" +msgstr "Add to this playlist" + +#: front/src/components/playlists/PlaylistModal.vue:54 +msgid "Add track" +msgstr "Add track" + +#: front/src/components/manage/users/UsersTable.vue:69 +msgid "Admin" +msgstr "Admin" + +#: front/src/components/Sidebar.vue:79 +msgid "Administration" +msgstr "Administration" + +#: front/src/components/audio/SearchBar.vue:26 src/components/audio/track/Table.vue:8 +#: front/src/components/library/Album.vue:153 +#: front/src/components/manage/library/FilesTable.vue:39 +#: front/src/components/metadata/Search.vue:134 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgid "Album" +msgstr "Album" + +#: front/src/components/library/Album.vue:183 +msgid "Album containing %{ count } track, by %{ artist }" +msgid_plural "Album containing %{ count } tracks, by %{ artist }" +msgstr[0] "Album containing %{ count } track, by %{ artist }" +msgstr[1] "Album containing %{ count } tracks, by %{ artist }" + +#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:25 +msgid "Album name" +msgstr "Album name" + +#: front/src/components/library/Track.vue:27 +msgid "Album page" +msgstr "Album page" + +#: front/src/components/audio/Search.vue:19 src/components/instance/Stats.vue:48 +#: front/src/views/admin/moderation/AccountsDetail.vue:321 +#: front/src/views/admin/moderation/DomainsDetail.vue:257 +msgid "Albums" +msgstr "Albums" + +#: front/src/components/library/Artist.vue:44 +msgid "Albums by this artist" +msgstr "Albums by this artist" + +#: front/src/components/manage/users/InvitationsTable.vue:19 +#: front/src/views/content/libraries/FilesTable.vue:13 +msgid "All" +msgstr "All" + +#: front/src/components/playlists/Editor.vue:13 +msgid "An error occured while saving your changes" +msgstr "An error occurred while saving your changes" + +#: front/src/components/auth/Login.vue:10 +msgid "An unknown error happend, this can mean the server is down or cannot be reached" +msgstr "" +"An unknown error happened, this can mean the server is down or cannot be " +"reached" + +#: front/src/components/notifications/NotificationRow.vue:66 +msgid "Approve" +msgstr "Approve" + +#: front/src/components/auth/Logout.vue:5 +msgid "Are you sure you want to log out?" +msgstr "Are you sure you want to log out?" + +#: front/src/components/audio/SearchBar.vue:25 src/components/audio/track/Table.vue:7 +#: front/src/components/library/Artist.vue:137 +#: front/src/components/manage/library/FilesTable.vue:38 +#: front/src/components/metadata/Search.vue:130 +#: front/src/views/content/libraries/FilesTable.vue:55 +msgid "Artist" +msgstr "Artist" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgid "Artist name" +msgstr "Artist name" + +#: front/src/components/library/Album.vue:16 src/components/library/Track.vue:33 +msgid "Artist page" +msgstr "Artist page" + +#: front/src/components/audio/Search.vue:65 +msgid "Artist, album, track…" +msgstr "Artist, album, track…" + +#: front/src/components/audio/Search.vue:10 src/components/instance/Stats.vue:42 +#: front/src/components/library/Artists.vue:119 src/components/library/Library.vue:7 +#: front/src/views/admin/moderation/AccountsDetail.vue:313 +#: front/src/views/admin/moderation/DomainsDetail.vue:249 +msgid "Artists" +msgstr "Artists" + +#: front/src/components/favorites/List.vue:33 src/components/library/Artists.vue:25 +#: front/src/components/library/Radios.vue:44 +#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/manage/moderation/AccountsTable.vue:21 +#: front/src/components/manage/moderation/DomainsTable.vue:19 +#: front/src/components/manage/users/UsersTable.vue:19 +#: front/src/views/content/libraries/FilesTable.vue:31 +#: front/src/views/playlists/List.vue:27 +msgid "Ascending" +msgstr "Ascending" + +#: front/src/views/auth/PasswordReset.vue:28 +msgid "Ask for a password reset" +msgstr "Ask for a password reset" + +#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgid "Audio content" +msgstr "Audio content" + +#: front/src/components/ShortcutsModal.vue:55 +msgid "Audio player shortcuts" +msgstr "Audio player shortcuts" + +#: front/src/components/playlists/PlaylistModal.vue:26 +msgid "Available playlists" +msgstr "Available playlists" + +#: front/src/components/auth/Settings.vue:34 +msgid "Avatar" +msgstr "Avatar" + +#: front/src/views/auth/PasswordReset.vue:25 +#: front/src/views/auth/PasswordResetConfirm.vue:18 +msgid "Back to login" +msgstr "Back to login" + +#: front/src/components/library/Track.vue:129 +#: front/src/components/manage/library/FilesTable.vue:42 +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgid "Bitrate" +msgstr "Bitrate" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:19 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgid "Block everything" +msgstr "Block everything" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" +msgstr "" +"Block everything from this account or domain. This will prevent any " +"interaction with the entity, and purge related content (uploads, libraries, " +"follows, etc.)" + +#: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgid "Browse" +msgstr "Browse" + +#: front/src/components/Sidebar.vue:65 +msgid "Browse library" +msgstr "Browse library" + +#: front/src/components/library/Artists.vue:4 +msgid "Browsing artists" +msgstr "Browsing artists" + +#: front/src/views/playlists/List.vue:3 +msgid "Browsing playlists" +msgstr "Browsing playlists" + +#: front/src/components/library/Radios.vue:4 +msgid "Browsing radios" +msgstr "Browsing radios" + +#: front/src/components/library/radios/Builder.vue:5 +msgid "Builder" +msgstr "Builder" + +#: front/src/components/audio/album/Card.vue:13 +msgid "By %{ artist }" +msgstr "By %{ artist }" + +#: front/src/views/content/remote/Card.vue:103 +msgid "By unfollowing this library, you loose access to its content." +msgstr "By unfollowing this library, you lose access to its content." + +#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgid "Cached size" +msgstr "Cached size" + +#: front/src/components/common/DangerousButton.vue:17 +#: front/src/components/library/Album.vue:52 src/components/library/Track.vue:76 +#: front/src/components/library/radios/Filter.vue:53 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:63 +msgid "Cancel" +msgstr "Cancel" + +#: front/src/components/library/radios/Builder.vue:63 +msgid "Candidates" +msgstr "Candidates" + +#: front/src/components/auth/Settings.vue:75 +msgid "Cannot change your password" +msgstr "Cannot change your password" + +#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:224 +msgid "Cannot upload this file, ensure it is not too big" +msgstr "Cannot upload this file, ensure it is not too big" + +#: front/src/components/Footer.vue:21 +msgid "Change language" +msgstr "Change language" + +#: front/src/components/auth/Settings.vue:67 +msgid "Change my password" +msgstr "Change my password" + +#: front/src/components/auth/Settings.vue:94 +msgid "Change password" +msgstr "Change password" + +#: front/src/views/auth/PasswordResetConfirm.vue:4 +#: front/src/views/auth/PasswordResetConfirm.vue:62 +msgid "Change your password" +msgstr "Change your password" + +#: front/src/components/auth/Settings.vue:95 +msgid "Change your password?" +msgstr "Change your password?" + +#: front/src/components/playlists/Editor.vue:21 +msgid "Changes synced with server" +msgstr "Changes synced with server" + +#: front/src/components/auth/Settings.vue:70 +msgid "Changing your password will also change your Subsonic API password if you have requested one." +msgstr "" +"Changing your password will also change your Subsonic API password if you " +"have requested one." + +#: front/src/components/auth/Settings.vue:97 +msgid "Changing your password will have the following consequences" +msgstr "Changing your password will have the following consequences" + +#: front/src/components/Footer.vue:40 +msgid "Chat room" +msgstr "Chat room" + +#: front/src/App.vue:13 +msgid "Choose your instance" +msgstr "Choose your instance" + +#: front/src/components/Home.vue:64 +msgid "Clean library" +msgstr "Clean library" + +#: front/src/components/manage/users/InvitationForm.vue:37 +msgid "Clear" +msgstr "Clear" + +#: front/src/components/playlists/Editor.vue:40 +#: front/src/components/playlists/Editor.vue:45 +msgid "Clear playlist" +msgstr "Clear playlist" + +#: front/src/components/audio/Player.vue:366 +msgid "Clear your queue" +msgstr "Clear your queue" + +#: front/src/components/Home.vue:44 +msgid "Click once, listen for hours using built-in radios" +msgstr "Click once, listen for hours using built-in radios" + +#: front/src/components/library/FileUpload.vue:75 +msgid "Click to select files to upload or drag and drop files or directories" +msgstr "Click to select files to upload or drag and drop files or directories" + +#: front/src/components/ShortcutsModal.vue:20 +msgid "Close" +msgstr "Close" + +#: front/src/components/manage/users/InvitationForm.vue:26 +#: front/src/components/manage/users/InvitationsTable.vue:42 +msgid "Code" +msgstr "Code" + +#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/artist/Card.vue:33 +msgid "Collapse" +msgstr "Collapse" + +#: front/src/components/library/radios/Builder.vue:62 +msgid "Config" +msgstr "Config" + +#: front/src/components/common/DangerousButton.vue:21 +msgid "Confirm" +msgstr "Confirm" + +#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 +#: front/src/views/auth/EmailConfirm.vue:51 +msgid "Confirm your e-mail address" +msgstr "Confirm your e-mail address" + +#: front/src/views/auth/EmailConfirm.vue:13 +msgid "Confirmation code" +msgstr "Confirmation code" + +#: front/src/components/common/ActionTable.vue:7 +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "Content has been updated. Click refresh to see up-to-date content" + +#: front/src/components/Footer.vue:48 +msgid "Contribute" +msgstr "Contribute" + +#: front/src/components/audio/EmbedWizard.vue:19 +#: front/src/components/common/CopyInput.vue:8 +msgid "Copy" +msgstr "Copy" + +#: front/src/components/playlists/Editor.vue:163 +msgid "Copy queued tracks to playlist" +msgstr "Copy queued tracks to playlist" + +#: front/src/components/audio/EmbedWizard.vue:21 +msgid "Copy/paste this code in your website HTML" +msgstr "Copy/paste this code in your website HTML" + +#: front/src/components/library/Track.vue:91 +msgid "Copyright" +msgstr "Copyright" + +#: front/src/views/auth/EmailConfirm.vue:7 +msgid "Could not confirm your e-mail address" +msgstr "Could not confirm your e-mail address" + +#: front/src/views/content/remote/ScanForm.vue:3 +msgid "Could not fetch remote library" +msgstr "Could not fetch remote library" + +#: front/src/views/content/libraries/FilesTable.vue:213 +msgid "Could not process this track, ensure it is tagged correctly" +msgstr "Could not process this track, ensure it is tagged correctly" + +#: front/src/components/Home.vue:85 +msgid "Covers, lyrics, our goal is to have them all ;)" +msgstr "Covers, lyrics, our goal is to have them all ;)" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgid "Create" +msgstr "Create" + +#: front/src/components/auth/Signup.vue:4 +msgid "Create a funkwhale account" +msgstr "Create a Funkwhale account" + +#: front/src/views/content/libraries/Home.vue:14 +msgid "Create a new library" +msgstr "Create a new library" + +#: front/src/components/playlists/Form.vue:2 +msgid "Create a new playlist" +msgstr "Create a new playlist" + +#: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgid "Create an account" +msgstr "Create an account" + +#: front/src/views/content/libraries/Form.vue:26 +msgid "Create library" +msgstr "Create library" + +#: front/src/components/auth/Signup.vue:53 +msgid "Create my account" +msgstr "Create my account" + +#: front/src/components/playlists/Form.vue:34 +msgid "Create playlist" +msgstr "Create playlist" + +#: front/src/components/library/Radios.vue:23 +msgid "Create your own radio" +msgstr "Create your own radio" + +#: front/src/components/manage/users/InvitationsTable.vue:40 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +msgid "Creation date" +msgstr "Creation date" + +#: front/src/components/auth/Settings.vue:54 +msgid "Current avatar" +msgstr "Current avatar" + +#: front/src/views/content/libraries/DetailArea.vue:4 +msgid "Current library" +msgstr "Current library" + +#: front/src/components/playlists/PlaylistModal.vue:8 +msgid "Current track" +msgstr "Current track" + +#: front/src/views/content/libraries/Quota.vue:2 +msgid "Current usage" +msgstr "Current usage" + +#: front/src/views/content/libraries/Detail.vue:27 +msgid "Date" +msgstr "Date" + +#: front/src/components/ShortcutsModal.vue:75 +msgid "Decrease volume" +msgstr "Decrease volume" + +#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:61 +#: front/src/components/manage/users/InvitationsTable.vue:167 +#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/content/libraries/Form.vue:29 src/views/playlists/Detail.vue:33 +msgid "Delete" +msgstr "Delete" + +#: front/src/views/content/libraries/Form.vue:39 +msgid "Delete library" +msgstr "Delete library" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgid "Delete moderation rule" +msgstr "Delete moderation rule" + +#: front/src/views/playlists/Detail.vue:38 +msgid "Delete playlist" +msgstr "Delete playlist" + +#: front/src/views/radios/Detail.vue:28 +msgid "Delete radio" +msgstr "Delete radio" + +#: front/src/views/content/libraries/Form.vue:31 +msgid "Delete this library?" +msgstr "Delete this library?" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgid "Delete this moderation rule?" +msgstr "Delete this moderation rule?" + +#: front/src/components/favorites/List.vue:34 src/components/library/Artists.vue:26 +#: front/src/components/library/Radios.vue:47 +#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/manage/moderation/AccountsTable.vue:22 +#: front/src/components/manage/moderation/DomainsTable.vue:20 +#: front/src/components/manage/users/UsersTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:32 +#: front/src/views/playlists/List.vue:28 +msgid "Descending" +msgstr "Descending" + +#: front/src/components/library/radios/Builder.vue:25 +#: front/src/views/content/libraries/Form.vue:14 +msgid "Description" +msgstr "Description" + +#: front/src/views/content/libraries/Card.vue:47 +msgid "Detail" +msgstr "Detail" + +#: front/src/views/content/remote/Card.vue:50 +msgid "Details" +msgstr "Details" + +#: front/src/views/admin/moderation/AccountsDetail.vue:455 +msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." +msgstr "" +"Determine how much content the user can upload. Leave empty to use the " +"default value of the instance." + +#: front/src/components/mixins/Translations.vue:8 +#: front/src/components/mixins/Translations.vue:9 +msgid "Determine the visibility level of your activity" +msgstr "Determine the visibility level of your activity" + +#: front/src/components/auth/Settings.vue:103 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgid "Disable access" +msgstr "Disable access" + +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgid "Disable Subsonic access" +msgstr "Disable Subsonic access" + +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgid "Disable Subsonic API access?" +msgstr "Disable Subsonic API access?" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:18 +#: front/src/views/admin/moderation/AccountsDetail.vue:128 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgid "Disabled" +msgstr "Disabled" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgid "Discover how to use Funkwhale from other apps" +msgstr "Discover how to use Funkwhale from other apps" + +#: front/src/views/admin/moderation/AccountsDetail.vue:103 +msgid "Display name" +msgstr "Display name" + +#: front/src/components/library/radios/Builder.vue:30 +msgid "Display publicly" +msgstr "Display publicly" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." +msgstr "" +"Do not download any media files (audio, album cover, account avatar…) from " +"this account or domain. This will purge existing content as well." + +#: front/src/components/playlists/Editor.vue:42 +msgid "Do you want to clear the playlist \"%{ playlist }\"?" +msgstr "Do you want to clear the playlist \"%{ playlist }\"?" + +#: front/src/components/common/DangerousButton.vue:7 +msgid "Do you want to confirm this action?" +msgstr "Do you want to confirm this action?" + +#: front/src/views/playlists/Detail.vue:35 +msgid "Do you want to delete the playlist \"%{ playlist }\"?" +msgstr "Do you want to delete the playlist \"%{ playlist }\"?" + +#: front/src/views/radios/Detail.vue:26 +msgid "Do you want to delete the radio \"%{ radio }\"?" +msgstr "Do you want to delete the radio \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:36 +msgid "Do you want to launch %{ action } on %{ count } element?" +msgid_plural "Do you want to launch %{ action } on %{ count } elements?" +msgstr[0] "Do you want to launch %{ action } on %{ count } element?" +msgstr[1] "Do you want to launch %{ action } on %{ count } elements?" + +#: front/src/components/Sidebar.vue:107 +msgid "Do you want to restore your previous queue?" +msgstr "Do you want to restore your previous queue?" + +#: front/src/components/Footer.vue:31 +msgid "Documentation" +msgstr "Documentation" + +#: front/src/components/manage/moderation/AccountsTable.vue:40 +#: front/src/components/mixins/Translations.vue:34 +#: front/src/views/admin/moderation/AccountsDetail.vue:93 +#: front/src/components/mixins/Translations.vue:35 +msgid "Domain" +msgstr "Domain" + +#: front/src/views/admin/moderation/Base.vue:5 +#: front/src/views/admin/moderation/DomainsList.vue:3 +#: front/src/views/admin/moderation/DomainsList.vue:48 +msgid "Domains" +msgstr "Domains" + +#: front/src/components/library/Track.vue:55 +msgid "Download" +msgstr "Download" + +#: front/src/components/playlists/Editor.vue:49 +msgid "Drag and drop rows to reorder tracks in the playlist" +msgstr "Drag and drop rows to reorder tracks in the playlist" + +#: front/src/components/audio/track/Table.vue:9 src/components/library/Track.vue:111 +#: front/src/components/manage/library/FilesTable.vue:43 +#: front/src/components/mixins/Translations.vue:30 +#: front/src/views/content/libraries/FilesTable.vue:59 +#: front/src/components/mixins/Translations.vue:31 +msgid "Duration" +msgstr "Duration" + +#: front/src/views/auth/EmailConfirm.vue:23 +msgid "E-mail address confirmed" +msgstr "E-mail address confirmed" + +#: front/src/components/Home.vue:93 +msgid "Easy to use" +msgstr "Easy to use" + +#: front/src/views/content/libraries/Detail.vue:9 +msgid "Edit" +msgstr "Edit" + +#: front/src/components/About.vue:22 +msgid "Edit instance info" +msgstr "Edit instance info" + +#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 +msgid "Edit…" +msgstr "Edit…" + +#: front/src/components/auth/Signup.vue:30 +#: front/src/components/manage/users/UsersTable.vue:38 +msgid "Email" +msgstr "Email" + +#: front/src/views/admin/moderation/AccountsDetail.vue:111 +msgid "Email address" +msgstr "Email address" + +#: front/src/components/library/Album.vue:38 src/components/library/Track.vue:62 +msgid "Embed" +msgstr "Embed" + +#: front/src/components/audio/EmbedWizard.vue:20 +msgid "Embed code" +msgstr "Embed code" + +#: front/src/components/library/Album.vue:42 +msgid "Embed this album on your website" +msgstr "Embed this album on your website" + +#: front/src/components/library/Track.vue:66 +msgid "Embed this track on your website" +msgstr "Embed this track on your website" + +#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgid "Emitted library follows" +msgstr "Emitted library follows" + +#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgid "Emitted messages" +msgstr "Emitted messages" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:8 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:17 +#: front/src/views/admin/moderation/AccountsDetail.vue:127 +#: front/src/views/admin/moderation/AccountsDetail.vue:131 +msgid "Enabled" +msgstr "Enabled" + +#: front/src/views/playlists/Detail.vue:29 +msgid "End edition" +msgstr "End edition" + +#: front/src/views/content/remote/ScanForm.vue:50 +msgid "Enter a library URL" +msgstr "Enter a library URL" + +#: front/src/components/library/Radios.vue:140 +msgid "Enter a radio name…" +msgstr "Enter a radio name…" + +#: front/src/components/library/Artists.vue:118 +msgid "Enter artist name…" +msgstr "Enter artist name…" + +#: front/src/views/playlists/List.vue:107 +msgid "Enter playlist name…" +msgstr "Enter playlist name…" + +#: front/src/components/auth/Signup.vue:102 +msgid "Enter your email" +msgstr "Enter your email" + +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:99 +msgid "Enter your invitation code (case insensitive)" +msgstr "Enter your invitation code (case insensitive)" + +#: front/src/components/metadata/Search.vue:114 +msgid "Enter your search query…" +msgstr "Enter your search query…" + +#: front/src/components/auth/Signup.vue:101 +msgid "Enter your username" +msgstr "Enter your username" + +#: front/src/components/auth/Login.vue:83 +msgid "Enter your username or email" +msgstr "Enter your username or email" + +#: front/src/components/auth/SubsonicTokenForm.vue:19 +#: front/src/views/content/libraries/Form.vue:4 +msgid "Error" +msgstr "Error" + +#: front/src/views/admin/Settings.vue:87 +msgid "Error reporting" +msgstr "Error reporting" + +#: front/src/components/common/ActionTable.vue:92 +msgid "Error while applying action" +msgstr "Error while applying action" + +#: front/src/views/auth/PasswordReset.vue:7 +msgid "Error while asking for a password reset" +msgstr "Error while asking for a password reset" + +#: front/src/views/auth/PasswordResetConfirm.vue:7 +msgid "Error while changing your password" +msgstr "Error while changing your password" + +#: front/src/views/admin/moderation/DomainsList.vue:6 +msgid "Error while creating domain" +msgstr "Error while creating domain" + +#: front/src/components/manage/users/InvitationForm.vue:4 +msgid "Error while creating invitation" +msgstr "Error while creating invitation" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgid "Error while creating rule" +msgstr "Error while creating rule" + +#: front/src/views/admin/moderation/DomainsDetail.vue:126 +msgid "Error while fetching node info" +msgstr "Error while fetching node info" + +#: front/src/components/admin/SettingsGroup.vue:5 +msgid "Error while saving settings" +msgstr "Error while saving settings" + +#: front/src/views/content/libraries/FilesTable.vue:212 +msgid "Errored" +msgstr "Errored" + +#: front/src/views/content/libraries/Quota.vue:75 +msgid "Errored files" +msgstr "Errored files" + +#: front/src/components/playlists/Form.vue:89 +msgid "Everyone" +msgstr "Everyone" + +#: front/src/components/mixins/Translations.vue:11 +#: front/src/components/playlists/Form.vue:85 src/views/content/libraries/Form.vue:73 +#: front/src/components/mixins/Translations.vue:12 +msgid "Everyone on this instance" +msgstr "Everyone on this instance" + +#: front/src/views/content/libraries/Form.vue:74 +msgid "Everyone, across all instances" +msgstr "Everyone, across all instances" + +#: front/src/components/library/radios/Builder.vue:61 +msgid "Exclude" +msgstr "Exclude" + +#: front/src/components/manage/users/InvitationsTable.vue:41 +#: front/src/components/mixins/Translations.vue:22 +#: front/src/components/mixins/Translations.vue:23 +msgid "Expiration date" +msgstr "Expiry date" + +#: front/src/components/manage/users/InvitationsTable.vue:50 +msgid "Expired" +msgstr "Expired" + +#: front/src/components/manage/users/InvitationsTable.vue:21 +msgid "Expired/used" +msgstr "Expired/used" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." +msgstr "" +"Explain why you're applying this policy. Depending on your instance " +"configuration, this will help you remember why you acted on this account or " +"domain, and may be displayed publicly to help users understand what " +"moderation rules are in place." + +#: front/src/views/content/libraries/FilesTable.vue:16 +msgid "Failed" +msgstr "Failed" + +#: front/src/views/content/remote/Card.vue:58 +msgid "Failed tracks:" +msgstr "Failed tracks:" + +#: front/src/components/Sidebar.vue:66 +msgid "Favorites" +msgstr "Favourites" + +#: front/src/views/admin/Settings.vue:84 +msgid "Federation" +msgstr "Federation" + +#: front/src/components/library/FileUpload.vue:84 +msgid "Filename" +msgstr "Filename" + +#: front/src/views/admin/library/Base.vue:5 src/views/admin/library/FilesList.vue:21 +msgid "Files" +msgstr "Files" + +#: front/src/components/library/radios/Builder.vue:60 +msgid "Filter name" +msgstr "Filter name" + +#: front/src/views/content/libraries/FilesTable.vue:17 +#: front/src/views/content/libraries/FilesTable.vue:216 +msgid "Finished" +msgstr "Finished" + +#: front/src/components/manage/moderation/AccountsTable.vue:42 +#: front/src/components/manage/moderation/DomainsTable.vue:41 +#: front/src/views/admin/moderation/AccountsDetail.vue:159 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgid "First seen" +msgstr "First seen" + +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +msgid "First seen date" +msgstr "First seen date" + +#: front/src/views/content/remote/Card.vue:83 +msgid "Follow" +msgstr "Follow" + +#: front/src/views/content/Home.vue:16 +msgid "Follow remote libraries" +msgstr "Follow remote libraries" + +#: front/src/views/content/remote/Card.vue:88 +msgid "Follow request pending approval" +msgstr "Follow request pending approval" + +#: front/src/components/mixins/Translations.vue:38 +#: front/src/views/content/libraries/Detail.vue:7 +#: front/src/components/mixins/Translations.vue:39 +msgid "Followers" +msgstr "Followers" + +#: front/src/views/content/remote/Card.vue:93 +msgid "Following" +msgstr "Following" + +#: front/src/components/library/Track.vue:17 +msgid "From album %{ album } by %{ artist }" +msgstr "From album %{ album } by %{ artist }" + +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgid "Funkwhale is compatible with other music players that support the Subsonic API." +msgstr "" +"Funkwhale is compatible with other music players that support the Subsonic " +"API." + +#: front/src/components/Home.vue:95 +msgid "Funkwhale is dead simple to use." +msgstr "Funkwhale is dead simple to use." + +#: front/src/components/Home.vue:39 +msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." +msgstr "" +"Funkwhale is designed to make it easy to listen to music you like, or to " +"discover new artists." + +#: front/src/components/Home.vue:116 +msgid "Funkwhale is free and gives you control on your music." +msgstr "Funkwhale is free and gives you control of your music." + +#: front/src/components/Home.vue:66 +msgid "Funkwhale takes care of handling your music" +msgstr "Funkwhale takes care of handling your music" + +#: front/src/components/ShortcutsModal.vue:38 +msgid "General shortcuts" +msgstr "General shortcuts" + +#: front/src/components/manage/users/InvitationForm.vue:16 +msgid "Get a new invitation" +msgstr "Get a new invitation" + +#: front/src/components/Home.vue:13 +msgid "Get me to the library" +msgstr "Get me to the library" + +#: front/src/components/Home.vue:76 +msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" +msgstr "" +"Get quality metadata about your music thanks to <a href=\"%{ url }\" target=" +"\"_blank\">MusicBrainz</a>" + +#: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgid "Get started" +msgstr "Get started" + +#: front/src/components/Footer.vue:37 +msgid "Getting help" +msgstr "Getting help" + +#: front/src/components/common/ActionTable.vue:34 +#: front/src/components/common/ActionTable.vue:54 +msgid "Go" +msgstr "Go" + +#: front/src/components/PageNotFound.vue:14 +msgid "Go to home page" +msgstr "Go to home page" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgid "Hide account or domain content, except from followers." +msgstr "Hide account or domain content, except from followers." + +#: front/src/components/library/Home.vue:65 +msgid "Home" +msgstr "Home" + +#: front/src/components/instance/Stats.vue:36 +msgid "Hours of music" +msgstr "Hours of music" + +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgid "However, accessing Funkwhale from those clients require a separate password you can set below." +msgstr "" +"However, accessing Funkwhale from those clients requires a separate password " +"you can set below." + +#: front/src/views/auth/PasswordResetConfirm.vue:24 +msgid "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." +msgstr "" +"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." + +#: front/src/components/manage/library/FilesTable.vue:40 +msgid "Import date" +msgstr "Import date" + +#: front/src/components/Home.vue:71 +msgid "Import music from various platforms, such as YouTube or SoundCloud" +msgstr "Import music from various platforms, such as YouTube or SoundCloud" + +#: front/src/components/library/FileUpload.vue:51 +msgid "Import reference" +msgstr "Import reference" + +#: front/src/views/content/libraries/FilesTable.vue:11 +#: front/src/views/content/libraries/FilesTable.vue:58 +msgid "Import status" +msgstr "Import status" + +#: front/src/views/content/libraries/FilesTable.vue:217 +msgid "Imported" +msgstr "Imported" + +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgid "Imported date" +msgstr "Imported date" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgid "In favorites" +msgstr "In favourites" + +#: front/src/components/manage/users/UsersTable.vue:54 +msgid "Inactive" +msgstr "Inactive" + +#: front/src/components/ShortcutsModal.vue:71 +msgid "Increase volume" +msgstr "Increase volume" + +#: front/src/views/auth/PasswordReset.vue:54 +msgid "Input the email address binded to your account" +msgstr "Input the email address bound to your account" + +#: front/src/components/playlists/Editor.vue:31 +msgid "Insert from queue (%{ count } track)" +msgid_plural "Insert from queue (%{ count } tracks)" +msgstr[0] "Insert from queue (%{ count } track)" +msgstr[1] "Insert from queue (%{ count } tracks)" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgid "Instance data" +msgstr "Instance data" + +#: front/src/views/admin/Settings.vue:80 +msgid "Instance information" +msgstr "Instance information" + +#: front/src/components/library/Radios.vue:9 +msgid "Instance radios" +msgstr "Instance radios" + +#: front/src/views/admin/Settings.vue:75 +msgid "Instance settings" +msgstr "Instance settings" + +#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/library/FileUpload.vue:231 +msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "" +"Invalid file type, ensure you are uploading an audio file. Supported file " +"extensions are %{ extensions }" + +#: front/src/components/auth/Signup.vue:44 +#: front/src/components/manage/users/InvitationForm.vue:11 +msgid "Invitation code" +msgstr "Invitation code" + +#: front/src/views/admin/users/Base.vue:8 src/views/admin/users/InvitationsList.vue:3 +#: front/src/views/admin/users/InvitationsList.vue:24 +msgid "Invitations" +msgstr "Invitations" + +#: front/src/components/Footer.vue:41 +msgid "Issue tracker" +msgstr "Issue tracker" + +#: front/src/components/Home.vue:50 +msgid "Keep a track of your favorite songs" +msgstr "Keep track of your favourite songs" + +#: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgid "Keyboard shortcuts" +msgstr "Keyboard shortcuts" + +#: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgid "Known accounts" +msgstr "Known accounts" + +#: front/src/views/content/remote/Home.vue:14 +msgid "Known libraries" +msgstr "Known libraries" + +#: front/src/components/manage/users/UsersTable.vue:41 +#: front/src/components/mixins/Translations.vue:32 +#: front/src/views/admin/moderation/AccountsDetail.vue:184 +#: front/src/components/mixins/Translations.vue:33 +msgid "Last activity" +msgstr "Last activity" + +#: front/src/views/admin/moderation/AccountsDetail.vue:167 +#: front/src/views/admin/moderation/DomainsDetail.vue:86 +msgid "Last checked" +msgstr "Last checked" + +#: front/src/components/playlists/PlaylistModal.vue:32 +msgid "Last modification" +msgstr "Last modification" + +#: front/src/components/manage/moderation/AccountsTable.vue:43 +msgid "Last seen" +msgstr "Last seen" + +#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:19 +msgid "Last seen date" +msgstr "Last seen date" + +#: front/src/views/content/remote/Card.vue:56 +msgid "Last update:" +msgstr "Last update:" + +#: front/src/components/common/ActionTable.vue:47 +msgid "Launch" +msgstr "Launch" + +#: front/src/components/Home.vue:10 +msgid "Learn more about this instance" +msgstr "Learn more about this instance" + +#: front/src/components/manage/users/InvitationForm.vue:58 +msgid "Leave empty for a random code" +msgstr "Leave empty for a random code" + +#: front/src/components/audio/EmbedWizard.vue:7 +msgid "Leave empty for a responsive widget" +msgstr "Leave empty for a responsive widget" + +#: front/src/views/admin/moderation/AccountsDetail.vue:297 +#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/content/Base.vue:5 +msgid "Libraries" +msgstr "Libraries" + +#: front/src/views/content/libraries/Form.vue:2 +msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." +msgstr "" +"Libraries help you organise and share your music collections. You can upload " +"your own music collection to Funkwhale and share it with your friends and " +"family." + +#: front/src/components/instance/Stats.vue:30 +#: front/src/components/manage/users/UsersTable.vue:173 +#: front/src/views/admin/moderation/AccountsDetail.vue:464 +msgid "Library" +msgstr "Library" + +#: front/src/views/content/libraries/Form.vue:109 +msgid "Library created" +msgstr "Library created" + +#: front/src/views/content/libraries/Form.vue:129 +msgid "Library deleted" +msgstr "Library deleted" + +#: front/src/views/admin/library/FilesList.vue:3 +msgid "Library files" +msgstr "Library files" + +#: front/src/views/content/libraries/Form.vue:106 +msgid "Library updated" +msgstr "Library updated" + +#: front/src/components/library/Track.vue:100 +msgid "License" +msgstr "License" + +#: front/src/views/content/libraries/Detail.vue:21 +msgid "Loading followers…" +msgstr "Loading followers…" + +#: front/src/views/content/libraries/Home.vue:3 +msgid "Loading Libraries…" +msgstr "Loading Libraries…" + +#: front/src/views/content/libraries/Detail.vue:3 +#: front/src/views/content/libraries/Upload.vue:3 +msgid "Loading library data…" +msgstr "Loading library data…" + +#: front/src/views/Notifications.vue:4 +msgid "Loading notifications…" +msgstr "Loading notifications…" + +#: front/src/views/content/remote/Home.vue:3 +msgid "Loading remote libraries…" +msgstr "Loading remote libraries…" + +#: front/src/views/content/libraries/Quota.vue:4 +msgid "Loading usage data…" +msgstr "Loading usage data…" + +#: front/src/components/favorites/List.vue:5 +msgid "Loading your favorites…" +msgstr "Loading your favourites…" + +#: front/src/components/manage/moderation/AccountsTable.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:18 +msgid "Local account" +msgstr "Local account" + +#: front/src/components/auth/Login.vue:84 +msgid "Log In" +msgstr "Log In" + +#: front/src/components/auth/Login.vue:4 +msgid "Log in to your Funkwhale account" +msgstr "Log in to your Funkwhale account" + +#: front/src/components/auth/Logout.vue:20 +msgid "Log Out" +msgstr "Log Out" + +#: front/src/components/Sidebar.vue:38 +msgid "Logged in as %{ username }" +msgstr "Logged in as %{ username }" + +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +msgid "Login" +msgstr "Login" + +#: front/src/views/admin/moderation/AccountsDetail.vue:119 +msgid "Login status" +msgstr "Login status" + +#: front/src/components/Sidebar.vue:52 +msgid "Logout" +msgstr "Logout" + +#: front/src/views/content/libraries/Home.vue:9 +msgid "Looks like you don't have a library, it's time to create one." +msgstr "Looks like you don't have a library, it's time to create one." + +#: front/src/components/audio/Player.vue:356 src/components/audio/Player.vue:357 +msgid "Looping disabled. Click to switch to single-track looping." +msgstr "Looping disabled. Click to switch to single-track looping." + +#: front/src/components/audio/Player.vue:359 src/components/audio/Player.vue:360 +msgid "Looping on a single track. Click to switch to whole queue looping." +msgstr "Looping on a single track. Click to switch to whole queue looping." + +#: front/src/components/audio/Player.vue:362 src/components/audio/Player.vue:363 +msgid "Looping on whole queue. Click to disable looping." +msgstr "Looping on whole queue. Click to disable looping." + +#: front/src/components/library/Track.vue:150 +msgid "Lyrics" +msgstr "Lyrics" + +#: front/src/components/Sidebar.vue:210 +msgid "Main menu" +msgstr "Main menu" + +#: front/src/views/admin/library/Base.vue:16 +msgid "Manage library" +msgstr "Manage library" + +#: front/src/components/playlists/PlaylistModal.vue:3 +msgid "Manage playlists" +msgstr "Manage playlists" + +#: front/src/views/admin/users/Base.vue:20 +msgid "Manage users" +msgstr "Manage users" + +#: front/src/views/playlists/List.vue:8 +msgid "Manage your playlists" +msgstr "Manage your playlists" + +#: front/src/views/Notifications.vue:17 +msgid "Mark all as read" +msgstr "Mark all as read" + +#: front/src/components/notifications/NotificationRow.vue:46 +msgid "Mark as read" +msgstr "Mark as read" + +#: front/src/components/notifications/NotificationRow.vue:47 +msgid "Mark as unread" +msgstr "Mark as unread" + +#: front/src/views/admin/moderation/AccountsDetail.vue:281 +msgid "MB" +msgstr "MB" + +#: front/src/components/audio/Player.vue:349 +msgid "Media player" +msgstr "Media player" + +#: front/src/components/auth/Profile.vue:12 +msgid "Member since %{ date }" +msgstr "Member since %{ date }" + +#: front/src/components/Footer.vue:32 +msgid "Mobile and desktop apps" +msgstr "Mobile and desktop apps" + +#: front/src/components/Sidebar.vue:97 src/components/manage/users/UsersTable.vue:177 +#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/Base.vue:21 +msgid "Moderation" +msgstr "Moderation" + +#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgid "Moderation policies help you control how your instance interact with a given domain or account." +msgstr "" +"Moderation policies help you control how your instance interact with a given " +"domain or account." + +#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:21 +msgid "Modification date" +msgstr "Modification date" + +#: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +msgid "Music" +msgstr "Music" + +#: front/src/components/audio/Player.vue:355 +msgid "Mute" +msgstr "Mute" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +msgid "Mute activity" +msgstr "Mute activity" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +msgid "Mute notifications" +msgstr "Mute notifications" + +#: front/src/components/Sidebar.vue:34 +msgid "My account" +msgstr "My account" + +#: front/src/components/library/radios/Builder.vue:236 +msgid "My awesome description" +msgstr "My awesome description" + +#: front/src/views/content/libraries/Form.vue:70 +msgid "My awesome library" +msgstr "My awesome library" + +#: front/src/components/playlists/Form.vue:74 +msgid "My awesome playlist" +msgstr "My awesome playlist" + +#: front/src/components/library/radios/Builder.vue:235 +msgid "My awesome radio" +msgstr "My awesome radio" + +#: front/src/views/content/libraries/Home.vue:6 +msgid "My libraries" +msgstr "My libraries" + +#: front/src/components/audio/track/Row.vue:40 src/components/library/Track.vue:115 +#: front/src/components/library/Track.vue:124 src/components/library/Track.vue:133 +#: front/src/components/library/Track.vue:142 +#: front/src/components/manage/library/FilesTable.vue:63 +#: front/src/components/manage/library/FilesTable.vue:69 +#: front/src/components/manage/library/FilesTable.vue:75 +#: front/src/components/manage/library/FilesTable.vue:81 +#: front/src/components/manage/users/UsersTable.vue:61 +#: front/src/views/admin/moderation/AccountsDetail.vue:171 +#: front/src/views/admin/moderation/DomainsDetail.vue:90 +#: front/src/views/content/libraries/FilesTable.vue:92 +#: front/src/views/content/libraries/FilesTable.vue:98 +#: front/src/views/admin/moderation/DomainsDetail.vue:109 +#: front/src/views/admin/moderation/DomainsDetail.vue:117 +msgid "N/A" +msgstr "N/A" + +#: front/src/components/manage/moderation/AccountsTable.vue:39 +#: front/src/components/manage/moderation/DomainsTable.vue:38 +#: front/src/components/mixins/Translations.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:31 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +#: front/src/views/content/libraries/Form.vue:10 +#: front/src/components/mixins/Translations.vue:27 +msgid "Name" +msgstr "Name" + +#: front/src/components/auth/Settings.vue:87 +#: front/src/views/auth/PasswordResetConfirm.vue:14 +msgid "New password" +msgstr "New password" + +#: front/src/components/Sidebar.vue:160 +msgid "New tracks will be appended here automatically." +msgstr "New tracks will be appended here automatically." + +#: front/src/components/audio/Player.vue:353 +msgid "Next track" +msgstr "Next track" + +#: front/src/components/Sidebar.vue:119 +msgid "No" +msgstr "No" + +#: front/src/components/Home.vue:100 +msgid "No add-ons, no plugins : you only need a web library" +msgstr "No add-ons, no plugins : you only need a web library" + +#: front/src/components/audio/Search.vue:25 +msgid "No album matched your query" +msgstr "No album matched your query" + +#: front/src/components/audio/Search.vue:16 +msgid "No artist matched your query" +msgstr "No artist matched your query" + +#: front/src/components/library/Track.vue:158 +msgid "No lyrics available for this track." +msgstr "No lyrics available for this track." + +#: front/src/components/federation/LibraryWidget.vue:6 +msgid "No matching library." +msgstr "No matching library." + +#: front/src/views/Notifications.vue:26 +msgid "No notification to show." +msgstr "No notification to show." + +#: front/src/components/mixins/Translations.vue:10 +#: front/src/components/playlists/Form.vue:81 src/views/content/libraries/Form.vue:72 +#: front/src/components/mixins/Translations.vue:11 +msgid "Nobody except me" +msgstr "Nobody except me" + +#: front/src/views/content/libraries/Detail.vue:57 +msgid "Nobody is following this library" +msgstr "Nobody is following this library" + +#: front/src/components/manage/users/InvitationsTable.vue:51 +msgid "Not used" +msgstr "Not used" + +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +msgid "Notifications" +msgstr "Notifications" + +#: front/src/components/Footer.vue:47 +msgid "Official website" +msgstr "Official website" + +#: front/src/components/auth/Settings.vue:82 +msgid "Old password" +msgstr "Old password" + +#: front/src/components/manage/users/InvitationsTable.vue:20 +msgid "Open" +msgstr "Open" + +#: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgid "Open profile" +msgstr "Open profile" + +#: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgid "Open website" +msgstr "Open website" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgid "Or customize your rule" +msgstr "Or customise your rule" + +#: front/src/components/favorites/List.vue:31 src/components/library/Radios.vue:41 +#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/users/UsersTable.vue:17 +#: front/src/views/playlists/List.vue:25 +msgid "Order" +msgstr "Order" + +#: front/src/components/favorites/List.vue:23 src/components/library/Artists.vue:15 +#: front/src/components/library/Radios.vue:33 +#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/manage/moderation/AccountsTable.vue:11 +#: front/src/components/manage/moderation/DomainsTable.vue:9 +#: front/src/components/manage/users/InvitationsTable.vue:9 +#: front/src/components/manage/users/UsersTable.vue:9 +#: front/src/views/content/libraries/FilesTable.vue:21 +#: front/src/views/playlists/List.vue:17 +msgid "Ordering" +msgstr "Ordering" + +#: front/src/components/library/Artists.vue:23 +#: front/src/components/manage/moderation/AccountsTable.vue:19 +#: front/src/components/manage/moderation/DomainsTable.vue:17 +#: front/src/views/content/libraries/FilesTable.vue:29 +msgid "Ordering direction" +msgstr "Ordering direction" + +#: front/src/components/manage/users/InvitationsTable.vue:38 +msgid "Owner" +msgstr "Owner" + +#: front/src/components/PageNotFound.vue:33 +msgid "Page Not Found" +msgstr "Page Not Found" + +#: front/src/components/PageNotFound.vue:7 +msgid "Page not found!" +msgstr "Page not found!" + +#: front/src/components/Pagination.vue:39 +msgid "Pagination" +msgstr "Pagination" + +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +msgid "Password" +msgstr "Password" + +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgid "Password updated" +msgstr "Password updated" + +#: front/src/views/auth/PasswordResetConfirm.vue:28 +msgid "Password updated successfully" +msgstr "Password updated successfully" + +#: front/src/components/audio/Player.vue:352 +msgid "Pause track" +msgstr "Pause track" + +#: front/src/components/ShortcutsModal.vue:59 +msgid "Pause/play the current track" +msgstr "Pause/play the current track" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgid "Paused" +msgstr "Paused" + +#: front/src/components/library/FileUpload.vue:107 +#: front/src/views/content/libraries/FilesTable.vue:14 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgid "Pending" +msgstr "Pending" + +#: front/src/views/content/libraries/Detail.vue:37 +msgid "Pending approval" +msgstr "Pending approval" + +#: front/src/views/content/libraries/Quota.vue:22 +msgid "Pending files" +msgstr "Pending files" + +#: front/src/components/Sidebar.vue:212 +msgid "Pending follow requests" +msgstr "Pending follow requests" + +#: front/src/components/manage/users/UsersTable.vue:42 +#: front/src/views/admin/moderation/AccountsDetail.vue:137 +msgid "Permissions" +msgstr "Permissions" + +#: front/src/components/audio/PlayButton.vue:9 src/components/library/Track.vue:40 +msgid "Play" +msgstr "Play" + +#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/artist/Card.vue:44 src/components/library/Album.vue:22 +#: front/src/components/library/Album.vue:67 src/views/playlists/Detail.vue:23 +msgid "Play all" +msgstr "Play all" + +#: front/src/components/library/Artist.vue:26 +msgid "Play all albums" +msgstr "Play all albums" + +#: front/src/components/audio/PlayButton.vue:15 +#: front/src/components/audio/PlayButton.vue:65 +msgid "Play next" +msgstr "Play next" + +#: front/src/components/ShortcutsModal.vue:67 +msgid "Play next track" +msgstr "Play next track" + +#: front/src/components/audio/PlayButton.vue:16 +#: front/src/components/audio/PlayButton.vue:63 +msgid "Play now" +msgstr "Play now" + +#: front/src/components/ShortcutsModal.vue:63 +msgid "Play previous track" +msgstr "Play previous track" + +#: front/src/components/Sidebar.vue:211 +msgid "Play this track" +msgstr "Play this track" + +#: front/src/components/audio/Player.vue:351 +msgid "Play track" +msgstr "Play track" + +#: front/src/components/audio/PlayButton.vue:70 +msgid "Play..." +msgstr "Play…" + +#: front/src/views/playlists/Detail.vue:90 +msgid "Playlist" +msgstr "Playlist" + +#: front/src/views/playlists/Detail.vue:12 +msgid "Playlist containing %{ count } track, by %{ username }" +msgid_plural "Playlist containing %{ count } tracks, by %{ username }" +msgstr[0] "Playlist containing %{ count } track, by %{ username }" +msgstr[1] "Playlist containing %{ count } tracks, by %{ username }" + +#: front/src/components/playlists/Form.vue:9 +msgid "Playlist created" +msgstr "Playlist created" + +#: front/src/components/playlists/Editor.vue:4 +msgid "Playlist editor" +msgstr "Playlist editor" + +#: front/src/components/playlists/Form.vue:21 +msgid "Playlist name" +msgstr "Playlist name" + +#: front/src/components/playlists/Form.vue:6 +msgid "Playlist updated" +msgstr "Playlist updated" + +#: front/src/components/playlists/Form.vue:25 +msgid "Playlist visibility" +msgstr "Playlist visibility" + +#: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 +#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 +#: front/src/views/playlists/List.vue:106 +msgid "Playlists" +msgstr "Playlists" + +#: front/src/components/Home.vue:56 +msgid "Playlists? We got them" +msgstr "Playlists? We got them" + +#: front/src/components/auth/Settings.vue:78 +msgid "Please double-check your password is correct" +msgstr "Please double-check your password is correct" + +#: front/src/components/auth/Login.vue:9 +msgid "Please double-check your username/password couple is correct" +msgstr "Please double-check your username/password couple is correct" + +#: front/src/components/auth/Settings.vue:46 +msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." +msgstr "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgid "Prevent account or domain from triggering notifications, except from followers." +msgstr "" +"Prevent account or domain from triggering notifications, except from " +"followers." + +#: front/src/components/audio/EmbedWizard.vue:29 +msgid "Preview" +msgstr "Preview" + +#: front/src/components/audio/Player.vue:350 +msgid "Previous track" +msgstr "Previous track" + +#: front/src/views/content/remote/Card.vue:39 +msgid "Problem during scanning" +msgstr "Problem during scanning" + +#: front/src/components/library/FileUpload.vue:58 +msgid "Proceed" +msgstr "Proceed" + +#: front/src/views/auth/EmailConfirm.vue:26 +#: front/src/views/auth/PasswordResetConfirm.vue:31 +msgid "Proceed to login" +msgstr "Proceed to login" + +#: front/src/components/library/FileUpload.vue:17 +msgid "Processing" +msgstr "Processing" + +#: front/src/components/manage/moderation/AccountsTable.vue:188 +#: front/src/components/manage/moderation/DomainsTable.vue:168 +#: front/src/views/content/libraries/Quota.vue:36 +#: front/src/views/content/libraries/Quota.vue:39 +#: front/src/views/content/libraries/Quota.vue:62 +#: front/src/views/content/libraries/Quota.vue:65 +#: front/src/views/content/libraries/Quota.vue:88 +#: front/src/views/content/libraries/Quota.vue:91 +msgid "Purge" +msgstr "Purge" + +#: front/src/views/content/libraries/Quota.vue:89 +msgid "Purge errored files?" +msgstr "Purge errored files?" + +#: front/src/views/content/libraries/Quota.vue:37 +msgid "Purge pending files?" +msgstr "Purge pending files?" + +#: front/src/views/content/libraries/Quota.vue:63 +msgid "Purge skipped files?" +msgstr "Purge skipped files?" + +#: front/src/components/Sidebar.vue:20 +msgid "Queue" +msgstr "Queue" + +#: front/src/components/audio/Player.vue:283 +msgid "Queue shuffled!" +msgstr "Queue shuffled!" + +#: front/src/views/radios/Detail.vue:80 +msgid "Radio" +msgstr "Radio" + +#: front/src/components/library/radios/Builder.vue:233 +msgid "Radio Builder" +msgstr "Radio Builder" + +#: front/src/components/library/radios/Builder.vue:15 +msgid "Radio created" +msgstr "Radio created" + +#: front/src/components/library/radios/Builder.vue:21 +msgid "Radio name" +msgstr "Radio name" + +#: front/src/components/library/radios/Builder.vue:12 +msgid "Radio updated" +msgstr "Radio updated" + +#: front/src/components/library/Library.vue:10 src/components/library/Radios.vue:141 +msgid "Radios" +msgstr "Radios" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:39 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgid "Reason" +msgstr "Reason" + +#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgid "Received library follows" +msgstr "Received library follows" + +#: front/src/components/manage/moderation/DomainsTable.vue:40 +#: front/src/components/mixins/Translations.vue:36 +#: front/src/components/mixins/Translations.vue:37 +msgid "Received messages" +msgstr "Received messages" + +#: front/src/components/library/Home.vue:24 +msgid "Recently added" +msgstr "Recently added" + +#: front/src/components/library/Home.vue:11 +msgid "Recently favorited" +msgstr "Recently favourited" + +#: front/src/components/library/Home.vue:6 +msgid "Recently listened" +msgstr "Recently listened" + +#: front/src/views/content/remote/Home.vue:15 +msgid "Refresh" +msgstr "Refresh" + +#: front/src/views/admin/moderation/DomainsDetail.vue:135 +msgid "Refresh node info" +msgstr "Refresh node info" + +#: front/src/components/common/ActionTable.vue:272 +msgid "Refresh table content" +msgstr "Refresh table content" + +#: front/src/components/auth/Signup.vue:9 +msgid "Registration are closed on this instance, you will need an invitation code to signup." +msgstr "" +"Registration are closed on this instance, you will need an invitation code " +"to signup." + +#: front/src/components/manage/users/UsersTable.vue:71 +msgid "regular user" +msgstr "regular user" + +#: front/src/views/content/libraries/Detail.vue:51 +msgid "Reject" +msgstr "Reject" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:32 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +msgid "Reject media" +msgstr "Reject media" + +#: front/src/views/content/libraries/Detail.vue:43 +msgid "Rejected" +msgstr "Rejected" + +#: front/src/views/content/libraries/FilesTable.vue:234 +msgid "Relaunch import" +msgstr "Relaunch import" + +#: front/src/views/content/remote/Home.vue:6 +msgid "Remote libraries" +msgstr "Remote libraries" + +#: front/src/views/content/remote/Home.vue:7 +msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." +msgstr "" +"Remote libraries are owned by other users on the network. You can access " +"them as long as they are public or you are granted access." + +#: front/src/components/library/radios/Filter.vue:59 +msgid "Remove" +msgstr "Remove" + +#: front/src/components/auth/Settings.vue:58 +msgid "Remove avatar" +msgstr "Remove avatar" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:26 +msgid "Remove from favorites" +msgstr "Remove from favourites" + +#: front/src/views/content/libraries/Quota.vue:38 +msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." +msgstr "" +"Removes uploaded but yet to be processed tracks completely, adding the " +"corresponding data to your quota." + +#: front/src/views/content/libraries/Quota.vue:64 +msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." +msgstr "" +"Removes uploaded tracks skipped during the import processes completely, " +"adding the corresponding data to your quota." + +#: front/src/views/content/libraries/Quota.vue:90 +msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." +msgstr "" +"Removes uploaded tracks that could not be processed by the server " +"completely, adding the corresponding data to your quota." + +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +msgid "Request a new password" +msgstr "Request a new password" + +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgid "Request a new Subsonic API password?" +msgstr "Request a new Subsonic API password?" + +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgid "Request a password" +msgstr "Request a password" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +msgid "Reset your password" +msgstr "Reset your password" + +#: front/src/components/favorites/List.vue:38 src/components/library/Artists.vue:30 +#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +msgid "Results per page" +msgstr "Results per page" + +#: front/src/views/auth/EmailConfirm.vue:17 +msgid "Return to login" +msgstr "Return to login" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgid "Rule" +msgstr "Rule" + +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:33 +msgid "Save" +msgstr "Save" + +#: front/src/views/content/remote/Card.vue:165 +msgid "Scan launched" +msgstr "Scan launched" + +#: front/src/views/content/remote/Card.vue:63 +msgid "Scan now" +msgstr "Scan now" + +#: front/src/views/content/remote/Card.vue:166 +msgid "Scan skipped (previous scan is too recent)" +msgstr "Scan skipped (previous scan is too recent)" + +#: front/src/views/content/remote/Card.vue:31 +msgid "Scan waiting" +msgstr "Scan waiting" + +#: front/src/views/content/remote/Card.vue:43 +msgid "Scanned" +msgstr "Scanned" + +#: front/src/views/content/remote/Card.vue:47 +msgid "Scanned with errors" +msgstr "Scanned with errors" + +#: front/src/views/content/remote/Card.vue:35 +msgid "Scanning… (%{ progress }%)" +msgstr "Scanning… (%{ progress }%)" + +#: front/src/components/library/Artists.vue:10 src/components/library/Radios.vue:29 +#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/manage/moderation/AccountsTable.vue:5 +#: front/src/components/manage/moderation/DomainsTable.vue:5 +#: front/src/components/manage/users/InvitationsTable.vue:5 +#: front/src/components/manage/users/UsersTable.vue:5 +#: front/src/views/content/libraries/FilesTable.vue:5 src/views/playlists/List.vue:13 +msgid "Search" +msgstr "Search" + +#: front/src/views/content/remote/ScanForm.vue:9 +msgid "Search a remote library" +msgstr "Search a remote library" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgid "Search by domain, username, bio…" +msgstr "Search by domain, username, bio…" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgid "Search by name…" +msgstr "Search by name…" + +#: front/src/views/content/libraries/FilesTable.vue:201 +msgid "Search by title, artist, album…" +msgstr "Search by title, artist, album…" + +#: front/src/components/manage/library/FilesTable.vue:176 +msgid "Search by title, artist, domain…" +msgstr "Search by title, artist, domain…" + +#: front/src/components/manage/users/InvitationsTable.vue:153 +msgid "Search by username, e-mail address, code…" +msgstr "Search by username, e-mail address, code…" + +#: front/src/components/manage/users/UsersTable.vue:163 +msgid "Search by username, e-mail address, name…" +msgstr "Search by username, e-mail address, name…" + +#: front/src/components/audio/SearchBar.vue:20 +msgid "Search for artists, albums, tracks…" +msgstr "Search for artists, albums, tracks…" + +#: front/src/components/audio/Search.vue:2 +msgid "Search for some music" +msgstr "Search for some music" + +#: front/src/components/library/Track.vue:162 +msgid "Search on lyrics.wikia.com" +msgstr "Search on lyrics.wikia.com" + +#: front/src/components/library/Album.vue:27 src/components/library/Artist.vue:31 +#: front/src/components/library/Track.vue:47 +msgid "Search on Wikipedia" +msgstr "Search on Wikipedia" + +#: front/src/components/library/Library.vue:32 src/views/admin/library/Base.vue:17 +#: front/src/views/admin/moderation/Base.vue:22 src/views/admin/users/Base.vue:21 +#: front/src/views/content/Base.vue:19 +msgid "Secondary menu" +msgstr "Secondary menu" + +#: front/src/views/admin/Settings.vue:15 +msgid "Sections" +msgstr "Sections" + +#: front/src/components/library/radios/Builder.vue:45 +msgid "Select a filter" +msgstr "Select a filter" + +#: front/src/components/common/ActionTable.vue:77 +msgid "Select all %{ total } elements" +msgid_plural "Select all %{ total } elements" +msgstr[0] "Select all %{ total } elements" +msgstr[1] "Select all %{ total } elements" + +#: front/src/components/common/ActionTable.vue:86 +msgid "Select only current page" +msgstr "Select only current page" + +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/manage/users/UsersTable.vue:181 +#: front/src/views/admin/moderation/AccountsDetail.vue:472 +msgid "Settings" +msgstr "Settings" + +#: front/src/components/auth/Settings.vue:10 +msgid "Settings updated" +msgstr "Settings updated" + +#: front/src/components/admin/SettingsGroup.vue:11 +msgid "Settings updated successfully." +msgstr "Settings updated successfully." + +#: front/src/components/manage/users/InvitationForm.vue:27 +msgid "Share link" +msgstr "Share link" + +#: front/src/views/content/libraries/Detail.vue:15 +msgid "Share this link with other users so they can request access to your library." +msgstr "" +"Share this link with other users so they can request access to your library." + +#: front/src/views/content/libraries/Detail.vue:14 +#: front/src/views/content/remote/Card.vue:73 +msgid "Sharing link" +msgstr "Sharing link" + +#: front/src/components/audio/album/Card.vue:40 +msgid "Show %{ count } more track" +msgid_plural "Show %{ count } more tracks" +msgstr[0] "Show %{ count } more track" +msgstr[1] "Show %{ count } more tracks" + +#: front/src/components/audio/artist/Card.vue:30 +msgid "Show 1 more album" +msgid_plural "Show %{ count } more albums" +msgstr[0] "Show 1 more album" +msgstr[1] "Show %{ count } more albums" + +#: front/src/components/ShortcutsModal.vue:42 +msgid "Show available keyboard shortcuts" +msgstr "Show available keyboard shortcuts" + +#: front/src/views/Notifications.vue:10 +msgid "Show read notifications" +msgstr "Show read notifications" + +#: front/src/components/forms/PasswordInput.vue:26 +msgid "Show/hide password" +msgstr "Show/hide password" + +#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/moderation/AccountsTable.vue:88 +#: front/src/components/manage/moderation/DomainsTable.vue:74 +#: front/src/components/manage/users/InvitationsTable.vue:76 +#: front/src/components/manage/users/UsersTable.vue:87 +#: front/src/views/content/libraries/FilesTable.vue:114 +msgid "Showing results %{ start }-%{ end } on %{ total }" +msgstr "Showing results %{ start }-%{ end } on %{ total }" + +#: front/src/components/ShortcutsModal.vue:83 +msgid "Shuffle queue" +msgstr "Shuffle queue" + +#: front/src/components/audio/Player.vue:365 +msgid "Shuffle your queue" +msgstr "Shuffle your queue" + +#: front/src/components/auth/Signup.vue:97 +msgid "Sign Up" +msgstr "Sign Up" + +#: front/src/components/manage/users/UsersTable.vue:40 +msgid "Sign-up" +msgstr "Sign-up" + +#: front/src/components/mixins/Translations.vue:31 +#: front/src/views/admin/moderation/AccountsDetail.vue:176 +#: front/src/components/mixins/Translations.vue:32 +msgid "Sign-up date" +msgstr "Sign-up date" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +msgid "Silence notifications" +msgstr "Silence notifications" + +#: front/src/components/library/FileUpload.vue:85 +#: front/src/components/library/Track.vue:120 +#: front/src/components/manage/library/FilesTable.vue:44 +#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:29 +msgid "Size" +msgstr "Size" + +#: front/src/views/content/libraries/FilesTable.vue:15 +#: front/src/views/content/libraries/FilesTable.vue:204 +msgid "Skipped" +msgstr "Skipped" + +#: front/src/views/content/libraries/Quota.vue:49 +msgid "Skipped files" +msgstr "Skipped files" + +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +msgid "Software" +msgstr "Software" + +#: front/src/components/Footer.vue:49 +msgid "Source code" +msgstr "Source code" + +#: front/src/components/auth/Profile.vue:23 +#: front/src/components/manage/users/UsersTable.vue:70 +msgid "Staff member" +msgstr "Staff member" + +#: front/src/components/radios/Button.vue:4 +msgid "Start" +msgstr "Start" + +#: front/src/views/admin/Settings.vue:86 +msgid "Statistics" +msgstr "Statistics" + +#: front/src/views/admin/moderation/AccountsDetail.vue:454 +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" +msgstr "" +"Statistics are computed from known activity and content on your instance, " +"and do not reflect general activity for this account" + +#: front/src/views/admin/moderation/DomainsDetail.vue:358 +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" +msgstr "" +"Statistics are computed from known activity and content on your instance, " +"and do not reflect general activity for this domain" + +#: front/src/components/library/FileUpload.vue:86 +#: front/src/components/manage/users/InvitationsTable.vue:17 +#: front/src/components/manage/users/InvitationsTable.vue:39 +#: front/src/components/manage/users/UsersTable.vue:43 +#: front/src/views/admin/moderation/DomainsDetail.vue:123 +#: front/src/views/content/libraries/Detail.vue:28 +msgid "Status" +msgstr "Status" + +#: front/src/components/radios/Button.vue:3 +msgid "Stop" +msgstr "Stop" + +#: front/src/components/Sidebar.vue:161 +msgid "Stop radio" +msgstr "Stop radio" + +#: front/src/App.vue:22 +msgid "Submit" +msgstr "Submit" + +#: front/src/views/admin/Settings.vue:85 +msgid "Subsonic" +msgstr "Subsonic" + +#: front/src/components/auth/SubsonicTokenForm.vue:2 +msgid "Subsonic API password" +msgstr "Subsonic API password" + +#: front/src/App.vue:26 +msgid "Suggested choices" +msgstr "Suggested choices" + +#: front/src/components/library/FileUpload.vue:3 +msgid "Summary" +msgstr "Summary" + +#: front/src/components/Footer.vue:39 +msgid "Support forum" +msgstr "Support forum" + +#: front/src/components/library/FileUpload.vue:78 +msgid "Supported extensions: %{ extensions }" +msgstr "Supported extensions: %{ extensions }" + +#: front/src/components/playlists/Editor.vue:9 +msgid "Syncing changes to server…" +msgstr "Syncing changes to server…" + +#: front/src/components/common/CopyInput.vue:3 +msgid "Text copied to clipboard!" +msgstr "Text copied to clipboard!" + +#: front/src/components/Home.vue:26 +msgid "That's simple: we loved Grooveshark and we want to build something even better." +msgstr "" +"That's simple: we loved Grooveshark and we want to build something even " +"better." + +#: front/src/components/Footer.vue:53 +msgid "The funkwhale logo was kindly designed and provided by Francis Gading." +msgstr "The funkwhale logo was kindly designed and provided by Francis Gading." + +#: front/src/views/content/libraries/Form.vue:34 +msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "The library and all its tracks will be deleted. This can not be undone." + +#: front/src/components/library/FileUpload.vue:39 +msgid "The music files you are uploading are tagged properly:" +msgstr "The music files you are uploading are tagged properly:" + +#: front/src/components/audio/Player.vue:67 +msgid "The next track will play automatically in a few seconds…" +msgstr "The next track will play automatically in a few seconds…" + +#: front/src/components/Home.vue:121 +msgid "The plaform is free and open-source, you can install it and modify it without worries" +msgstr "" +"The platform is free and open-source, you can install it and modify it " +"without worries" + +#: front/src/components/auth/SubsonicTokenForm.vue:4 +msgid "The Subsonic API is not available on this Funkwhale instance." +msgstr "The Subsonic API is not available on this Funkwhale instance." + +#: front/src/components/library/FileUpload.vue:43 +msgid "The uploaded music files are in OGG, Flac or MP3 format" +msgstr "The uploaded music files are in OGG, Flac or MP3 format" + +#: front/src/views/content/Home.vue:4 +msgid "There are various ways to grab new content and make it available here." +msgstr "There are various ways to grab new content and make it available here." + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgid "This action is irreversible." +msgstr "This action is irreversible." + +#: front/src/components/library/Album.vue:85 +msgid "This album is present in the following libraries:" +msgstr "This album is present in the following libraries:" + +#: front/src/components/library/Artist.vue:63 +msgid "This artist is present in the following libraries:" +msgstr "This artist is present in the following libraries:" + +#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgid "This domain is subject to specific moderation rules" +msgstr "This domain is subject to specific moderation rules" + +#: front/src/views/content/Home.vue:9 +msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "This instance offers up to %{quota} of storage space for every user." + +#: front/src/components/auth/Profile.vue:16 +msgid "This is you!" +msgstr "This is you!" + +#: front/src/views/content/libraries/Form.vue:71 +msgid "This library contains my personal music, I hope you like it." +msgstr "This library contains my personal music, I hope you like it." + +#: front/src/views/content/remote/Card.vue:131 +msgid "This library is private and your approval from its owner is needed to access its content" +msgstr "" +"This library is private and approval from its owner is needed to access its " +"content" + +#: front/src/views/content/remote/Card.vue:132 +msgid "This library is public and you can access its content freely" +msgstr "This library is public and you can access its content freely" + +#: front/src/components/common/ActionTable.vue:45 +msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "" +"This may affect a lot of elements or have irreversible consequences, please " +"double check this is really what you want." + +#: front/src/components/library/FileUpload.vue:52 +msgid "This reference will be used to group imported files together." +msgstr "his reference will be used to group imported files together." + +#: front/src/components/audio/PlayButton.vue:73 +msgid "This track is not available in any library you have access to" +msgstr "This track is not available in any library you have access to" + +#: front/src/components/library/Track.vue:171 +msgid "This track is present in the following libraries:" +msgstr "This track is present in the following libraries:" + +#: front/src/views/playlists/Detail.vue:37 +msgid "This will completely delete this playlist and cannot be undone." +msgstr "This will completely delete this playlist and cannot be undone." + +#: front/src/views/radios/Detail.vue:27 +msgid "This will completely delete this radio and cannot be undone." +msgstr "This will completely delete this radio and cannot be undone." + +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgid "This will completely disable access to the Subsonic API using from account." +msgstr "" +"This will completely disable access to the Subsonic API using from account." + +#: front/src/App.vue:132 src/components/Footer.vue:72 +msgid "This will erase your local data and disconnect you, do you want to continue?" +msgstr "" +"This will erase your local data and disconnect you, do you want to continue?" + +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgid "This will log you out from existing devices that use the current password." +msgstr "" +"This will log you out from existing devices that use the current password." + +#: front/src/components/playlists/Editor.vue:44 +msgid "This will remove all tracks from this playlist and cannot be undone." +msgstr "This will remove all tracks from this playlist and cannot be undone." + +#: front/src/components/audio/track/Table.vue:6 +#: front/src/components/manage/library/FilesTable.vue:37 +#: front/src/components/mixins/Translations.vue:27 +#: front/src/views/content/libraries/FilesTable.vue:54 +#: front/src/components/mixins/Translations.vue:28 +msgid "Title" +msgstr "Title" + +#: front/src/components/ShortcutsModal.vue:79 +msgid "Toggle queue looping" +msgstr "Toggle queue looping" + +#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgid "Total size" +msgstr "Total size" + +#: front/src/views/content/libraries/Card.vue:61 +msgid "Total size of the files in this library" +msgstr "Total size of the files in this library" + +#: front/src/views/admin/moderation/DomainsDetail.vue:113 +msgid "Total users" +msgstr "Total users" + +#: front/src/components/audio/SearchBar.vue:27 src/components/library/Track.vue:262 +#: front/src/components/metadata/Search.vue:138 +msgid "Track" +msgstr "Track" + +#: front/src/views/content/libraries/FilesTable.vue:205 +msgid "Track already present in one of your libraries" +msgstr "Track already present in one of your libraries" + +#: front/src/components/library/Track.vue:85 +msgid "Track information" +msgstr "Track information" + +#: front/src/components/library/radios/Filter.vue:44 +msgid "Track matching filter" +msgstr "Track matching filter" + +#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:24 +msgid "Track name" +msgstr "Track name" + +#: front/src/views/content/libraries/FilesTable.vue:209 +msgid "Track uploaded, but not processed by the server yet" +msgstr "Track uploaded, but not processed by the server yet" + +#: front/src/components/instance/Stats.vue:54 +msgid "tracks" +msgstr "tracks" + +#: front/src/components/library/Album.vue:75 +#: front/src/components/playlists/PlaylistModal.vue:33 +#: front/src/views/admin/moderation/AccountsDetail.vue:329 +#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 +#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +msgid "Tracks" +msgstr "Tracks" + +#: front/src/components/library/Artist.vue:54 +msgid "Tracks by this artist" +msgstr "Tracks by this artist" + +#: front/src/components/instance/Stats.vue:25 +msgid "Tracks favorited" +msgstr "Tracks favourited" + +#: front/src/components/instance/Stats.vue:19 +msgid "tracks listened" +msgstr "tracks listened" + +#: front/src/components/library/Track.vue:138 +#: front/src/components/manage/library/FilesTable.vue:41 +#: front/src/views/admin/moderation/AccountsDetail.vue:151 +msgid "Type" +msgstr "Type" + +#: front/src/components/manage/moderation/AccountsTable.vue:44 +#: front/src/components/manage/moderation/DomainsTable.vue:42 +msgid "Under moderation rule" +msgstr "Under moderation rule" + +#: front/src/views/content/remote/Card.vue:100 src/views/content/remote/Card.vue:105 +msgid "Unfollow" +msgstr "Unfollow" + +#: front/src/views/content/remote/Card.vue:101 +msgid "Unfollow this library?" +msgstr "Unfollow this library?" + +#: front/src/components/About.vue:17 +msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +msgstr "" +"Unfortunately, owners of this instance did not yet take the time to complete " +"this page." + +#: front/src/components/Home.vue:37 +msgid "Unlimited music" +msgstr "Unlimited music" + +#: front/src/components/audio/Player.vue:354 +msgid "Unmute" +msgstr "Unmute" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgid "Update" +msgstr "Update" + +#: front/src/components/auth/Settings.vue:50 +msgid "Update avatar" +msgstr "Update avatar" + +#: front/src/views/content/libraries/Form.vue:25 +msgid "Update library" +msgstr "Update library" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgid "Update moderation rule" +msgstr "Update moderation rule" + +#: front/src/components/playlists/Form.vue:33 +msgid "Update playlist" +msgstr "Update playlist" + +#: front/src/components/auth/Settings.vue:27 +msgid "Update settings" +msgstr "Update settings" + +#: front/src/views/auth/PasswordResetConfirm.vue:21 +msgid "Update your password" +msgstr "Update your password" + +#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/DetailArea.vue:24 +msgid "Upload" +msgstr "Upload" + +#: front/src/components/auth/Settings.vue:45 +msgid "Upload a new avatar" +msgstr "Upload a new avatar" + +#: front/src/views/content/Home.vue:6 +msgid "Upload audio content" +msgstr "Upload audio content" + +#: front/src/views/content/libraries/FilesTable.vue:57 +msgid "Upload date" +msgstr "Upload date" + +#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:221 +msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "" +"Upload denied, ensure the file is not too big and that you have not reached " +"your quota" + +#: front/src/views/content/Home.vue:7 +msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." +msgstr "" +"Upload music files (MP3, OGG, FLAC, etc.) from your personal library " +"directly from your browser to enjoy them here." + +#: front/src/components/library/FileUpload.vue:31 +msgid "Upload new tracks" +msgstr "Upload new tracks" + +#: front/src/views/admin/moderation/AccountsDetail.vue:269 +msgid "Upload quota" +msgstr "Upload quota" + +#: front/src/components/library/FileUpload.vue:229 +msgid "Upload timeout, please try again" +msgstr "Upload timeout, please try again" + +#: front/src/components/library/FileUpload.vue:100 +msgid "Uploaded" +msgstr "Uploaded" + +#: front/src/components/library/FileUpload.vue:5 +msgid "Uploading" +msgstr "Uploading" + +#: front/src/components/library/FileUpload.vue:103 +msgid "Uploading…" +msgstr "Uploading…" + +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:305 +#: front/src/views/admin/moderation/DomainsDetail.vue:241 +#: front/src/components/mixins/Translations.vue:38 +msgid "Uploads" +msgstr "Uploads" + +#: front/src/components/Footer.vue:16 +msgid "Use another instance" +msgstr "Use another instance" + +#: front/src/views/auth/PasswordReset.vue:12 +msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." +msgstr "" +"Use this form to request a password reset. We will send an email to the " +"given address with instructions to reset your password." + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgid "Use this setting to temporarily enable/disable the policy without completely removing it." +msgstr "" +"Use this setting to temporarily enable/disable the policy without completely " +"removing it." + +#: front/src/components/manage/users/InvitationsTable.vue:49 +msgid "Used" +msgstr "Used" + +#: front/src/views/content/libraries/Detail.vue:26 +msgid "User" +msgstr "User" + +#: front/src/components/instance/Stats.vue:5 +msgid "User activity" +msgstr "User activity" + +#: front/src/components/library/Album.vue:82 src/components/library/Artist.vue:60 +#: front/src/components/library/Track.vue:168 +msgid "User libraries" +msgstr "User libraries" + +#: front/src/components/library/Radios.vue:20 +msgid "User radios" +msgstr "User radios" + +#: front/src/components/auth/Signup.vue:19 +#: front/src/components/manage/users/UsersTable.vue:37 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/views/admin/moderation/AccountsDetail.vue:85 +#: front/src/components/mixins/Translations.vue:34 +msgid "Username" +msgstr "Username" + +#: front/src/components/auth/Login.vue:15 +msgid "Username or email" +msgstr "Username or email" + +#: front/src/components/instance/Stats.vue:13 +msgid "users" +msgstr "users" + +#: front/src/components/Sidebar.vue:91 +#: front/src/components/manage/moderation/DomainsTable.vue:39 +#: front/src/components/mixins/Translations.vue:35 src/views/admin/Settings.vue:81 +#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:3 +#: front/src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:36 +msgid "Users" +msgstr "Users" + +#: front/src/components/Footer.vue:29 +msgid "Using Funkwhale" +msgstr "Using Funkwhale" + +#: front/src/components/Footer.vue:13 +msgid "Version %{version}" +msgstr "Version %{version}" + +#: front/src/views/content/libraries/Quota.vue:29 +#: front/src/views/content/libraries/Quota.vue:56 +#: front/src/views/content/libraries/Quota.vue:82 +msgid "View files" +msgstr "View files" + +#: front/src/components/library/Album.vue:31 src/components/library/Artist.vue:35 +#: front/src/components/library/Track.vue:51 +#: front/src/components/metadata/ArtistCard.vue:49 +#: front/src/components/metadata/ReleaseCard.vue:53 +msgid "View on MusicBrainz" +msgstr "View on MusicBrainz" + +#: front/src/views/content/libraries/Form.vue:18 +msgid "Visibility" +msgstr "Visibility" + +#: front/src/views/content/libraries/Card.vue:59 +msgid "Visibility: everyone on this instance" +msgstr "Visibility: everyone on this instance" + +#: front/src/views/content/libraries/Card.vue:60 +msgid "Visibility: everyone, including other instances" +msgstr "Visibility: everyone, including other instances" + +#: front/src/views/content/libraries/Card.vue:58 +msgid "Visibility: nobody except me" +msgstr "Visibility: nobody except me" + +#: front/src/components/library/Album.vue:61 +msgid "Volume %{ number }" +msgstr "Volume %{ number }" + +#: front/src/components/playlists/PlaylistModal.vue:20 +msgid "We cannot add the track to a playlist" +msgstr "We cannot add the track to a playlist" + +#: front/src/components/playlists/Form.vue:14 +msgid "We cannot create the playlist" +msgstr "We cannot create the playlist" + +#: front/src/components/auth/Signup.vue:13 +msgid "We cannot create your account" +msgstr "We cannot create your account" + +#: front/src/components/audio/Player.vue:64 +msgid "We cannot load this track" +msgstr "We cannot load this track" + +#: front/src/components/auth/Login.vue:7 +msgid "We cannot log you in" +msgstr "We cannot log you in" + +#: front/src/components/auth/Settings.vue:38 +msgid "We cannot save your avatar" +msgstr "We cannot save your avatar" + +#: front/src/components/auth/Settings.vue:14 +msgid "We cannot save your settings" +msgstr "We cannot save your settings" + +#: front/src/components/Home.vue:127 +msgid "We do not track you or bother you with ads" +msgstr "We do not track you or bother you with ads" + +#: front/src/components/library/Track.vue:95 +msgid "We don't have any copyright information for this track" +msgstr "We don't have any copyright information for this track" + +#: front/src/components/library/Track.vue:106 +msgid "We don't have any licensing information for this track" +msgstr "We don't have any licensing information for this track" + +#: front/src/components/library/FileUpload.vue:40 +msgid "We recommend using Picard for that purpose." +msgstr "We recommend using Picard for that purpose." + +#: front/src/components/Home.vue:7 +msgid "We think listening to music should be simple." +msgstr "We think listening to music should be simple." + +#: front/src/components/PageNotFound.vue:10 +msgid "We're sorry, the page you asked for does not exist:" +msgstr "We're sorry, the page you asked for does not exist:" + +#: front/src/components/Home.vue:153 +msgid "Welcome" +msgstr "Welcome" + +#: front/src/components/Home.vue:5 +msgid "Welcome on Funkwhale" +msgstr "Welcome to Funkwhale" + +#: front/src/components/Home.vue:24 +msgid "Why funkwhale?" +msgstr "Why funkwhale?" + +#: front/src/components/audio/EmbedWizard.vue:13 +msgid "Widget height" +msgstr "Widget height" + +#: front/src/components/audio/EmbedWizard.vue:6 +msgid "Widget width" +msgstr "Widget width" + +#: front/src/components/Sidebar.vue:118 +#: front/src/components/manage/moderation/AccountsTable.vue:72 +#: front/src/components/manage/moderation/DomainsTable.vue:58 +msgid "Yes" +msgstr "Yes" + +#: front/src/components/auth/Logout.vue:8 +msgid "Yes, log me out!" +msgstr "Yes, log me out!" + +#: front/src/views/content/libraries/Form.vue:19 +msgid "You are able to share your library with other people, regardless of its visibility." +msgstr "" +"You are able to share your library with other people, regardless of its " +"visibility." + +#: front/src/components/library/FileUpload.vue:33 +msgid "You are about to upload music to your library. Before proceeding, please ensure that:" +msgstr "" +"You are about to upload music to your library. Before proceeding, please " +"ensure that:" + +#: front/src/components/auth/Logout.vue:7 +msgid "You are currently logged in as %{ username }" +msgstr "You are currently logged in as %{ username }" + +#: front/src/views/content/Home.vue:17 +msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." +msgstr "" +"You can follow libraries from other users to get access to new music. Public " +"libraries can be followed immediately, while following a private library " +"requires approval from its owner." + +#: front/src/components/Home.vue:133 +msgid "You can invite friends and family to your instance so they can enjoy your music" +msgstr "" +"You can invite friends and family to your instance so they can enjoy your " +"music" + +#: front/src/views/auth/EmailConfirm.vue:24 +msgid "You can now use the service without limitations." +msgstr "You can now use the service without limitations." + +#: front/src/components/library/radios/Builder.vue:7 +msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." +msgstr "" +"You can use this interface to build your own custom radio, which will play " +"tracks according to your criteria." + +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." +msgstr "" +"You can use those to enjoy your playlist and music in offline mode, on your " +"smartphone or tablet, for instance." + +#: front/src/views/admin/moderation/AccountsDetail.vue:46 +msgid "You don't have any rule in place for this account." +msgstr "You don't have any rule in place for this account." + +#: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgid "You don't have any rule in place for this domain." +msgstr "You don't have any rule in place for this domain." + +#: front/src/components/Sidebar.vue:158 +msgid "You have a radio playing" +msgstr "You have a radio playing" + +#: front/src/components/audio/Player.vue:71 +msgid "You may have a connectivity issue." +msgstr "You may have a connectivity issue." + +#: front/src/App.vue:17 +msgid "You need to select an instance in order to continue" +msgstr "You need to select an instance in order to continue" + +#: front/src/components/auth/Settings.vue:99 +msgid "You will be logged out from this session and have to log in with the new one" +msgstr "" +"You will be logged out from this session and have to log in with the new one" + +#: front/src/components/auth/Settings.vue:70 +msgid "You will have to update your password on your clients that use this password." +msgstr "" +"You will have to update your password on your clients that use this password." + +#: front/src/components/favorites/List.vue:115 +msgid "Your Favorites" +msgstr "Your Favourites" + +#: front/src/components/Home.vue:114 +msgid "Your music, your way" +msgstr "Your music, your way" + +#: front/src/views/Notifications.vue:7 +msgid "Your notifications" +msgstr "Your notifications" + +#: front/src/views/auth/PasswordResetConfirm.vue:29 +msgid "Your password has been updated successfully." +msgstr "Your password has been updated successfully." + +#: front/src/components/auth/Settings.vue:100 +msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" +msgstr "" +"Your Subsonic password will be changed to a new, random one, logging you out " +"from devices that used the old Subsonic password" diff --git a/front/locales/eo/LC_MESSAGES/app.po b/front/locales/eo/LC_MESSAGES/app.po index 18fcb5a7e73e47835782c1e17ffb7d9128f4026c..360e6a23796b60c774f3f5baf2c301d696205d6d 100644 --- a/front/locales/eo/LC_MESSAGES/app.po +++ b/front/locales/eo/LC_MESSAGES/app.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-20 10:50+0000\n" -"Last-Translator: Elza Gelez <elza@gelez.xyz>\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-03-19 16:47+0000\n" +"Last-Translator: Mélanie Chauvel <perso@hack-libre.org>\n" "Language-Team: none\n" "Language: eo\n" "MIME-Version: 1.0\n" @@ -19,1867 +19,3243 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{title}\" je %{artist}" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{index} da %{length})" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(malplena)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Ensaluti en via Funkwhale konto" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" -msgstr[0] "Unu el %{total} estas selekta" -msgstr[1] "%{count} el %{total} estas selektaj" - -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +msgstr[0] "Unu el %{ total } estas selektita" +msgstr[1] "%{ count } el %{ total } estas selektitaj" + +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" -msgstr[0] "%{count} kanto" -msgstr[1] "%{count} kantoj" +msgstr[0] "%{ count } kanto" +msgstr[1] "%{ count } kantoj" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" -msgstr[0] "%{count} kanto en %{albumsCount} albumo" -msgstr[1] "%{count} kantoj en %{albumsCount} albumoj" +msgstr[0] "%{ count } kanto en %{ albumsCount } albumo" +msgstr[1] "%{ count } kantoj en %{ albumsCount } albumoj" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{count} kanto kongruas kun la tutaj filtriloj" msgstr[1] "%{count} kantoj kongruas kun la tutaj filtriloj" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "Aldonis unu kanto al atendovico" -msgstr[1] "Aldonis %{ count }kantoj al atendovico" - #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" -msgstr[0] "%{count} kanto" -msgstr[1] "%{count} kantoj" +msgstr[0] "%{ count } kanto" +msgstr[1] "%{ count } kantoj" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{current} uzantas el %{max} rajtantas" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{hours} h %{minutes} min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{minutes} min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "%{ username } akceptis vian sekvadon de muzikejo \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } sekvis vian muzikejon \"%{ library }\"" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } volas sekvi vian muzikejon “%{ library }â€" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Profilo de %{username}" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">Pri " -"%{instanceName}</translate>" #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "Unu albumo" -msgstr[1] "%{count} albumoj" +msgstr[1] "%{ count } albumoj" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "Unu stelumo" -msgstr[1] "%{count} stelumoj" +msgstr[1] "%{ count } stelumoj" + +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Orda muzikejo" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" -msgstr "Eraro okazis dum alÅuto de tiu dosiero" +msgstr "Reta eraro okazis dum alÅuto de tiu dosiero" + +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Mallonga resumo priskribante viaj ÅanÄoj" #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "Pri %{instance}" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "Pri %{instanceName}" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "Pri Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "PripaÄo" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" -msgstr "Pri tiu instanco" +msgstr "Pri ĉi tiu instanco" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Akcepti" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" -msgstr "akceptanta" +msgstr "Akceptita" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Atingo malaktivigas" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Elekti filtrilon" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Atingo malaktivigas" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Aldoni al stelumoj" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Viaj sciigoj" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Aldoni al ludlisto…" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Atingo malaktivigas" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" msgstr "Atingas vian muzikon kun pura interfaco ke koncentras Äin sur gravaĵo" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Atingdato" +msgstr "Atingo malaktivigas" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Kontoj" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Kontoj" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Kontdatumo" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Preferoj de via konto" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Preferoj de via konto" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" -msgstr "Pozicio de via konto" +msgstr "Konta statuso" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" -msgstr "Retadreso de via konto" +msgstr "Konta retadreso" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +#, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Kontoj" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Ago" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" -msgstr[0] "Ago %{action} sukcese komenciÄis por %{count} ero" -msgstr[1] "Ago %{action} sukcese komenciÄis por %{count} eroj" +msgstr[0] "Ago %{ action } sukcese komenciÄis por %{ count } ero" +msgstr[1] "Agoj %{ action } sukcese komenciÄis por %{ count } eroj" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Agoj" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Aktiva" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Aktivo" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" -msgstr "Videblo de Akto" +msgstr "Videblo de Aktivo" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Aldoni" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Aldoni domajnon" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Aldoni moderecan regulon" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Aldoni novan moderecan regulon" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Aldoni kaj administri datumoj" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" -msgstr "Aldoni muzikon" +msgstr "Aldoni enhavon" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" -msgstr "Aldoni filtrilo" +msgstr "Aldoni filtrilon" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" -msgstr "Aldonu filtriloj por agordi vian radion" +msgstr "Aldonu filtrilojn por tajlori vian radion" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Aldoni al aktuala atendovico" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Aldoni al stelumoj" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Aldoni al ludlisto…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Aldoni al atendovico" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" -msgstr "Aldoni al tiu ludlisto" +msgstr "Aldoni al ĉi tiu ludlisto" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Aldoni kanton" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Administranto" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administrejo" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Albumo" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Albumo kun %{count} kanto, je %{artist}" -msgstr[1] "Albumo kun %{count} kantoj, je %{artist}" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Albumo" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" -msgstr "Albumnomo" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Albumoj je ĉi-tiu artisto" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" +msgstr "Nomo de albumo" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Albumpagô" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Nomo de albumo" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Albumoj" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Albumoj je ĉi-tiu artisto" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Ĉia" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "Ĉiu %{ count } ero estas selekta" +msgstr[1] "Ĉiuj %{ count } eroj estas selektaj" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" -msgstr "Eraro okazis kiam konservi viajn ÅanÄojn" +msgstr "Eraro okazis dum konservo de viaj ÅanÄoj" + +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Eraro okazis dum konservo de viaj ÅanÄoj" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Eraro okazis dum konservo de viaj ÅanÄoj" #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Nekonata eraro okazis, povus signifi ke la servilo paneas aÅ ne estas atingebla" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Ago" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Akcepti" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Akceptita" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "Akceptita kaj aplikita" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Ĉu vi vere volas elsaluti?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artisto" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artisto" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Nomo de artisto" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "ArtistpaÄo" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Nomo de artisto" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artisto, albumo, kanto…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistoj" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistoj" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Foste" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Demandi pasvortrenuligadon" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Muzika datumo" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Fulmoklavoj de muzika ludilo" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Disponeblaj ludlistoj" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avataro" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Reiri al ensalutpaÄo" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Äœisdati agordojn" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrapido" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Bloki ĉiu" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Bloki ĉiu el tiu konto aÅ domajno. Preventos interagojn kaj malaperigis Äian " -"datumon (dosieroj, muzikejoj, sekvadoj…)" +msgstr "Bloki ĉiu el tiu konto aÅ domajno. Preventos interagojn kaj malaperigis Äian datumon (dosieroj, muzikejoj, sekvadoj…)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Folii" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Folii muzikejon" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Folii radiojn" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Folii artistojn" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Folii ludlistojn" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Folii radiojn" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Konstruilo" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Je %{artist}" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "Se vi malsekvantus tiun muzikejon, vi perdus Äian muzikon." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "KaÅmemora grando" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Nuligi" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Kandidatoj" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Ne eblas ÅanÄi vian pasvorton" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "Ne povis alÅuti tiun dosieron, certigi ne tro grandas" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "ÅœanÄi lingvon" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "ÅœanÄi mian pasvorton" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "ÅœanÄi pasvorton" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "ÅœanÄi vian pasvorton" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Ĉu vi volas ÅanÄi vian pasvorton?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Konservis ÅanÄoj" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "ÅœanÄi vian pasvorton ankaÅ ÅanÄos vian Subsonic API pasvorto se vi petis tiun." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "ÅœanÄi vian pasvorton tiel rezultigos" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Babilejo" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Elekti vian instanco" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Purigi muzikejon" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Purigi" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Purigi" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Purigi ludliston" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Purigi vian atendovico" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Alklaki unu foje, aÅskulti enkonstruitajn radiojn dum horoj" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Alklaki por selekti elÅutontaj dosieroj, aÅ Åovi kaj demeti dosierojn aÅ dosierujojn" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" msgid "Close" msgstr "Fermi" +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" +msgid "Close" +msgstr "Fermi" + +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Kodo" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Malgrandigi" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Preferoj" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Konfirmi" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Konfirmi vian retadreson" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Konfirmada kodo" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Elekti filtrilon" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Elekti filtrilon" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "Äœisdatigis datumon, alklaku aktualigi por vidi novan datumon" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Kontribui" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Kopii" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "Kopii kantojn el la aktuala atendovico en tiu ludlisto" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "Kopi kaj alglui tiun kodon en via retejo" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Kopirajto" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Ne povis konfirmi vian retadreson" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Eraro dum skano de malloka muzikejo" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "Ne povis traktadi tiun kanton, certiÄi Äi estas bone etikedata" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Reludversioj, kantparoloj, nian celon estas havi ĉiujn ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Krei" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Krei Funkwhale konton" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Krei novan ludliston" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Krei novan ludliston" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Krei novan muzikejon" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Krei novan ludliston" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Krei konton" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Krei ludliston" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Krei muzikejon" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Kreu mian konton" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Krei ludliston" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Krei vian propran radion" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Kreodato" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Aktuala avataro" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Aktuala muzikejo" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Aktuala kanto" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Aktuala uzo" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Dato" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Kantodatumo" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Malgrandigi volumo" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Forigi" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Forigi ludliston" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Forigi muzikejon" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Forigi moderecan regulon" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Forigi ludliston" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Forigi radion" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Forigi tiun muzikejon?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Forigi tiun muzikejon?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Forigi tiun muzikejon?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Forigi tiun moderecan regulon?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Forigi tiun moderecan regulon?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Forigi tiun muzikejon?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Malfoste" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Resumo" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detalo" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Resumo" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Detaloj" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." -msgstr "" -"Kiom da datumo la uzanto povas elÅuti. Lasi malplene por uzi la defaÅlta " -"valoro de la instanco." +msgstr "Kiom da datumo la uzanto povas elÅuti. Lasi malplene por uzi la defaÅlta valoro de la instanco." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Kiu estas la videblo de viaj aktoj" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Malatingeblu" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Malatingeblu Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Ĉu vi volas malatingeblu la Subsonic API?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Malaktiva" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Malkovri kiel vi povas uzi Funkwhale el aliaj aplikaĵoj" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "AfiÅata nomo" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Publike montri" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Neniam alÅuti aÅdovidaĵojn (muzikoj, albumbildoj, avataroj…) el tiu konto aÅ " -"domajno. AnkaÅ forigos aktualajn datumojn." +msgstr "Neniam alÅuti aÅdovidaĵojn (muzikoj, albumbildoj, avataroj…) el tiu konto aÅ domajno. AnkaÅ forigos aktualajn datumojn." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Ĉu vi volas purigi la \"%{playlist}\" ludliston?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Ĉu vi volas konfirmi tiun akton?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Ĉu vi volas forigi la \"%{playlist}\" ludliston?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Ĉu vi volas forigi la \"%{radio}\" radion?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Ĉu vi volas forigi la \"%{radio}\" radion?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Ĉu vi volas komenci %{action} por %{count} ero?" msgstr[1] "Ĉu vi volas komenci %{action} por %{count} eroj?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Ĉu vi volas reÅargi vian antaÅan atendovicon?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Dokumentaro" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Domajno" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Domajnoj" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "ElÅuti" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Treni kaj guti horizontaloj por reordigi kantojn en la ludlisto" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "DaÅro" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Konfirmintas retadreson" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Facila uzo" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Redakti" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Redakti" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Eraro kiam ruli akton" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Redakti informoj de instanco" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Redakti…" +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Äœisdati moderecan regulon" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Ludi tiun kanton" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Ludi tiun kanton" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Ludi tiun kanton" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Redakti" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Redakti" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "RetmesaÄo" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Retadreso" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Enkorpigi" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Enkorpiga kodo" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Enkorpigi tiun albumon en via retejo" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Enkorpigi tiun kanton en via retejo" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Enkorpigi tiun kanton en via retejo" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "Sendintaj sekvadoj de muzikejo" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Sendintaj mesaÄoj" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Aktiva" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Fini redakto" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Tajpu retadreson de muzikejo" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Tajpu nomon de radio…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Tajpu nomon de artisto…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Tajpu nomon de ludlisto…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +#, fuzzy +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Tajpu la retadreson bindanta al via konto" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Tajpu vian retadreson" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Tajpu vian invitkodon (usklecoblindan)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Tajpu vian serĉon…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Tajpu vian uzantnomon" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Tajpu vian uzantnomon aÅ retadreson" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Eraro" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Eraroraportado" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Eraroraportado" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Eraris" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Eraro kiam ruli akton" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Eraro kiam demandi renuligadon de pasvorto" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Eraro kiam ruli akton" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Eraro kiam ÅanÄi vian pasvorton" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Eraro kiam krei domajnon" +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Eraro kiam krei regulon" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Eraro kiam krei inviton" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Eraro kiam krei regulon" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Eraro kiam krei inviton" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Eraro dum skano de malloka instanco" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" msgid "Error while saving settings" msgstr "Eraro kiam konservi preferojn" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "Error while saving settings" +msgstr "Eraro kiam konservi preferojn" + +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Eraro kiam konservi preferojn" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Eraris" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Erarintaj dosieroj" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Ĉiu" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Ĉiu en ĉi-tiu instanco" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +#, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Ĉiu en ĉiuj instancoj" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Ekskluzivi" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "FortempiÄa dato" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "FortempiÄis" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "FortempiÄis aÅ uzantiÄis" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" -"Ekspliku kial vi aplikas tiun regularon. Depende de la preferado de via " -"instanco, Äi helpos vin memori kial vi agis sur tiu konto aÅ domajno, kaj " -"povus esti afiÅa por helpi uzantoj kompreni kiuj moderecaj reguloj ekzistas." +msgstr "Ekspliku kial vi aplikas tiun regularon. Depende de la preferado de via instanco, Äi helpos vin memori kial vi agis sur tiu konto aÅ domajno, kaj povus esti afiÅa por helpi uzantoj kompreni kiuj moderecaj reguloj ekzistas." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Eraris" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Erarintaj kantoj:" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Erarintaj kantoj:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Stelumoj" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Stelumoj" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federo" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Federo" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Dosiernomo" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Dosieroj" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Filtri nomon" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Finanto" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "Unua vido" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Dato de unua vido" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Sekvi" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Sekvi mallokajn muzikejojn" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Peto da sekvado atendanta konsenton" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" msgid "Followers" msgstr "Sekvantoj" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Followers" +msgstr "Sekvantoj" + +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Sekvata" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "El %{album} albumo je %{artist}" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Sekvi" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Malatingeblu" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale funkcias kun aliaj muzikludiloj ke apogas la Subsonic API." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Uzi Funkwhale facilegas." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale estas dizajna tiel ke estas facile aÅskulti muzikon vi Åatas, aÅ malkovri novajn artistojn." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale estas senpaga kaj lasis vin estri vian muzikon." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale atentas manipuli vian muzikon" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Generalaj fulmoklavoj" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Akiri novan inviton" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Iru al la muzikejo" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" -msgstr "" -"Akiri kvalitaj metadatumoj pri vian muzikon kun <a href=\"%{url}\" target=\"" -"_blank\">MusicBrainz</a>" +msgstr "Akiri kvalitaj metadatumoj pri vian muzikon kun <a href=\"%{url}\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Komencu" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Ricevi helpon" + #: front/src/components/Footer.vue:37 +#, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "Ricevi helpon" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Komenci" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Iru hejme" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Folii artistojn" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "KaÅi konton aÅ domajnon, krom sekvantoj." +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Aldoni muzikon" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Hejmo" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Muzikhoroj" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Tamen, atingi Funkwhale el tiuj aplikaĵo bezonas alian pasvorton ke vi povas difini malsupre." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Se la retadreso provizanta dum la antaÅa etapo korektas kaj bindas al uzantkonto, vi baldaÅ ricevus retmesaÄon kun renuligadaj instrukcioj." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Importdato" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importi muzikon el multe da servicoj, kiel YouTube aÅ SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Importstato" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Importfonto" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Importstato" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Importstato" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importinta" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Importdato" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Novaj aldonoj" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "En stelumoj" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Malaktiva" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Pliigi volumon" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Tajpu la retadreson bindanta al via konto" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Internigi el atendovico (unu kanto)" msgstr[1] "Internigi el atendovico (%{count} kantoj)" +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Instanca datumo" + #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Instanca datumo" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Instanca informo" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Instancaj radioj" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Instancaj preferoj" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Instanca datumo" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Malbona dosiertipo, aserti Äi estas aÅda dosiero. %{ extensions } funkcias" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -"Malbona dosiertipo, aserti Äi estas aÅda dosiero. %{ extensions } funkcias" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Invita kodo" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Invita kodo (nedeviga)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Invitoj" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Cimspuradilo" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Konservi postsignojn de viaj preferitaj kantoj" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "Fulmoklavo" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Konataj kontoj" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Konataj muzikejoj" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Lasta akto" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Lasta kontrolado" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Lasta redakto" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Lasta vidanto" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Dato de lasta vido" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Lasta Äisdatigo:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Lanĉi" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Lerni pli pri tiu instanco" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Lasu malplena por hazarda kodo" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Lasu malplena por adaptiÄema fenestraĵo" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Muzikejoj" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Muzikejoj" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Äœisdatigas muzikejon" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Muzikejoj helpas vin organizi kaj diskonigi viajn muzikarojn. Vi povas elÅuti vian propran muzikaron je Funkwhale kaj diskonigi Äin kun viajn amikojn kaj familio." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Muzikejo" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Kreis muzikejon" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Äœisdatigas muzikejon" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Forigis muzikejon" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Muzikejaj dosieroj" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Äœisdatigas muzikejon" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Permesilo" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +#, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Åœargas sekvantojn…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Åœargas sekvantojn…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "ÅœarÄas muzikejojn…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Åœargas datumon de muzikejo…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "ÅœarÄas sciigojn…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" msgstr "Åœargas mallokajn muzikejojn…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "ÅœarÄas uzdatumon…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "ÅœarÄas viajn stelumojn…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +#, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Loka konto" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Ensaluti" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Ensaluti en via Funkwhale konto" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Elsaluti" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Elsuta je %{username}" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Ensaluti" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Stato de konektado" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Elsaluti" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "Åœajni ke vi ne jam havas muzikejon, kreu unu." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Ripeto malaktivas. Alklaki por aktivi ripetado de la aktuala kanto." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Ripetas unu kanton. Alklaki por aktivi ripetado de la tutan atendovico." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Ripetas la tutan atendovicon. Alklaki por malaktivi ripeto." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Teksto" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Precipa menuo" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Administri muzikejon" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Manipuli ludlistojn" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Manipuli uzantojn" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Manipuli viajn ludlistojn" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Marki ĉiujn legata" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Marki legata" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Marki mallegata" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "Mb" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "AÅdilo" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Aligis je %{date}" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "PoÅkomputilaj kaj komputilaj aplikaĵoj" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +#, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Modereco" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"Moderecaj regularoj helpas vin kontroli kial via instanco interagis kun " -"domajnon aÅ konton." +msgstr "Moderecaj regularoj helpas vin kontroli kial via instanco interagis kun domajnon aÅ konton." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Modifdato" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Modifdato" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Muziko" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Silentigi" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Lasta akto" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Viaj sciigoj" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Mia konto" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Mia mojosa priskribo" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Mia mojosa muzikejo" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Mia mojosa ludlisto" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Mia mojosa radio" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Miaj muzikejoj" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "ND" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nomo" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nomo" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nomo" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nova pasvorto" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Novaj kantoj estos aldonataj ĉi-tie aÅtomate." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "BaldaÅa kanto" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Ne" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Nek aldonaĵoj, nek kromprogramoj: vi nur bezonas retmuzikejo" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Neniom albumo kongruas kun via serĉo" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Neniom artisto kongruas kun via serĉo" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "Nenio teksto disponeblas por tiu kanto." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Ni havas nenia licenca informado pri tiu kanto" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Neniu muzikejo korespondas." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." msgstr "Nenio sciigoj jam." +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" + #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Neniu krom mi" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Neniu sekvas tiun muzikejon" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Ne uzantata" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Sciigoj" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Sciigoj" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Oficiala retejo" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Malnova pasvorto" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Malferma" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Äœisdati moderecan regulon" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Malfermi profilon" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Vidi en MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Malfermi profilon" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Malfermi profilon" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Malfermi retejon" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "AÅ agordi vian regulon" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Ordo" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Ordo" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Orda direkto" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Proprietulo" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Maltrovita paÄo" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Maltrovitas paÄon!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "PaÄeco" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Pasvorto" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Pasvorto aktuliginta" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Pasvorto sukcese aktualiginta" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "PaÅzi kanton" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "PaÅzi/ludi la aktualan kanton" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "PaÅza" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "Atendas" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Atendas aprobon" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Atendantaj dosieroj" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Atendantaj petoj da sekvado" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Atendantaj dosieroj" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Atendantaj dosieroj" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Rajtoj" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Rajtoj" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Ludi" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Ludi ĉiu" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Ludi ĉiuj albumoj" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Ludi baldaÅe" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Ludi sekvan kanton" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Ludi tuj" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Ludi antaÅa kanto" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Ludi tiun kanton" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Ludi kanton" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Ludi…" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Ludlisto" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Ludisto enhavanta unu kanto, je %{username}" msgstr[1] "Ludisto enhavanta %{count} kantoj, je %{username}" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Ludlisto kreiintas" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Ludlista redaktilo" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nomo de la ludlisto" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Ludlisto aktualigintas" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Ludlistvideblo" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Ludlistoj" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Ludlistoj" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "Ludlistoj? Jen" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Bonvolu rekontroli ke via pasvorto Äustas" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Bonvolu rekontroli ke vian uzantnomo kaj pasvorto Äustas" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF, aÅ JPG. Maksimume 2Mo. La bildo malgrandigos al 400×400 rastrumero." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "PaÄeco" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." msgstr "Preventi konton aÅ domajno de sendi sciigoj, krom el sekvantoj." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "AntaÅvido" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "AntaÅa kanto" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Eraro dum skano" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Konfirmi" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Ensalutu" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Procedas" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Malfermi profilon" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1888,1100 +3264,1973 @@ msgstr "Procedas" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Purigi" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "Purigi erarajn dosierojn?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "Purigi atendantajn dosierojn?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "Purigi ignoratajn dosierojn?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Atendovico" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Atendovico miksiÄis!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Faranto de radio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Kreis radion" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nomo de la radio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Äœisdatigas radion" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radioj" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radioj" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "Kialo" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "Ricevintaj sekvadoj de muzikejo" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Ricevintaj mesaÄoj" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Novaj aldonoj" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Novaj aldonoj" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Novaj stelumoj" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Lastatempaj aÅskultantoj" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Äœisdatigi" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Äœisdatigi" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Aktualigi instancinformon" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Aktualigi instancinformon" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "AktualiÄi datumon de tabelo" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Aligis je %{date}" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "Registrigadoj fermitas je tiu instanco, vi bezonos invitkodon por registrigi." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "normala uzanto" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Malakcepti" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Malakcepti aÅdovidaĵon" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Malakceptinta" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Rekomenci importadon" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Dato de lasta vido" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Mallokaj muzikejoj" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Mallokaj muzikejoj apartenas al aliaj uzantoj el la reto. Vi povas atingi ilin se ili estas publika aÅ vi estas akceptinta." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Forigi" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Forigi profilbildon" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Forigi profilbildon" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Forigi el stelumoj" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Forigas elÅutatajn sed jam procezontajn kantojn, aldonante tiu datumo al via " -"kvoto." +msgstr "Forigas elÅutatajn sed jam procezontajn kantojn, aldonante tiu datumo al via kvoto." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" -"Forigas elÅutatajn preterlasinta kantojn, aldonante tiu datumo al via kvoto." +msgstr "Forigas elÅutatajn preterlasinta kantojn, aldonante tiu datumo al via kvoto." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" -"Forigas elÅutatajn kantojn ke la servilo ne povis procezi, aldonante tiu " -"datumo al via kvoto." +msgstr "Forigas elÅutatajn kantojn ke la servilo ne povis procezi, aldonante tiu datumo al via kvoto." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Demandi novan pasvorton" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Demandi novan Subsonic API pasvorton?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Demandi pasvorton" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Renuligadi vian pasvorton" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Rekomenci importadon" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Rezultoj per paÄo" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Reiru al ensalutpaÄo" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Vidi dosierojn" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Regulo" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Konservi" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Skano komencis" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Skani nun" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Foste" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Preterlasis skanon (antaÅa skano tro junas)" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Skano atendas" - -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Skana" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Malsukcese skanis" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Skanas… (%{progress}%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Serĉi" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Serĉi mallokan muzikejon" +#: front/src/components/manage/library/EditsCardList.vue:211 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Serĉu per titolo, artisto, domajno…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Serĉu per domajno, uzantnomo, biografio…" + +#: front/src/components/manage/library/UploadsTable.vue:241 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Serĉu per domajno, uzantnomo, biografio…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Serĉu per domajno, uzantnomo, biografio…" + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Serĉu per titolo, artisto, albumo…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Serĉu per titolo, artisto, albumo…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" msgstr "Serĉu per domajno, uzantnomo, biografio…" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" msgstr "Serĉu per nomo…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "Serĉu per titolo, artisto, albumo…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Serĉu per titolo, artisto, domajno…" - #: front/src/components/manage/users/InvitationsTable.vue:153 +#, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Serĉu per uzantnomo, retpoÅtdreso, kodo…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Serĉu per uzantnomo, retpoÅtadreso, nomo…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Serĉu artistoj, albumoj, kantoj…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Serĉi muzikon" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Serĉi je lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Serĉi je Vikipedio" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "Dua menuo" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Sekcioj" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Elekti filtrilon" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Elekti unu eron" msgstr[1] "Elekti ĉiun la %{total} erojn" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Elekti nur la aktualan uzon" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Agordoj" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Agordoj Äisdatigas" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Agordoj sukcese Äisdatigas." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Diskonigi ligilon" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Diskonigu tiu ligilo kun aliaj uzantoj, do ili povas peti atingon al via " -"muzikejo." +msgstr "Diskonigu tiu ligilo kun aliaj uzantoj, do ili povas peti atingon al via muzikejo." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Diskoniga ligilo" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "AfiÅi %{count} kanto" msgstr[1] "AfÅi %{count} kantoj" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "AfiÅi unu alian albumon" msgstr[1] "AfiÅi %{count} aliajn albumojn" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "AfiÅi disponeblajn fulmoklavojn" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "AfiÅi mallegintajn sciigojn" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "AfiÅi/kaÅi pasvorton" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "AfiÅas rezultoj de %{start} al %{end} de %{total}" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Miksi atendocivo" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Miksi vian atendovico" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "RegistriÄi" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "RegistriÄi" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Registrada dato" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "Mutigi akto" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Mutigi sciigojn" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +#, fuzzy +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Grando" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Grando" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Preterlasinta" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Preterlasintaj dosieroj" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Aplikaĵo" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Pardonon, la paÄo vi petis ne ekzistas:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Fontkodo" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Skipano" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Komenci" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Stopi radion" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Statistikoj" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" -"Statistikoj estas kalkula el konata aktiveco kaj datumoj de via instanco, " -"kaj ne kongruas la generalan aktivecon de tiu konto" +msgstr "Statistikoj estas kalkula el konata aktiveco kaj datumoj de via instanco, kaj ne kongruas la generalan aktivecon de tiu konto" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" -"Statistikoj estas kalkula el konata aktiveco kaj datumoj de via instanco, " -"kaj ne kongruas la generalan aktivecon de tiu domajno" +msgstr "Statistikoj estas kalkula el konata aktiveco kaj datumoj de via instanco, kaj ne kongruas la generalan aktivecon de tiu domajno" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "Statistikoj estas kalkula el konata aktiveco kaj datumoj de via instanco, kaj ne kongruas la generalan aktivecon de tiu konto" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Stato" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Stato" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Stato" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Stato" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Stato" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Stopi" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Stato" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Stopi radion" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Submeti" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsono" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Subsona API pasvorto" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Ni ne povis Åargi tiun kanton" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Ni ne povis Åargi tiun kanton" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Ni ne povis Åargi tiun kanton" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Sugestaj elektoj" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Resumo" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Helpretejo" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Funkcianta sufikso: %{extensions}" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Sinkronigas ÅanÄojn al servilo…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Kopiis teksto al tondujo!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Simplas: ni amis Grooveshark kaj ni volas konstrui iu plue bona." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "La emblemo de Funkwhale estis dizajni kaj disponigi je Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." msgstr "La muzikejo kaj ĉiuj Äiaj kantoj forigos. Ne povos malfari." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "La muzika dosiero vi elÅutas bone etikedas:" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" msgstr "La sekva kanto ludos aÅtomate je kelkajn sekundoj…" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" +msgstr "La platformo estas libera kaj malfermitkoda, vi povas instali Äin malzorge" + +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Ludlisto kreiintas" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Tiu akto ne estas malfaronta." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" msgstr "" -"La platformo estas libera kaj malfermitkoda, vi povas instali Äin malzorge" #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "La Subsona API ne disponeblas en tiu Funkwhale instanco." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Ni ne povas aldoni kanton al ludlisto" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Tiu akto ne estas malfaronta." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "La elÅutintaj muzikaj dosieroj estas OGG, Flac aÅ MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "Estas multe vojoj akiri novan datumon kaj aldoni ĉi-tie." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Tiu akto ne estas malfaronta." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Tiu albumo estas en ĉi-tiuj muzikejoj:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Tiu artisto estas en ĉi-tiuj muzikejoj:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "Tiu domajno havas specialajn moderecajn regulojn" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "Tiu instanco oferi Äis %{quota} de memorado per uzanto." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Estas vin!" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Tiu muzikejo enhavas mian personan muzikon, mi esperas vi Åatas Äin." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Tiu muzikejo estas privata kaj vi bezonas akceptadon el Äia proprulo por " -"vidi Äia enhavo" +msgstr "Tiu muzikejo estas privata kaj vi bezonas akceptadon el Äia proprulo por vidi Äia enhavo" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "Tiu muzikejo estas publika do vi povas vidi Äia enhavo libere" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "Tiu efektus multe da ero aÅ havus malfarontajn konsekvencojn, bonvolu recertiÄi vi vere volas tiun." + +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." msgstr "" -"Tiu efektus multe da ero aÅ havus malfarontajn konsekvencojn, bonvolu " -"recertiÄi vi vere volas tiun." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Tiu referenco estus uzonta por grupigi importadajn dosierojn kune." -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Ne povis traktadi tiun kanton, certiÄi Äi estas bone etikedata" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Kanto elÅutanta, sed la servilo ne jam procezis Äin" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Kanto jam estas en unu de viaj muzikejoj" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "Tiu kanto ne disponeblas en iu aj muzikejo vi povas atingi" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Tiu kanto ne estas en ĉi-tiu muzikejoj:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Tute forigos tiun ludliston kaj ne povus esti malfaronta." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Tute forigos tiun radion kaj ne povus esti malfaronta." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Tute malaktivas atingo al la subsona API el tiu konto." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "Forigos vian lokan datumon kaj elsalutos vin, ĉu vi volas kontinui?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Elsalutos vin el ĉiu viaj aparatoj ke uzas la aktualan pasvorton." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Tute forigos tiun ludliston kaj ne povus esti malfaronta." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Forigos ĉiujn kantojn el tiu ludlisto kaj ne povus esti malfaronta." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "Titolo" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Title" msgstr "Titolo" +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "Titolo" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "Baskuli ripetadon de la atendovico" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Tuta grando" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Tuta grando de tiu muzikejo" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Tutaj uzantoj" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Kanto" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "Kanto jam estas en unu de viaj muzikejoj" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Kanto" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Nomo de kanto" + +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Kantodatumo" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Kanto kongruanta filtrilo" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Nomo de kanto" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "Kanto elÅutanta, sed la servilo ne jam procezis Äin" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Kantoj" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "kantoj" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Kantoj" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Kantoj je tiu artisto" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Stelumantaj kantoj" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "aÅskultintaj kantoj" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Kanto kongruanta filtrilo" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipo" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipo" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" msgstr "Moderece" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Malsekvi" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Malsekvi tiun muzikejon?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Malfeliĉe, proprulo de tiu instanco ne jam plenigis tiu paÄon." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Infinita muziko" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Malmutigi" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Äœisdati" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Äœisdati ludliston" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Äœisdati avataron" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Äœisdati muzikejon" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Äœisdati moderecan regulon" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Äœisdati ludliston" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Äœisdati agordojn" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Äœisdati vian pasvorton" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "ElÅuti" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "ElÅuti novan avataron" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "ElÅuti aÅdon" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "ElÅutdato" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "ElÅutdato" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "Malakcepti elÅuto, certiÄi la dosieron ne tro grandas kaj vi nur havas spaco" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"Malakcepti elÅuto, certiÄi la dosieron ne tro grandas kaj vi nur havas spaco" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" -"ElÅuti muzikajn dosierojn (MP3, OGG, FLAC, ktp.) el via persona muzika " -"dosierujo direkte en via retumilo por Äui ilin ĉi-tie." +msgstr "ElÅuti muzikajn dosierojn (MP3, OGG, FLAC, ktp.) el via persona muzika dosierujo direkte en via retumilo por Äui ilin ĉi-tie." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "ElÅuti novajn kantojn" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "ElÅutlimito" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "ElÅuto tempolimis, bonvolu reprovi" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "ElÅutinta" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "ElÅutanta" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "ElÅutanta…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "ElÅutoj" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "ElÅutoj" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "ElÅutoj" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Uzi alian instancon" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." -msgstr "" -"Uzu tiun formularo por peti pasvortnuligado. Ni sendos retpoÅtmesaÄon kun " -"instrukcioj por nuligi vian pasvorton." +msgstr "Uzu tiun formularo por peti pasvortnuligado. Ni sendos retpoÅtmesaÄon kun instrukcioj por nuligi vian pasvorton." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Uzu tiun agordon por tempe aktivigi/malaktivigi la regularon sen tute forigi " -"Äin." +msgstr "Uzu tiun agordon por tempe aktivigi/malaktivigi la regularon sen tute forigi Äin." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Uzinta" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Uzanto" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Uzanta aktivado" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Uzantaj muzikejoj" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Uzantaj radioj" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Uzantnomo" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Uzantnomo aÅ retpoÅtadreso" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "uzantoj" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Uzantoj" #: front/src/components/Footer.vue:29 +#, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "Uzi Funkwhale" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Versio %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Vidi dosierojn" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Vidi en MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Videblo" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Videblo: ĉiu en ĉi-tiu instanco" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Videblo: ĉiu, enhave aliaj instancoj" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Videblo: neniu krom mi" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Videblo" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Volumo %{number}" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Ni ne povas aldoni kanton al ludlisto" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Ni ne povis krei ludliston" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Ni ne povis krei vian konton" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Ni ne povis Åargi tiun kanton" +#: front/src/components/federation/FetchButton.vue:69 +#, fuzzy +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "ÅœarÄas viajn stelumojn…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Ni ne povis ensaluti vin" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Ni ne povis memori vian avataron" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Ni ne povis memori viajn agordojn" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Ni ne povis krei vian konton" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Ni ne spionas vin aÅ Äenas vin kun reklamoj" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Ni havas nenia kopirajta informado pri tiu kanto" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Ni havas nenia licenca informado pri tiu kanto" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Ni rekomendas Picard pro fari tiun." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Ni pensas ke aÅskulti muzikon devus simpli." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Pardonon, la paÄo vi petis ne ekzistas:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Bonvenon" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Bonvenon en Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Kial Funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Alto de fenestraĵo" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "LarÄo de fenestraĵo" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Jes" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Jes, elsaluti min!" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" -"Vi povas diskonigi vian muzikejon kun aliaj uloj, sendepende Äia videblo." +msgstr "Vi povas diskonigi vian muzikejon kun aliaj uloj, sendepende Äia videblo." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" +msgstr "Vi estas elÅutota muziko al via muzikejo. AntaÅ komenci, bonvolu certiÄi ke:" + +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." msgstr "" -"Vi estas elÅutota muziko al via muzikejo. AntaÅ komenci, bonvolu certiÄi ke:" #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Vi aktuale estas ensaluta al %{username}" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." -msgstr "" -"Vi povas sekvi muzikejoj el aliaj uzantoj por atingi novan muzikon. Publikaj " -"muzikejoj sekvadeblas rekte, sed sekvado de privataj muzikejoj bezonas " -"akceptado el Äia proprulo." +msgstr "Vi povas sekvi muzikejoj el aliaj uzantoj por atingi novan muzikon. Publikaj muzikejoj sekvadeblas rekte, sed sekvado de privataj muzikejoj bezonas akceptado el Äia proprulo." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" +msgstr "Vi povas inviti amikojn kaj familio en via instanco do ili povas Äui vian muzikon" + +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." msgstr "" -"Vi povas inviti amikojn kaj familio en via instanco do ili povas Äui vian " -"muzikon" #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Vi nun povas uzi la servico senlime." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." -msgstr "" -"Vi povas uzi tiun interfaco por konstrui viajn proprajn radiojn, ke ludos " -"kantojn laÅ viaj reguloj." +msgstr "Vi povas uzi tiun interfaco por konstrui viajn proprajn radiojn, ke ludos kantojn laÅ viaj reguloj." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." -msgstr "" -"Vi povas uzi tiujn por Äui vian muzikon kaj ludlistojn nekonektite, kun via " -"poÅtelefono aÅ tabuleto ekzemple." +msgstr "Vi povas uzi tiujn por Äui vian muzikon kaj ludlistojn nekonektite, kun via poÅtelefono aÅ tabuleto ekzemple." + +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Vi ne havas iu ajn regulon por tiu konto." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Vi ne havas iu ajn regulon por tiu konto." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Vi ne havas iu ajn regulon por tiu konto." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Vi ne havas iu ajn regulon por tiu domajno." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "" + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Vi ludas radion" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "Vi havus konekta problemo." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Vi devu elekti instanco por kontinui" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Vi estos elÅaluta el tiu seanco kaj devus ensaluti denove" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Vi devos rekte ÅanÄi vian pasvorton en la aplikaĵo ke uzas Äin." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Ludlisto kreiintas" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Viaj sciigoj" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Viaj stelumoj" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Via muziko, via vojo" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Viaj sciigoj" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Via pasvorto sukcese ÅanÄis." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Agordoj Äisdatigas" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" -msgstr "" -"Via Subsona pasvorto ÅanÄos hazarde, elsalutontas vin el aparetoj ke uzis la " -"malnovan pasvorton" +msgstr "Via Subsona pasvorto ÅanÄos hazarde, elsalutontas vin el aparetoj ke uzis la malnovan pasvorton" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "PaÄeco" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Kopirajto" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Albumo kun %{count} kanto, je %{artist}" +msgstr[1] "Albumo kun %{count} kantoj, je %{artist}" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "Aldonis unu kanto al atendovico" +msgstr[1] "Aldonis %{ count }kantoj al atendovico" diff --git a/front/locales/es/LC_MESSAGES/app.po b/front/locales/es/LC_MESSAGES/app.po index 23a8832895e28560600ac3ae2fd8a436d0b95256..3f31090e08c2b0586877b2dc18b46df30493dc5e 100644 --- a/front/locales/es/LC_MESSAGES/app.po +++ b/front/locales/es/LC_MESSAGES/app.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-17 05:50+0000\n" -"Last-Translator: Allan Nordhøy <epost@anotheragency.no>\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-02-25 08:45+0000\n" +"Last-Translator: Ale London <alelondon@gmail.com>\n" "Language-Team: none\n" "Language: es\n" "MIME-Version: 1.0\n" @@ -19,162 +19,300 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", por %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } de %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(vacÃo)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Iniciar sesión con tu cuenta de Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } de %{ total } elemento seleccionado" msgstr[1] "%{ count } de %{ total } elementos seleccionados" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } canción" msgstr[1] "%{ count } canciones" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } canción en %{ albumsCount } álbumes" msgstr[1] "%{ count } canciones en %{ albumsCount } álbumes" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } canción coincidiendo con filtros combinados" msgstr[1] "%{ count } canciones coincidiendo con filtros combinados" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } canción ha sido añadida a tu cola de reproducción" -msgstr[1] "%{ count } canciones han sido añadidas a tu cola de reproducción" - #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} canción" msgstr[1] "%{ count } canciones" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } usados de %{ max } permitidos" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "" +msgstr "%{ username } ha aceptado tu follow en la librerÃa \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" -msgstr "" +msgstr "%{ username } ha seguido tu librerÃa \"%{ library }\"" + +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } quiere seguir tu librerÃa \"%{ library }\"" #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Perfil de %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 álbum" msgstr[1] "%{ count } álbumes" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 favorito" msgstr[1] "%{ count } favoritos" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Una biblioteca impecable" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Ha ocurrido un error al subir este archivo" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Ha ocurrido un error al guardar los cambios" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "Sobre %{ instance }" #: front/src/components/Footer.vue:6 -#, fuzzy +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "Sobre %{ instance }" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "Acerca de Funkwhale" #: front/src/components/Footer.vue:10 -#, fuzzy +msgctxt "Footer/About/List item.Link" msgid "About page" -msgstr "Página del álbum" +msgstr "Acerca de" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "Acerca de esta instancia" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Aceptar" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Aceptado" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Acceso deshabilitado" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" -msgstr "Accede a tu música con una interfaz limpia y concéntrate en lo que realmente importa" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Seleccionar un filtro" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Acceso deshabilitado" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Añadir a favoritos" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Silenciar notificaciones" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Añadir a lista de reproducción…" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Acceso deshabilitado" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "Accede a tu música con una interfaz limpia enfocada a lo que realmente importa" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Fecha de acceso" +msgstr "Acceso deshabilitado" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Cuentas" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 #, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Cuentas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" -msgstr "Cuenta activa" +msgstr "Datos de cuenta" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Configuración de cuenta" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Configuración de Cuenta" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Estado de cuenta" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Correo de la cuenta" @@ -182,1754 +320,2951 @@ msgstr "Correo de la cuenta" #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 #, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" -msgstr "Estado de cuenta" +msgstr "Cuentas" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Acción" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "Acción %{ action } fue iniciado exitosamente en %{ count } elemento" msgstr[1] "Acción %{ action } fue iniciado exitosamente en %{ count } elementos" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Acciones" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Activo" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Actividad" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Mostrar mi actividad" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" -msgstr "" +msgstr "Añadir" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" -msgstr "" +msgstr "Añadir un dominio" + +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +#, fuzzy +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Añadir una nueva regla de moderación" #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" -msgstr "" +msgstr "Añadir una nueva regla de moderación" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Agregar y gestionar contenido" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Añadir contenido" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Añadir filtro" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Añade filtros para personalizar tu radio" -#: front/src/components/audio/PlayButton.vue:64 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" -msgstr "Añadir a la cola de reproducción" +msgstr "Añadir a la cola de reproducción actual" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Añadir a favoritos" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 -#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Añadir a lista de reproducción…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Añadir a la cola de reproducción" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Añadir a esta lista de reproducción" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Añadir canción" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administración" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Ãlbum" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Ãlbum que contiene %{ count } canción, de %{ artist }" -msgstr[1] "Ãlbum que contiene %{ count } canciones, de %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Ãlbum" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Ãlbumes de este artista" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Ãlbum" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Página del álbum" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Ãlbum" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Ãlbumes" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Ãlbumes de este artista" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Todo" +#: front/src/components/common/ActionTable.vue:59 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } de %{ total } elemento seleccionado" +msgstr[1] "%{ count } de %{ total } elementos seleccionados" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Ha ocurrido un error al guardar los cambios" +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Ha ocurrido un error al guardar los cambios" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Ha ocurrido un error al guardar los cambios" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Ha ocurrido un error desconocido, esto puede significar que el servidor está fuera de servicio o no se puede conectar" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Acción" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" +msgstr "Aprobar" + +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Aprobar" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" msgstr "" #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "¿Seguro que quieres cerrar la sesión?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artista" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artista" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Nombre del artista" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Página del artista" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Nombre del artista" #: front/src/components/audio/Search.vue:65 -#, fuzzy +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artista, álbum, canción…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistas" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistas" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Ascendente" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Restablecer contraseña" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 -#, fuzzy +msgctxt "Content/Moderation/Title" msgid "Audio content" -msgstr "Añadir contenido" +msgstr "Contenido de Audio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" +msgstr "Atajos de teclado del reproductor de Audio" + +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Listas de reproducción disponibles" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Volver a la página de conección" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Actualizar ajustes" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrate" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" -msgstr "" +msgstr "Bloquear todo" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" +msgstr "Bloquear todo de esta cuenta o dominio. Ésto prevendrá cualquier interacción con la entidad, y eliminará los contenidos relacionados (subidas, librerÃas, follows, etc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Explorar" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Explorar biblioteca" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Explorando radios" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Explorando artistas" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Explorando listas de reproducción" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Explorando radios" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Editor" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "De %{ artist }" -#: front/src/views/content/remote/Card.vue:103 -#, fuzzy +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "Si dejas de seguir esta biblioteca, perderás acceso a su contenido." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" -msgstr "" +msgstr "Tamaño en caché" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Cancelar" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Candidatos" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "No se puede cambiar la contraseña" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 -#, fuzzy +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" -msgstr "Imposible subir este archivo, asegúrate que no es demasiado grande" +msgstr "No es posible subir este archivo, asegúrate que no es demasiado grande" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Cambiar idioma" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Cambiar mi contraseña" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Cambiar contraseña" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Cambiar tu contraseña" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "¿Cambiar tu contraseña?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Cambios sincronizados con el servidor" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Cambiar tu contraseña también cambiará tu contraseña Subsonic API si pediste una." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "Cambiar tu contraseña tendrá las siguientes consecuencias" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" +msgstr "Sala Chat" + +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." msgstr "" -#: front/src/App.vue:13 +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Escoge tu instancia" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Una biblioteca impecable" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Limpiar" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Limpiar" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Vaciar lista de reproducción" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Vaciar cola de reproducción" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Pulsa una sola vez y escucha durante horas, gracias a las radios integradas" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Haz click para seleccionar archivos o arrastralos aquà para subirlos" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" msgid "Close" +msgstr "Cerrar" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" +msgid "Close" +msgstr "Cerrar" + +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Código" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Contraer" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configurar" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Confirmar" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 -#, fuzzy +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Confirma tu correo electrónico" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Código de confirmación" -#: front/src/components/common/ActionTable.vue:7 -msgid "Content have been updated, click refresh to see up-to-date content" +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Seleccionar un filtro" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Seleccionar un filtro" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." msgstr "" +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "El contenido ha sido actualizado, haz click en refrescar para ver el contenido actualizado" + #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" -msgstr "" +msgstr "Contribuye" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Copiar" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" -msgstr "Copiar canciones de cola de reproducción actual a lista de reproducción" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "Copiar canciones de cola de reproducción a lista de reproducción" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" -msgstr "" +msgstr "Copia/Pega este código en el HTML de tu página web" -#: front/src/components/library/Track.vue:91 -#, fuzzy +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" -msgstr "Copiar" +msgstr "Copyright" #: front/src/views/auth/EmailConfirm.vue:7 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" -msgstr "Confirma tu correo electrónico" +msgstr "No hemos podido confirmar tu dirección electrónica" #: front/src/views/content/remote/ScanForm.vue:3 -#, fuzzy +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" -msgstr "Error al importar la biblioteca externa" - -#: front/src/views/content/libraries/FilesTable.vue:213 -#, fuzzy -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "Ha ocurrido un error al procesar esta pista, asegúrate que esta etiquetada correctamente" +msgstr "Error al importar la biblioteca remote" -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Covers, letras, queremos conseguirlo todo ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" -msgstr "Crear importación" +msgstr "Crear" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Crear una cuenta de funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Crear una nueva lista de reproducción" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Crear una nueva lista de reproducción" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Crear una nueva biblioteca" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Crear una nueva lista de reproducción" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Crear una cuenta" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Crear una lista de reproducción" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Crear biblioteca" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Crear mi cuenta" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Crear una lista de reproducción" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Crear tu propia radio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Fecha de creación" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar actual" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Biblioteca actual" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Canción actual" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Uso actual" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Fecha" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Información de la canción" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" -msgstr "" - -#: front/src/components/manage/library/FilesTable.vue:190 +msgstr "Reducir volumen" + +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Borrar" -#: front/src/views/content/libraries/Form.vue:39 +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Borrar lista de reproducción" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Borrar biblioteca" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 -#, fuzzy +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" -msgstr "Borrar radio" +msgstr "Borrar regla de moderación" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Borrar lista de reproducción" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Borrar radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "¿Eliminar la biblioteca?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "¿Eliminar la biblioteca?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "¿Eliminar la biblioteca?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 -#, fuzzy +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" +msgstr "¿Eliminar ésta regla de moderación?" + +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "¿Eliminar ésta regla de moderación?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" msgstr "¿Eliminar la biblioteca?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Descendente" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Descripción" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detalle" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Descripción" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Detalles" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Establecer cuanto contenido puede subir el usuario. Déjalo en blanco para usar el valor por defecto de la instancia." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Determina el nivel de visibilidad de tu actividad" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Desactivar acceso" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Desactivar el acceso Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "¿Desactivar el acceso al API de Subsonic?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" -msgstr "Desactivar acceso" +msgstr "Desactivado" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Descubre cómo utilizar Funkwhale desde otras aplicaciones" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" -msgstr "Nombre del archivo" +msgstr "Mostrar nombre" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Mostrar públicamente" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" +msgstr "No descargar ningún archivo media (audio, portada de album, avatar de cuenta...) de ésta cuenta o dominio. Ésto borrará el contenido existente también." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "¿Quieres vaciar la lista de reproducción \"%{ playlist }\"?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "¿Quieres confirmar esta acción?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "¿Quieres borrar la lista de reproducción \"%{ playlist }\"?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "¿Quieres borrar la radio \"%{ radio }\"?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "¿Quieres borrar la radio \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "¿Quieres realizar la acción %{ action } en %{ count } elemento?" msgstr[1] "¿Quieres realizar la acción %{ action } en %{ count } elementos?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "¿Quieres restaurar tu cola de reproducción anterior?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentación" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" -msgstr "" +msgstr "Dominio" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" -msgstr "" +msgstr "Dominios" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Descargar" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Arrastra y suelta las filas para reordenar canciones en la lista de reproducción" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Duración" #: front/src/views/auth/EmailConfirm.vue:23 -#, fuzzy +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" -msgstr "Correo electrónico confirmado" +msgstr "Dirección e-mail confirmada" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Fácil de usar" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Editar" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Editar" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Error al aplicar la acción" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Editar la información de esta instancia" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Actualizar regla de moderación" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Reproducir canción" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Reproducir canción" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Reproducir canción" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Editar" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 #, fuzzy -msgid "Edit…" +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" msgstr "Editar" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Correo electrónico" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Dirección de correo electrónico" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" -msgstr "" +msgstr "Incrustar" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" -msgstr "" +msgstr "Código empotrado" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" -msgstr "" +msgstr "Inserta éste álbum en tu página web" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Inserta esta canción en tu página web" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" -msgstr "" +msgstr "Inserta esta canción en tu página web" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 -#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" -msgstr "Introducir url de biblioteca" +msgstr "La biblioteca emitida sigue" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" -msgstr "" +msgstr "Mensajes emitidos" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" -msgstr "" +msgstr "Habilitado" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Terminar la edición" #: front/src/views/content/remote/ScanForm.vue:50 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" -msgstr "Introducir url de biblioteca" +msgstr "Introducir URL de biblioteca" -#: front/src/components/library/Radios.vue:140 -#, fuzzy +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Introducir un nombre de radio…" -#: front/src/components/library/Artists.vue:118 -#, fuzzy +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" -msgstr "Introducir un nombre de artista…" +msgstr "Introduce un nombre de artista…" #: front/src/views/playlists/List.vue:107 -#, fuzzy +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" -msgstr "Introducir un nombre de lista de reproducción…" +msgstr "Introduce un nombre de lista de reproducción…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +#, fuzzy +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Ingresa la dirección de correo electrónico vinculada a tu cuenta" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Introducir tu correo electrónico" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Introducir tu código de invitación (no distingue mayúsculas de minúsculas)" #: front/src/components/metadata/Search.vue:114 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" -msgstr "Introducir tu búsqueda…" +msgstr "Introduce tu búsqueda…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Introduce tu nombre de usuario" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Introduce tu nombre de usuario o correo electrónico" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Error" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Informes de error" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Informes de error" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Error" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Error al aplicar la acción" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Error al pedir restablecimiento de contraseña" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Error al aplicar la acción" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Error al cambiar tu contraseña" #: front/src/views/admin/moderation/DomainsList.vue:6 -#, fuzzy +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" -msgstr "Error al crear la invitación" +msgstr "Error al crear dominio" + +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Error al crear la regla" #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Error al crear la invitación" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 -#, fuzzy +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" -msgstr "Error al crear la invitación" +msgstr "Error al crear la regla" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 #, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Error al crear la invitación" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" -msgstr "Error al importar la biblioteca externa" +msgstr "Error al obtener información del nodo" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Error al guardar los cambios" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Error al guardar los cambios" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Error al guardar los cambios" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Error" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Archivos con error" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Todo el mundo" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Todo el mundo en esta instancia" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 #, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" -msgstr "Todo el mundo en esta instancia" +msgstr "Todo el mundo, en todas las instancias" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Excluir" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Fecha de caducidad" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Caducada" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Caducada/usada" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" +msgstr "Explique por qué está aplicando esta polÃtica. Dependiendo de la configuración de su instancia, esto le ayudará a recordar por qué actuó en esta cuenta o dominio, y puede mostrarse públicamente para ayudar a los usuarios a comprender qué reglas de moderación existen." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" -msgstr "" +msgstr "Ha fallado" -#: front/src/views/content/remote/Card.vue:58 -#, fuzzy +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" -msgstr "Canciones con error:" +msgstr "Pistas fallidas:" + +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Pistas fallidas:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favoritos" #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favoritos" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federación" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 #, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Federación" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nombre del archivo" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Archivos" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Nombre del filtro" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Terminado" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" -msgstr "" +msgstr "Primera vista" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 -#, fuzzy +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" -msgstr "Fecha de caducidad" +msgstr "Primera fecha de visualización" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Seguir" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Seguir bibliotecas remotas" -#: front/src/views/content/remote/Card.vue:88 -#, fuzzy +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Solicitud de seguimiento pendiente de aprobación" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Seguidores" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Seguidores" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Siguiendo" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Del álbum %{ album } de %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Seguir" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Desactivar acceso" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale es compatible con otros reproductores de música que soportan la API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale es facilÃsimo de usar." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale está diseñado para que sea fácil escuchar la música que te gusta, o descubrir nuevos artistas." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale es gratis y te da el control de tu música." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale te ayuda a gestionar tu música" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" -msgstr "" +msgstr "Atajos generales" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Obtener una nueva invitación" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Llévame a la biblioteca" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Obtén metadatos de calidad para tu música con <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Comenzar" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Obteniendo ayuda" + #: front/src/components/Footer.vue:37 #, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" -msgstr "Ajustes" +msgstr "Obteniendo ayuda" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Ir" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Ir a la página principal" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Explorando artistas" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." +msgstr "Ocultar contenido de la cuenta o dominio, excepto de los seguidores." + +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Añadir contenido" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" msgstr "" #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Inicio" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Horas de música" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Sin embargo, acceder a Funkwhale desde estos clientes requiere una contraseña distinta que podrás configurar a continuación." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Si la dirección de correo electrónico proporcionada en el paso anterior es válida y asociada a una cuenta de usuario, deberÃas recibir un correo electrónico con las instrucciones de restablecimiento dentro de unos minutos." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Fecha de importación" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importa música desde otras plataformas, como YouTube o SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Estado de la importación" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Fuente de la importación" -#: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Import status" msgstr "Estado de la importación" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/manage/library/UploadsTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:11 +#: front/src/views/content/libraries/FilesTable.vue:59 #, fuzzy +msgctxt "Content/Library/*/Noun" +msgid "Import status" +msgstr "Estado de la importación" + +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" -msgstr "Fecha de importación" +msgstr "Importado" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Fecha de importación" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Añadidos recientemente" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "En favoritos" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Inactivo" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" -msgstr "" - -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "" +msgstr "Aumentar volument" -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Agregar de la cola (%{ count } pista)" msgstr[1] "Agregar de la cola (%{ count } pistas)" -#: front/src/views/admin/moderation/DomainsDetail.vue:71 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 #, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Datos de Instancia" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" -msgstr "Radios de la instancia" +msgstr "Datos de Instancia" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Información de esta instancia" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radios de la instancia" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Ajustes de la instancia" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Datos de Instancia" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Tipo de archivo no válido, asegúrese de que está cargando un archivo de audio. Las extensiones de archivo admitidas son %{ extensions }" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Código de invitación" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Código de invitación (opcional)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Invitaciones" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Sistema de seguimiento de incidentes" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Apunta tus canciones favoritas" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" -msgstr "" +msgstr "Atajos de teclado" #: front/src/views/admin/moderation/DomainsDetail.vue:161 -#, fuzzy +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" -msgstr "Mi cuenta" +msgstr "Cuentas conocidas" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Bibliotecas conocidas" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Última actividad" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" -msgstr "" +msgstr "Última comprobación" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Última modificación" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" -msgstr "" +msgstr "Visto por última vez" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 -#, fuzzy +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" -msgstr "Última actualización:" +msgstr "Última visualización" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Última actualización:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Iniciar" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Aprender más acerca de esta instancia" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Dejar en blanco para un código aleatorio" #: front/src/components/audio/EmbedWizard.vue:7 -#, fuzzy +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" -msgstr "Dejar en blanco para un código aleatorio" +msgstr "Dejar en blanco para widget responsive" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Bibliotecas" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Bibliotecas" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Biblioteca actualizada" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Las bibliotecas te ayudan a organizar tu colección de música. Puedes subir tu propia colección de musica a Funkwhale y compartirla con tus familiares y amigos." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Biblioteca" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Biblioteca creada" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 #, fuzzy -msgid "Library deleted" +msgctxt "Content/Moderation/Title" +msgid "Library data" msgstr "Biblioteca actualizada" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" +msgid "Library deleted" +msgstr "Biblioteca eliminada" + +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Archivos de biblioteca" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Biblioteca actualizada" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" +msgstr "Licencia" + +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" msgstr "" -#: front/src/views/content/libraries/Detail.vue:21 +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 #, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Cargando seguidores…" + +#: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Cargando seguidores…" #: front/src/views/content/libraries/Home.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Cargando bibliotecas…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Cargando datos de la biblioteca…" -#: front/src/views/Notifications.vue:4 -#, fuzzy +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Cargando notificaciones…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "Cargando bibliotecas externas…" +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Cargando bibliotecas remotas…" #: front/src/views/content/libraries/Quota.vue:4 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Cargando datos de uso…" #: front/src/components/favorites/List.vue:5 -#, fuzzy +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Cargando tus favoritos…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 #, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Mi cuenta" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Iniciar sesión" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Iniciar sesión con tu cuenta de Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Cerrar sesión" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Sesión iniciada como %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Iniciar sesión" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" -msgstr "Estado de cuenta" +msgstr "Estado de sesión" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Cerrar sesión" #: front/src/views/content/libraries/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "Parece que aún no tienes ninguna biblioteca, ¡Es hora de crear una!" +msgstr "Parece que aún no tienes ninguna biblioteca, Es hora de crear una!" -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Bucle deshabilitado. Pulsa para cambiar a reproducción en bucle de la canción actual." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Bucle de la canción actual. Pulsa para cambiar a la reproducción en bucle de la cola de reproducción entera." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Bucle de la cola de reproducción entera. Pulsa para desactivar la reproducción en bucle." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Letras" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" -msgstr "" +msgstr "Menú principal" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Gestionar biblioteca" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Gestionar listas de reproducción" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Gestionar usuarios" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Gestionar tus listas de reproducción" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Marcar todo como leÃdo" -#: front/src/components/notifications/NotificationRow.vue:44 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" -msgstr "Marcar todo como leÃdo" +msgstr "Marcar como leÃdo" -#: front/src/components/notifications/NotificationRow.vue:45 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" -msgstr "Marcar todo como leÃdo" +msgstr "Marcar como leÃdo" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" -msgstr "" +msgstr "Reproductor multimedia" + +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Miembro desde %{ date }" #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" -msgstr "" +msgstr "Apps móviles y de escritorio" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 #, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" -msgstr "Federación" +msgstr "Moderación" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" +msgstr "Las polÃticas de moderación te ayudan a controlar cómo tu instancia interactúa con un dominio o cuenta concretos." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Fecha de modificación" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Fecha de modificación" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Música" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Silencio" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Silenciar actividad" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Silenciar notificaciones" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Mi cuenta" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Mi descripción molona" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Mi biblioteca molona" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Mi lista de reproducción molona" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Mi radio molona" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Mis bibliotecas" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "N/A" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nombre" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nombre" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nombre" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nueva contraseña" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Las nuevas canciones se agregarán aquà de forma automática." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Próxima canción" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "No" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Sin complementos, sin extensiones : basta con una biblioteca en la web" #: front/src/components/audio/Search.vue:25 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Lo sentimos, no hemos encontrado ningún álbum que corresponda con tu búsqueda" #: front/src/components/audio/Search.vue:16 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Lo sentimos, no hemos encontrado ningún artista que corresponda con tu búsqueda" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "No hay letra disponible para esta canción." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "No tenemos ninguna información de licencia para esta pista" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Biblioteca no encontrada." -#: front/src/views/Notifications.vue:26 -#, fuzzy -msgid "No notifications yet." -msgstr "Tus notificaciones" +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "No hay notificaciones para mostrar." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Solo yo" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Nadie está siguiendo esta biblioteca" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "No usado" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Notificaciones" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Notificaciones" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Página oficial" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Antigua contraseña" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Abierta" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Actualizar regla de moderación" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Abrir perfil" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Ver en MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" -msgstr "" +msgstr "Abrir perfil" + +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Abrir perfil" #: front/src/views/admin/moderation/DomainsDetail.vue:16 -#, fuzzy +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" -msgstr "Página oficial" +msgstr "Abrir website" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 -#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" -msgstr "Añade filtros para personalizar tu radio" +msgstr "Añade filtros para personalizar tus reglas" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 -#, fuzzy +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Orden" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Orden" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Dirección del orden" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Propietario" #: front/src/components/PageNotFound.vue:33 -#, fuzzy +msgctxt "Head/*/Title" msgid "Page Not Found" -msgstr "¡Página no encontrada!" +msgstr "Página no encontrada!" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "¡Página no encontrada!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" -msgstr "" +msgstr "Paginación" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Contraseña" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Contraseña actualizada" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Contraseña actualizada con éxito" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Pausar la canción" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" -msgstr "" +msgstr "Pausar / reproducir la pista actual" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" -msgstr "" +msgstr "En pausa" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "En espera" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Aprobación pendiente" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Archivos pendientes" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Solicitudes de seguimiento pendientes" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Archivos pendientes" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Archivos pendientes" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Permisos" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Permisos" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Reproducir" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Reproducir todo" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Reproducir todos los álbumes" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Reproducir siguiente" #: front/src/components/ShortcutsModal.vue:67 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" -msgstr "Reproducir canción" +msgstr "Reproducir siguiente canción" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Reproducir ahora" #: front/src/components/ShortcutsModal.vue:63 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Canción anterior" -#: front/src/components/Sidebar.vue:211 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Reproducir canción" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Reproducir canción" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Reproducir..." + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Lista de reproducción" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Lista de reproducción con %{ count } canción, de %{ username }" msgstr[1] "Lista de reproducción con %{ count } canciones, de %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Lista de reproducción creada" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Editor de listas de reproducción" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nombre de lista de reproducción" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Lista de reproducción actualizada" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilidad de lista de reproducción" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Listas de reproducción" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Listas de reproducción" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "¿Listas de reproducción? Las tenemos" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Por favor, comprueba que tu contraseña es correcta" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Por favor, comprueba que tu nombre de usuario y contraseña son correctos" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF o JPG. Máximo de 2MB. La imagen será reducida a 400x400px." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Paginación" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" +msgstr "Evita que la cuenta o el dominio activen notificaciones, excepto de los seguidores." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" -msgstr "" +msgstr "Previsualización" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Canción anterior" -#: front/src/views/content/remote/Card.vue:39 -#, fuzzy +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Error durante el análisis" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Continuar" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Proceder a inicio de sesión" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Procesando" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Abrir perfil" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1938,1108 +3273,1973 @@ msgstr "Procesando" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Eliminar" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "¿Eliminar los archivos con errores?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "¿Eliminar los archivos pendientes?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "¿Eliminar los archivos omitidos?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Cola de reproducción" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "¡Cola de reproducción mezclada!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Editor de radio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Radio creada" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nombre de la radio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Radio actualizada" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" -msgstr "" +msgstr "Razón" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" -msgstr "" +msgstr "Seguidores recibidos en la biblioteca" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" +msgstr "Mensajes recibidos" + +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Añadidos recientemente" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" msgstr "" #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Añadidos recientemente" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Añadidos a favoritos recientemente" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Escuchados recientemente" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Recargar" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Recargar" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" +msgstr "Actualizar la información del nodo" + +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Actualizar la información del nodo" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" +msgstr "Actualiza el contenido de la tabla" + +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" msgstr "" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Inscritx desde %{ date }" +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "La inscripción a esta instancia está cerrada, necesitarás un código de invitación para inscribirte." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "Usuario estándar" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Rechazar" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 #, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" -msgstr "Rechazada" +msgstr "Rechazar media" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Rechazada" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Reiniciar importación" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Última visualización" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Bibliotecas remotas" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Las bibliotecas remotas pertenecen a otros usuarios de internet. Podrás acceder a ellas cuando sean públicas o cuando te permitan el acceso." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Quitar" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Quitar avatar" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Quitar avatar" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Quitar de favoritos" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Elimina las pistas cargadas pero aún no procesadas, agregando los datos correspondientes a su cuota." #: front/src/views/content/libraries/Quota.vue:64 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." msgstr "Esto borrará las pistas que fueron subidas pero se omitieron por alguna razón. Se borrarán completamente y recuperarás la cuota correspondiente." #: front/src/views/content/libraries/Quota.vue:90 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." msgstr "Esto borrará las pistas que fueron subidas pero no se procesaron en el servidor. Se borrarán completamente y recuperarás la cuota correspondiente." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Solicitar una nueva contraseña" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "¿Solicitar una nueva contraseña de la API Subsonic?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Solicitar una contraseña" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Restablecer tu contraseña" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Reiniciar importación" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Resultados por página" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 -#, fuzzy +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" -msgstr "Proceder a inicio de sesión" +msgstr "Volver a inicio de sesión" + +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Ver archivos" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" -msgstr "" +msgstr "Regla" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Guardar" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Análisis iniciado" -#: front/src/views/content/remote/Card.vue:63 -#, fuzzy +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" -msgstr "Reproducir ahora" +msgstr "Analizar ahora" + +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Ascendente" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Análisis omitido (el anterior análisis es demasiado reciente)" -#: front/src/views/content/remote/Card.vue:31 -#, fuzzy -msgid "Scan waiting" -msgstr "Análisis pendiente" - -#: front/src/views/content/remote/Card.vue:43 -#, fuzzy +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" -msgstr "Análisis iniciado" +msgstr "Análisis finalizado" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Análisis erróneo" -#: front/src/views/content/remote/Card.vue:35 -#, fuzzy +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Analizando…(%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Buscar" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Buscar una biblioteca externa" -#: front/src/components/manage/moderation/AccountsTable.vue:171 +#: front/src/components/manage/library/EditsCardList.vue:211 #, fuzzy -msgid "Search by domain, username, bio..." -msgstr "Buscar por nombre de usuario, correo electrónico, código…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Buscar por tÃtulo, artista, dominio…" -#: front/src/components/manage/moderation/DomainsTable.vue:151 +#: front/src/components/manage/library/LibrariesTable.vue:191 #, fuzzy -msgid "Search by name..." -msgstr "Buscar por usuario, correo electrónico, nombre…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Buscar por dominio, nombre de usuario, bio…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/components/manage/library/UploadsTable.vue:241 #, fuzzy -msgid "Search by title, artist, album…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Buscar por dominio, nombre de usuario, bio…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Buscar por dominio, nombre de usuario, bio…" + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" msgstr "Buscar por tÃtulo, artista, álbum…" -#: front/src/components/manage/library/FilesTable.vue:176 +#: front/src/components/manage/library/AlbumsTable.vue:174 #, fuzzy -msgid "Search by title, artist, domain…" -msgstr "Buscar por tÃtulo, artista, dominio…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Buscar por tÃtulo, artista, álbum…" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Buscar por dominio, nombre de usuario, bio…" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Buscar por nombre…" + +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" +msgid "Search by title, artist, album…" +msgstr "Buscar por tÃtulo, artista, álbum…" #: front/src/components/manage/users/InvitationsTable.vue:153 #, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Buscar por nombre de usuario, correo electrónico, código…" #: front/src/components/manage/users/UsersTable.vue:163 -#, fuzzy +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Buscar por usuario, correo electrónico, nombre…" #: front/src/components/audio/SearchBar.vue:20 -#, fuzzy +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Buscar artistas, álbumes, canciones…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Buscar música" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Buscar en lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Buscar en Wikipedia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" -msgstr "" +msgstr "Menú secundario" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Secciones" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Seleccionar un filtro" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Seleccionar %{ total } elemento" msgstr[1] "Seleccionar los %{ total } elementos" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Seleccionar solo la página actual" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Ajustes" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Ajustes actualizados" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Ajustes actualizados con éxito." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Compartir enlace" #: front/src/views/content/libraries/Detail.vue:15 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." msgstr "Comparte este link con otros usuarios para que puedan pedir acceso a tu biblioteca." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Compartir enlace" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "Mostrar %{ count } canción más" msgstr[1] "Mostrar %{ count } canciones más" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Mostrar 1 álbum adicional" msgstr[1] "Mostrar %{ count } álbumes adicionales" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" -msgstr "" +msgstr "Mostrar atajos de teclado disponibles" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Mostrar notificaciones leÃdas" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Mostrar/ocultar contraseña" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Mostrando resultados %{ start }-%{ end } de %{ total }" #: front/src/components/ShortcutsModal.vue:83 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Mezclar tu cola de reproducción" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Mezclar tu cola de reproducción" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Inscripción" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Inscripción" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Fecha de inscripción" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 #, fuzzy -msgid "Silence activity" -msgstr "Actividad de usuario" +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Tamaño" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 #, fuzzy -msgid "Silence notifications" -msgstr "Mostrar notificaciones leÃdas" - -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Tamaño" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Omitido" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Archivos omitidos" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" +msgstr "Software" + +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" msgstr "" +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Lo sentimos, la página solicitada no existe:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Código fuente" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Miembro del equipo" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Iniciar" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Parar radio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "EstadÃsticas" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" +msgstr "Las estadÃsticas se calculan a partir de la actividad y el contenido conocidos en tu instancia y no reflejan la actividad general de esta cuenta" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" +msgstr "Las estadÃsticas se calculan a partir de la actividad y el contenido conocidos en tu instancia y no reflejan la actividad general de éste dominio" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "Las estadÃsticas se calculan a partir de la actividad y el contenido conocidos en tu instancia y no reflejan la actividad general de esta cuenta" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Estado" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Estado" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Estado" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Parar" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Estado" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Parar radio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Enviar" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Contraseña de la API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "No podemos añadir la canción a una lista de reproducción" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "No podemos añadir la canción a una lista de reproducción" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "No podemos añadir la canción a una lista de reproducción" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Sugerencias" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Resumen" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" -msgstr "" +msgstr "Foro de soporte" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" -msgstr "" +msgstr "Extensiones soportadas: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 -#, fuzzy +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Sincronizando los cambios con el servidor…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "¡Texto copiado al portapapeles!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Asà de simple: nos encantó Grooveshark y quisimos hacer algo incluso mejor." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." +msgstr "El logotipo de Funkwhale fue generosamente diseñado y suplido por Francis Gading." + +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" msgstr "" -"El logotipo de Funkwhale fue generosamente diseñado y suplido por Francis " -"Gading." #: front/src/views/content/libraries/Form.vue:34 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." msgstr "La biblioteca y todas sus pistas serán borradas. Esta acción es irreversible." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" -msgstr "Las canciones que estás subiendo estén etiquetadas correctamente:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." msgstr "" -#: front/src/components/Home.vue:121 +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." +msgstr "Las canciones que estás subiendo estén etiquetadas correctamente:" + +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "La siguiente pista se reproducirá automáticamente en unos segundos…" + +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "La plataforma es gratuita y de código fuente libre, puedes instalarla y modificarla sin restricciones" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Lista de reproducción creada" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Ésta acción es irreversible." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "La API Subsonic no está disponible en esta instancia de Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "No podemos añadir la canción a una lista de reproducción" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Ésta acción es irreversible." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Las canciones subidas estén en formato OGG, Flac o MP3" #: front/src/views/content/Home.vue:4 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "Ofrecemos varias formas de obtener nuevo contenido para hacerlo disponible aquÃ." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." -msgstr "" +msgstr "Ésta acción es irreversible." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Este album ya está en las siguientes bibliotecas:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Este artista ya está en las siguientes bibliotecas:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" -msgstr "" +msgstr "Este dominio está sujeto a reglas de moderación especÃficas" #: front/src/views/content/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "Esta instancia ofrece %{ quota } de almacenamiento a cada usuario." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "¡Eres tú!" -#: front/src/views/content/libraries/Form.vue:71 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Esta biblioteca contiene mi música personal, ¡espero que te guste!" -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" +msgstr "Esta biblioteca es privada y se necesita la aprobación de su propietario para acceder a su contenido" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "" +msgstr "Esta biblioteca es pública y puedes acceder a su contenido libremente" -#: front/src/components/common/ActionTable.vue:45 -#, fuzzy +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." msgstr "Esto puede afectar a muchos elementos, por favor comprueba si esto es realmente lo que quieres." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Esta referencia será usada para agrupar los archivos subidos." -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Ha ocurrido un error al procesar esta pista, asegúrate que está etiquetada correctamente" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "La pista se ha subido pero aún no la ha procesado el servidor" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "La pista ya está en alguna de tus bibliotecas" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" -msgstr "" +msgstr "Esta pista no está disponible en ninguna biblioteca a la que tenga acceso" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Esta canción ya está en las siguientes bibliotecas:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Esto borrará por completo esta lista de reproducción y no se podrá deshacer." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Esto borrará por completo esta radio y no se podrá deshacer." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Esto desactivará por completo el acceso a la API Subsonic desde esta cuenta." -#: front/src/App.vue:129 src/components/Footer.vue:72 -#, fuzzy -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "Esto borrará tus datos locales y te desconectará, ¿quieres continuar?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Esto cerrará tus sesiones en todos los dispositivos que usan esa contraseña." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Esto borrará por completo esta lista de reproducción y no se podrá deshacer." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Esto borrará todas las canciones de esta lista de reproducción y no se podrá deshacer." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" msgid "Title" msgstr "TÃtulo" +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" -msgstr "" +msgstr "Alternar cola en bucle" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" -msgstr "" +msgstr "Tamaño total" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Tamaño total de la biblioteca" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 -#, fuzzy +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" -msgstr "No usado" +msgstr "Usuarios totales" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Track" +msgstr "Canción" + +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" msgid "Track" msgstr "Canción" -#: front/src/views/content/libraries/FilesTable.vue:205 +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:91 #, fuzzy -msgid "Track already present in one of your libraries" -msgstr "La pista ya está en alguna de tus bibliotecas" +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Nombre" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Información de la canción" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Canción correspondiente al filtro" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Nombre" -#: front/src/views/content/libraries/FilesTable.vue:209 -#, fuzzy -msgid "Track uploaded, but not processed by the server yet" -msgstr "La pista se ha subido pero aún no la ha procesado el servidor" - -#: front/src/components/instance/Stats.vue:54 -msgid "tracks" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" msgstr "Canciones" -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/instance/Stats.vue:54 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Canciones" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Canciones de este artista" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Canciones en favoritos" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "Canciones escuchadas" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Canción correspondiente al filtro" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipo" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipo" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "Bajo regla de moderación" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Dejar de seguir" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "¿Dejar de seguir esta biblioteca?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Desafortunadamente, lxs propietarixs de esta instancia aún no han tomado el tiempo para completar esta página." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Música ilimitada" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Activar sonido" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" -msgstr "Fecha de subida" +msgstr "Actualizar" + +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Actualizar lista de reproducción" #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Actualizar avatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Actualizar biblioteca" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Actualizar lista de reproducción" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Actualizar ajustes" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Actualizar tu contraseña" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Subir" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Subir un nuevo avatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Subir nuevo contenido" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Fecha de subida" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Fecha de subida" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 -#, fuzzy +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" msgstr "Subida rechazada, asegúrate que el archivo no es demasiado grande y que no has alcanzado tu cuota" +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." +msgstr "" + #: front/src/views/content/Home.vue:7 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." msgstr "Sube archivos de música (mp3, OGG, Flac, etc…) de tu biblioteca personal a tu navegador y disfrútala aquÃ." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Subir nuevas pistas" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Cuota de subida" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Timeout en la subida, intentalo de nuevo" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Subido" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "Subiendo" -#: front/src/components/library/FileUpload.vue:103 -#, fuzzy +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" -msgstr "Subiendo" +msgstr "Subiendo…" -#: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/manage/library/LibrariesTable.vue:52 #, fuzzy +msgctxt "Content/*/*/Noun" msgid "Uploads" -msgstr "Subir" +msgstr "Subidas" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Subidas" + +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Uploads" +msgstr "Subidas" + +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Usar otra instancia" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Usa este formulario para solicitar un restablecimiento de contraseña. Te mandaremos un correo electrónico a la dirección proporcionada con instrucciones para restablecer tu contraseña." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" +msgstr "Use esta configuración para habilitar / deshabilitar temporalmente la polÃtica sin eliminarla por completo." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Usado" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Usuario" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Actividad de usuario" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Bibliotecas de usuarios" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Radios de los usuarios" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nombre de usuario" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nombre de usuario o correo electónico" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "usuario" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Usuario" #: front/src/components/Footer.vue:29 #, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" -msgstr "Acerca de Funkwhale" +msgstr "Usando Funkwhale" #: front/src/components/Footer.vue:13 -#, fuzzy +msgctxt "Footer/*/List item" msgid "Version %{version}" -msgstr "Código fuente (%{version})" +msgstr "Versión %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Ver archivos" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Ver en MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Visibilidad" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Visibilidad: Todo el mundo en esta instancia" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Visibilidad: todos, incluyendo otras instancias" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Visibilidad: Solo yo" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilidad" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" -msgstr "" - -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "No podemos añadir la canción a una lista de reproducción" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "No podemos crear la lista de reproducción" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "No podemos crear tu cuenta" +msgstr "Volúmen %{ number }" -#: front/src/components/audio/Player.vue:64 +#: front/src/components/federation/FetchButton.vue:69 #, fuzzy -msgid "We cannot load this track" -msgstr "No podemos añadir la canción a una lista de reproducción" +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Cargando tus favoritos…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "No se puede iniciar la sesión" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "No podemos guardar tu avatar" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "No podemos guardar tus ajustes" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "No podemos crear tu cuenta" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "No te rastreamos y no te molestamos con anuncios" -#: front/src/components/library/Track.vue:95 -#, fuzzy -msgid "We don't have any copyright information for this track" -msgstr "¡No tienes ninguna notificación!" - -#: front/src/components/library/Track.vue:106 -#, fuzzy -msgid "We don't have any licensing information for this track" -msgstr "¡No tienes ninguna notificación!" - -#: front/src/components/library/FileUpload.vue:40 -#, fuzzy +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." -msgstr "Recomendamos usar Picard" +msgstr "Recomendamos usar Picard para este propósito." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Pensamos que debe ser simple escuchar música." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Lo sentimos, la página solicitada no existe:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Bienvenido" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Bienvenidx a Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "¿Por qué funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" -msgstr "" +msgstr "Altura del widget" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" +msgstr "Ancho del widget" + +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" msgstr "" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "SÃ" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "SÃ, cierra mi sesión!" #: front/src/views/content/libraries/Form.vue:19 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "Puedes compartir tu biblioteca con otras personas, independientemente de su vilibilidad." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Estás a punto de subir música a tu biblioteca. Antes de continuar asegúrate que:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Tienes iniciada actualmente sesión como %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Puedes seguir bibliotecas de otros usuarios para obtener nueva música. Puedes seguir bibliotecas públicas instantáneamente, mientras que las biliotecas privadas necesitan aprovación por parte de su dueño." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Puedes invitar a tus amigxs y tu familia a tu instancia para que ellxs puedan disfrutar de tu música" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." -msgstr "Tu dirección de correo electrónico ha sido confirmada, ahora puedes usar el servicio sin limitaciones." +msgstr "Ahora puede utilizar el servicio sin limitaciones." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Puedes usar esta interfaz para hacer tu propia radio personalizada, que reproducirá canciones según tus criterios." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Puedes usarlos para disfrutar de tus listas de reproducción y tu música en modo sin conexión, en tu smartphone o tu tablet, por ejemplo." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "No tienes ninguna regla para esta cuenta." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "No tienes ninguna regla para esta cuenta." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." -msgstr "" +msgstr "No tienes ninguna regla para esta cuenta." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." +msgstr "No tienes ninguna regla para este dominio." + +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/Sidebar.vue:158 +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Estás escuchando una radio" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." -msgstr "" - -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Tienes que seleccionar una instancia para poder continuar" +msgstr "Puede tener un problema de conectividad." #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Se cerrará esta sesión y tendrás que reiniciar sesión con la nueva contraseña" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Tendrás que actualizar tu contraseña en los clientes que usan esta contraseña." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Lista de reproducción creada" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Tus notificaciones" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Tus favoritos" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Tu música, a tu manera" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Tus notificaciones" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Tu contraseña ha sido cambiada con éxito." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Ajustes actualizados" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Tu contraseña Subsonic se cambiará a una nueva contraseña aleatoria, cerrando tus sesiones en los dispositivos que usaban la antigua contraseña Subsonic" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Paginación" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Copyright" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Ãlbum que contiene %{ count } canción, de %{ artist }" +msgstr[1] "Ãlbum que contiene %{ count } canciones, de %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } canción ha sido añadida a tu cola de reproducción" +msgstr[1] "%{ count } canciones han sido añadidas a tu cola de reproducción" diff --git a/front/locales/fr_FR/LC_MESSAGES/app.po b/front/locales/fr_FR/LC_MESSAGES/app.po index a8712c60b975e4b36b3512ad342333c8513a6b31..382c7d1077cd768d3e69233c7d1622e8861f897f 100644 --- a/front/locales/fr_FR/LC_MESSAGES/app.po +++ b/front/locales/fr_FR/LC_MESSAGES/app.po @@ -1,10 +1,11 @@ +# msgid "" msgstr "" "Project-Id-Version: French (Funkwhale)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-22 08:45+0000\n" -"Last-Translator: jovuit <jo.vuitton@gmail.com>\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-05-03 07:47+0000\n" +"Last-Translator: Pierrick <contact@pierrick.io>\n" "Language-Team: French <https://translate.funkwhale.audio/projects/funkwhale/" "front/fr/>\n" "Language: fr_FR\n" @@ -15,1879 +16,3187 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" -msgstr "\"%{ title }\", de %{ artist }" +msgstr "« %{ title } », de %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } sur %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(vide)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "%{ app } Veut accéder à votre compte funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } sur %{ total } élément sélectionné" msgstr[1] "%{ count } sur %{ total } éléments sélectionnés" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } piste" msgstr[1] "%{ count } pistes" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } piste dans %{ albumsCount } albums" msgstr[1] "%{ count } pistes dans %{ albumsCount } albums" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } piste correspondant aux filtres sélectionnés" msgstr[1] "%{ count } pistes correspondant aux filtres sélectionnés" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } piste a été ajouté à votre queue" -msgstr[1] "%{ count } pistes ont été ajoutées à votre queue" - #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} piste" msgstr[1] "%{ count } pistes" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" -msgstr "%{ current } utilisé(s) sur %{ max } autorisé(s)" +msgstr "%{ current } utilisé·s sur %{ max } autorisé·s" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" -msgstr "%{hours} h %{minutes} min" +msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" -msgstr "%{minutes} min" +msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "%{ username } a accepté votre suivi sur la librairie \"%{ library }\"" +msgstr "%{ username } a accepté votre suivi de la bibliothèque « %{ library } »" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" -msgstr "%{ username } a suivi votre librairie \"%{ library }\"" +msgstr "%{ username } suit votre bibliothèque « %{ library } »" + +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } veut suivre votre bibliothèque « %{ library } »" #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Profil de %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">À propos " -"de %{instanceName}</translate>" +"<strong>%{ track }</strong> est déjà dans <strong>%{ playlist }</strong>." #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 album" msgstr[1] "%{ count } albums" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 favori" msgstr[1] "%{ count } favoris" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Une bibliothèque propre" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Une erreur réseau s'est produite lors du téléversement de ce fichier" +#: front/src/components/library/EditForm.vue:145 +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Une courte description décrivant vos changements." + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" -msgstr "À propos de %{instance}" +msgstr "À propos de %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "À propos de %{instanceName}" #: front/src/components/Footer.vue:45 +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "À propos de Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "À propos" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +msgctxt "Content/About/Title" msgid "About this instance" msgstr "À propos de cette instance" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Accepter" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Accepté" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Accès désactivé" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" -msgstr "\"Accédez à votre musique depuis une interface épurée" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "Accès aux fichiers audio, librairies, artistes, albums et pistes" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Accès aux filtres de contenu" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Accès désactivé" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "Accès au courriel, nom d'utilisateur et informations du profil" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Accès aux favoris" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "Accès aux suivis" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "Accès à l'historique d'écoute" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Accès aux notifications" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Accès aux listes de lecture" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Accès aux radios" + +#: front/src/components/Home.vue:101 +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "" +"Accédez à votre musique depuis une interface épurée qui se concentre sur ce " +"qui compte vraiment" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +msgctxt "Content/*/*/Noun" msgid "Accessed date" msgstr "Date d'accès" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Compte" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +msgctxt "*/*/*" +msgid "Account" +msgstr "Compte" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" -msgstr "Informations de compte" +msgstr "Données du compte" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Paramètres du compte" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Paramètres du compte" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Statut du compte" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Courriel du compte" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Comptes" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Action" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "L'action %{ action } a été lancée avec succès sur %{ count } élément" msgstr[1] "L'action %{ action } a été lancée avec succès sur %{ count } éléments" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Actions" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Actif" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Activité" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Visibilité de l'activité" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Ajouter" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Ajouter un domaine" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Ajouter une politique de modération" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Ajouter une nouvelle règle de modération" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Ajouter et gérer du contenu" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "Ajouter quand même" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Ajouter du contenu" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" -msgstr "Ajouter des filtres" +msgstr "Ajouter le filtre" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Ajouter des filtres pour personnaliser votre radio" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Ajouter à la file d'attente actuelle" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Ajouter aux favoris" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Ajouter à la liste de lecture…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" -msgstr "Ajouter à la file d’attente" +msgstr "Ajouter à la liste d'attente" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" -msgstr "Ajouter à cette playlist" +msgstr "Ajouter à cette liste de lecture" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" -msgstr "Ajouter une piste" +msgstr "Ajouter la piste" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administration" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Album" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Album contenant %{ count } piste, par %{ artist }" -msgstr[1] "Album contenant %{ count } pistes, par %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Album" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 +#: front/src/views/admin/library/TrackDetail.vue:128 +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Artiste de cet album" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +msgctxt "Content/Moderation/Title" +msgid "Album data" +msgstr "Données de l'album" + +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" msgid "Album name" msgstr "Nom de l'album" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Page de l'album" - #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +msgctxt "*/*/*" msgid "Albums" msgstr "Albums" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Albums de cet·te artiste" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Tout" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "Unique élément sélectionné" +msgstr[1] "Tous les %{ count } éléments sélectionnés" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "Autoriser l'application" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" +"Une erreur a eu lieu durant le processus de téléversement. Vous trouverez " +"plus d'information ci-dessous." + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Une erreur s'est produite pendant l'enregistrement de vos modifications" +#: front/src/components/federation/FetchButton.vue:21 +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Une erreur s'est produite pendant la mise à jour des données:" + +#: front/src/components/federation/FetchButton.vue:41 +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Une erreur HTTP s'est produite en contactant le serveur distant" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" +msgstr "Une erreur inconnue s'est produite, le serveur est peut-être en panne ou injoignable" + +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "Une erreur inconnue a eu lieu" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Application" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "Détails de l'application" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "ID de l'application" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." msgstr "" -"Une erreur inconnue s'est produite, le serveur est peut-être défaillant ou " -"indisponible" +"L'ID et le \"secret\" de l'application sont des données vraiment sensibles " +"et doivent être traitées comme des mots de passe. Ne les partagez avec " +"personne." -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/auth/ApplicationEdit.vue:25 +#, fuzzy +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "Secret de l'application" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Approuver" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Approuvée" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "Approuvée et appliquée" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" -msgstr "Êtes-vous sur de vouloir vous déconnecter ?" +msgstr "Êtes-vous sûr·e de vouloir vous déconnecter ?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artiste" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artiste" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Artist data" +msgstr "Données de l'artiste" + +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" msgid "Artist name" msgstr "Nom de l'artiste" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Page de l'artiste" - #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artiste, album, piste…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistes" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistes" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" -msgstr "Ascendant" +msgstr "Croissant" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" -msgstr "Demander à réinitialiser votre mot de passe" - -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +msgstr "Demander la réinitialisation du mot de passe" + +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Contenu audio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Raccourcis du lecteur audio" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "Autoriser %{ app }" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "Autoriser une application tierce" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "Applications autorisées" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" -msgstr "Playlists disponibles" +msgstr "Listes de lecture disponibles" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Retour à la page de connexion" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Retour aux paramètres" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" -msgstr "Bitrate" +msgstr "Débit binaire" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Bloquer tout" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Bloquer tout depuis ce compte ou domaine. Cela empêche toute interaction " -"avec l'entité, et purge le contenu lié (pistes, librairies, suivis, etc.)" +msgstr "Bloquer tout de ce compte ou domaine. Cela empêche toute interaction avec l'entité, et purge le contenu lié (pistes, librairies, suivis, etc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Parcourir" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Parcourir la bibliothèque" +#: front/src/components/library/Albums.vue:4 +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Parcourir les albums" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Parcourir les artistes" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" -msgstr "Parcourir les playlists" +msgstr "Parcourir les listes de lecture" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Parcourir les radios" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Éditeur" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "De %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." -msgstr "" -"En cessant de suivre cette bibliothèque, vous perdez l’accès à son contenu." - -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +msgstr "En cessant de suivre cette bibliothèque, vous perdez l’accès à son contenu." + +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" -msgstr "Taille en cache" +msgstr "Taille du cache" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Annuler" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Pistes candidates" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Mot de passe ne peut pas être changé" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" -msgstr "" -"Impossible de transférer ce fichier, assurez-vous qu'il n'est pas trop gros" +msgstr "Impossible de transférer ce fichier, assurez-vous qu'il n'est pas trop gros" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Changer la langue" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Changer mon mot de passe" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Changer le mot de passe" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Changer votre mot de passe" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" -msgstr "Changer le mot de passe ?" +msgstr "Changer le mot de passe ?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Changements synchronisés avec le serveur" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." -msgstr "La mise à jour de votre mot de passe impactera également le mot de passe de l'API Subsonic si vous en avez demandé un." +msgstr "La mise à jour de votre mot de passe changera également le mot de passe de l'API Subsonic si vous en avez demandé un." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" -msgstr "Modifier votre mot de passe aura les conséquences suivantes" +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" +msgstr "Modifier votre mot de passe aura les conséquences suivantes :" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Salle de discussion" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" +"Cocher le périmètre \"Lecture\" ou \"Écriture\" sur le parent implique " +"l'accès à tous les périmètres enfants correspondants." + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Choisissez votre instance" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Une bibliothèque musicale de qualité" +#: front/src/components/library/EditForm.vue:75 +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Effacer" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Effacer" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" -msgstr "Vider la playlist" +msgstr "Vider la liste de lecture" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" -msgstr "Vider votre file d’attente" +msgstr "Vider la liste d'attente" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" -msgstr "Écoutez de la musique pendant des heures, en un clic, grâce aux radios intégrées." +msgstr "En un clic, écoutez de la musique pendant des heures grâce aux radios intégrées" + +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" +"Cliquer pour afficher plus d'information à propos du processus d'import pour " +"cet envoi" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" -msgstr "Cliquer pour sélectionner les fichiers a téléverser ou glisser-déposer les fichiers ou répertoires" +msgstr "Cliquez pour sélectionner les fichiers a téléverser ou glisser-déposer les fichiers ou répertoires" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Fermer" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Fermer" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "Fermer et recharger la page" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Code" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Réduire" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configuration" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Confirmer" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Confirmer votre courriel" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Code de confirmation" -#: front/src/components/common/ActionTable.vue:7 -msgid "Content have been updated, click refresh to see up-to-date content" +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "Filtre de contenu ajouté avec succès" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Filtres de contenu" + +#: front/src/components/auth/Settings.vue:116 +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Filtres de contenu" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." msgstr "" -"Le contenu a été modifié, cliquez sur rafraîchir pour voir le contenu à jour" +"Les filtres de contenu vous aident à cacher les contenus que vous ne voulez " +"pas voir sur ce service." + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "Le contenu a été modifié, cliquez sur rafraichir pour voir le contenu à jour" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Contribuer" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Copier" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" -msgstr "Copier les pistes de la file d’attente dans la playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "Copier les pistes de la file d’attente dans la liste de lecture" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "Copiez-coller le code suivant dans votre application:" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" -msgstr "Copiez/collez ce code dans votre site web HTML" +msgstr "Copiez-collez ce code dans le HTML de votre site web" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Copyright" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Nous n'avons pas pu confirmer votre adresse de courriel" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Impossible de récupérer la bibliothèque distante" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Une erreur s'est produite lors du traitement de cette piste, assurez-vous " -"que la piste est correctement étiquetée" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" -msgstr "Pochettes d'albums, paroles, notre but est de tout implémenter ;)" +msgstr "Pochettes, paroles, notre but est de tout gérer ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Créer" #: front/src/components/auth/Signup.vue:4 +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Créer un compte Funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Créer une nouvelle application" + +#: front/src/components/auth/Settings.vue:220 +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Créer une nouvelle application" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Créer une nouvelle bibliothèque" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" -msgstr "Créer une nouvelle playlist" +msgstr "Créer une nouvelle liste de lecture" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Créer un compte" +#: front/src/components/auth/ApplicationForm.vue:65 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Créer une application" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" -msgstr "Créer une bibliothèque" +msgstr "Créer la bibliothèque" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Créer mon compte" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "Créez-en une pour intégrer Funkwhale avec des applications tierces." + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" -msgstr "Créer une playlist" +msgstr "Créer la liste de lecture" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Créer votre propre radio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Date de création" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar actuel" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Bibliothèque actuelle" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Piste actuelle" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Utilisation actuelle" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" +"Les données renvoyées par le serveur distant ont des attributs manquants ou " +"invalides" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "Les données ont été rafraîchies avec succès depuis le serveur distant." + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Date" +#: front/src/components/library/ImportStatusModal.vue:64 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Informations de débogage" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Diminuer le volume" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Supprimer" +#: front/src/components/auth/Settings.vue:254 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Supprimer l'application" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "Supprimer l'application \"%{ application }\"?" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Supprimer la bibliothèque" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Supprimer la règle de modération" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" -msgstr "Supprimer la playlist" +msgstr "Supprimer la liste de lecture" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Supprimer la radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Supprimer cet album ?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Supprimer cet artiste ?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" -msgstr "Supprimer cette bibliothèque ?" +msgstr "Supprimer cette bibliothèque ?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" -msgstr "Supprimer cette règle de modération ?" - -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +msgstr "Supprimer cette règle de modération ?" + +#: front/src/components/library/EditCard.vue:94 +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Supprimer cette suggestion ?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Supprimer cet envoi ?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" -msgstr "Descendant" +msgstr "Décroissant" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Description" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Détail" +#: front/src/views/admin/library/LibraryDetail.vue:123 +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Description" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Détails" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Déterminez la quantité de contenu que l'utilisateur peut importer. Laissez vide pour utiliser la valeur par défaut de l'instance." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" -msgstr "Déterminer le niveau de visibilité de votre activité" +msgstr "Détermine le niveau de visibilité de votre activité" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Désactiver l'accès" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Désactiver l'accès via Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Désactiver l'accès à l'API Subsonic ?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Désactivé" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" -msgstr "Découvrez comment utiliser Funkwhale sur d'autres applications" +msgstr "Découvrez comment utiliser Funkwhale depuis d'autres applications" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Nom d'affichage" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Afficher publiquement" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Ne télécharger aucun média (audio, album, couverture, avatar de compte...) " -"de ce compte ou domaine. Cela purgera aussi le contenu existant." +msgstr "Ne jamais télécharger de médias (audio, album, couverture, avatar de compte...) de ce compte ou domaine. Cela purgera aussi le contenu existant." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" -msgstr "Voulez-vous vider la playlist \"%{ playlist }\" ?" +msgstr "Voulez-vous vider la liste de lecture « %{ playlist } » ?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Souhaitez-vous confirmer cette action ?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" -msgstr "Voulez-vous supprimer la playlist \"%{ playlist }\" ?" +msgstr "Voulez-vous supprimer la liste de lecture « %{ playlist } » ?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Voulez-vous supprimer la radio « %{ radio } » ?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Voulez-vous cacher le contenu de l’artiste « %{ name } » ?" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" -msgstr[0] "Voulez-vous effectuer l'action \"%{ action } sur %{ count } élément ?" -msgstr[1] "Voulez-vous effectuer l'action \"%{ action } sur %{ count } éléments ?" +msgstr[0] "Voulez-vous effectuer l'action « %{ action } » sur %{ count } élément ?" +msgstr[1] "Voulez-vous effectuer l'action « \"%{ action } » sur %{ count } éléments ?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" -msgstr "Souhaitez-vous restaurer votre file d’attente précédente ?" +msgstr "Souhaitez-vous restaurer la dernière liste d'attente ?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentation" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Domaine" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Domaines" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Télécharger" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" -msgstr "Glissez et déposer les lignes pour réordonner les pistes dans la playlist" +msgstr "Glissez et déposer les lignes pour réordonner les pistes dans la liste de lecture" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Durée" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Courriel confirmé" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Simple à utiliser" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Éditer" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Éditer" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Erreur lors du traitement de l'action" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" -msgstr "Éditer les informations de cette instance" +msgstr "Éditer les informations concernant cette instance" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Mettre à jour les règles de modération" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Modifier cette piste" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Éditer…" +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Modifier cette piste" + +#: front/src/components/library/TrackEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Modifier cette piste" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Éditer" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Éditer" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Courriel" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Adresse de courriel" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Insérer" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Code inséré" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Insérez cet album dans votre site web" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Insérez cet·te artiste dans votre site web" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Insérez cette piste dans votre site web" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" -msgstr "Suivis de librairies émis" +msgstr "Suivi de la bibliothèque émise" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Messages émis" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Activé" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Terminer l'édition" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Saisissez une URL de bibliothèque" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Saisissez un nom de radio…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "Entrer le titre de l’album…" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Saisissez un nom d'artiste…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Saisissez un nom de playlist…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Saisissez l'adresse de courriel associée à votre compte" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Saisissez votre courriel" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Saisissez votre code d'invitation (insensible à la casse)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Saisissez votre recherche…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Saisissez votre nom d'utilisateur·rice" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Saisissez votre nom d'utilisateur·rice ou courriel" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Erreur" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Rapports d'erreur" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Rapports d'erreur" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "En erreur" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Erreur lors du traitement de l'action" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" -msgstr "Erreur pendant la demande de réinitialisation de mot de passe" +msgstr "Erreur lors de la demande de réinitialisation de mot de passe" + +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Erreur lors du traitement de l'action" #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Erreur pendant le changement de mot de passe" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Erreur lors de la création du domaine" +#: front/src/components/moderation/FilterModal.vue:13 +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Erreur lors de la création de la règle" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Erreur lors de la création de l'invitation" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Erreur lors de la création de la règle" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Erreur lors de la création de l'invitation" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Erreur lors de la récupération des informations du nÅ“ud" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Erreur pendant l'enregistrement des paramètres" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Erreur pendant l'enregistrement des paramètres" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Erreur durant l'envoi des modifications" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "En erreur" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Fichiers erronés" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Tout le monde" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Tout le monde sur cette instance" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +#, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Tout le monde, sur toutes les instances" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Exclure" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Date d'expiration" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Expirée" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Expirée/utilisée" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" -"Expliquez pourquoi vous appliquez cette règle. En fonction de votre " -"configuration d'instance, cela vous aidera à vous rappeler pourquoi vous " -"avez agis sur ce compte ou domaine, et peut être affiché publiquement pour " -"aider les utilisateurs à comprendre quelles règles de modération sont en " -"place." +msgstr "Expliquez pourquoi vous appliquez cette règle. En fonction de votre configuration d'instance, cela vous aidera à vous rappeler pourquoi vous avez agis sur ce compte ou domaine, et peut être affiché publiquement pour aider les utilisateurs à comprendre quelles règles de modération sont en place." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Échoué" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Pistes échouées :" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Pistes échouées :" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favoris" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favoris" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Fédération" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Fédération" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "Champ" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nom du fichier" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Fichiers" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Nom du filtre" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Terminé" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "Découverte" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Date de découverte" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Suivre" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Suivre des bibliothèques distantes" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Demande de suivi en attente de validation" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Abonnés" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Abonnés" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Abonné" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "De l'album %{ album } par %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Suivre" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "De l’album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> par <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Désactiver l'accès" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale est compatible avec d'autres lecteurs de musique qui supportent l'API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale est très simple à utiliser." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale est conçu pour faciliter l'écoute des musiques que vous aimez et découvrir de nouveaux artistes." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale est gratuit et vous donne le contrôle sur votre musique." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale prend soin de votre musique" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Raccourcis généraux" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Obtenir une nouvelle invitation" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" -msgstr "Amenez moi à la bibliothèque" +msgstr "Amenez-moi à la bibliothèque" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Obtenez des métadonnées de qualité pour votre musique grâce à <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Commencer" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Obtenir de l'aide" + #: front/src/components/Footer.vue:37 +#, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "Obtenir de l'aide" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Aller" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Retourner à la page d'accueil" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Artistes caché·e·s" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "Cacher le contenu du compte ou du domaine, sauf aux abonnés." +#: front/src/components/moderation/FilterModal.vue:40 +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Cacher le contenu" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "Cacher le contenu de cet·te artiste" + +#: front/src/components/audio/Player.vue:615 +#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "Cacher le contenu de cet·te artiste" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Accueil" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Heures de musique" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Cependant, accéder à Funkwhale depuis ces clients requiert un mot de passe distinct que vous pouvez configurer ci-dessous." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Si l'adresse email que vous avez fournie est valide et associée à un compte utilisateur, vous allez recevoir un email contenant les instructions de réinitialisation au cours des prochaines minutes." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Date d'import" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importez de la musique de différentes plate-formes, comme YouTube ou Soundcloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Statut de l'importation" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Référence de l'importation" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Statut de l'importation" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" -msgstr "Status de l'import" +msgstr "Statut de l'importation" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importé" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Date d'import" +#: front/src/components/federation/FetchButton.vue:47 +#, fuzzy +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "Impossible de se connecter à l'URL renseignée" + +#: front/src/components/moderation/FilterModal.vue:26 +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Dans le widget \"Ajoutés récemment\"" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "Dans les listes d'artistes et d'albums" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Dans les favoris" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "Dans les favoris des autres utilisateurs et dans l'historique d'écoute" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "Dans les suggestions radio" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Inactif" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Augmenter le volume" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Saisissez l'adresse de courriel liée à votre compte" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Insérer depuis la queue (%{ count } piste)" msgstr[1] "Insérer depuis la queue (%{ count } pistes)" +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Adresse de l'instance" + #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Informations de l'instance" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Informations relatives à cette instance" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radios de l'instance" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Paramètres de l'instance" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Adresse de l'instance" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Mauvais type de fichier, assurez-vous de charger un fichier audio. Les formats de fichiers supportés sont %{ extensions }" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -"Mauvais type de fichier, assurez-vous de charger un fichier audio. Les " -"formats de fichiers supportés sont %{ extensions }" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Code d'invitation" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Code d'invitation (optionnel)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Invitations" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" -msgstr "Issue tracker" +msgstr "Suivi des problèmes" + +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "Impossible de se connecter à l'URL renseignée" #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" -msgstr "Gardez une trace de vos chansons favorites" +msgstr "Sauvegardez vos chansons favorites" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" -msgstr "Raccouris clavier" +msgstr "Raccourcis clavier" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Comptes connus" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Bibliothèques connues" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Dernière activité" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Dernière vérification" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Dernière modification" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Vu pour la dernière fois" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Date de dernier aperçu" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" -msgstr "Dernière mise a jour :" +msgstr "Dernière mise à jour :" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Démarrer" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" -msgstr "En apprendre plus à propos de cette instance" +msgstr "En savoir plus à propos de cette instance" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Laisser vide pour obtenir un code aléatoire" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Laisser vide pour un widget adaptatif" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Bibliothèques" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Bibliothèques" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Bibliothèque mise à jour" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Les bibliothèques vous aident à organiser et à partager votre collection de musique. Vous pouvez téléverser votre bibliothèque musicale sur Funkwhale et la partager avec vos amis et votre famille." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Bibliothèque" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Bibliothèque créée" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Bibliothèque mise à jour" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Bibliothèque supprimée" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" -msgstr "Fichiers de la bibliothèque" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" +msgstr "Modifications de la bibliothèque" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Bibliothèque mise à jour" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Licence" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +#, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Chargement des abonnés…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Chargement des abonnés…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Chargement des bibliothèques…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Chargement des données de la bibliothèque…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Chargement des notifications…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "Chargement de bibliothèques distantes…" +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Chargement des bibliothèques distantes…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Chargement des données d'utilisation…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Chargement de vos favoris…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +#, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Compte local" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Connexion" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" -msgstr "Connectez vous à votre compte Funkwhale" +msgstr "Connectez-vous à votre compte Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Déconnexion" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Connecté·e en tant que %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Connexion" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Statut de connexion" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Déconnexion" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "" -"On dirait que vous n'avez pas encore de bibliothèque, il est temps d'en " -"créer une." +msgstr "On dirait que vous n'avez pas encore de bibliothèque, il est temps d'en créer une." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." -msgstr "Répétition désactivée. Cliquez pour activer la répétition sur la piste actuelle." +msgstr "Répétition désactivée. Cliquez ici pour activer la répétition sur la piste actuelle." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." -msgstr "" -"Répétition sur la piste en cours. Cliquer pour répéter sur l'intégralité de " -"la file d’attente." +msgstr "Répétition sur la piste en cours. Cliquez pour répéter l'intégralité de la liste d'attente." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." -msgstr "\"Répétition sur l'intégralité" - -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Paroles" +msgstr "Répétition de l'intégralité de la liste d'attente, cliquez pour désactiver la répétition." -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Menu principal" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Gérer la bibliothèque" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" -msgstr "Gérer les playlists" +msgstr "Gérer les listes de lecture" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Gérer les utilisateur·rices" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" -msgstr "Gérer vos playlists" +msgstr "Gérer vos listes de lecture" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Tout marquer comme lu" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Marquer comme lu" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Marquer comme non lu" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "Mo" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "Lecteur média" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Membre depuis le %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "Applications mobiles et de bureau" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +#, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Modération" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"Les règles de modération vous aident à contrôler comment votre instance " -"interagi avec un domaine ou compte donné." +msgstr "Les règles de modération vous aident à contrôler comment votre instance interagit avec un domaine ou compte donné." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Modification %{ id }" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Date de modification" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Musique" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Couper le son" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Masquer l’activité" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Masquer les notifications" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Mon compte" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Ma description géniale" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Ma bibliothèque d'enfer" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" -msgstr "Ma playlist d'enfer" +msgstr "Ma liste de lecture d'enfer" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Ma radio d'enfer" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Mes bibliothèques" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "ND" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nom" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nom" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nom" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nouveau mot de passe" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Les nouvelles pistes seront ajoutées ici automatiquement." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "Nouvelle valeur" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Piste suivante" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Non" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Pas d'addons ou d'extension à installer, il vous suffit d'une bibliothèque sur le web" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Aucun album ne correspond à votre recherche" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Aucun·e artiste ne correspond à votre recherche" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." -msgstr "Pas de paroles disponibles pour cette piste." +#: front/src/components/library/TrackDetail.vue:14 +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" +msgstr "Aucune information sur les droits d'auteur n'est disponible pour cette piste" + +#: front/src/components/library/TrackDetail.vue:25 +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Aucune information de licence pour cette piste" #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Aucune bibliothèque correspondante." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." msgstr "Pas encore de notifications." +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "Aucun résultat n'a été trouvé." + #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Personne à part moi" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Personne ne suit cette bibliothèque" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" -msgstr "Pas utilisé" +msgstr "Non utilisé" + +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Notifications" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Notifications" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Site officiel" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Ancien mot de passe" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "Ancienne valeur" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Accès libre" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Mettre à jour les règles de modération" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Ouvrir le profil" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Voir sur MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Ouvrir le profil" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Ouvrir le profil" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Ouvrir le site web" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "Ou personnalisez votre règle" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Ordre" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" -msgstr "Ordre" - -#: front/src/components/library/Artists.vue:23 +msgstr "Trier par" + +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" -msgstr "Direction" +msgstr "Sens" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Propriétaire" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Page non trouvée" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" -msgstr "Page non trouvée !" +msgstr "Page introuvable !" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "Pagination" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Mot de passe" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Mot de passe mis à jour" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Mot de passe modifié avec succès" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Mettre en pause" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "Mettre en pause/jouer la piste en cours" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "En pause" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "En attente" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "En attente de validation" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Fichiers en attente" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Demandes d'abonnement en attente" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Vérification en attente" + +#: front/src/components/Sidebar.vue:226 +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Vérification des modifications en attente" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Permissions" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Permissions" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Jouer" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Tout lire" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Lire tous les albums" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Lire ensuite" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Jouer la piste suivante" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Lire maintenant" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Jouer la piste précédente" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "Jouer des chansons similaires" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Jouer cette piste" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Jouer" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Jouer…" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" -msgstr "Playlist" +msgstr "Liste de lecture" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Playlist contenant %{ count } piste, par %{ username }" msgstr[1] "Playlist contenant %{ count } pistes, par %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Playlist créée" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Éditeur de playlist" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nom de la playlist" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Playlist mise à jour" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilité de la playlist" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Listes de lecture" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" -msgstr "Playlists" +msgstr "Listes de lecture" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "Les playlists ? Elles sont là !" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Merci de vérifier que votre mot de passe est correct" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Merci de vérifier que votre nom d'utilisateur et mot de passe sont corrects" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF ou JPG. 2Mo maximum. L'image sera réduite à 400×400 pixels." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Pagination" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" -"Empêche le compte ou domaine de déclencher des notifications, sauf pour les " -"abonnés." +msgstr "Empêche le compte ou domaine de déclencher des notifications, sauf pour les abonné·e·s." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "Aperçu" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Piste précédente" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Problème lors de l'analyse" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Continuer" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Poursuivre vers la page de connexion" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "En cours de traitement" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Ouvrir le profil" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1896,1108 +3205,1973 @@ msgstr "En cours de traitement" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Supprimer" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "Supprimer les fichiers erronés ?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "Supprimer les fichiers en attente ?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "Supprimer les fichiers oubliés ?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "File d’attente" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "La file d’attente a été mélangée !" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Éditeur de radio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Radio créée" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nom de la radio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Radio mise à jour" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "Raison" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "Suivis de bibliothèque reçus" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Messages reçus" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Ajoutés récemment" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Ajoutés récemment" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Récemment ajouté aux favoris" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Écouté récemment" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Rafraîchir" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Rafraîchir" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Rafraîchir les informations du nÅ“ud" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Rafraîchir les informations du nÅ“ud" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "Rafraîchir le contenu de la table" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Inscrit·e depuis le %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "Les inscriptions sont fermées sur cette instance, vous aurez besoin d'un code d'invitation pour vous inscrire." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "utilisateur·rice standard·e" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Rejeter" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Rejeter le média" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Rejeté" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Relancer l'importation" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Date de dernier aperçu" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Bibliothèques distantes" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Les bibliothèques distantes appartiennent à d'autres utilisateurs du réseau. Vous pouvez y accéder tant qu'elles sont publiques ou qu'on vous en donne l'accès." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Retirer" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Supprimer mon avatar" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Supprimer mon avatar" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Retirer des favoris" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Les pistes importées qui n'ont pas encore été complètement traitées par le " -"serveur seront supprimées complètement. Le quota correspondant vous sera " -"restitué." +msgstr "Les pistes importées qui n'ont pas encore été complètement traitées par le serveur seront supprimées complètement. Le quota correspondant vous sera restitué." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" -"Les pistes téléversées mais ignorées pendant l'importation pour diverses " -"raisons seront supprimées complètement. L'espace de stockage correspondant " -"vous sera restitué." +msgstr "Les pistes téléversées mais ignorées pendant l'importation pour diverses raisons seront supprimées complètement. L'espace de stockage correspondant vous sera restitué." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" -"Les pistes téléversées mais pas encore complètement traitées pas le serveur " -"seront supprimées. L'espace de stockage correspondant vous sera restitué." +msgstr "Les pistes téléversées mais pas encore complètement traitées pas le serveur seront supprimées. L'espace de stockage correspondant vous sera restitué." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Demander un nouveau mot de passe" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Demander un nouveau mot de passe pour l'API Subsonic ?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Demander un mot de passe" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Réinitialiser votre mot de passe" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Relancer l'importation" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Résultats par page" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Retourner à la page de connexion" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Afficher les fichiers" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Règle" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Enregistrer" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Scan démarré" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Analyser maintenant" -#: front/src/views/content/remote/Card.vue:166 -msgid "Scan skipped (previous scan is too recent)" -msgstr "Scan sauté (le scan précédent est trop récent)" +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Croissant" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Analyse en attente" +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" +msgid "Scan skipped (previous scan is too recent)" +msgstr "Scan non lancé (le scan précédent est trop récent)" -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Analyse démarrée" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Scanné avec des erreurs" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Analyse en cours… (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Rechercher" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Rechercher une bibliothèque distante" -#: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." -msgstr "Rechercher par domaine, nom d'utilisateur·trice, bio…" +#: front/src/components/manage/library/EditsCardList.vue:211 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Rechercher par titre, artiste, domaine…" -#: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." -msgstr "Rechercher par nom…" +#: front/src/components/manage/library/LibrariesTable.vue:191 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Rechercher par domaine, nom d'utilisateur·rice, bio…" -#: front/src/views/content/libraries/FilesTable.vue:201 -msgid "Search by title, artist, album…" -msgstr "Rechercher par titre, artiste, album…" +#: front/src/components/manage/library/UploadsTable.vue:241 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Rechercher par domaine, nom d'utilisateur·rice, bio…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Rechercher par titre, artiste, domaine…" +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Rechercher par domaine, nom d'utilisateur·rice, bio…" -#: front/src/components/manage/users/InvitationsTable.vue:153 -msgid "Search by username, e-mail address, code…" +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Rechercher par titre, artiste, album…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Rechercher par titre, artiste, album…" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Rechercher par domaine, nom d'utilisateur·rice, bio…" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Rechercher par nom…" + +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" +msgid "Search by title, artist, album…" +msgstr "Rechercher par titre, artiste, album…" + +#: front/src/components/manage/users/InvitationsTable.vue:153 +#, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" +msgid "Search by username, e-mail address, code…" msgstr "Rechercher par nom d'utilisateur·rice, courriel, code…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Rechercher par nom d'utilisateur·rice, courriel, nom…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Rechercher des artistes, albums, pistes…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Rechercher de la musique" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Rechercher sur lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Rechercher sur Wikipédia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "Menu secondaire" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Sections" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Sélectionner un filtre" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 #, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" -msgstr[0] "Selectionner l'ensemble des %{ total } élément" -msgstr[1] "Selectionner l'ensemble des %{ total } éléments" +msgstr[0] "Sélectionner l'ensemble de %{ total } élément" +msgstr[1] "Sélectionner l'ensemble des %{ total } éléments" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Sélectionner seulement la page actuelle" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Paramètres" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Paramètres mis à jour" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Paramètres modifiés avec succès." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Lien de partage" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Partagez ce lien avec d'autres utilisateurs afin qu'ils puissent demander " -"l'accès à votre bibliothèque." +msgstr "Partagez ce lien avec d'autres utilisateurs afin qu'ils puissent demander l'accès à votre bibliothèque." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Lien de partage" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "Afficher %{ count } autre piste" msgstr[1] "Afficher %{ count } autres pistes" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Montrer 1 album supplémentaire" msgstr[1] "Montrer %{ count } albums supplémentaires" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "Montrer les raccourcis clavier disponibles" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Afficher les notifications lues" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Afficher/masquer le mot de passe" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Affichage des résultats %{ start }-%{ end } sur %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Mélanger la file d'attente" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Mélanger votre file d’attente" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Inscription" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Inscription" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Date d'inscription" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "Rendre l'activité silencieuse" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Rendre les notifications silencieuses" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +#, fuzzy +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Taille" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Taille" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Ignoré" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Fichiers ignorés" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Logiciel" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Désolé, la page demandée n’existe pas :" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Code source" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Membre de l'équipe" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Démarrer" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Arrêter la radio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Statistiques" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" -"Les statistiques sont calculées depuis les activités connues et le contenu " -"sur votre instance, et ne reflètent pas l'activité générale de ce compte" +msgstr "Les statistiques sont calculées depuis les activités connues et le contenu sur votre instance, et ne reflètent pas l'activité générale de ce compte" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" -"Les statistiques sont calculées depuis les activités connues et le contenu " -"sur votre instance, et ne reflètent pas l'activité générale de ce domaine" +msgstr "Les statistiques sont calculées depuis les activités connues et le contenu sur votre instance, et ne reflètent pas l'activité générale de ce domaine" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "Les statistiques sont calculées depuis les activités connues et le contenu sur votre instance, et ne reflètent pas l'activité générale de ce compte" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Statut" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Statut" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Statut" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Statut" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Statut" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Arrêter" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Statut" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Arrêter la radio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Valider" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Mot de passe de l'API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Nous ne pouvons pas charger cette piste" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Nous ne pouvons pas charger cette piste" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Nous ne pouvons pas charger cette piste" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Suggestions" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Résumé" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Forum d'aide" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Extensions supportées: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Synchronisation des changements avec le serveur…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "La texte a été copié dans le presse-papier !" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "C'est simple : nous adorions Grooveshark et nous voulions construire quelque chose d'encore mieux." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "Le logo de Funkwhale a été généreusement dessiné et fourni par Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "La bibliothèque et toutes ses pistes seront supprimées. Cette action est irréversible." + +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -"La bibliothèque et toutes ses pistes seront supprimées. Cette action est " -"irréversible." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "Les fichiers musicaux que vous téléversez sont correctement tagués :" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." -msgstr "La piste suivante va se jouer automatiquement dans quelques secondes..." +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "La piste suivante va se jouer automatiquement dans quelques secondes…" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "La plateforme est gratuite et open-source, vous pouvez l'installer et la modifier sans restrictions" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Playlist créée" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Cette action est irréversible." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "L'API Subsonic n'est pas disponible sur cette instance Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Nous ne pouvons pas ajouter cette piste à une playlist" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Cette action est irréversible." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Les fichiers musicaux uploadés sont au format OGG, Flac ou MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "" -"Il y a différentes manières de récupérer du nouveau contenu et de le rendre " -"disponible ici." +msgstr "Il y a différentes manières de récupérer du nouveau contenu et de le rendre disponible ici." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Cette action est irréversible." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Cet album est présent dans les bibliothèques suivantes :" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Cetâ‹…te artiste est présentâ‹…e dans les bibliothèques suivantes :" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "Ce domaine est sujet à des règles de modération spécifiques" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "Cette instance offre jusque %{quota} d'espace disque à chaque utilisateur." + +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." msgstr "" -"Cette instance offre jusque %{quota} d'espace disque à chaque utilisateur." #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "C'est vous !" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." -msgstr "" -"Cette bibliothèque contient ma musique personnelle, j'espère que vous " -"l'aimerez." +msgstr "Cette bibliothèque contient ma musique personnelle, j'espère que vous l'aimerez." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Cette bibliothèque est privée et son propriétaire doit vous approuver pour " -"accéder à son contenu" +msgstr "Cette bibliothèque est privée et son propriétaire doit vous approuver pour accéder à son contenu" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "" -"Cette bibliothèque est publique et vous pouvez accéder à son contenu " -"librement" +msgstr "Cette bibliothèque est publique et vous pouvez accéder à son contenu librement" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "Cela peut affecter de nombreux éléments ou avoir des conséquences irréversibles, merci de vérifier que c'est bien ce que vous souhaitez." + +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." msgstr "" -"Cela peut affecter de nombreux éléments ou avoir des conséquences " -"irréversibles, merci de vérifier que c'est bien ce que vous souhaitez." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Cette référence sera utilisée pour regrouper les fichiers importés." -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Le traitement de cette piste a échoué, assurez-vous qu’elle est correctement étiquetée" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "La piste est importée mais n'a pas encore été traitée par le serveur" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "La piste est déjà présente dans l'une de vos bibliothèques" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" -msgstr "" -"Cette piste n'est pas accessible dans les bibliothèques auxquelles vous avez " -"accès" +msgstr "Cette piste n'est pas accessible dans les bibliothèques auxquelles vous avez accès" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Cette piste est présente dans les bibliothèques suivantes :" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Cela supprimera définitivement cette playlist et ne pourra pas être annulé." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Cela supprimera définitivement cette radio et ne pourra pas être annulé." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Cela désactivera complétement l'accès à l'API Subsonic depuis votre compte." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" -"Cela effacera vos données locales et vous déconnectera, voulez-vous " -"continuer ?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Cela vous déconnectera sur l'ensemble de vos appareils utilisant ce mot de passe." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Cela supprimera définitivement cette playlist et ne pourra pas être annulé." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Cela supprimera toutes les pistes de la playlist et ne pourra pas être annulé." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "Titre" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "Titre" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" msgid "Title" msgstr "Titre" +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "Basculer la boucle de file d'attente" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Taille totale" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Taille totale des fichiers de cette bibliothèque" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Nombre total d'utilisateurs" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Piste" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "La piste est déjà présente dans l'une de vos bibliothèques" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Piste" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Nom de la piste" + +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Information de la piste" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Piste correspondant au filtre" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Nom de la piste" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "La piste est importée mais n'a pas encore été traitée par le serveur" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Pistes" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "pistes" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Pistes" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Pistes par cetâ‹…te artiste" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Pistes en favoris" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "pistes écoutées" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Piste correspondant au filtre" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Type" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Type" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" msgstr "Sous règle de modération" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Se désabonner" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Se désabonner de cette bibliothèque ?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Malheureusement, les gestionnaires de cette instance n'ont pas encore pris le temps de compléter cette page." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Musique illimitée" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Réactiver le son" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Mettre à jour" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Mettre à jour la playlist" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Mettre à jour l'avatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Mettre à jour la bibliothèque" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Mettre à jour les règles de modération" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Mettre à jour la playlist" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Mettre à jour les paramètres" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Mettre à jour votre mot de passe" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Envoi" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Téléverser un nouvel avatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Téléverser du contenu audio" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Date d'envoi" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Date d'envoi" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "Import refusée, assurez-vous que le fichier n'est pas trop gros et que vous n'avez pas atteint votre quota" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"Import refusée, assurez-vous que le fichier n'est pas trop gros et que vous " -"n'avez pas atteint votre quota" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" -"Importez des fichiers musicaux (MP3, OGG, Flac, etc.) depuis votre " -"bibliothèque personnelle directement depuis votre navigateur pour en " -"profiter ici." +msgstr "Importez des fichiers musicaux (MP3, OGG, Flac, etc.) depuis votre bibliothèque personnelle directement depuis votre navigateur pour en profiter ici." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Téléverser de nouveaux morceaux" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Quota d’envoi" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Dépassement du délai d'envoi, veuillez réessayer" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Téléversé" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "Envoi en cours" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "Envoi en cours…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "Téléversements" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Téléversements" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "Téléversements" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Utiliser une autre instance" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." -msgstr "" -"Utilisez ce formulaire pour demander à réinitialiser votre mot de passe. " -"Vous recevrez un courriel à l'adresse indiquée contenant les instructions de " -"réinitialisation." +msgstr "Utilisez ce formulaire pour demander à réinitialiser votre mot de passe. Vous recevrez un courriel à l'adresse indiquée contenant les instructions de réinitialisation." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Utilisez ce paramètre pour activer/désactiver temporairement la règle sans " -"complètement la supprimer." +msgstr "Utilisez ce paramètre pour activer/désactiver temporairement la règle sans la supprimer complètement." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Utilisé" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Utilisateur·rice" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Activité des utilisateur·ice·s" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Bibliothèques utilisateur" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Radios des utilisateur·ice·s" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nom d'utilisateur" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nom d'utilisateur ou email" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "utilisateur·rice·s" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Utilisateur·ice·s" #: front/src/components/Footer.vue:29 +#, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "Utiliser Funkwhale" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Version %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Afficher les fichiers" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Voir sur MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Visibilité" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Visibilité : tout le monde sur cette instance" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Visibilité : tout le monde, y compris les autres instances" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Visibilité : personne sauf moi" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilité" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Volume %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Nous ne pouvons pas ajouter cette piste à une playlist" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Nous ne pouvons pas créer cette playlist" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Nous ne pouvons pas créer votre compte" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Nous ne pouvons pas charger cette piste" +#: front/src/components/federation/FetchButton.vue:69 +#, fuzzy +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Chargement de vos favoris…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Erreur lors de la connexion" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Nous ne pouvons pas enregistrer votre avatar" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Nous ne pouvons pas enregistrer vos paramètres" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Création de votre compte impossible." -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Nous ne vous pistons pas et ne vous exposons pas à des publicités" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Nous n'avons aucune information de copyright pour cette piste" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Nous n'avons aucune information de licence pour cette piste" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "nous vous recommandons d'utiliser le logiciel Picard pour cela." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Nous pensons que l'accès à la musique devrait être simple." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Désolé, la page demandée n’existe pas :" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Bienvenue" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Bienvenue sur Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Pourquoi Funkwhale ?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Hauteur du widget" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "Largeur du widget" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Oui" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Oui, déconnectez-moi !" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" -"Vous pouvez partager votre bibliothèque avec d'autres personnes, quelle que " -"soit sa visibilité." +msgstr "Vous pouvez partager votre bibliothèque avec d'autres personnes, quelle que soit sa visibilité." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Vous êtes sur le point de transférer de la musique dans votre bibliothèque. Avant de procéder, veuillez vous assurer que :" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Vous êtes actuellement connecté·e en tant que %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Vous pouvez suivre les bibliothèques d'autres utilisateurs pour avoir accès à de la nouvelle musique. Les bibliothèques publiques peuvent être suivies immédiatement, tandis que le suivi d'une bibliothèque privée nécessite l'approbation de son propriétaire." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Vous pouvez inviter vos ami·es et votre famille sur votre instance pour qu'ils·elles puissent profiter de votre musique" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Vous pouvez maintenant utiliser le service sans limitations." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Vous pouvez utiliser cette interface pour réaliser votre propre radio personnalisée, qui jouera les pistes correspondant aux critères indiqués." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Vous pouvez les utiliser pour profiter de vos playlists et de votre musique en mode hors-ligne sur votre smatphone ou tablette, par exemple." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Vous n'avez aucune règle en place pour ce compte." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Vous n'avez aucune règle en place pour ce compte." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Vous n'avez aucune règle en place pour ce compte." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Vous n'avez aucune règle en place pour ce domaine." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "" + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Vous écoutez une radio" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." -msgstr "Il se peut que vous ailliez des problèmes de connexion." - -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Vous devez choisir une instance pour continuer" +msgstr "Il se peut que vous ayez des problèmes de connexion." #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Vous allez être déconnecté⋅e de cette session et vous devrez vous connecter avec votre nouveau mot de passe" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Vous devrez mettre à jour votre mot de passe sur l'ensemble des clients utilisant ce mot de passe." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Playlist créée" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Vos notifications" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Vos favoris" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Votre musique, à votre façon" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Vos notifications" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Votre mot de passe a été mis à jour avec succès." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Paramètres mis à jour" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Votre mot de passe Subsonic sera remplacé par un nouveau mot de passe aléatoire, ce qui vous déconnectera de tous les appareils utilisant l'ancien mot de passe" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Pagination" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Copyright" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Album contenant %{ count } piste, par %{ artist }" +msgstr[1] "Album contenant %{ count } pistes, par %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } piste a été ajoutée à votre liste d'attente" +msgstr[1] "%{ count } pistes ont été ajoutées à votre liste d'attente" diff --git a/front/locales/gl/LC_MESSAGES/app.po b/front/locales/gl/LC_MESSAGES/app.po index 06cd843c1593e1967abe91cbfa1130f31eb19cab..265433abf0bfda2cee9946f93652edff3f29ee68 100644 --- a/front/locales/gl/LC_MESSAGES/app.po +++ b/front/locales/gl/LC_MESSAGES/app.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 16:04+0100\n" -"PO-Revision-Date: 2019-01-17 08:33+0000\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-03-05 05:58+0000\n" "Last-Translator: Xosé M <xosem@disroot.org>\n" "Language-Team: none\n" "Language: gl\n" @@ -19,1924 +19,3251 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", de %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } de %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(baldeiro)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Conecte coa súa conta Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } de %{ total } seleccionado" msgstr[1] "%{ count } de %{ total } seleccionados" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } canción" msgstr[1] "%{ count } cancións" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } canción en %{ albumsCount } álbumes" msgstr[1] "%{ count } cancións en %{ albumsCount } álbumes" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } canción coicidente cos filtros combinados" msgstr[1] "%{ count } cancións coincidentes cos filtros combinados" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "Engadiuse %{ count } canción a cola" -msgstr[1] "EngadÃronse %{ count } cancións a cola" - #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} canción" msgstr[1] "%{ count } cancións" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } utilizado de %{ max } permitido" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "%{ username } aceptou o seu seguimento na biblioteca \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } segue a súa biblioteca \"%{ library }\"" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } quere seguir a súa biblioteca \"%{ library }\"" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Perfil de %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">Acerca de " -"%{instanceName}</translate>" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 álbume" msgstr[1] "%{ count } álbumes" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 favorita" msgstr[1] "%{ count } favoritas" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Limpar biblioteca" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Fallou a rede mentras se subÃa o ficheiro" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Algo fallou ao gardar os cambios" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "Acerca de %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "Acerca de %{instanceName}" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "Acerca de Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Páxina Acerca de" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "Acerca de esta instancia" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Aceptar" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Aceptado" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Acceso desactivado" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Escolla un filtro" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Acceso desactivado" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Engadir a favoritas" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Acalar notificacións" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Engadir a lista de reprodución…" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Acceso desactivado" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" msgstr "Acceda a súa música desde unha interface clara que se centra no realmente importante" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Data de acceso" +msgstr "Acceso desactivado" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Contas" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Contas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Datos da conta" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Axustes da conta" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Axustes da conta" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Estado da conta" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Correo-e da conta" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +#, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Contas" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Acción" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "A acción %{ action } foi lanzada correctamente sobre %{ count } elemento" msgstr[1] "A accións %{ action } foi lanzada correctamente sobre %{ count } elementos" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Accións" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Activo" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Actividade" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Visibilidade da actividade" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Engadir" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Engadir un dominio" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +#, fuzzy +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Engadir nova regra de moderación" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Engadir nova regra de moderación" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Engadir e xestionar contido" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Engadir contido" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Engadir filtro" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Engada filtros para personalizar a súa radio" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Engadir a cola actual" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Engadir a favoritas" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Engadir a lista de reprodución…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Engadir a cola" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Engadir a esta lista de reprodución" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Engadir canción" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administración" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Ãlbume" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Ãlbume que contén %{ count } canción, de %{ artist }" -msgstr[1] "Ãlbume que contén %{ count } cancións, de %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Ãlbume" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Ãlbumes de este artista" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Nome do álbume" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Páxina do álbume" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Nome do álbume" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Ãlbumes" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Ãlbumes de este artista" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Todos" +#: front/src/components/common/ActionTable.vue:59 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } de %{ total } seleccionado" +msgstr[1] "%{ count } de %{ total } seleccionados" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Algo fallou ao gardar os cambios" +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Algo fallou ao gardar os cambios" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Algo fallou ao gardar os cambios" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Aconteceu un fallo descoñecido, esto pode significar que o servidor está caÃdo ou non pode ser alcanzado" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Acción" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Aprovar" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Aprovar" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Está segura de que quere desconectar?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artista" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artista" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Nome do artista" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Páxina do artista" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Nome do artista" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artista, álbume, canción…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistas" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistas" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Ascendente" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Solicitar restablecer o contrasinal" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Contido de audio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Atallos do reprodutor de audio" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Listas de reprodución dispoñibles" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Voltar a conectar" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Actualizar axustes" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Taxa de bits" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Bloquear todo" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Bloquear todo de esta conta ou dominio. Esto evitará calquera interacción " -"coa entidade, e eliminará o contido relacionado (subidas, bibliotecas, " -"seguimentos, etc.)" +msgstr "Bloquear todo de esta conta ou dominio. Esto evitará calquera interacción coa entidade, e eliminará o contido relacionado (subidas, bibliotecas, seguimentos, etc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Buscar" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Buscar na biblioteca" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Buscando radios" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Buscando artistas" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Buscando nas listas de reprodución" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Buscando radios" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Construtor" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "De %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "Ao deixar de seguir esta biblioteca perderá o acceso ao seu contido." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "Tamaño da caché" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Cancelar" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Candidatas" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Non pode cambiar o contrasinal" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "Fallou a subida, asegúrese de que non é demasiado grande" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Cambiar idioma" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Cambiar o contrasinal" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Cambiar contrasinal" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Cambiar o seu contrasinal" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Cambiar o contrasinal?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Cambios sincronizados co servidor" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Ao cambiar o contrasinal tamén cambia o seu contrasinal no API Subsonic si é que solicitou un." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "Cambiar o contrasinal terá as seguintes consecuencias" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Sala de conversa" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Escolla a súa instancia" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Limpar biblioteca" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Limpar" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Limpar" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Limpar lista reprodución" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Limpar cola de reprodución" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Pulse unha vez, escoite durante horas utilizando as radios incrustadas" -#: front/src/components/library/FileUpload.vue:75 -msgid "Click to select files to upload or drag and drop files or directories" +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" msgstr "" -"Pulse para escoller os ficheiros a subir ou arrastre e solte ficheiros ou " -"directorios" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" +msgid "Click to select files to upload or drag and drop files or directories" +msgstr "Pulse para escoller os ficheiros a subir ou arrastre e solte ficheiros ou directorios" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Pechar" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Pechar" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Código" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Pechar" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configurar" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Confirmar" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Confirme o seu correo-e" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Código de confirmación" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Escolla un filtro" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Escolla un filtro" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "Actualizouse o contido, pulse actualizar para ver o contido actualizado" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "ContribuÃr" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Copiar" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" -msgstr "Copiar cancións da cola actual a lista de reprodución" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "Copiar cancións da cola a lista de reprodución" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "Copiar/pegar este código no HTML da súa web" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Copyright" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Non se confirmou o seu correo-e" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Non se obtivo a biblioteca remota" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Non se procesou esta canción, asegúrese que está correctamente etiquetada" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Portadas, letras, o noso obxetivo é telas todas ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Crear" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Crear unha conta funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Crear unha nova lista de reprodución" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Crear unha nova lista de reprodución" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Crear nova biblioteca" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Crear unha nova lista de reprodución" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Crear unha conta" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Crear lista reprodución" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Crear biblioteca" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Crear a miña conta" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Crear lista reprodución" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Cree a súa propia radio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Data de creación" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar actual" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Biblioteca actual" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Canción actual" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Uso actual" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Data" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Información da canción" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Baixar volume" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Borrar" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Borrar lista de reprodución" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Eliminar biblioteca" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Borrar regra de moderación" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Borrar lista de reprodución" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Borrar radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Eliminar esta biblioteca?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Eliminar esta biblioteca?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Eliminar esta biblioteca?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Eliminar esta regra de moderación?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 -#: front/src/components/manage/moderation/AccountsTable.vue:22 -#: front/src/components/manage/moderation/DomainsTable.vue:20 -#: front/src/components/manage/users/UsersTable.vue:20 -#: front/src/views/content/libraries/FilesTable.vue:32 -#: front/src/views/playlists/List.vue:28 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Eliminar esta regra de moderación?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Eliminar esta biblioteca?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 +#: front/src/components/manage/moderation/AccountsTable.vue:22 +#: front/src/components/manage/moderation/DomainsTable.vue:20 +#: front/src/components/manage/users/UsersTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:32 +#: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Descendente" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Descrición" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detalle" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Descrición" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Detalles" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." -msgstr "" -"Indique canto contido pode subir a usuaria. Deixe baldeiro para utilizar o " -"valor por omisión da instancia." +msgstr "Indique canto contido pode subir a usuaria. Deixe baldeiro para utilizar o valor por omisión da instancia." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Indique o nivel de visibilidade da súa actividade" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Desactivar o acceso" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Desactivar o acceso Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Desactivar o acceso a API Subsonic?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Desactivado" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Descubra cómo utilizar Funkwhale desde outras apps" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Mostrar nome" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Mostrar públicamente" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Non baixar ningún ficheiro de medios (audio, portada, avatar da conta...) de " -"esta conta ou dominio. Esto eliminará tamén o contido existente." +msgstr "Non baixar ningún ficheiro de medios (audio, portada, avatar da conta...) de esta conta ou dominio. Esto eliminará tamén o contido existente." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Quere baldeirar a lista de reprodución \"%{ playlist }\"?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Quere confirmar esta acción?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Quere eliminar esta lista de reprodución \"%{ playlist }\"?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Quere eliminar a radio \"%{ radio }\"?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Quere eliminar a radio \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Quere executar %{ action } sobre %{ count } elemento?" msgstr[1] "Quere executar %{ action } sobre %{ count } elementos?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Quere restaurar a súa cola anterior?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentación" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Dominio" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Dominios" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Descargar" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Arrastre e solte filas para reordenar as cancións na lista" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Duración" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Enderezo correo-e confirmado" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Fácil de utilizar" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Editar" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Editar" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Fallo mentres se aplicaba a acción" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Editar a info da instancia" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Editar…" +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Actualizar regra de moderación" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Reproducir esta canción" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Reproducir esta canción" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Reproducir esta canción" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Editar" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Editar" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Correo-e" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Enderezo de correo" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Incrustado" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Código incrustado" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Incruste este álbume no seu sitio web" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Incruste esta canción no seu sitio web" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Incruste esta canción no seu sitio web" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" -msgstr "" +msgstr "Seguimentos da biblioteca emitidos" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" -msgstr "" +msgstr "Mensaxes emitidas" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" -msgstr "" +msgstr "Activado" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Finalizar edición" #: front/src/views/content/remote/ScanForm.vue:50 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" -msgstr "Introduza un nome de dominio de biblioteca..." +msgstr "Introduza un URL de biblioteca" -#: front/src/components/library/Radios.vue:140 -#, fuzzy +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" -msgstr "Introduza un nome de unha radio..." +msgstr "Introduza un nome de radio…" -#: front/src/components/library/Artists.vue:118 -#, fuzzy +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" -msgstr "Introduza un nome de un artista..." +msgstr "Introduza nome de artista…" #: front/src/views/playlists/List.vue:107 -#, fuzzy +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" -msgstr "Introduza un nome de lista de reprodución..." +msgstr "Nome de lista de reprodución…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +#, fuzzy +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Introducir o enderezo de correo ligado a súa conta" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Introduza o seu correo-e" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Introduza o seu código de convite (dif. maiúsculas)" #: front/src/components/metadata/Search.vue:114 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" -msgstr "Introduza os termos de busca..." +msgstr "Introduza a consulta de busca…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Introduza o nome de usuaria" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Introduza o nome de usuaria ou correo-e" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Fallo" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Fallo ao informar" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Fallo ao informar" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Con fallo" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Fallo mentres se aplicaba a acción" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Fallo ao solicitar o restablecemento do contrasinal" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Fallo mentres se aplicaba a acción" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Fallo ao intentar cambiar o contrasinal" #: front/src/views/admin/moderation/DomainsList.vue:6 -#, fuzzy +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" -msgstr "Fallo ao crear o convite" +msgstr "Fallo ao crear o dominio" + +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Fallo ao crear a regra" #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Fallo ao crear o convite" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 -#, fuzzy +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" -msgstr "Fallo ao crear o convite" +msgstr "Fallo ao crear a regra" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 #, fuzzy -msgid "Error while fetching node info" +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" msgstr "Fallo ao crear o convite" +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" +msgid "Error while fetching node info" +msgstr "Fallo ao obter info da instancia" + #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Fallo ao gardar os axustes" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Fallo ao gardar os axustes" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Fallo ao gardar os axustes" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Con fallo" #: front/src/views/content/libraries/Quota.vue:75 -#, fuzzy +msgctxt "Content/Library/Label" msgid "Errored files" -msgstr "Con fallo" +msgstr "Ficheiros con fallos" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Todas" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Todas en esta instancia" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 #, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" -msgstr "Todas en esta instancia" +msgstr "Todos, en todas as instancias" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "ExcluÃr" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Data de caducidade" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Caducado" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Caducado/utilizado" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" +msgstr "Explicar por que aplica esta polÃtica a todas as instancias. Dependendo da configuración da súa instancia esto axudaralle a lembrar por que actuou sobre esta conta ou dominio, e pode ser mostrado públicamente para que as usuarias entendan cales son as regras de moderación que se aplican." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" -msgstr "" +msgstr "Fallou" -#: front/src/views/content/remote/Card.vue:58 -#, fuzzy +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" -msgstr "Cancións federadas" +msgstr "Cancións fallidas:" + +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Cancións fallidas:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favoritas" #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favoritas" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federación" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 #, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Federación" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nome do ficheiro" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Ficheiros" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Nome do filtro" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Rematado" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" -msgstr "" +msgstr "Visto primeiro" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 -#, fuzzy +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" -msgstr "Data de caducidade" +msgstr "Data da primeira visión" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Seguir" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" -msgstr "" +msgstr "Seguir bibliotecas remotas" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Solicitude de seguimento pendente de aprobación" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Seguidoras" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Seguidoras" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Seguindo" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Do álbume %{ album } de %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Seguir" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Desactivar o acceso" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale é compatible con outros reprodutores de música compatibles coa API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funckwhale élle ben doado de utilizar." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale está deseñado para escoitar facilmente a música que lle gusta ou descubrir novos artistas." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale é gratuÃto e dalle o control sobre a súa música." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale ocúpase de xestionar a súa música" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" -msgstr "" +msgstr "Atallos xerais" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Obter un novo convite" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Lévame a biblioteca" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 #, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" -msgstr "" -"Obteña metadatos de calidade sobre a súa música grazas a\n" -" <a href=\"https://musicbrainz.org\" target=\"_blank\">\n" -" MusicBrainz\n" -" </a>" +msgstr "Obteña metadatos de calidade sobre a súa música grazas a <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 -#, fuzzy +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" -msgstr "Seguinte paso" +msgstr "Comezando" + +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Obter axuda" #: front/src/components/Footer.vue:37 #, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" -msgstr "Axustes" +msgstr "Obter axuda" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Ir" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Ir ao inicio" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Buscando artistas" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." +msgstr "Ocultar contido de conta ou dominio, excepto de seguidoras." + +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Engadir contido" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" msgstr "" #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Inicio" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Horas de música" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Porén, o acceso a Funkwhale desde estos outros clientes precisa un contrasinal separado que pode establecer aquà abaixo." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Si o enderezo de correo proporcionado no paso anterior é válido e ligado a unha conta de usuaria, deberÃa recibir un correo coas instrucións de restablecemento nun par de minutos." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Data de importación" - -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importar música desde varias plataformas, tales como YouTube ou SoundCloud" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/ImportStatusModal.vue:3 #, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Estado da importación" + +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" -msgstr "Fonte de importación" +msgstr "Importar referencia" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Estado da importación" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Estado da importación" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importado" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 #, fuzzy -msgid "Imported date" -msgstr "Data de importación" +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Recentemente engadida" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Nas favoritas" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Non activo" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" -msgstr "" - -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Introducir o enderezo de correo ligado a súa conta" +msgstr "Aumentar volume" -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Introducir desde a cola (%{ count } canción)" msgstr[1] "Introducir desde a cola (%{ count } cancións)" -#: front/src/views/admin/moderation/DomainsDetail.vue:71 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 #, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Datos da instancia" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" -msgstr "Radios da instancia" +msgstr "Datos da instancia" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Información da instancia" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radios da instancia" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Axustes da instancia" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Datos da instancia" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Tipo de ficheiro non válido, asegúrese de que sube un ficheiro de audio. Extensións de ficheiros soportadas %{ extensions }" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Código do convite" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Código do convite (optativo)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Convites" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Seguimento de problemas" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Faga seguimento das súas cancións favoritas" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" -msgstr "" +msgstr "Atallos de teclado" #: front/src/views/admin/moderation/DomainsDetail.vue:161 -#, fuzzy +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" -msgstr "A miña conta" +msgstr "Contas coñecidas" #: front/src/views/content/remote/Home.vue:14 -#, fuzzy +msgctxt "Content/Library/Title" msgid "Known libraries" -msgstr "Buscando nas bibliotecas" +msgstr "Bibliotecas coñecidas" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Última actividade" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" -msgstr "Última obtida" +msgstr "Última comprobación" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Última modificación" #: front/src/components/manage/moderation/AccountsTable.vue:43 -#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" -msgstr "Última obtida" +msgstr "Última vista" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 -#, fuzzy +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" -msgstr "Data de lanzamento" +msgstr "Data da última vista" -#: front/src/views/content/remote/Card.vue:56 -#, fuzzy +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" -msgstr "Lista de reprodución actualizada" +msgstr "Última actualización:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Lanzar" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Coñeza máis sobre esta instancia" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Deixar baldeiro para un código aleatorio" #: front/src/components/audio/EmbedWizard.vue:7 -#, fuzzy +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" -msgstr "Deixar baldeiro para un código aleatorio" +msgstr "Deixar baldeiro para un trebello interactivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Bibliotecas" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Bibliotecas" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Biblioteca actualizada" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." -msgstr "" +msgstr "As bibliotecas axúdanlle a organizar e compartir a súa colección musical. Pode subir a súa propia colección de música a Funkwhale e compartila cos seus amigos e familia." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Biblioteca" -#: front/src/views/content/libraries/Form.vue:109 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" -msgstr "Nome da biblioteca" +msgstr "Biblioteca creada" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 #, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Biblioteca actualizada" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" -msgstr "Ficheiros de biblioteca" +msgstr "Biblioteca eliminada" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Ficheiros de biblioteca" -#: front/src/views/content/libraries/Form.vue:106 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" -msgstr "Nome da biblioteca" +msgstr "Biblioteca actualizada" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" +msgstr "Licenza" + +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" msgstr "" -#: front/src/views/content/libraries/Detail.vue:21 +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 #, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Cargando seguidoras…" + +#: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" -msgstr "Buscando seguidoras" +msgstr "Cargando seguidoras…" #: front/src/views/content/libraries/Home.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" -msgstr "Buscando nas bibliotecas" +msgstr "Cargando Bibliotecas…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" -msgstr "" +msgstr "Cargando datos da biblioteca…" -#: front/src/views/Notifications.vue:4 -#, fuzzy +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" -msgstr "Última modificación" +msgstr "Cargando notificacións…" #: front/src/views/content/remote/Home.vue:3 -#, fuzzy -msgid "Loading remote libraries..." -msgstr "Cargando liña temporal..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Cargando bibliotecas remotas…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" -msgstr "" +msgstr "Cargando datos de uso…" #: front/src/components/favorites/List.vue:5 -#, fuzzy +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" -msgstr "Cargando favoritas..." +msgstr "Cargando as favoritas…" + +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 #, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" -msgstr "A miña conta" +msgstr "Conta local" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Conectar" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Conecte coa súa conta Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Desconectar" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Conectada como %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Conectar" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" -msgstr "Estado da conta" +msgstr "Estado da conexión" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Desconectar" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "" +msgstr "Semella que non ten unha biblioteca, é momento de crear unha." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Bucle desactivado. Pulse para cambiar ao bucle de unha soa canción." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Bucle de unha canción. Pulse para cambiar a bucle de toda a cola." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Bucle de toda a cola. Pulse para desactivar o bucle." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Letras" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" -msgstr "" +msgstr "Menú principal" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Xestionar biblioteca" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Xestionar listas de reprodución" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Xestionar usuarias" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Xestionar as súas listas de reprodución" -#: front/src/views/Notifications.vue:17 -#, fuzzy +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" -msgstr "Marcar como importado" +msgstr "Marcar todo como lido" -#: front/src/components/notifications/NotificationRow.vue:44 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" -msgstr "Marcar como importado" +msgstr "Marcar como lido" -#: front/src/components/notifications/NotificationRow.vue:45 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" -msgstr "Marcar como importado" +msgstr "Marcar como non lido" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" -msgstr "" +msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" -msgstr "" +msgstr "Reprodutor" + +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Rexistrada desde %{ date }" #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" -msgstr "" +msgstr "Apps de escritorio e móbil" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 #, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" -msgstr "Federación" +msgstr "Moderación" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" +msgstr "As polÃticas de moderación axúdanlle a controlar o xeito en que a súa instancia interactúa con determinado dominio ou conta." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 #, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Data de modificación" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" -msgstr "Data de caducidade" +msgstr "Data de modificación" + +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Música" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Acalar" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Acalar actividade" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Acalar notificacións" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "A miña conta" -#: front/src/components/library/radios/Builder.vue:236 -#, fuzzy +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" -msgstr "A miña increÃble radio" +msgstr "A miña abraiante descrición" -#: front/src/views/content/libraries/Form.vue:70 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" -msgstr "A miña increÃble radio" +msgstr "A miña abraiante biblioteca" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "A miña fantástica lista" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "A miña increÃble radio" #: front/src/views/content/libraries/Home.vue:6 -#, fuzzy +msgctxt "Content/Library/Title" msgid "My libraries" -msgstr "Bibliotecas" +msgstr "As miñas Bibliotecas" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "N/A" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nome" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Novo contrasinal" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "As novas cancións engadiranse aquà automáticamente." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Seguinte canción" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Non" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Sin engadidos nin complementos: só precisa unha biblioteca na web" #: front/src/components/audio/Search.vue:25 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" -msgstr "Lamentámolo, non atopamos ningún álbume que coincida coa consulta" +msgstr "Ningún álbume coincide coa busca" #: front/src/components/audio/Search.vue:16 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" -msgstr "Lamentámolo, non atopamos ningún artista que coincida coa súa consulta" +msgstr "Ningún artista coincide coa busca" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "Non hai letras dispoñibles para esta canción." -#: front/src/components/federation/LibraryWidget.vue:6 +#: front/src/components/library/TrackDetail.vue:25 #, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Non temos información da licenza para esta canción" + +#: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." -msgstr "Xestionar biblioteca" +msgstr "Sen biblioteca coincidente." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "Sen notificación para mostrar." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." msgstr "" #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Ninguén excepto eu" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" -msgstr "" +msgstr "Ninguén segue esta biblioteca" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Non utilizado" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 #, fuzzy +msgctxt "*/Notifications/*" msgid "Notifications" -msgstr "Última modificación" +msgstr "Notificacións" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Notifications" +msgstr "Notificacións" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Sitio web oficial" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Contrasinal antigo" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Abrir" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Actualizar regra de moderación" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Abrir perfil" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Ver en MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" -msgstr "" +msgstr "Abrir perfil" + +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Abrir perfil" #: front/src/views/admin/moderation/DomainsDetail.vue:16 -#, fuzzy +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" -msgstr "Sitio web oficial" +msgstr "Abrir sitio web" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 -#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" -msgstr "Engada filtros para personalizar a súa radio" +msgstr "Ou personalice a súa regra" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 -#, fuzzy +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" -msgstr "Ordenando" - -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +msgstr "Ordear" + +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Ordenando" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Dirección da orde" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Dona" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Páxina non atopada" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Non atopamos a páxina!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" -msgstr "" +msgstr "Paxinación" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Contrasinal" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Contrasinal actualizado" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Contrasinal actualizado correctamente" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Pausar canción" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" -msgstr "" +msgstr "Pausar/reproducir pista actual" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 -#, fuzzy +msgctxt "Content/Moderation/Card.List item" msgid "Paused" -msgstr "Rexeitado" +msgstr "Pausada" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "Pendente" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Pendente de aceptación" #: front/src/views/content/libraries/Quota.vue:22 -#, fuzzy +msgctxt "Content/Library/Label" msgid "Pending files" -msgstr "Pendente" +msgstr "Ficheiros pendentes" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Peticións de seguimento pendentes" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Ficheiros pendentes" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Ficheiros pendentes" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Permisos" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Permisos" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Reproducir" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Reproducir todo" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Reproducir todos os álbumes" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Reproducir seguinte" #: front/src/components/ShortcutsModal.vue:67 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" -msgstr "Reproducir canción" +msgstr "Reproducir canción seguinte" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Reproducir agora" #: front/src/components/ShortcutsModal.vue:63 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" -msgstr "Canción anterior" +msgstr "Reproducir canción anterior" -#: front/src/components/Sidebar.vue:211 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" -msgstr "Reproducir canción" +msgstr "Reproducir esta canción" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Reproducir canción" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Reproducir..." + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Lista de reprodución" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Lista de reprodución que contén %{ count } canción, de %{ username }" msgstr[1] "Lista de reprodución que contén %{ count } cancións, de %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Lista creada" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Editora da lista" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nome da lista" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Lista de reprodución actualizada" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilidade da lista de reprodución" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Listas de reprodución" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Listas de reprodución" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "Listas de reprodución? Témolas" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Por favor, comprobe que o seu contrasinal é correcto" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Por favor, comprobe que o par usuaria/contrasinal é correcto" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF ou JPG. 2MB como máximo. Será reducida a 400x400px." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Paxinación" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" +msgstr "Evitar que o dominio ou conta mostre notificacións, excepto das seguidoras." -#: front/src/components/audio/EmbedWizard.vue:29 -#, fuzzy +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" -msgstr "Paso anterior" +msgstr "Vista previa" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Canción anterior" -#: front/src/views/content/remote/Card.vue:39 -msgid "Problem during scanning" +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" msgstr "" -#: front/src/components/library/FileUpload.vue:58 -#, fuzzy +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" +msgid "Problem during scanning" +msgstr "Problema ao escanear" + +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" -msgstr "Ir a conectar" +msgstr "Proceder" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Ir a conectar" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" -msgstr "" +msgstr "Procesando" + +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Abrir perfil" #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 @@ -1946,1113 +3273,1973 @@ msgstr "" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" -msgstr "" +msgstr "Limpar" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" -msgstr "" +msgstr "Eliminar ficheiros con fallos?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" -msgstr "" +msgstr "Eliminar ficheiros pendentes?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" -msgstr "" +msgstr "Eliminar ficheiros saltados?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Cola" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Cola barallada!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Constructor de Radio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Radio creada" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nome da Radio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Radio actualizada" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" -msgstr "" +msgstr "Razón" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" -msgstr "" +msgstr "Seguimentos de biblioteca recibidos" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" +msgstr "Mensaxes recibidas" + +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Recentemente engadida" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" msgstr "" #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Recentemente engadida" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Favorecida recentemente" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Escoitada recentemente" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Actualizar" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Actualizar" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" +msgstr "Actualizar info da instancia" + +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Actualizar info da instancia" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" +msgstr "Actualizar contido da tabla" + +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" msgstr "" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Rexistrada desde %{ date }" +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "O rexistro está pechado en esta instancia, necesita un código de convite para rexistrarse." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "usuaria normal" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" -msgstr "" +msgstr "Rexeitar" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" -msgstr "" +msgstr "Rexeitar medios" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" -msgstr "" +msgstr "Rexeitado" -#: front/src/views/content/libraries/FilesTable.vue:234 +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 #, fuzzy -msgid "Relaunch import" -msgstr "Rematar importación" +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Data da última vista" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 -#, fuzzy +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" -msgstr "Lévame a biblioteca" +msgstr "Bibliotecas remotas" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." -msgstr "" +msgstr "As bibliotecas remotas pertences a outras usuarias na rede. Pode acceder a elas se son públicas ou lle outorgan acceso." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Eliminar" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Eliminar avatar" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Eliminar avatar" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Eliminar das favoritas" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Elimina completamente as cancións subidas pero aÃnda non procesadas, engadindo o espazo correspondente a súa cuota." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Elimina as cancións subidas saltadas durante o proceso de importación, engadindo o espazo correspondente a súa cuota." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Elimina as cancións subidas que non se procesaron completamente no servidor, engadindo o espazo correspondente a súa cuota." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Solicitar un novo contrasinal" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Solicitar un nonvo contrasinal para o API Subsonic?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Solicitar un contrasinal" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Restablecer o seu contrasinal" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Volte a lanzar importación" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Resultados por páxina" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 -#, fuzzy +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" -msgstr "Ir a conectar" +msgstr "Voltar a conectar" + +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Ver ficheiros" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" -msgstr "" +msgstr "Regra" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Gardar" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" -msgstr "" +msgstr "Escaneado iniciado" -#: front/src/views/content/remote/Card.vue:63 -#, fuzzy +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" -msgstr "Reproducir agora" +msgstr "Escanear agora" -#: front/src/views/content/remote/Card.vue:166 -msgid "Scan skipped (previous scan is too recent)" -msgstr "" +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Ascendente" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "" +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" +msgid "Scan skipped (previous scan is too recent)" +msgstr "Escaneado saltado (o escaneado anterior é moi recente)" -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" -msgstr "" +msgstr "Escaneado" -#: front/src/views/content/remote/Card.vue:47 -#, fuzzy +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" -msgstr "Cambios sincronizados co servidor" +msgstr "Escaneado con fallos" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" +msgstr "Escaneando... (%{ progress }%)" + +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" msgstr "" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Buscar" #: front/src/views/content/remote/ScanForm.vue:9 -#, fuzzy +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" -msgstr "Lévame a biblioteca" +msgstr "Buscar unha biblioteca remota" -#: front/src/components/manage/moderation/AccountsTable.vue:171 +#: front/src/components/manage/library/EditsCardList.vue:211 #, fuzzy -msgid "Search by domain, username, bio..." -msgstr "Buscar por artista, nome de usuaria, comentario..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Buscar por tÃtulo, artista, dominio…" -#: front/src/components/manage/moderation/DomainsTable.vue:151 +#: front/src/components/manage/library/LibrariesTable.vue:191 #, fuzzy -msgid "Search by name..." -msgstr "Buscar por fonte..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Buscar por dominio, nome de usuaria, bio…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/components/manage/library/UploadsTable.vue:241 #, fuzzy -msgid "Search by title, artist, album…" -msgstr "Buscar por tÃtulo, artista, dominio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Buscar por dominio, nome de usuaria, bio…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Buscar por dominio, nome de usuaria, bio…" -#: front/src/components/manage/library/FilesTable.vue:176 +#: front/src/components/manage/library/TracksTable.vue:174 #, fuzzy -msgid "Search by title, artist, domain…" -msgstr "Buscar por tÃtulo, artista, dominio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Buscar por tÃtulo, artista, álbume…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Buscar por tÃtulo, artista, álbume…" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Buscar por dominio, nome de usuaria, bio…" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Buscar por nome…" + +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" +msgid "Search by title, artist, album…" +msgstr "Buscar por tÃtulo, artista, álbume…" #: front/src/components/manage/users/InvitationsTable.vue:153 #, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" -msgstr "Buscar por nome de usuaria, correo-e, código..." +msgstr "Buscar por nome de usuaria, correo-e, código…" #: front/src/components/manage/users/UsersTable.vue:163 -#, fuzzy +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" -msgstr "Buscar por nome de usuaria, correo-e, nome..." +msgstr "Buscar por nome de usuaria, correo-e, nome…" #: front/src/components/audio/SearchBar.vue:20 -#, fuzzy +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" -msgstr "Buscar por artistas, álbumes, cancións..." +msgstr "Buscar por artistas, álbumes, cancións…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Buscar por algo de música" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Buscar en lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Buscar en Wikipedia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" -msgstr "" +msgstr "Menú secundario" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Seccións" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Escolla un filtro" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Escolla %{ total } elemento" msgstr[1] "Escolla todos os %{ total } elementos" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Seleccionar só páxina actual" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Axustes" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Axustes actualizados" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Axustes actualizados correctamente." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Compartir ligazón" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" +msgstr "Comparta esta ligazón con outras usuarias asà poderán solicitar acceso a súa biblioteca." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 -#, fuzzy +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Compartir ligazón" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 #, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" -msgstr[0] "%{ count } canción" -msgstr[1] "%{ count } cancións" +msgstr[0] "Mostrar %{ count } canción máis" +msgstr[1] "Mostrar %{ count } cancións máis" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Mostrar 1 álbume máis" msgstr[1] "Mostrar %{ count } álbumes máis" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" -msgstr "" +msgstr "Mostrar atallos de teclado dispoñibles" -#: front/src/views/Notifications.vue:10 -#, fuzzy +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" -msgstr "Última modificación" +msgstr "Mostrar notificacións lidas" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Mostrar/ocultar contrasinal" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Mostrando resultados %{ start }-%{ end } de %{ total }" #: front/src/components/ShortcutsModal.vue:83 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Barallar a cola" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Barallar a cola" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Rexistro" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Rexistrarse" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 #, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" -msgstr "Rexistrarse" +msgstr "Data de rexistro" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 #, fuzzy -msgid "Silence activity" -msgstr "Actividade da usuaria" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "" +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Tamaño" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Tamaño" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Saltado" #: front/src/views/content/libraries/Quota.vue:49 -#, fuzzy +msgctxt "Content/Library/Label" msgid "Skipped files" -msgstr "Saltado" +msgstr "Ficheiros saltados" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" +msgstr "Software" + +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" msgstr "" +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Lamentámolo, a páxina que solicitou non existe:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Código fonte" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Persoal do equipo" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Iniciar" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Deter radio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "EstatÃsticas" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" +msgstr "As estatÃsticas contabilÃzanse pola actividade coñecida e o contido da súa instancia, e non reflexan a actividade xeral de esta conta" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" +msgstr "As estatÃsticas contabilÃzanse pola actividade coñecida e o contido da súa instancia, e non reflexan a actividade xeral de este dominio" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "As estatÃsticas contabilÃzanse pola actividade coñecida e o contido da súa instancia, e non reflexan a actividade xeral de esta conta" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Estado" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Estado" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Estado" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Deter" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Estado" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Deter radio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Enviar" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Contrasinal API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Non podemos cargar a canción" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Non podemos cargar a canción" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Non podemos cargar a canción" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Opcións suxeridas" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" +msgstr "Resumo" + +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" msgstr "" #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" -msgstr "" +msgstr "Foro de axuda" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" -msgstr "" +msgstr "Extensións soportadas: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 -#, fuzzy +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" -msgstr "Sincronizando cambios co servidor..." +msgstr "Sincronizando cambios co servidor…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" -msgstr "" +msgstr "Texto copiado ao portapapeis!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "É simple: encantábanos Grooveshark e queremos construÃr algo aÃnda mellor." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "O logo de funckwhale foi amablemente deseñado e proporcionado por Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." -msgstr "Eliminará completamente a radio e non ten volta atrás." +msgstr "Eliminará completamente a biblioteca e as cancións. Non poderá voltar atrás." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." msgstr "" -#: front/src/components/Home.vue:121 +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." +msgstr "Os ficheiros de música que está a subir están etiquetados correctamente:" + +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "A seguinte canción reproducirase automáticamente en poucos segundos…" + +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "A plataforma é libre de código aberto, pode instalala e modificala sin preocupación" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Lista creada" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Esta acción non é reversible." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "O API Subsonic non está dispoñible en esta instancia Funkwhale." -#: front/src/components/library/FileUpload.vue:43 -msgid "The uploaded music files are in OGG, Flac or MP3 format" +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Non podemos engadir a canción a lista de reprodución" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" msgstr "" +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Esta acción non é reversible." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" +msgid "The uploaded music files are in OGG, Flac or MP3 format" +msgstr "Os ficheiros de música subidos están en formato OGG, Flac ou MP3" + #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "" +msgstr "Hai varios xeitos de obter contido novo e publicalo aquÃ." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." -msgstr "" +msgstr "Esta acción non é reversible." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" -msgstr "" +msgstr "Este álbume está presente nas seguintes bibliotecas:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" -msgstr "" +msgstr "Este artista está presente nas seguintes bibliotecas:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" -msgstr "" +msgstr "Este dominio está suxeito a regras especÃficas de moderación" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "Esta instancia ofrece %{quota} de almacenamento a cada usuaria." + +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." msgstr "" #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Este é vostede!" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." -msgstr "" +msgstr "Esta biblioteca contén a miña música persoal, espero que che guste." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" +msgstr "Esta biblioteca é privada e precisa que a usuaria lle conceda permiso para acceder ao contido" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "" +msgstr "Esta biblioteca é pública e pode acceder ao contido libremente" -#: front/src/components/common/ActionTable.vue:45 -#, fuzzy +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." -msgstr "Esto poderÃa afectar a moitos elementos, por favor comprobe si realmente é o que quere." +msgstr "Esto poderÃa afectar a moitos elementos ou ter consecuencias irreversibles, por favor comprobe si realmente é o que quere." -#: front/src/components/library/FileUpload.vue:52 -msgid "This reference will be used to group imported files together." +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." msgstr "" -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" +msgid "This reference will be used to group imported files together." +msgstr "Esta referencia usarase para agrupar os ficheiros importados." + +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Non se procesou esta canción, asegúrese que está correctamente etiquetada" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Canción subida, pero aÃnda non procesada polo servidor" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 #, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "A canción xa está presente nunha das súas bibliotecas" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" -msgstr "Cancións dispoñibles en esta biblioteca" +msgstr "Esta canción non está dispoñible en ningunha biblioteca a que teña acceso" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" -msgstr "" +msgstr "Esta canción está presente nas seguintes bibliotecas:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Eliminará completamente a lista de reprodución e non poderá voltar atrás." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Eliminará completamente a radio e non ten volta atrás." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Desactivará o acceso a API Subsonic desde a conta." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "Eliminará os datos locais e será desconectada, desexa continuar?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Será desconectada dos dispositivos existentes que utilicen o contrasinal actual." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Eliminará completamente a lista de reprodución e non poderá voltar atrás." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Esto eliminará todas as cancións da lista de reprodución e non hai volta." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Title" msgstr "TÃtulo" +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" -msgstr "" +msgstr "Activar a repetición da cola" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" -msgstr "" +msgstr "Tamaño total" -#: front/src/views/content/libraries/Card.vue:61 -#, fuzzy +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" -msgstr "Cancións dispoñibles en esta biblioteca" +msgstr "Tamaño total dos ficheiros de esta biblioteca" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 -#, fuzzy +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" -msgstr "Non utilizado" +msgstr "Conta de usuarias" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Canción" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Canción" + +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" msgstr "" -#: front/src/components/library/Track.vue:85 +#: front/src/views/admin/library/TrackDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Nome da canción" + +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Información da canción" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Filtro coincidente da canción" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 -#, fuzzy +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" -msgstr "Canción" - -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "" +msgstr "Nome da canción" + +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Cancións" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "cancións" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Cancións" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Cancións de este artista" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Cancións favorecidas" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "cancións escoitadas" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Filtro coincidente da canción" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipo" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipo" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "Baixo regra de moderación" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 #, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" -msgstr "Seguir" +msgstr "Deixar de seguir" -#: front/src/views/content/remote/Card.vue:101 -#, fuzzy +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" -msgstr "Buscar na biblioteca" +msgstr "Deixar de seguir biblioteca?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Desgraciadamente os donos de esta instancia non tiveron tempo de completar esta páxina." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Música sen lÃmites" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Dar voz" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" -msgstr "Actualizar avatar" +msgstr "Actualizar" + +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Actualizar lista de reprodución" #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Actualizar avatar" #: front/src/views/content/libraries/Form.vue:25 -#, fuzzy +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" -msgstr "Xestionar biblioteca" - -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "" +msgstr "Actualizar biblioteca" #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Actualizar lista de reprodución" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Actualizar axustes" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Actualizar contrasinal" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Subir" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Subir un novo avatar" #: front/src/views/content/Home.vue:6 -#, fuzzy +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" -msgstr "Subir un novo avatar" +msgstr "Subir contido de audio" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 #, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Data de subida" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" -msgstr "Subir" +msgstr "Data de subida" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "Subida denegada, asegúrese de que o ficheiro non é demasiado grande e que non acadou o lÃmite de cuota" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" +msgstr "Subir ficheiros de música (MP3, OGG, FLAC, etc.) desde a súa biblioteca persoal co seu navegador e desfrútea aquÃ." -#: front/src/components/library/FileUpload.vue:31 -#, fuzzy +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" -msgstr "Subir un novo avatar" +msgstr "Subir novas cancións" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" -msgstr "Subir" +msgstr "Cota de subida" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" +msgstr "Caducou a subida, inténteo de novo" + +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." msgstr "" -#: front/src/components/library/FileUpload.vue:100 -#, fuzzy +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" -msgstr "Subir" +msgstr "Subida" #: front/src/components/library/FileUpload.vue:5 -#, fuzzy +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" -msgstr "Subindo..." +msgstr "Subindo" -#: front/src/components/library/FileUpload.vue:103 -#, fuzzy +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" -msgstr "Subindo..." +msgstr "Subindo…" -#: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/manage/library/LibrariesTable.vue:52 #, fuzzy +msgctxt "Content/*/*/Noun" msgid "Uploads" -msgstr "Subir" +msgstr "Subidas" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Subidas" + +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Uploads" +msgstr "Subidas" + +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Utilizar outra instancia" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Utilice este formulario para solicitar o restablecemento do contrasinal. Enviarémoslle un correo-e con instrucións para restablecelo." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" +msgstr "Utilice este axuste para activar/desactivar temporalmente a condición sen elminala completamente." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Utilizado" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Usuaria" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Actividade da usuaria" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 #, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" -msgstr "Radios da usuaria" +msgstr "Bibliotecas da usuaria" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Radios da usuaria" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nome de usuaria" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nome de usuaria ou correo-e" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "usuarias" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Usuarias" #: front/src/components/Footer.vue:29 #, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" -msgstr "Acerca de Funkwhale" +msgstr "Utilizando Funkwhale" #: front/src/components/Footer.vue:13 -#, fuzzy +msgctxt "Footer/*/List item" msgid "Version %{version}" -msgstr "Código fonte (%{version})" +msgstr "Versión %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 -#, fuzzy +msgctxt "Content/Library/Link/Verb" msgid "View files" -msgstr "Ficheiros de biblioteca" +msgstr "Ver ficheiros" + +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Ver en MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 -#, fuzzy +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" -msgstr "Visibilidade da lista de reprodución" - -#: front/src/views/content/libraries/Card.vue:59 -#, fuzzy -msgid "Visibility: everyone on this instance" -msgstr "Todas en esta instancia" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "" - -#: front/src/views/content/libraries/Card.vue:58 +msgstr "Visibilidade" + +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 #, fuzzy -msgid "Visibility: nobody except me" -msgstr "Ninguén excepto eu" +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilidade" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" -msgstr "" - -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Non podemos engadir a canción a lista de reprodución" +msgstr "Volume %{ number }" -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Non podemos crear a lista de reprodución" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Non podemos crear a súa conta" - -#: front/src/components/audio/Player.vue:64 +#: front/src/components/federation/FetchButton.vue:69 #, fuzzy -msgid "We cannot load this track" -msgstr "Non podemos engadir a canción a lista de reprodución" +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Cargando as favoritas…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Non podemos conectala" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Non podemos gardar o seu avatar" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Non podemos gardar os axustes" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Non podemos crear a súa conta" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Nin a perseguimos na internet nin molestamos con publicidade" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Recomendámoslle utilizar Picard para ese propósito." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Cremos que escoitar música deberÃa ser simple." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Lamentámolo, a páxina que solicitou non existe:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Benvida" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Benvida a Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Por qué funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" -msgstr "" +msgstr "Alto do trebello" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" +msgstr "Ancho do trebello" + +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" msgstr "" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Si" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Si, desconectádeme!" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" +msgstr "Pode compartir a biblioteca con outa xente, independentemente da súa visibilidade." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" +msgstr "Vai subir música a súa biblioteca. Antes de seguir, asegúrese de que:" + +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." msgstr "" #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Está conectada como %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." -msgstr "" +msgstr "Pode seguir bibliotecas de outras usuarias para acceder a nova música. As bibliotecas públicas pódense seguir inmediatamente, mentras que as privadas precisan que a usuaria lle conceda acceso." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Pode convidar amigos e familiares a súa instancia para que desfruten da súa música" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." -msgstr "O seu enderezo de correo foi confirmado, xa pode utilizar o servizo sen limitacións." +msgstr "Xa pode utilizar o servizo sen limitacións." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Pode utilizar esta interface para construÃr a súa propia radio, que reproducirá cancións segundo o seu criterio." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Pode utilizalos para desfrutar da súa lista de reprodución e música en modo fora de liña, no seu dispositivo móbil ou tableta, por exemplo." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Non ten ningunha regra activada para esta conta." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Non ten ningunha regra activada para esta conta." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." -msgstr "" +msgstr "Non ten ningunha regra activada para esta conta." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." +msgstr "Non ten ningunha regra activada para este dominio." + +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/Sidebar.vue:158 +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Ten a radio a funcionar" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." -msgstr "" - -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Debe seleccionar unha instancia para poder continuar" +msgstr "PoderÃa ter problemas de conectividade." #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Será desconectada de esta sesión e deberá conectar co novo" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Deberá actualizar o contrasinal nos seus clientes que utilicen este contrasinal." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Lista creada" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "As súas notificacións" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "As súas Favoritas" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "A súa música, o seu xeito" -#: front/src/views/Notifications.vue:7 -#, fuzzy +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" -msgstr "Última modificación" +msgstr "As súas notificacións" + +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "O seu contrasinal foi actualizado correctamente." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Axustes actualizados" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "O seu contrasinal Subsonic será cambiado por un novo, aleatorio, desconectándoa de todos os dispositivos que utilicen os contrasinal antigo" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Paxinación" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Copyright" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Ãlbume que contén %{ count } canción, de %{ artist }" +msgstr[1] "Ãlbume que contén %{ count } cancións, de %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "Engadiuse %{ count } canción a cola" +msgstr[1] "EngadÃronse %{ count } cancións a cola" diff --git a/front/locales/it/LC_MESSAGES/app.po b/front/locales/it/LC_MESSAGES/app.po index d3251f2b75cf2102f61706f8749b37053c11e098..298b5b20167196eb9a871329858e9b18bbc5f3c5 100644 --- a/front/locales/it/LC_MESSAGES/app.po +++ b/front/locales/it/LC_MESSAGES/app.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-15 08:57+0000\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-05-03 10:33+0000\n" "Last-Translator: Sylke Vicious <silkevicious@tuta.io>\n" "Language-Team: none\n" "Language: it\n" @@ -19,1878 +19,3117 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", di %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } su %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(vuoto)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "%{ app } vuole accedere al tuo account Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } su %{ total } selezionato" msgstr[1] "%{ count } su %{ total } selezionati" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } traccia" msgstr[1] "%{ count } tracce" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } traccia in %{ albumsCount } album" msgstr[1] "%{ count } tracce in %{ albumsCount } album" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" -msgstr[0] "%{ count } traccia corrisponde ai filtri combinati" -msgstr[1] "%{ count } tracce corrispondono ai filtri combinati" - -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } traccia è stata aggiunta alla tua coda" -msgstr[1] "%{ count } tracce sono state aggiunte alla tua coda" +msgstr[0] "%{ count } traccia corrisponde ai filtri selezionati" +msgstr[1] "%{ count } tracce corrispondono ai filtri selezionati" #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} traccia" msgstr[1] "%{ count} tracce" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } usato su %{ max } consentito" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } o %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "" -"%{ username } ha accettato la tua richiesta di seguire la libreria \"%{ " -"library }\"" +msgstr "%{ username } ha accettato la tua richiesta di seguire la libreria \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } segue la tua libreria \"%{ library }\"" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } vuole seguire la tua libreria \"%{ library }\"" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Profilo di %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" -msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">A " -"proposito di %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." +msgstr "<strong>%{ track }</strong> è già nella <strong>%{ playlist }</strong>." #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 album" msgstr[1] "%{ count } album" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 mi piace" msgstr[1] "%{ count } mi piace" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Una libreria pulita" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "C'è stato un errore durante il caricamento di questo file" +#: front/src/components/library/EditForm.vue:145 +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Un breve riassunto che descrive le tue modifiche." + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "A proposito di %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "A proposito di %{instanceName}" #: front/src/components/Footer.vue:45 +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "A proposito di Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Pagina di informazioni" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +msgctxt "Content/About/Title" msgid "About this instance" msgstr "A proposito di questa istanza" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Accetta" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Accettato" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Accesso disabilitato" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" -msgstr "Accedi alla tua musica da un'interfaccia pulita che si focalizza su quello che conta davvero" - -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "Accedi ai file audio, librerie, artisti, album e tracce" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Accesso ai filtri del contenuto" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Accesso alle modifiche" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "Accedi ad email, nome utente e informazioni del profilo" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Accesso ai preferiti" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "Accesso ai seguiti" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "Accedi alla cronologia di ascolto" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Accesso alle notifiche" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Accesso alle playlist" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Accesso alle radio" + +#: front/src/components/Home.vue:101 +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "" +"Accedi alla tua musica da un'interfaccia pulita che si focalizza su quello " +"che conta davvero" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +msgctxt "Content/*/*/Noun" msgid "Accessed date" msgstr "Data di accesso" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Account" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +msgctxt "*/*/*" +msgid "Account" +msgstr "Account" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Dati dell'account" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Impostazioni dell'account" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Impostazioni dell'account" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Stato dell'account" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Email dell'account" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Account" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Azione" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" -msgstr[0] "L'azione %{ action } è stata lanciata con successo su %{ count } elemento" -msgstr[1] "L'azione %{ action } è stata lanciata con successo su %{ count } elementi" - -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +msgstr[0] "" +"L'azione %{ action } è stata lanciata con successo su %{ count } elemento" +msgstr[1] "" +"L'azione %{ action } è stata lanciata con successo su %{ count } elementi" + +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Azioni" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Attivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Attività " #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Visibilità dell'attività " #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Aggiungi" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Aggiungi un dominio" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Aggiungi una nuova regola di moderazione" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Aggiungi una nuova regola di moderazione" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Aggiungi e gestisci contenuti" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "Aggiungi comunque" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Aggiungi contenuto" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Aggiungi filtro" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Aggiungi filtri per personalizzare la tua radio" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Aggiungi alla coda attuale" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Aggiungi ai preferiti" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Aggiungi alla playlist…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Aggiungi alla coda" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Aggiungi a questa playlist" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Aggiungi traccia" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Amministratore" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Amministrazione" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" +msgid "Album" +msgstr "Album" + +#: front/src/views/admin/library/TrackDetail.vue:107 +msgctxt "*/*/*/Noun" msgid "Album" msgstr "Album" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Album contenente %{ count } traccia, di %{ artist }" -msgstr[1] "Album contenente %{ count } tracce, di %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:128 +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Artista dell'album" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 +#: front/src/views/admin/library/AlbumDetail.vue:92 +msgctxt "Content/Moderation/Title" +msgid "Album data" +msgstr "Dati dell'album" + +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" msgid "Album name" msgstr "Nome album" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Pagina dell'album" - #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +msgctxt "*/*/*" msgid "Albums" msgstr "Albums" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Albums di questo artista" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Tutto" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "Tutto, %{ count } elemento, selezionato" +msgstr[1] "Tutti gli %{ count } elementi selezionati" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "Permetti applicazione" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" +"Si è verificato un errore durante l'elaborazione del caricamento. Troverai " +"maggiori informazioni qui sotto." + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "C'è stato un errore durante il salvataggio delle tue modifiche" +#: front/src/components/federation/FetchButton.vue:21 +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "" +"Si è verificato un errore durante il tentativo di aggiornamento dei dati:" + +#: front/src/components/federation/FetchButton.vue:41 +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Si è verificato un errore HTTP contattando il server remoto" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Si è verificato un errore sconosciuto, questo significa che il server è offline o non può essere raggiunto" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "Si è verificato un errore sconosciuto" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Applicazione" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "Dettagli dell'applicazione" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "ID Applicazione" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" +"L'ID dell'applicazione e il suo secret sono valori molto sensibili e devono " +"essere trattati come password. Non condividerli con nessun altro." + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "Secret dell'applicazione" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Approva" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Approvato" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "Approvata ed applicata" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Sei sicuro di volerti disconnettere?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artista" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artista" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Artist data" +msgstr "Dati dell'artista" + +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" msgid "Artist name" msgstr "Nome dell'artista" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Pagina dell'artista" - #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artista, album, traccia…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artisti" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artisti" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Crescente" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Chiedi un reset della password" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Contenuto audio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Scorciatoie del lettore audio" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "Autorizza %{ app }" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "Autorizza app di terze parti" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "App autorizzate" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Playlist disponibili" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Torna alla pagina di accesso" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Torna alle impostazioni" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrate" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Blocca tutto" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Blocca tutto da questo account o dominio. Questo prevenirà qualsiasi " -"interazione con l'entità , ed eliminerà i relativi contenuti (caricamenti, " -"librerie, richieste di seguire, ecc.)" +msgstr "Blocca tutto da questo account o dominio. Questo prevenirà qualsiasi interazione con l'entità , ed eliminerà i relativi contenuti (caricamenti, librerie, richieste di seguire, ecc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Sfoglia" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Sfoglia libreria" +#: front/src/components/library/Albums.vue:4 +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Sfogliando album" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Sfogliando artisti" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Sfogliando playlists" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Sfogliando radio" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Crea" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Di %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." -msgstr "" -"Smettendo di seguire questa libreria, perderai l'accesso al suo contenuto." - -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +msgstr "Smettendo di seguire questa libreria, perderai l'accesso al suo contenuto." + +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "Dimensione in cache" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Annulla" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Candidati" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Non puoi cambiare la tua password" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "Impossibile caricare questo file, controlla che non sia troppo grande" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Cambia lingua" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Cambia la mia password" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Cambia password" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Cambia la tua password" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Cambiare la tua password?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Modifiche sincronizzate con il server" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Cambiando la tua password cambierà anche la password della API Subsonic se ne hai richiesta una." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" -msgstr "Cambiare la tua password avrà queste conseguenze" +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" +msgstr "Cambiare la tua password avrà queste conseguenze:" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Stanza di conversazione" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" +"Controllando i permessi \"Lettura\" o \"Scrittura\" del livello superiore " +"implica l'accesso a tutti i corrispondenti permessi dei livelli inferiori." + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Scegli la tua istanza" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Pulisci libreria" +#: front/src/components/library/EditForm.vue:75 +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Pulisci" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Pulisci" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Pulisci playlist" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Pulisci la tua coda" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Clicca una volta, ascolta per ore utilizzando le radio integrate" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" +"Clicca per visualizzare più informazioni sul processo di importazione di " +"questo caricamento" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Clicca per selezionare i file da caricare o trascina e rilascia file o cartelle" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Chiudi" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Chiudi" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "Chiudi e ricarica pagina" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Codice" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Riduci" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configurazione" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Conferma" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Conferma il tuo indirizzo e-mail" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Codice di conferma" -#: front/src/components/common/ActionTable.vue:7 -msgid "Content have been updated, click refresh to see up-to-date content" +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "Filtro di contenuto aggiunto con successo" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Filtri di contenuto" + +#: front/src/components/auth/Settings.vue:116 +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Filtri di contenuto" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." msgstr "" -"Il contenuto è stato aggiornato, clicca aggiorna per visualizzaare il " -"contenuto aggiornato" +"I filtri di contenuti ti aiutano a nascondere cose che non vuoi vedere su " +"questo servizio." + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "Il contenuto è stato aggiornato, clicca aggiorna per visualizzaare il contenuto aggiornato" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Contribuisci" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Copia" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" -msgstr "Copia tracce dalla tua coda corrente alla playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" +msgstr "Copia tracce dalla coda alla playlist" + +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "Copia-incolla il codice seguente in questa applicazione:" #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "Copia/incolla questo codice nel tuo sito HTML" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Copyright" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Non è stato possibile confermare il tuo indirizzo e-mail" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Non è stato possibile recuperare la libreria remota" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Non è stato possibile processare questa traccia, assicurati che sia " -"correttamente etichettata" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Copertine, testi, il nostro obbiettivo è averli tutti ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Crea" #: front/src/components/auth/Signup.vue:4 +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Crea un account funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Crea una nuova applicazione" + +#: front/src/components/auth/Settings.vue:220 +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Crea una nuova applicazione" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Crea una nuova libreria" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Crea una nuova playlist" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Crea un account" +#: front/src/components/auth/ApplicationForm.vue:65 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Crea applicazione" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Crea libreria" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Crea il mio account" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "Crea una per integrare Funkwhale con applicazioni di terze parti." + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Crea playlist" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Crea la tua radio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Data di creazione" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar attuale" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Libreria attuale" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Traccia corrente" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Utilizzo attuale" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "I dati riportati dal server remoto hanno attributi invalidi o mancanti" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "I dati sono stati aggiornati con successo dal server remoto." + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Data" +#: front/src/components/library/ImportStatusModal.vue:64 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Informazioni di debug" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Diminuisci volume" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Elimina" +#: front/src/components/auth/Settings.vue:254 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Elimina applicazione" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "Eliminare l'applicazione \"%{ application }\"?" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Elimina libreria" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Elimina regola di moderazione" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Elimina playlist" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Elimina radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Eliminare questo album?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Eliminare questo artista?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Eliminare questa libreria?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Eliminare questa regola di moderazione?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Eliminare questo suggerimento?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Eliminare questo caricamento?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Decrescente" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Descrizione" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Dettaglio" +#: front/src/views/admin/library/LibraryDetail.vue:123 +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Descrizione" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Dettagli" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Determina quanto contenuto un utente può caricare. Lascia vuoto per usare il valore predefinito dell'istanza." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Imposta il livello di visibilità delle tue attività " #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Disabilita accesso" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Disabilita accesso Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Disabilitare l'accesso alle API Subsonic?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Disabilitato" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "Numero disco" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Scopri come utilizzare Funkwhale attraverso altre app" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Nome visualizzato" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Mostra pubblicamente" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Non scaricare nessun media (audio, copertina dell'album, avatar " -"dell'account...) da questo account o dominio. Questo eliminerà anche i " -"contenuti già esistenti." +msgstr "Non scaricare nessun media (audio, copertina dell'album, avatar dell'account...) da questo account o dominio. Questo eliminerà anche i contenuti già esistenti." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Vuoi pulire la playlist \"%{ playlist }\"?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Vuoi confermare questa azione?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Vuoi eliminare la playlist \"%{ playlist }\"?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Vuoi eliminare la radio \"%{ radio }\"?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Vuoi nascondere i contenuti dell'artista \"%{ name }\"?" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Vuoi eseguire %{ action } su %{ count } elemento?" msgstr[1] "Vuoi eseguire %{ action } su %{ count } elementi?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Vuoi ripristinare la tua coda precedente?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentazione" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Dominio" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Domini" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Scarica" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Trascina e rilascia righe per riordinare le tracce nella playlist" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Durata" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Indirizzo e-mail confermato" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Facile da utilizzare" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Modifica" + +#: front/src/components/auth/Settings.vue:246 +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Modifica" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Modifica applicazione" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Modifica le info dell'istanza" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Modifica…" - -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Modifica regola di moderazione" + +#: front/src/components/library/AlbumEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Modifica questo album" + +#: front/src/components/library/ArtistEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Modifica questo artista" + +#: front/src/components/library/TrackEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Modifica questa traccia" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Modifiche" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Modifiche" + +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Email" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Indirizzo email" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Incorpora" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Incorpora codice" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Incorpora questo album nel tuo sito web" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Incorpora il lavoro di questo artista nel tuo sito web" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Incorpora questa traccia nel tuo sito web" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "Emessi i follow della libreria" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Messaggi emessi" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Abilitato" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Fine modifica" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Inserisci l'URL di una libreria" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Inserisci un nome di una radio…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "Inserisci il titolo dell'album..." + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Inserisci il nome di un artista…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Inserisci il nome di una playlist…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Inserisci l'indirizzo email collegato al tuo account" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Inserisci la tua email" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Inserisci il tuo codice di invito (non tiene conto di maiuscole o minuscole)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Inserisci i tuoi criteri di ricerca…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Inserisci il tuo nome utente" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Inserisci il tuo nome utente o l'email" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Errore" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Dettaglio dell'errore" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Segnalazione errore" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Tipo di errore" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Errore durante l'esecuzione dell'azione" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Errore durante la richiesta di un reset della password" +#: front/src/components/auth/Authorize.vue:6 +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Errore durante l'autorizzazione dell'applicazione" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Errore durante la modifica della password" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Errore durante la creazione del dominio" +#: front/src/components/moderation/FilterModal.vue:13 +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Errore durante la creazione del filtro" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Errore durante la creazione dell'invito" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Errore durante la creazione della regola" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Errore durante il recupero dei dati dell'applicazione" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Errore durante il recupero delle informazioni del nodo" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Errore durante il salvataggio delle impostazioni" + +#: front/src/components/federation/FetchButton.vue:73 +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Errore durante il salvataggio delle impostazioni" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Errore durante l'invio della modifica" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Si è verificato un errore" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "File con errore" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Tutti" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Tutti su questa istanza" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Tutti, su tutte le istanze" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Escludi" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Data di scadenza" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Scaduto" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Scaduto/utilizzato" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" -"Spiega perchè stai applicando questa regola. In base alla configurazione " -"della tua istanza, questo ti aiuterà a ricordare perchè hai eseguito questa " -"azione su questo account o dominio, e può essere mostrata pubblicamente per " -"aiutare gli utenti a capire quali regole sono applicate qui." +msgstr "Spiega perchè stai applicando questa regola. In base alla configurazione della tua istanza, questo ti aiuterà a ricordare perchè hai eseguito questa azione su questo account o dominio, e può essere mostrata pubblicamente per aiutare gli utenti a capire quali regole sono applicate qui." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Fallito" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Tracce con errore:" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Tracce preferite" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Preferiti" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Preferiti" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federazione" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "ID Federazione" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "Campo" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nome file" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Files" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Filtra nome" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +msgctxt "Content/Library/*" msgid "Finished" msgstr "Finito" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "Visto per la prima volta" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Visto per la prima volta in data" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Segui" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Segui librerie remote" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Richiesta di seguire in attesa di approvazione" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Seguito da" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Seguito da" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Seguendo" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Dall'album %{ album } di %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Segue" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" +"Dall'album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> di <a " +"class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" + +#: front/src/components/auth/Authorize.vue:28 +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Accesso completo" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale è compatibile con altri lettori musicali che supportano le Subsonic API." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale è semplicissimo da usare." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale è progettato per permetterti di ascoltare facilmente la musica che ti piace, o per farti scoprire nuovi artisti." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale è gratuito e ti da il controllo sulla tua musica." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale si preoccupa di gestire la tua musica" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Scorciatoie generali" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Ottieni un nuovo invito" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Portami alla libreria" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" -msgstr "Ottieni metadati di qualità sulla tua musica grazie a <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" +msgstr "" +"Ottieni metadati di qualità sulla tua musica grazie a <a href=\"%{ url }\" " +"target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" -msgstr "Iniziamo" +msgstr "Per iniziare" + +#: front/src/components/library/ImportStatusModal.vue:45 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Chiedi aiuto" #: front/src/components/Footer.vue:37 +msgctxt "Footer/*/Link" msgid "Getting help" -msgstr "Ricevendo aiuto" +msgstr "Chiedi aiuto" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Vai" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Vai alla pagina iniziale" +#: front/src/components/auth/Settings.vue:128 +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Artisti nascosti" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." -msgstr "" -"Nascondi contenuto dell'account o del dominio, ad esclusione dei seguaci." +msgstr "Nascondi contenuto dell'account o del dominio, ad esclusione dei seguaci." + +#: front/src/components/moderation/FilterModal.vue:40 +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Nascondi contenuto" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "Nascondi contenuti di questo artista" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "Nascondi contenuti di questo artista…" #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Pagina Iniziale" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Ore di musica" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Comunque, accedere a Funkwhale da quei client richiede un'altra password che puoi impostare qui sotto." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Se l'indirizzo email fornito nel passo precedente è valido e legato ad un account utente, dovresti ricevere un'email con le istruzioni per il reset nel prossimo paio di minuti." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Importa data" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" +"Se autorizzi applicazioni di terze parti ad accedere ai tuoi dati, queste " +"applicazioni verranno elencate qui." -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importa musica da varie piattaforme, come YouTube o SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Dettaglio dell'importazione" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Importa riferimento" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Stato dell'importazione" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Stato dell'importazione" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importato" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Data di importazione" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "Impossibile connettersi al server remoto" + +#: front/src/components/moderation/FilterModal.vue:26 +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Nel widget \"Aggiunti recentemente\"" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "Negli elenchi di artisti ed album" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Nei preferiti" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "Nei preferiti di altri utenti e nella cronologia di ascolto" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "Nei suggerimenti radio" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Inattivo" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Aumenta volume" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Inserisci l'indirizzo email collegato al tuo account" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" -msgstr[0] "Inserita dalla coda (%{ count } traccia)" -msgstr[1] "Inserite dalla coda (%{ count } tracce)" +msgstr[0] "Inserisci dalla coda (%{ count } traccia)" +msgstr[1] "Inserisci dalla coda (%{ count } tracce)" + +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Istanza" #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Dati dell'istanza" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Informazioni sull'istanza" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radio dell'istanza" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Impostazioni dell'istanza" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "URL dell'istanza" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" -msgstr "" -"Tipo di file non valido, assicurati che stai caricando un file audio. Le " -"estensioni di file supportate sono %{ extensions }" +msgstr "Tipo di file non valido, assicurati che stai caricando un file audio. Le estensioni di file supportate sono %{ extensions }" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" +msgstr "Metadati non validi" + +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Codice di invito" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Codice d'invito (opzionale)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Inviti" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Elenco problemi" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "Non è possibile connettersi all'URL dato" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Tieni d'occhio le tue tracce preferite" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "Scorciatoie da tastiera" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Account conosciuti" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Librerie conosciute" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" -msgstr "Ultime attività " +msgstr "Ultima attività " -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Controllato l'ultima volta" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Ultima modifica" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Visto l'ultima volta" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Visto l'ultima volta in data" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Ultimo aggiornamento:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Inizia" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Scopri di più su questa istanza" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Lascia vuoto per un codice casuale" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Lascia vuoto per un widget adattivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Librerie" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +msgctxt "*/*/*" msgid "Libraries" msgstr "Librerie" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Librerie e caricamenti" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Le librerie sono di aiuto per organizzare e condividere la tua collezione musicale. Puoi caricare la tua musica su Funkwhale e condividerla con amici e parenti." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +msgctxt "*/*/*" msgid "Library" msgstr "Libreria" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Libreria creata" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Dati della libreria" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Libreria eliminata" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" -msgstr "File della libreria" +#: front/src/views/admin/library/EditsList.vue:4 +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" +msgstr "Modifiche della libreria" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Libreria aggiornata" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Licenza" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "Ascolti" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "Ascolti" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Carica di più…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Caricando la lista di chi ti segue…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Caricando le Librerie…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Caricando i dati della libreria…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Caricando le notifiche…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "Caricando le librerie remote..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Caricando le librerie remote…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Caricando i dati di utilizzo…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Caricando i tuoi preferiti…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "Locale" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Account locale" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Accedi" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Accedi al tuo account Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Disconnetti" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Accesso effettuato come %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Accedi" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Stato dell'accesso" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Disconnettiti" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "" -"Sembra che tu non abbia ancora nessuna libreria, è tempo di crearne una." +msgstr "Sembra che tu non abbia ancora nessuna libreria, è tempo di crearne una." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Ripetizione disattivata. Clicca per attivare la ripetizione della singola traccia." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Ripeti una singola traccia. Clicca per ripetere l'intera coda." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Ripete l'intera coda. Clicca per disattivare la ripetizione." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Testi" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Menu principale" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Gestisci libreria" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Gestisci playlist" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Gestisci utenti" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Gestisci le tue playlist" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Segna tutte come lette" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Segna come letta" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Segna come non letta" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "Riproduttore musicale" +#: front/src/components/auth/Profile.vue:12 +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Membro da %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "Applicazioni desktop e mobile" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Moderazione" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"Le regole di moderazione ti aiutano a controllare come la tua istanza " -"interagisce con un dato dominio o account." +msgstr "Le regole di moderazione ti aiutano a controllare come la tua istanza interagisce con un dato dominio o account." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Modifica %{ id }" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Data di modifica" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "Di più…" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Musica" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Muto" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Silenzia attività " + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Silenzia notifiche" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Mio account" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "La mia stupenda descrizione" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "La mia eccezionale libreria" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "La mia eccezionale playlist" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "La mia eccezionale radio" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Le mie librerie" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "N/D" -#: front/src/components/manage/moderation/AccountsTable.vue:39 -#: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +msgctxt "*/*/*" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 +#: front/src/components/manage/moderation/AccountsTable.vue:39 +#: front/src/components/manage/moderation/DomainsTable.vue:38 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/ApplicationForm.vue:9 +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nome" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nuova password" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Le nuove tracce saranno allegate qui automaticamente." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "Nuovo valore" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Prossima traccia" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "No" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Nessun addon, nessun plugin: ti serve solo una libreria in rete" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Nessun album corrisponde alla tua ricerca" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Nessun artista corrisponde alla tua ricerca" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." -msgstr "Nessun testo disponibile per questa traccia." +#: front/src/components/library/TrackDetail.vue:14 +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" +msgstr "" +"Nessuna informazione sui diritti d'autore disponibile per questa traccia" + +#: front/src/components/library/TrackDetail.vue:25 +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Nessuna informazione sulla licenza per questa traccia" #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Nessuna libreria corrispondente." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." -msgstr "Ancora nessuna notifica." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "Nessuna notifica da visualizzare." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "Nessun risultato trovato." #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Nessuno tranne me" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Nessuno segue questa libreria" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Non utilizzato" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Notifiche" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Notifiche" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Sito ufficiale" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Vecchia password" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "Vecchio valore" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Aperto" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" +"Apri una conversazione di supporto (includi nel tuo messaggio le " +"informazioni di debug qui sotto)" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Apri nell'interfaccia di moderazione" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Apri profilo locale" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Apri su MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Apri profilo" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Apri profilo remoto" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Apri sito web" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "O personalizza la tua regola" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Ordine" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Ordinamento" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Direzione di ordinamento" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Proprietario" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Pagina non trovata" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Pagina non trovata!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "Impaginazione" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Password" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Password aggiornata" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Password aggiornata con successo" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Metti in pausa" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "Pausa/riproduci la traccia corrente" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "In pausa" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "In sospeso" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Approvazione in sospeso" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "File in sospeso" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Richiesta di seguire in sospeso" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Revisione in sospeso" + +#: front/src/components/Sidebar.vue:226 +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Revisioni delle modifiche in sospeso" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Permessi" + +#: front/src/components/auth/Settings.vue:176 +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Permessi" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Riproduci" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Riproduci tutto" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Riproduci tutti gli album" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Riproduci la prossima" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Riproduci traccia successiva" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Riproduci ora" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Riproduci traccia precedente" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "Riproduci canzoni simili" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Riproduci questa traccia" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Riproduci traccia" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Riproduci..." + +#: front/src/views/playlists/Detail.vue:91 +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Lista di riproduzione" #: front/src/views/playlists/Detail.vue:12 +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Lista di riproduzione contenente %{ count } traccia, di %{ username }" msgstr[1] "Lista di riproduzione contenente %{ count } tracce, di %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Lista di riproduzione creata" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Modifica lista di riproduzione" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nome lista di riproduzione" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Lista di riproduzione aggiornata" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilità lista di riproduzione" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Liste di riproduzione" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Liste di riproduzione" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "Liste di riproduzione? Ce le abbiamo" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Per favore controlla se la tua password è corretta" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Per favore controlla se la combinazione nome utente/password è corretta" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF or JPG. Massimo 2MB. Saranno scalate a 400x400px." +#: front/src/views/admin/library/TrackDetail.vue:137 +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Posizione" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" -"Evita che l'account o il dominio facciano scattare notifiche, ad esclusione " -"dei seguaci." +msgstr "Evita che l'account o il dominio facciano scattare notifiche, ad esclusione dei seguaci." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "Anteprima" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Traccia precedente" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "Privato" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Errore durante la scansione" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Procedi" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Procedi all'accesso" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Elaborazione" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Profilo" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1899,1097 +3138,1946 @@ msgstr "Elaborazione" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Rimuovi" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "Rimuovere file con errori?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "Rimuovere file in attesa?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "Rimuovere file saltati?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Coda" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Coda mischiata!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Creatore Radio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Radio creata" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nome radio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Radio aggiornata" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radio" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radio" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "Lettura" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "Leggi la nostra documentazione per questo errore" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "Sola lettura" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "Accesso in sola lettura ai dati utente" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgctxt "Content/Moderation/*/Noun" msgid "Reason" -msgstr "Motivazione" +msgstr "Motivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "Ricevuto un follow della libreria" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Messaggi ricevuti" +#: front/src/components/library/EditForm.vue:27 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Modifiche recenti" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "Modifiche recenti in attesa di revisione" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Aggiunte recentemente" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Preferiti recenti" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Ascoltate recentemente" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "URI di redirezione" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Aggiorna" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Errore di aggiornamento" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "Aggiorna da un server remoto" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Aggiorna informazioni del nodo" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Aggiornamento in sospeso" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" +"La richiesta di aggiornamento non è stata processata in tempo dal nostro " +"server. Sarà processata più tardi." + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "Aggiornamento riuscito" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "Aggiorna tabella dei contenuti" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Registrato da %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "L'aggiornamento è stato rimandato" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "Aggiornando oggetto da remoto…" #: front/src/components/auth/Signup.vue:9 +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." -msgstr "Le registrazioni sono chiuse su questa istanza, hai bisogno di un codice d'invito per registrarti." +msgstr "" +"Le registrazioni sono chiuse su questa istanza, avrai bisogno di un codice " +"d'invito per registrarti." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" -msgstr "utente semplice" +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" +msgstr "Utente semplice" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Rifiuta" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Rifiuta media" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Rifiutato" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Riavvia importazione" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Data di rilascio" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "Spazio di archiviazione rimanente" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Librerie remote" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Le librerie remote sono di proprietà di altri utenti nella rete. Puoi accedervi se sono pubbliche o se ti è stato dato l'accesso ad esse." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Rimuovi" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Rimuovi avatar" +#: front/src/components/library/ArtistDetail.vue:12 +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Rimuovi filtro" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Rimuovi dai preferiti" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Rimuovi completamente tracce caricate ma non ancora processate, aggiungendo " -"lo spazio corrispondente alla tua quota." +msgstr "Rimuovi completamente tracce caricate ma non ancora processate, aggiungendo lo spazio corrispondente alla tua quota." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" -"Rimuove completamente le tracce caricate ma che sono state saltate durante " -"la fase di importazione, aggiungendo lo spazio corrispondente alla tua quota." +msgstr "Rimuove completamente le tracce caricate ma che sono state saltate durante la fase di importazione, aggiungendo lo spazio corrispondente alla tua quota." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" -"Rimuove completamente le tracce caricate ma che non sono state processate " -"correttamente, aggiungendo lo spazio corrispondente alla tua quota." +msgstr "Rimuove completamente le tracce caricate ma che non sono state processate correttamente, aggiungendo lo spazio corrispondente alla tua quota." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Richiedi una nuova password" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Richiedere una nuova password API Subsonic?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Richiedi una password" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "Richiedendo un recupero…" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "Ripristina al valore iniziale: %{ value }" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Resetta la tua password" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Riavvia importazione" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Risultati per pagina" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "Restringi alle modifiche non revisionate" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Torna alla pagina di accesso" +#: front/src/components/library/ArtistDetail.vue:9 +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Revisiona i miei filtri" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "Revoca" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "Revoca accesso" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "Revoca accesso per l'applicazione \"%{ application }\"?" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Regola" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Salva" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Scansione avviata" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Scansiona ora" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Scansione in sospeso" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Scansione saltata (la scansione precedente è troppo recente)" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Scansione in attesa" - -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Scansionata" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Scansione eseguita con errori" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Scansionando... (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "Visibilità " + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "Visibilità " + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Cerca" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Cerca una libreria remota" +#: front/src/components/manage/library/EditsCardList.vue:211 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Cerca per account, informazioni, dominio…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Cerca per dominio, attore, nome, descrizione…" + +#: front/src/components/manage/library/UploadsTable.vue:241 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Cerca per dominio, attore, nome, riferimento, sorgente…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Cerca per dominio, nome, ID MusicBrainz…" + +#: front/src/components/manage/library/TracksTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Cerca per dominio, titolo, artista, album, ID MusicBrainz…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Cerca per dominio, titolo, artista, ID MusicBrainz…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." -msgstr "Cerca per dominio, nome utente, descrizione..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Cerca per dominio, nome utente, descrizione…" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." -msgstr "Cerca per nome..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Cerca per nome…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "Cerca per titolo, artista, album…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Cerca per titolo, artista, dominio…" - #: front/src/components/manage/users/InvitationsTable.vue:153 +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" -msgstr "Cerca per nome utente, email, codice…" +msgstr "Cerca per nome utente, indirizzo e-mail, codice…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Cerca per nome utente, indirizzo e-mail, nome…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Cerca per artisti, album, tracce…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Cerca un po' di musica" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Cerca su lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Cerca su Wikipedia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "Menu secondario" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Sezioni" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Seleziona un filtro" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" -msgstr[0] "Seleziona tutti %{ total } elemento" -msgstr[1] "Seleziona tutti %{ total } elementi" +msgstr[0] "Seleziona tutto, %{ total } elemento" +msgstr[1] "Seleziona tutti e %{ total } elementi" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Seleziona solo la pagina attuale" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Impostazioni" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Impostazioni aggiornate" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Impostazioni aggiornate con successo." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Condividi collegamento" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Condividi questo collegamento con altri utenti in modo che possano " -"richiedere l'accesso alla tua libreria." +msgstr "Condividi questo collegamento con altri utenti in modo che possano richiedere l'accesso alla tua libreria." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Condividi collegamento" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "Mostra %{ count } traccia in più" msgstr[1] "Mostra %{ count } tracce in più" #: front/src/components/audio/artist/Card.vue:30 +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Mostra 1 altro album" msgstr[1] "Mostra %{ count } altri album" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "Mostra tutte le modifiche" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "Mostra scorciatoie da tastiera disponibili" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Mostra notifiche lette" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Mostra/nascondi password" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" -msgstr "Mostrando risultati %{ start }-%{ end } su %{ total }" +msgstr "Mostrando i risultati da %{ start } a %{ end } su %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Mischia la tua coda" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Mischia la tua coda" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Registrati" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Registrati" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Data di registrazione" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "Silenzia l'attività " - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Silenzia le notifiche" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Dimensione" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Dimensione" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Saltato" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "File saltati" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Software" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" +"Alcune tracce nella tua coda sono già presenti in questo elenco di " +"riproduzione:" + +#: front/src/components/PageNotFound.vue:10 +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Ci dispiace, la pagina che hai richiesto non esiste:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Codice sorgente" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Membro dello staff" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Inizia" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Riproduci radio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Statistiche" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" -"Le statistiche sono calcolate da attività conosciute e contenuti della tua " -"istanza, e non riflette l'attività generale per questo account" +msgstr "Le statistiche sono calcolate da attività conosciute e contenuti della tua istanza, e non riflette l'attività generale per questo account" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" +msgstr "Le statistiche sono calcolate da attività conosciute e contenuti della tua istanza, e non riflette l'attività generale per questo dominio" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" msgstr "" "Le statistiche sono calcolate da attività conosciute e contenuti della tua " -"istanza, e non riflette l'attività generale per questo dominio" +"istanza, e non riflette l'attività generale per questo oggetto" + +#: front/src/components/library/FileUpload.vue:95 +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Stato" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Stato" + +#: front/src/components/manage/library/EditsCardList.vue:12 +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Stato" + +#: front/src/components/manage/users/UsersTable.vue:43 +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Stato" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Stato" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Ferma" +#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Stato" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Ferma radio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Invia" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "Invia e applica modifica" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "Invia un'altra modifica" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "Invia suggerimento" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Password API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "Suggerisci una modifica utilizzando il modulo qui sotto." + +#: front/src/components/library/AlbumEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Suggerisci una modifica su questo album" + +#: front/src/components/library/ArtistEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Suggerisci una modifica su questo artista" + +#: front/src/components/library/TrackEdit.vue:5 +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Suggerisci una modifica su questa traccia" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Scelte suggerite" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Riepilogo" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "Riassunto (opzionale)" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Forum di supporto" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Estensioni supportate: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Sincronizzando le modifiche con il server…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Testo copiato negli appunti!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "È molto semplice: amavamo Grooveshark e volevamo creare qualcosa ancora più bello." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" +"L'album sarà rimosso, ed anche i caricamenti associati, tracce, preferiti e " +"cronologia di ascolto. Questa azione è irreversibile." + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "L'applicazione sta anche richiedendo i seguenti permessi sconosciuti:" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" +"L'artista sarà rimosso, ed anche i caricamenti associati, tracce, album, " +"preferiti e cronologia di ascolto. Questa azione è irreversibile." + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "Il logo di Funkwhale è stato gentilmente disegnato e concesso da Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "L'indirizzo fornito non è quello di un server Funkwhale" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." -msgstr "" -"La libreria e tutte le sue tracce saranno eliminate. Questa azione è " -"irreversibile." +msgstr "La libreria e tutte le sue tracce saranno eliminate. Questa azione è irreversibile." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" -msgstr "I file musicali che stai caricando sono correttamente etichettati:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" +"La libreria sarà rimossa, ed anche i caricamenti associati e gli iscritti. " +"Questa azione è irreversibile." -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." msgstr "" -"La traccia seguente verrà riprodotta automaticamente tra pochi secondi..." +"I metadati inclusi nel file non sono validi o alcuni campi obbligatori sono " +"mancanti." -#: front/src/components/Home.vue:121 +#: front/src/components/library/FileUpload.vue:38 +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." +msgstr "I file musicali che stai caricando sono correttamente etichettati." + +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "La traccia seguente verrà riprodotta automaticamente tra pochi secondi…" + +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "La piattaforma è libera e open source, puoi installarla e modificarla senza problemi" +#: front/src/components/playlists/Form.vue:14 +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "La lista di riproduzione non può essere creata" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "Il server remoto ha risposto con il codice HTTP %{ status }" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" +"Il server remoto ha risposto, ma i dati ricevuti non sono supportati da " +"Funkwhale." + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "Il server remoto non ha risposto abbastanza velocemente" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "Il server remoto ha risposto con dati JSON o JSON-LD non validi" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" +"Gli album selezionati saranno rimossi, ed anche i caricamenti associati, " +"tracce, preferiti e cronologia di ascolto. Questa azione è irreversibile." + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" +"Gli artisti selezionati saranno rimossi, ed anche i caricamenti associati, " +"tracce, album, preferiti e cronologia di ascolto. Questa azione è " +"irreversibile." + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" +"Le librerie selezionate saranno rimosse, ed anche i caricamenti associati, " +"ed iscritti. Questa azione è irreversibile." + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" +"Le tracce selezionate saranno rimosse, ed anche i caricamenti associati, " +"preferiti e cronologia di ascolto. Questa azione è irreversibile." + +#: front/src/components/manage/library/UploadsTable.vue:256 +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Il caricamento selezionato sarà rimosso. Questa azione è irreversibile." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "Il server potrebbe essere caduto" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "L'API Subsonic non è disponibile su questa istanza Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" +"Il suggerimento sarà completamente rimosso, questa azione è irreversibile." + +#: front/src/components/playlists/PlaylistModal.vue:34 +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "La traccia non può essere aggiunta alla lista di riproduzione" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "La traccia non può essere caricata" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" +"La traccia sarà rimossa, ed anche i caricamenti associati, preferiti e " +"cronologia di ascolto. Questa azione è irreversibile." + +#: front/src/views/admin/library/UploadDetail.vue:68 +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Il caricamento sarà rimosso. Questa azione è irreversibile." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "I file musicali caricati sono in formato OGG, Flac o MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "" -"Offriamo vari modi per recuperare nuovi contenuti e renderli disponibili qui." +msgstr "Offriamo vari modi per recuperare nuovi contenuti e renderli disponibili qui." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Questa azione è irreversibile." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Questo album è presente nelle seguenti librerie:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Questo artista è presente nelle seguenti librerie:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "Questo dominio è soggetto a regole specifiche di moderazione" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "Questa istanza offre fino a %{quota} di spazio di archiviazione per ogni utente." + +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." msgstr "" -"Questa istanza offre fino a %{quota} di spazio di archiviazione per ogni " -"utente." +"Questa è la lista di applicazioni che hanno accesso ai dati del tuo account." + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "Questa è la lista di applicazioni che hai creato." #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Questo sei tu!" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Questa libreria contiene la mia musica personale, spero vi piaccia." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Questa libreria è privata e la tua approvazione dal suo proprietario è " -"necessaria per accedere al suo contenuto" +msgstr "Questa libreria è privata e la tua approvazione dal suo proprietario è necessaria per accedere al suo contenuto" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "" -"Questa libreria è pubblica e tu puoi accedere al suo contenuto liberamente" +msgstr "Questa libreria è pubblica e tu puoi accedere al suo contenuto liberamente" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." -msgstr "" -"Questo può coinvolgere molti elementi o avere conseguenze irreversibili, per " -"favore ricontrolla se è proprio quello che vuoi." +msgstr "Questo può coinvolgere molti elementi o avere conseguenze irreversibili, per favore ricontrolla se è proprio quello che vuoi." + +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "Questo oggetto è gestito da un altro server, non puoi modificarlo." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Questo riferimento sarà utilizzato per raggruppare file importati." -#: front/src/components/audio/PlayButton.vue:73 -msgid "This track is not available in any library you have access to" +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "" +"Non è stato possibile processare questa traccia, assicurati che sia " +"correttamente etichettata" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" msgstr "" -"Questa traccia non è disponibile in nessuna libreria alla quale hai accesso" +"Questa traccia è stata caricata, ma non è ancora stata processata dal server" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Questa traccia è già presente in una delle tue librerie" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" +msgid "This track is not available in any library you have access to" +msgstr "Questa traccia non è disponibile in nessuna libreria alla quale hai accesso" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Questa traccia è presente nelle seguenti librerie:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Questo cancellerà questa lista di riproduzione e non può essere annullato." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Questo cancellerà questa radio e non può essere annullato." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Questo disabiliterà completamente l'accesso alla API Subsonic dagli account." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" -"Questo cancellerà i tuoi dati locali e ti disconnetterà , vuoi continuare?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Questo ti disconnetterà dai dispositivi esistenti che utilizzano la password attuale." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "" +"Questo cancellerà permanentemente l'applicazione e tutti i token associati." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" +"Questo eviterà a questa applicazione di accedere al servizio a tuo nome." + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Questo cancellerà tutte le tracce da questa lista di riproduzione e non può essere annullato." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "Titolo" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "Titolo" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" msgid "Title" msgstr "Titolo" +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" +"Per continuare, per favore seleziona l'istanza Funkwhale alla quale vuoi " +"connetterti. Inserisci l'indirizzo direttamente, o selezionane uno dalla " +"lista di suggerimenti." + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "Cambia tipo di riproduzione della coda" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Dimensione totale" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Dimensione totale dei files in questa libreria" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Utenti totali" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +msgctxt "*/*/*/Noun" +msgid "Track" +msgstr "Traccia" + +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" msgid "Track" msgstr "Traccia" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "La traccia era già presente in una delle tue librerie" +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "Traccia #%{ id } - %{ name }" -#: front/src/components/library/Track.vue:85 +#: front/src/views/admin/library/TrackDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Dati della traccia" + +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Informazioni traccia" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Filtro corrispondenze traccia" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Nome traccia" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "La traccia è stata caricata ma non ancora processata dal server" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Tracce" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "tracce" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Tracce" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Tracce di questo artista" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Tracce preferite" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "tracce ascoltate" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Tracce che corrispondono al filtro" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipo" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipo" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" msgstr "Sotto regole di moderazione" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Smetti di seguire" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Smettere di seguire questa libreria?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." -msgstr "Sfortunatamente i proprietari di questa istanza non hanno ancora avuto tempo di completare questa pagina." +#: front/src/components/About.vue:17 +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." +msgstr "" +"Sfortunatamente i proprietari di questa istanza non hanno ancora avuto tempo " +"di completare questa pagina." + +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "Errore sconosciuto" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "Errore sconosciuto" #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Musica illimitata" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Non silenziare" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Aggiorna" +#: front/src/components/auth/ApplicationForm.vue:64 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Aggiorna applicazione" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Aggiorna avatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Aggiorna libreria" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Aggiorna regole di moderazione" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Aggiorna lista di riproduzione" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Aggiorna impostazioni" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Aggiorna la tua password" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Carica" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Carica un nuovo avatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Carica contenuto audio" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Dati di caricamento" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Data di caricamento" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" -msgstr "" -"Caricamento non riuscito, assicurati che il file non sia troppo grande e di " -"non aver esaurito la tua quota" +msgstr "Caricamento non riuscito, assicurati che il file non sia troppo grande e di non aver esaurito la tua quota" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." +msgstr "Il caricamento è ancora in corso e presto sarà processato dal server." #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" -"Carica file musicali (mp3, ogg, flac, ecc.) dalla tua libreria personale " -"direttamente dal tuo browser per goderne qui." +msgstr "Carica file musicali (mp3, ogg, flac, ecc.) dalla tua libreria personale direttamente dal tuo browser per goderne qui." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Carica nuove tracce" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Quota di caricamento" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Caricamento scaduto, per favore riprova" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" +"Il caricamento è stato saltato perchè uno simile è già disponibile in una " +"delle tue librerie." + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "Il caricamento è stato processato con successo dal server." + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Caricato" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "Caricamento" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "Caricamento…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "Caricamenti" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Caricamenti" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" -msgstr "Caricati" +msgstr "Caricamenti" + +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" +"Usa \"urn:ietf:wg:oauth:2.0:oob\" come URI di reindirizzamento se la tua " +"applicazione non è servita sul web." #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Usa un'altra istanza" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Usa questo modulo per richiedere un reset della password. Ti invieremo una email all'indirizzo fornito con le istruzioni per resettare la tua password." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Usa questa impostazione per abilitare/disabilitare temporaneamente la regola " -"senza rimuoverla completamente." +msgstr "Usa questa impostazione per abilitare/disabilitare temporaneamente la regola senza rimuoverla completamente." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Usati" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Utente" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Attività utente" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +msgctxt "Content/*/Title/Noun" msgid "User libraries" -msgstr "Librerie utente" +msgstr "Librerie dell'utente" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Radio dell'utente" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nome utente" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nome utente o email" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "utenti" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Utenti" #: front/src/components/Footer.vue:29 +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "Utilizzando Funkwhale" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Versione (%{version})" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Vedi files" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "Vedi nell'amministrazione di Django" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Vedi su MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Visibilità " -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Visibilità : tutti su questa istanza" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Visibilità : tutti, incluse altre istanze" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Visibilità : nessuno tranne me" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilità " -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Volume %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Non possiamo aggiungere la traccia alla lista di riproduzione" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Non possiamo creare la lista di riproduzione" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Non possiamo creare il tuo account" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Non possiamo caricare questa traccia" +#: front/src/components/federation/FetchButton.vue:69 +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Aspettando i risultati…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Non riusciamo a farti accedere" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Non possiamo salvare il tuo avatar" +#: front/src/components/auth/ApplicationForm.vue:3 +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Non è stato possibile salvare le tue modifiche" -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Non possiamo salvare le tue impostazioni" - -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Noi non ti tracciamo o infastidiamo con pubblicità " -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Non abbiamo nessuna informazione sul copyright per questa traccia" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Non abbiamo nessuna informazione sulla licenza di questa traccia" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Ti consigliamo di utilizzare Picard per quello scopo." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Noi pensiamo che ascoltare musica debba essere semplice." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Ci dispiace, la pagina che hai richiesto non esiste:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Benvenuto" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Benvenuto su Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Perchè Funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Altezza del widget" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "Larghezza del widget" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "Scrivi" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "Sola-scrittura" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "Accesso in sola-scrittura ai dati utente" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Si" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Si, disconnettimi!" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" -"Sarai in grado di condividere la tua libreria con altre persone, " -"indipendentemente dalla sua visibilità ." +msgstr "Sarai in grado di condividere la tua libreria con altre persone, indipendentemente dalla sua visibilità ." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Stai per caricare della musica nella tua libreria. Prima di procedere, per favore assicurati che:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" +"Attualmente sei connesso a <a href=\"%{ url }\" target=\"_blank\">%{ " +"hostname } <i class=\"external icon\"/></a>. Se continui, sarai " +"disconnesso dalla tua istanza attuale ed i tuoi dati locali saranno " +"eliminati." + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "Stai attualmente nascondendo i contenuti di questo artista." + #: front/src/components/auth/Logout.vue:7 +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Sei attualmente connesso come %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" +"Non stai caricando contenuti protetti da diritti d'autore in una libreria " +"pubblica, altrimenti potresti infrangere la legge" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "Ora stai utilizzando l'istanza Funkwhale su %{ url }" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Puoi seguire librerie di altri utenti per avere accesso a nuova musica. Le librerie pubbliche possono essere seguite immediatamente, mentre seguire le librerie private richiede l'approvazione del suo proprietario." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Puoi invitare amici e familiari sulla tua istanza così possono fruire della tua musica" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" +"Puoi gestire ed aggiornare i tuoi filtri in qualsiasi momento dalle " +"impostazioni del tuo account." + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Ora puoi usare il servizio senza limitazioni." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Puoi usare questa interfaccia per creare la tua radio personalizzata, che riprodurrà tracce in accordo con i tuoi criteri." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Puoi usarli per godere delle tue liste di riproduzione e musica anche quando non collegato, dal tuo cellulare o tablet, per esempio." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Non hai nessuna applicazione connessa al tuo account." + +#: front/src/components/auth/Settings.vue:261 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Non hai ancora nessuna applicazione configurata." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Non hai nessuna regola attiva per questo account." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Non hai nessuna regola attiva per questo dominio." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "" +"Non hai i permessi per modificare questo oggetto, ma puoi suggerire " +"modifiche. Una volta inviati, i suggerimenti saranno revisionati prima " +"dell'approvazione." + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Hai una radio in riproduzione" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "Potresti avere un problema di connettività ." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Devi selezionare un'istanza per continuare" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Sarai disconnesso da questa sessione e dovrai accedere con una nuova" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "Sarai reindirizzato su <strong>%{ url }</strong>" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "Ti sarà mostrato un codice da copiare-incollare nell'applicazione." + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Dovrai aggiornare la tua password sui tuoi dispositivi che utilizzano questa password." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" +"Non vedrai tracce, album e attività utente collegate a questo artista in " +"futuro:" + +#: front/src/components/auth/Signup.vue:13 +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Il tuo account non può essere creato." + +#: front/src/components/auth/Settings.vue:215 +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Le tue applicazioni" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "La tua immagine di profilo non può essere salvata" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "La tua modifica è stata inviata correttamente." + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "I Tuoi Preferiti" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "La tua musica, il tuo modo di essere" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Le tue notifiche" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "La tua password non può essere cambiata" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "La tua password è stata aggiornata con successo." +#: front/src/components/auth/Settings.vue:14 +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Le tue impostazioni non possono essere aggiornate" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "La tua password Subsonic sarà cambiata con una nuova e casuale, e sarai disconnesso dai dispositivi che utilizzano ancora la vecchia password Subsonic" + +#: front/src/edits.js:47 +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Posizione" + +#: front/src/edits.js:54 +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Diritti d'autore" + +#: front/src/components/library/AlbumBase.vue:183 +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "" +"Album contenente %{ count } traccia, di <a class=\"internal\" href=\"%{ " +"artistUrl }\">%{ artist }</a>" +msgstr[1] "" +"Album contenente %{ count } tracce, di <a class=\"internal\" href=\"%{ " +"artistUrl }\">%{ artist }</a>" + +#: front/src/components/audio/PlayButton.vue:220 +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } traccia è stata aggiunta alla tua coda" +msgstr[1] "%{ count } tracce sono state aggiunte alla tua coda" diff --git a/front/locales/nl/LC_MESSAGES/app.po b/front/locales/nl/LC_MESSAGES/app.po index 76df472324b24f952da00248660ffcf2654ef307..4ea82bf516104808c853db63ebbf93ceba9d516c 100644 --- a/front/locales/nl/LC_MESSAGES/app.po +++ b/front/locales/nl/LC_MESSAGES/app.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: front 0.1.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-10-05 20:10+0200\n" -"PO-Revision-Date: 2019-01-18 11:50+0000\n" -"Last-Translator: Vierkantor <vierkantor@vierkantor.com>\n" +"PO-Revision-Date: 2019-04-08 08:01+0000\n" +"Last-Translator: Koen <koen.glotzbach@gmail.com>\n" "Language-Team: none\n" "Language: nl\n" "MIME-Version: 1.0\n" @@ -64,7 +64,7 @@ msgstr[1] "%{ count } nummers" #: front/src/views/content/libraries/Quota.vue:11 msgid "%{ current } used on %{ max } allowed" -msgstr "%{ current } in gebruik van %{ max } toegestaan" +msgstr "%{ current } in gebruik; maximaal %{ max } toegestaan" #: front/src/components/common/Duration.vue:2 msgid "%{ hours } h %{ minutes } min" @@ -80,12 +80,12 @@ msgstr "%{ user } heeft een nummer als favoriet gemarkeerd" #: front/src/components/activity/Listen.vue:7 msgid "%{ user } listened to a track" -msgstr "%{ user } heeft een nummer geluisterd" +msgstr "%{ user } heeft een nummer beluisterd" #: front/src/components/audio/artist/Card.vue:41 msgid "1 album" msgid_plural "%{ count } albums" -msgstr[0] "%{ count } album" +msgstr[0] "1 album" msgstr[1] "%{ count } albums" #: front/src/components/favorites/List.vue:10 @@ -104,7 +104,7 @@ msgstr "Over Funkwhale" #: front/src/App.vue:34 src/components/About.vue:8 src/components/About.vue:55 msgid "About this instance" -msgstr "Over deze instantiëring" +msgstr "Over deze instantie" #: front/src/views/content/libraries/Detail.vue:48 msgid "Accept" @@ -117,8 +117,8 @@ msgstr "Geaccepteerd" #: front/src/components/Home.vue:106 msgid "Access your music from a clean interface that focus on what really matters" msgstr "" -"Toegang tot je muziek met een nette interface die de nadruk legt op wat echt " -"belangrijk is" +"Toegang tot je muziek, met een nette gebruikersomgeving die de nadruk legt " +"op wat écht belangrijk is" #: front/src/views/admin/users/UsersDetail.vue:54 msgid "Account active" @@ -130,11 +130,11 @@ msgstr "Accountinstellingen" #: front/src/components/manage/users/UsersTable.vue:39 msgid "Account status" -msgstr "Accounttoestand" +msgstr "Accountstatus" #: front/src/views/auth/PasswordReset.vue:14 msgid "Account's email" -msgstr "E-mail van account" +msgstr "E-mailadres van account" #: front/src/views/content/libraries/Detail.vue:29 msgid "Action" @@ -143,8 +143,8 @@ msgstr "Actie" #: front/src/components/common/ActionTable.vue:86 msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" -msgstr[0] "Actie %{ action } was succesvol begonnen voor %{ count } element" -msgstr[1] "Actie %{ action } was succesvol begonnen voor %{ count } elementen" +msgstr[0] "Actie %{ action } is begonnen voor %{ count } element" +msgstr[1] "Actie %{ action } is begonnen voor %{ count } elementen" #: front/src/components/common/ActionTable.vue:8 #: front/src/components/library/radios/Builder.vue:64 @@ -169,7 +169,7 @@ msgstr "Filter toevoegen" #: front/src/components/library/radios/Builder.vue:40 msgid "Add filters to customize your radio" -msgstr "Voeg filters toe om jouw radio te personaliseren" +msgstr "Voeg filters toe om je radio te personaliseren" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:21 @@ -191,7 +191,7 @@ msgstr "Nummer toevoegen" #: front/src/components/manage/users/UsersTable.vue:69 msgid "Admin" -msgstr "Administrator" +msgstr "Beheerder" #: front/src/components/Sidebar.vue:82 msgid "Administration" @@ -213,7 +213,7 @@ msgstr[1] "Album met %{ count } nummers, van %{ artist }" #: front/src/components/library/Track.vue:20 msgid "Album page" -msgstr "Pagina van album" +msgstr "Albumpagina" #: front/src/components/audio/Search.vue:19 src/components/instance/Stats.vue:48 #: front/src/components/requests/Form.vue:9 @@ -231,12 +231,12 @@ msgstr "Alles" #: front/src/components/playlists/Editor.vue:13 msgid "An error occured while saving your changes" -msgstr "Een fout is opgetreden bij het opslaan van de veranderingen" +msgstr "Er is een fout opgetreden tijdens het opslaan van de wijzigingen" #: front/src/components/auth/Login.vue:10 msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "" -"Een onbekende fout is opgetreden. Mogelijk staat de server uit of is " +"Er is een onbekende fout opgetreden: mogelijk staat de server uit of is " "onbereikbaar" #: front/src/components/auth/Logout.vue:5 @@ -253,11 +253,11 @@ msgstr "Artiest" #: front/src/components/requests/Form.vue:5 src/components/mixins/Translations.vue:24 msgid "Artist name" -msgstr "Naam artiest" +msgstr "Artiestnaam" #: front/src/components/library/Album.vue:22 src/components/library/Track.vue:23 msgid "Artist page" -msgstr "Pagina van artiest" +msgstr "Artiestpagina" #: front/src/components/audio/Search.vue:10 src/components/instance/Stats.vue:42 #: front/src/components/library/Library.vue:7 src/components/library/Artists.vue:120 @@ -275,46 +275,46 @@ msgstr "Oplopend" #: front/src/views/auth/PasswordReset.vue:27 msgid "Ask for a password reset" -msgstr "Wachtwoordreset aanvragen" +msgstr "Wachtwoordherstel aanvragen" #: front/src/components/playlists/PlaylistModal.vue:26 msgid "Available playlists" -msgstr "Beschikbare playlists" +msgstr "Beschikbare afspeellijsten" #: front/src/components/auth/Settings.vue:34 msgid "Avatar" -msgstr "Avatar" +msgstr "Gebruikersafbeelding" #: front/src/views/auth/EmailConfirm.vue:17 src/views/auth/PasswordReset.vue:24 #: front/src/views/auth/PasswordResetConfirm.vue:18 msgid "Back to login" -msgstr "Terug naar inloggen" +msgstr "Terug naar inlogpagina" #: front/src/components/library/Track.vue:80 #: front/src/components/manage/library/FilesTable.vue:42 #: front/src/components/mixins/Translations.vue:28 msgid "Bitrate" -msgstr "Bitrate" +msgstr "Bitsnelheid" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 msgid "Browse" -msgstr "Doorzoeken" +msgstr "Bladeren" #: front/src/components/Sidebar.vue:65 msgid "Browse library" -msgstr "Bibliotheek doorzoeken" +msgstr "Verzameling doorbladeren" #: front/src/components/library/Artists.vue:4 msgid "Browsing artists" -msgstr "Artiesten doorzoeken" +msgstr "Artiesten doorbladeren" #: front/src/views/playlists/List.vue:3 msgid "Browsing playlists" -msgstr "Afspeellijsten doorzoeken" +msgstr "Afspeellijsten doorbladeren" #: front/src/components/library/Radios.vue:4 msgid "Browsing radios" -msgstr "Radio's doorzoeken" +msgstr "Radio's doorbladeren" #: front/src/components/library/radios/Builder.vue:5 msgid "Builder" @@ -327,7 +327,7 @@ msgstr "Van %{ artist }" #: front/src/views/content/remote/Card.vue:103 msgid "By unfollowing this library, you will loose access to its content." msgstr "" -"Als je de bibliotheek niet meer volgt, verlies je toegang tot alle inhoud." +"Als je deze verzameling ontvolgt, dan verlies je toegang tot alle inhoud." #: front/src/components/common/DangerousButton.vue:17 #: front/src/components/library/radios/Filter.vue:53 @@ -341,7 +341,7 @@ msgstr "Mogelijkheden" #: front/src/components/auth/Settings.vue:76 msgid "Cannot change your password" -msgstr "Je wachtwoord kon niet aangepast worden" +msgstr "Kan wachtwoord niet wijzigen" #: front/src/App.vue:65 msgid "Change language" @@ -349,42 +349,42 @@ msgstr "Taal kiezen" #: front/src/components/auth/Settings.vue:67 msgid "Change my password" -msgstr "Wachtwoord aanpassen" +msgstr "Wachtwoord wijzigen" #: front/src/components/auth/Settings.vue:95 msgid "Change password" -msgstr "Wachtwoord aanpassen" +msgstr "Wachtwoord wijzigen" #: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 msgid "Change your password" -msgstr "Wachtwoord aanpassen" +msgstr "Wachtwoord wijzigen" #: front/src/components/auth/Settings.vue:96 msgid "Change your password?" -msgstr "Jouw wachtwoord aanpassen?" +msgstr "Wil je je wachtwoord wijzigen?" #: front/src/components/playlists/Editor.vue:21 msgid "Changes synced with server" -msgstr "Wijzigingen opgeslagen op de server" +msgstr "Wijzigingen opgeslagen op server" #: front/src/components/auth/Settings.vue:70 msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "" -"Als je je wachtwoord aanpast, wordt ook het wachtwoord voor de Subsonic-API " -"aangepast als je er een ingesteld had." +"Als je je wachtwoord wijzigt, dan wordt ook het wachtwoord voor de Subsonic-" +"API gewijzigd als je deze had ingesteld." #: front/src/components/auth/Settings.vue:98 msgid "Changing your password will have the following consequences" -msgstr "Het aanpassen van je wachtwoord heeft deze gevolgen" +msgstr "Het aanpassen van je wachtwoord heeft gevolgen" #: front/src/App.vue:6 msgid "Choose your instance" -msgstr "Kies je instantiëring" +msgstr "Kies je instantie" #: front/src/components/Home.vue:64 msgid "Clean library" -msgstr "Een nette bibliotheek" +msgstr "Een nette verzameling" #: front/src/components/manage/users/InvitationForm.vue:37 msgid "Clear" @@ -397,7 +397,7 @@ msgstr "Afspeellijst wissen" #: front/src/components/Home.vue:44 msgid "Click once, listen for hours using built-in radios" -msgstr "Met een druk op de knop urenlang muziek uit ingebouwde radio's" +msgstr "Met één druk op de knop urenlang muziek uit ingebouwde radiostations" #: front/src/components/library/FileUpload.vue:76 msgid "Click to select files to upload or drag and drop files or directories" @@ -441,56 +441,56 @@ msgstr "Kopiëren" #: front/src/components/Home.vue:85 msgid "Covers, lyrics, our goal is to have them all ;)" -msgstr "Covers, songteksten, ons doel is om ze allemaal te hebben ;)" +msgstr "Hoezen, songteksten - ons doel is om ze allemaal te hebben ;)" #: front/src/components/auth/Signup.vue:4 msgid "Create a funkwhale account" -msgstr "Maak een account voor funkwhale" +msgstr "Creëer een Funkwhale-account" #: front/src/views/content/libraries/Home.vue:14 msgid "Create a new library" -msgstr "Nieuwe bibliotheek maken" +msgstr "Creëer een nieuwe verzameling" #: front/src/components/playlists/Form.vue:2 msgid "Create a new playlist" -msgstr "Nieuwe afspeellijst maken" +msgstr "Creëer een nieuwe afspeellijst" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 msgid "Create an account" -msgstr "Account aanmaken" +msgstr "Creëer een account" #: front/src/components/requests/Card.vue:25 msgid "Create import" -msgstr "Importering aanmaken" +msgstr "Importeren" #: front/src/views/content/libraries/Form.vue:26 msgid "Create library" -msgstr "Bibliotheek aanmaken" +msgstr "Verzameling creëren" #: front/src/components/auth/Signup.vue:51 msgid "Create my account" -msgstr "Account aanmaken" +msgstr "Account creëren" #: front/src/components/playlists/Form.vue:34 msgid "Create playlist" -msgstr "Afspeellijst aanmaken" +msgstr "Afspeellijst creëren" #: front/src/components/library/Radios.vue:23 msgid "Create your own radio" -msgstr "Je eigen radio aanmaken" +msgstr "Creëer je eigen radiostation" #: front/src/components/manage/users/InvitationsTable.vue:40 #: front/src/components/mixins/Translations.vue:17 msgid "Creation date" -msgstr "Aanmaakdatum" +msgstr "Gecreëerd op" #: front/src/components/auth/Settings.vue:54 msgid "Current avatar" -msgstr "Huidige avatar" +msgstr "Huidige gebruikersafbeelding" #: front/src/views/content/libraries/DetailArea.vue:4 msgid "Current library" -msgstr "Huidige bibliotheek" +msgstr "Huidige verzameling" #: front/src/components/playlists/PlaylistModal.vue:8 msgid "Current track" @@ -498,7 +498,7 @@ msgstr "Huidig nummer" #: front/src/views/content/libraries/Quota.vue:2 msgid "Current usage" -msgstr "Huidig gebruik" +msgstr "Huidig verbruik" #: front/src/views/content/libraries/Detail.vue:27 msgid "Date" @@ -513,7 +513,7 @@ msgstr "Verwijderen" #: front/src/views/content/libraries/Form.vue:39 msgid "Delete library" -msgstr "Bibliotheek verwijderen" +msgstr "Verzameling verwijderen" #: front/src/views/playlists/Detail.vue:38 msgid "Delete playlist" @@ -521,11 +521,11 @@ msgstr "Afspeellijst verwijderen" #: front/src/views/radios/Detail.vue:28 msgid "Delete radio" -msgstr "Radio verwijderen" +msgstr "Radiostation verwijderen" #: front/src/views/content/libraries/Form.vue:31 msgid "Delete this library?" -msgstr "Deze bibliotheek verwijderen?" +msgstr "Wil je deze verzameling verwijderen?" #: front/src/components/favorites/List.vue:34 src/components/library/Artists.vue:26 #: front/src/components/library/Radios.vue:47 @@ -556,11 +556,11 @@ msgstr "Toegang uitschakelen" #: front/src/components/auth/SubsonicTokenForm.vue:49 msgid "Disable Subsonic access" -msgstr "Toegang met Subsonic uitschakelen" +msgstr "Subsonic-toegang uitschakelen" #: front/src/components/auth/SubsonicTokenForm.vue:50 msgid "Disable Subsonic API access?" -msgstr "Toegang van de Subsonic-API uitzetten?" +msgstr "Subsonic-API-toegang uitschakelen?" #: front/src/components/auth/SubsonicTokenForm.vue:14 msgid "Discover how to use Funkwhale from other apps" @@ -568,23 +568,24 @@ msgstr "Ontdek hoe je Funkwhale met andere apps kunt gebruiken" #: front/src/components/library/radios/Builder.vue:30 msgid "Display publicly" -msgstr "Openbaar tonen" +msgstr "Openbaren" #: front/src/components/playlists/Editor.vue:42 msgid "Do you want to clear the playlist \"%{ playlist }\"?" -msgstr "Wil je de afspeellijst \"%{ playlist }\" echt wissen?" +msgstr "Weet je zeker dat je de afspeellijst \"%{ playlist }\" wilt wissen?" #: front/src/components/common/DangerousButton.vue:7 msgid "Do you want to confirm this action?" -msgstr "Actie bevestigen?" +msgstr "Wil je de actie bevestigen?" #: front/src/views/playlists/Detail.vue:35 msgid "Do you want to delete the playlist \"%{ playlist }\"?" -msgstr "Wil je echt de afspeellijst \"%{ playlist }\" verwijderen?" +msgstr "" +"Weet je zeker dat je de afspeellijst \"%{ playlist }\" wilt verwijderen?" #: front/src/views/radios/Detail.vue:26 msgid "Do you want to delete the radio \"%{ radio }\"?" -msgstr "Wil je echt de radio \"%{ radio }\" verwijderen?" +msgstr "Weet je zeker dat je het radiostation \"%{ radio }\" wilt verwijderen?" #: front/src/components/common/ActionTable.vue:29 msgid "Do you want to launch %{ action } on %{ count } element?" @@ -594,7 +595,7 @@ msgstr[1] "Wil je %{ action } toepassen op %{ count } elementen?" #: front/src/components/Sidebar.vue:104 msgid "Do you want to restore your previous queue?" -msgstr "Wil je de vorige wachtrij terugzetten?" +msgstr "Wil je de vorige wachtrij herstellen?" #: front/src/App.vue:37 msgid "Documentation" @@ -606,14 +607,14 @@ msgstr "Downloaden" #: front/src/components/playlists/Editor.vue:49 msgid "Drag and drop rows to reorder tracks in the playlist" -msgstr "Om de afspeellijst te herordenen, kun je de regels verslepen" +msgstr "Versleep de rijen om nummers op de afspeellijst te herordenen" #: front/src/components/audio/track/Table.vue:9 src/components/library/Track.vue:58 #: front/src/components/manage/library/FilesTable.vue:43 #: front/src/views/content/libraries/FilesTable.vue:56 #: front/src/components/mixins/Translations.vue:29 msgid "Duration" -msgstr "Speelduur" +msgstr "Duur" #: front/src/components/Home.vue:93 msgid "Easy to use" @@ -621,15 +622,15 @@ msgstr "Makkelijk te gebruiken" #: front/src/views/content/libraries/Detail.vue:9 msgid "Edit" -msgstr "Wijzigen" +msgstr "Bewerken" #: front/src/components/About.vue:21 msgid "Edit instance info" -msgstr "Info van instantiëring wijzigen" +msgstr "Instantie-informatie bewerken" #: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 msgid "Edit..." -msgstr "Wijzigen…" +msgstr "Bewerken…" #: front/src/components/auth/Signup.vue:29 #: front/src/components/manage/users/UsersTable.vue:38 @@ -646,7 +647,7 @@ msgstr "E-mailadres bevestigd" #: front/src/views/playlists/Detail.vue:29 msgid "End edition" -msgstr "Wijzigingen afronden" +msgstr "Bewerken afronden" #: front/src/components/auth/SubsonicTokenForm.vue:20 #: front/src/views/content/libraries/Form.vue:4 @@ -659,48 +660,48 @@ msgstr "Fout tijdens scannen" #: front/src/components/common/ActionTable.vue:79 msgid "Error while applying action" -msgstr "Fout bij het toepassen van actie" +msgstr "Fout tijdens toepassen van actie" #: front/src/views/auth/PasswordReset.vue:7 msgid "Error while asking for a password reset" -msgstr "Fout bij het aanvragen van wachtwoordvervanging" +msgstr "Fout tijdens aanvragen van wachtwoordherstel" #: front/src/views/auth/PasswordResetConfirm.vue:7 msgid "Error while changing your password" -msgstr "Fout bij het aanpassen van je wachtwoord" +msgstr "Fout tijdens wijzigen van wachtwoord" #: front/src/views/auth/EmailConfirm.vue:7 msgid "Error while confirming your email" -msgstr "Fout bij het bevestigen van je e-mailadres" +msgstr "Fout tijdens bevestigen van e-mailadres" #: front/src/components/manage/users/InvitationForm.vue:4 msgid "Error while creating invitation" -msgstr "Fout bij het aanmaken van de uitnodiging" +msgstr "Fout tijdens creëren van uitnodiging" #: front/src/views/content/remote/ScanForm.vue:3 msgid "Error while fetching remote library" -msgstr "Fout bij het ophalen van de verre bibliotheek" +msgstr "Fout tijdens ophalen van externe verzameling" #: front/src/components/admin/SettingsGroup.vue:5 msgid "Error while saving settings" -msgstr "Fout bij het opslaan van de instellingen" +msgstr "Fout tijdens opslaan van instellingen" #: front/src/views/content/libraries/FilesTable.vue:16 #: front/src/views/content/libraries/FilesTable.vue:237 msgid "Errored" -msgstr "Gefaald" +msgstr "Mislukt" #: front/src/views/content/libraries/Quota.vue:75 msgid "Errored files" -msgstr "Gefaalde bestanden" +msgstr "Mislukte bestanden" #: front/src/views/content/remote/Card.vue:58 msgid "Errored tracks:" -msgstr "Gefaalde nummers:" +msgstr "Mislukte nummers:" #: front/src/components/library/radios/Builder.vue:61 msgid "Exclude" -msgstr "Weglaten" +msgstr "Uitsluiten" #: front/src/components/discussion/Comment.vue:14 msgid "Expand" @@ -738,7 +739,7 @@ msgstr "Filternaam" #: front/src/views/content/libraries/FilesTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:241 msgid "Finished" -msgstr "Klaar" +msgstr "Afgerond" #: front/src/views/content/remote/Card.vue:83 msgid "Follow" @@ -746,11 +747,11 @@ msgstr "Volgen" #: front/src/views/content/remote/Card.vue:88 msgid "Follow pending approval" -msgstr "Volgen na toestemming" +msgstr "Wachten op toestemming" #: front/src/views/content/Home.vue:16 msgid "Follow remote libraries" -msgstr "Verre bibliotheken volgen" +msgstr "Externe verzamelingen volgen" #: front/src/views/content/libraries/Detail.vue:7 msgid "Followers" @@ -758,22 +759,22 @@ msgstr "Volgers" #: front/src/views/content/remote/Card.vue:93 msgid "Following" -msgstr "Aan het volgen" +msgstr "Volgend" #: front/src/components/activity/Like.vue:14 src/components/activity/Listen.vue:14 msgid "from %{ album } by %{ artist }" -msgstr "uit %{ album } door %{ artist }" +msgstr "van %{ album } door %{ artist }" #: front/src/components/library/Track.vue:13 msgid "From album %{ album } by %{ artist }" -msgstr "Uit het album %{ album } van %{ artist }" +msgstr "Van het album %{ album } van %{ artist }" #: front/src/App.vue:55 msgid "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!" msgstr "" -"Funkwhale is vrije, open-source software gemaakt door vrijwilligers. Je kan " -"ons helpen dit platform te verbeteren door bugs te melden, features voor te " -"stellen en het project met je vrienden te delen!" +"Funkwhale is vrije, opensourcesoftware, gemaakt door vrijwilligers. Je kunt " +"ons helpen dit platform te verbeteren door bugs te melden, functies voor te " +"stellen en het project te delen met je vrienden!" #: front/src/components/auth/SubsonicTokenForm.vue:7 msgid "Funkwhale is compatible with other music players that support the Subsonic API." @@ -787,16 +788,16 @@ msgstr "Funkwhale is zeer eenvoudig te gebruiken." #: front/src/components/Home.vue:39 msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "" -"Funkwhale is ontworpen om het makkelijk te maken naar jouw favoriete muziek " -"te luisteren, of nieuwe artiesten te ontdekken." +"Funkwhale is ontworpen om het je makkelijk te maken je favoriete muziek te " +"beluisteren of nieuwe artiesten te ontdekken." #: front/src/components/Home.vue:116 msgid "Funkwhale is free and gives you control on your music." -msgstr "Funkwhale is gratis en geeft jou het beheer over jouw muziek." +msgstr "Funkwhale is gratis en geeft je het beheer over je muziek." #: front/src/components/Home.vue:66 msgid "Funkwhale takes care of handling your music" -msgstr "Funkwhale zorgt voor jouw muziek" +msgstr "Funkwhale zorgt voor je muziek" #: front/src/components/manage/users/InvitationForm.vue:16 msgid "Get a new invitation" @@ -804,13 +805,13 @@ msgstr "Nieuwe uitnodiging aanvragen" #: front/src/components/Home.vue:13 msgid "Get me to the library" -msgstr "Breng me naar de bibliotheek" +msgstr "Breng me naar de verzameling" #: front/src/components/Home.vue:76 msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "" -"Verkrijg hoogwaardige metadata over je muziek met behulp van <a href=\"%{ " -"url }\" target=\"_blank\">MusicBrainz</a>" +"Verkrijg hoogwaardige metagegevens over je muziek met behulp van <a href=\"%{" +" url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 msgid "Get started" @@ -819,7 +820,7 @@ msgstr "Aan de slag" #: front/src/components/common/ActionTable.vue:21 #: front/src/components/common/ActionTable.vue:27 msgid "Go" -msgstr "Doen!" +msgstr "Ga" #: front/src/components/PageNotFound.vue:14 msgid "Go to home page" @@ -836,15 +837,15 @@ msgstr "Uren aan muziek" #: front/src/components/auth/SubsonicTokenForm.vue:11 msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "" -"Om Funkwhale te benaderen van die apps is een los wachtwoord nodig. Dit kan " -"je hieronder instellen." +"Om Funkwhale te benaderen via die apps is een apart wachtwoord nodig. Dit " +"kan je hieronder instellen." #: front/src/views/auth/PasswordResetConfirm.vue:24 msgid "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." msgstr "" -"Als het e-mailadres uit de vorige stap geldig is en hoort bij een gebruiker, " -"dan zou je een e-mail moeten ontvangen met instructies voor het vervangen " -"van je wachtwoord." +"Als het e-mailadres uit de vorige stap geldig is en toebehoort aan een " +"gebruiker, dan zou je een e-mail moeten ontvangen met instructies voor " +"wachtwoordherstel." #: front/src/components/manage/library/FilesTable.vue:40 msgid "Import date" @@ -852,20 +853,20 @@ msgstr "Importdatum" #: front/src/components/Home.vue:71 msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importeer muziek uit meerdere platformen zoals YouTube of SoundCloud" +msgstr "Importeer muziek uit meerdere platformen, zoals YouTube of SoundCloud" #: front/src/components/library/FileUpload.vue:51 msgid "Import reference" -msgstr "Importeringsnummer" +msgstr "Importnummer" #: front/src/views/content/libraries/FilesTable.vue:11 #: front/src/views/content/libraries/FilesTable.vue:55 msgid "Import status" -msgstr "Importeringstoestand" +msgstr "Importstatus" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 msgid "In favorites" -msgstr "Tussen favorieten" +msgstr "In je favorieten" #: front/src/components/manage/users/UsersTable.vue:54 msgid "Inactive" @@ -879,7 +880,7 @@ msgstr[1] "Aan de wachtrij toevoegen (%{ count } nummers)" #: front/src/components/library/Radios.vue:9 msgid "Instance radios" -msgstr "Radio's van de instantiëring" +msgstr "Radio's van de instantie" #: front/src/components/auth/Signup.vue:42 #: front/src/components/manage/users/InvitationForm.vue:11 @@ -902,8 +903,8 @@ msgstr "Bugtracker" #: front/src/views/content/libraries/Home.vue:9 msgid "It looks like you don't have any library yet, it's time to create one!" msgstr "" -"Het lijkt erop dat je nog geen bibliotheek hebt. Het is tijd om er een aan " -"te maken!" +"Het lijkt erop dat je nog geen verzameling hebt; de hoogste tijd om er één " +"te creëren!" #: front/src/components/Home.vue:50 msgid "Keep a track of your favorite songs" @@ -911,7 +912,7 @@ msgstr "Beheer je lievelingsmuziek" #: front/src/views/content/remote/Home.vue:14 msgid "Known libraries" -msgstr "Bekende bibliotheken" +msgstr "Bekende verzamelingen" #: front/src/components/manage/users/UsersTable.vue:41 #: front/src/views/admin/users/UsersDetail.vue:45 @@ -933,11 +934,11 @@ msgstr "Opstarten" #: front/src/views/content/remote/Card.vue:63 msgid "Launch scan" -msgstr "Begin scan" +msgstr "Scannen" #: front/src/components/Home.vue:10 msgid "Learn more about this instance" -msgstr "Meer over deze instantiëring ontdekken" +msgstr "Meer informatie over deze instantie" #: front/src/components/requests/Form.vue:10 msgid "Leave this field empty if you're requesting the whole discography." @@ -945,24 +946,24 @@ msgstr "Laat dit veld leeg als je de hele discografie wilt opvragen." #: front/src/views/content/Base.vue:5 msgid "Libraries" -msgstr "Bibliotheken" +msgstr "Verzamelingen" #: front/src/views/content/libraries/Form.vue:2 msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "" -"Bibliotheken helpen je met het organiseren en delen van je " -"muziekverzamelingen. Je kan je eigen muziek uploaden naar Funkwhale en delen " -"met je vrienden en familie." +"Verzamelingen helpen je bij het organiseren en delen van je " +"muziekverzamelingen. Je kunt je eigen muziek uploaden naar Funkwhale en " +"delen met vrienden en familie." #: front/src/components/instance/Stats.vue:30 #: front/src/components/manage/users/UsersTable.vue:177 #: front/src/views/admin/users/UsersDetail.vue:181 msgid "Library" -msgstr "Bibliotheek" +msgstr "Verzameling" #: front/src/views/admin/library/FilesList.vue:3 msgid "Library files" -msgstr "Bibliotheekbestanden" +msgstr "Verzamelingsbestanden" #: front/src/App.vue:31 msgid "Links" @@ -970,36 +971,36 @@ msgstr "Links" #: front/src/views/content/libraries/Detail.vue:21 msgid "Loading followers..." -msgstr "Volgers aan het laden..." +msgstr "Bezig met laden van volgers..." #: front/src/views/content/libraries/Home.vue:3 msgid "Loading Libraries..." -msgstr "Bibliotheken aan het laden…" +msgstr "Bezig met laden van verzamelingen..." #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 msgid "Loading library data..." -msgstr "Bibliotheekdata aan het laden…" +msgstr "Bezig met laden van verzamelingsgegevens..." #: front/src/views/Notifications.vue:4 msgid "Loading notifications..." -msgstr "Meldingen aan het laden…" +msgstr "Bezig met laden van meldingen..." #: front/src/views/content/remote/Home.vue:3 msgid "Loading remote libraries..." -msgstr "Verre bibliotheken aan het laden…" +msgstr "Bezig met laden van externe verzamelingen..." #: front/src/views/instance/Timeline.vue:4 msgid "Loading timeline..." -msgstr "Tijdlijn aan het laden…" +msgstr "Bezig met laden van tijdlijn..." #: front/src/views/content/libraries/Quota.vue:4 msgid "Loading usage data..." -msgstr "Verbruiksdata aan het laden…" +msgstr "Bezig met laden van verbruiksgegevens..." #: front/src/components/favorites/List.vue:5 msgid "Loading your favorites..." -msgstr "Jouw favorieten aan het laden…" +msgstr "Bezig met laden van je favorieten..." #: front/src/components/auth/Login.vue:4 msgid "Log in to your Funkwhale account" @@ -1027,11 +1028,11 @@ msgstr "Afspeellijsten beheren" #: front/src/views/playlists/List.vue:8 msgid "Manage your playlists" -msgstr "Jouw afspeellijsten beheren" +msgstr "Beheer je afspeellijsten" #: front/src/views/Notifications.vue:17 msgid "Mark all as read" -msgstr "Alles als gelezen markeren" +msgstr "Alles markeren als gelezen" #: front/src/views/admin/users/UsersDetail.vue:94 msgid "MB" @@ -1047,7 +1048,7 @@ msgstr "Mijn account" #: front/src/views/content/libraries/Home.vue:6 msgid "My libraries" -msgstr "Mijn bibliotheken" +msgstr "Mijn verzamelingen" #: front/src/components/audio/track/Row.vue:40 src/components/library/Track.vue:64 #: front/src/components/library/Track.vue:75 src/components/library/Track.vue:86 @@ -1085,7 +1086,8 @@ msgstr "Nee" #: front/src/components/Home.vue:100 msgid "No add-ons, no plugins : you only need a web library" -msgstr "Geen uitbreidingen of plugins: je hebt alleen een webbibliotheek nodig" +msgstr "" +"Geen uitbreidingen of plug-ins: je hebt alleen een online verzameling nodig" #: front/src/components/library/Track.vue:113 msgid "No lyrics available for this track." @@ -1093,11 +1095,11 @@ msgstr "Geen songtekst beschikbaar voor dit nummer." #: front/src/components/federation/LibraryWidget.vue:6 msgid "No matching library." -msgstr "Geen passende bibliotheek gevonden." +msgstr "Geen overeenkomende verzameling gevonden." #: front/src/views/content/libraries/Detail.vue:57 msgid "Nobody is following this library" -msgstr "Niemand volgt deze bibliotheek" +msgstr "Niemand volgt deze verzameling" #: front/src/components/manage/users/InvitationsTable.vue:51 msgid "Not used" @@ -1144,7 +1146,7 @@ msgstr "Sorteervolgorde" #: front/src/components/manage/users/InvitationsTable.vue:38 msgid "Owner" -msgstr "Beheerder" +msgstr "Eigenaar" #: front/src/components/PageNotFound.vue:7 msgid "Page not found!" @@ -1156,30 +1158,30 @@ msgstr "Wachtwoord" #: front/src/views/auth/PasswordResetConfirm.vue:28 msgid "Password updated successfully" -msgstr "Wachtwoord succesvol aangepast" +msgstr "Wachtwoord is gewijzigd" #: front/src/components/library/FileUpload.vue:105 #: front/src/views/content/libraries/FilesTable.vue:14 #: front/src/views/content/libraries/FilesTable.vue:233 msgid "Pending" -msgstr "Openstaand" +msgstr "In behandeling" #: front/src/views/content/libraries/Detail.vue:37 msgid "Pending approval" -msgstr "Goedkeuring staat open" +msgstr "Wachtend op goedkeuring" #: front/src/views/content/libraries/Quota.vue:22 msgid "Pending files" -msgstr "Openstaande bestanden" +msgstr "In behandeling zijnde bestanden" #: front/src/components/requests/Form.vue:26 msgid "Pending requests" -msgstr "Openstaande verzoeken" +msgstr "In behandeling zijnde verzoeken" #: front/src/components/manage/users/UsersTable.vue:42 #: front/src/views/admin/users/UsersDetail.vue:68 msgid "Permissions" -msgstr "Toestemmingen" +msgstr "Machtigingen" #: front/src/components/audio/PlayButton.vue:9 src/components/library/Track.vue:30 msgid "Play" @@ -1206,63 +1208,63 @@ msgstr "Nu afspelen" #: front/src/views/playlists/Detail.vue:12 msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Afspeellijst met %{ count } nummer, door %{ username }" +msgstr[1] "Afspeellijst met %{ count } nummers, door %{ username }" #: front/src/components/playlists/Form.vue:9 msgid "Playlist created" -msgstr "" +msgstr "Afspeellijst gecreëerd" #: front/src/components/playlists/Editor.vue:4 msgid "Playlist editor" -msgstr "" +msgstr "Afspeellijst wijzigen" #: front/src/components/playlists/Form.vue:21 msgid "Playlist name" -msgstr "" +msgstr "Afspeellijstnaam" #: front/src/components/playlists/Form.vue:6 msgid "Playlist updated" -msgstr "" +msgstr "Afspeellijst gewijzigd" #: front/src/components/playlists/Form.vue:25 msgid "Playlist visibility" -msgstr "" +msgstr "Zichtbaarheid van afspeellijst" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 #: front/src/components/library/Library.vue:13 src/views/playlists/List.vue:104 #: front/src/views/admin/Settings.vue:82 msgid "Playlists" -msgstr "" +msgstr "Afspeellijsten" #: front/src/components/Home.vue:56 msgid "Playlists? We got them" -msgstr "" +msgstr "Afspeellijsten? Die hebben we" #: front/src/components/auth/Settings.vue:79 msgid "Please double-check your password is correct" -msgstr "" +msgstr "Controleer of je wachtwoord klopt" #: front/src/components/auth/Login.vue:9 msgid "Please double-check your username/password couple is correct" -msgstr "" +msgstr "Controleer of je gebruikersnaam en wachtwoord kloppen" #: front/src/components/auth/Settings.vue:46 msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." -msgstr "" +msgstr "PNG, GIF of JPG. Maximaal 2MB. Wordt verkleind tot 400x400px." #: front/src/components/library/FileUpload.vue:58 msgid "Proceed" -msgstr "" +msgstr "Doorgaan" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 msgid "Proceed to login" -msgstr "" +msgstr "Doorgaan met inloggen" #: front/src/components/library/FileUpload.vue:17 msgid "Processing" -msgstr "" +msgstr "Bezig met verwerken..." #: front/src/views/content/libraries/Quota.vue:36 #: front/src/views/content/libraries/Quota.vue:39 @@ -1271,143 +1273,147 @@ msgstr "" #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 msgid "Purge" -msgstr "" +msgstr "Leegmaken" #: front/src/views/content/libraries/Quota.vue:89 msgid "Purge errored files?" -msgstr "" +msgstr "Foutieve bestanden verwijderen?" #: front/src/views/content/libraries/Quota.vue:37 msgid "Purge pending files?" -msgstr "" +msgstr "In behandeling zijnde bestanden verwijderen?" #: front/src/views/content/libraries/Quota.vue:63 msgid "Purge skipped files?" -msgstr "" +msgstr "Overgeslagen bestanden verwijderen?" #: front/src/components/Sidebar.vue:20 msgid "Queue" -msgstr "" +msgstr "Wachtrij" #: front/src/components/library/radios/Builder.vue:15 msgid "Radio created" -msgstr "" +msgstr "Radio gecreëerd" #: front/src/components/library/radios/Builder.vue:21 msgid "Radio name" -msgstr "" +msgstr "Radionaam" #: front/src/components/library/radios/Builder.vue:12 msgid "Radio updated" -msgstr "" +msgstr "Radio bijgewerkt" #: front/src/components/library/Library.vue:10 src/components/library/Radios.vue:142 msgid "Radios" -msgstr "" +msgstr "Radio's" #: front/src/views/instance/Timeline.vue:7 msgid "Recent activity on this instance" -msgstr "" +msgstr "Recente activiteit op deze instantie" #: front/src/components/library/Home.vue:24 msgid "Recently added" -msgstr "" +msgstr "Recent toegevoegd" #: front/src/components/library/Home.vue:11 msgid "Recently favorited" -msgstr "" +msgstr "Recent toegevoegd aan favorieten" #: front/src/components/library/Home.vue:6 msgid "Recently listened" -msgstr "" +msgstr "Recent beluisterd" #: front/src/views/admin/users/UsersDetail.vue:103 #: front/src/views/content/remote/Home.vue:15 msgid "Refresh" -msgstr "" +msgstr "Verversen" #: front/src/components/auth/Profile.vue:12 msgid "Registered since %{ date }" -msgstr "" +msgstr "Geregistreerd sinds %{ date }" #: front/src/components/auth/Signup.vue:9 msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "" +"Registraties zijn gesloten op deze instantie. Je hebt een uitnodiging nodig " +"om te registreren." #: front/src/components/manage/users/UsersTable.vue:71 msgid "regular user" -msgstr "" +msgstr "standaard gebruiker" #: front/src/views/content/libraries/Detail.vue:51 msgid "Reject" -msgstr "" +msgstr "Afkeuren" #: front/src/views/content/libraries/Detail.vue:43 msgid "Rejected" -msgstr "" +msgstr "Afgekeurd" #: front/src/views/content/remote/Home.vue:6 msgid "Remote libraries" -msgstr "" +msgstr "Externe verzamelingen" #: front/src/views/content/remote/Home.vue:7 msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "" +"Externe verzamelingen worden beheerd door andere gebruikers op het netwerk. " +"Je kunt ze gebruiken als ze openbaar zijn of als jou toegang is verleend." #: front/src/components/library/radios/Filter.vue:59 msgid "Remove" -msgstr "" +msgstr "Verwijderen" #: front/src/components/auth/Settings.vue:58 msgid "Remove avatar" -msgstr "" +msgstr "Gebruikersafbeelding verwijderen" #: front/src/components/auth/SubsonicTokenForm.vue:34 #: front/src/components/auth/SubsonicTokenForm.vue:37 msgid "Request a new password" -msgstr "" +msgstr "Nieuw wachtwoord aanvragen" #: front/src/components/auth/SubsonicTokenForm.vue:35 msgid "Request a new Subsonic API password?" -msgstr "" +msgstr "Nieuw Subsonic-API-wachtwoord aanvragen?" #: front/src/components/auth/SubsonicTokenForm.vue:43 msgid "Request a password" -msgstr "" +msgstr "Wachtwoord aanvragen" #: front/src/components/requests/Form.vue:20 msgid "Request submitted!" -msgstr "" +msgstr "Verzoek ingediend!" #: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 msgid "Reset your password" -msgstr "" +msgstr "Wachtwoord opnieuw instellen" #: front/src/components/favorites/List.vue:38 src/components/library/Artists.vue:30 #: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 msgid "Results per page" -msgstr "" +msgstr "Aantal resultaten per pagina" #: front/src/components/admin/SettingsGroup.vue:63 #: front/src/components/library/radios/Builder.vue:33 msgid "Save" -msgstr "" +msgstr "Opslaan" #: front/src/views/content/remote/Card.vue:31 msgid "Scan pending" -msgstr "" +msgstr "Nog te scannen" #: front/src/views/content/remote/Card.vue:43 msgid "Scanned successfully" -msgstr "" +msgstr "Scannen afgerond" #: front/src/views/content/remote/Card.vue:47 msgid "Scanned with errors" -msgstr "" +msgstr "Afgerond, maar met foutmeldingen" #: front/src/views/content/remote/Card.vue:35 msgid "Scanning... (%{ progress }%)" -msgstr "" +msgstr "Bezig met scannen... (% {progress }%)" #: front/src/components/library/Artists.vue:10 src/components/library/Radios.vue:29 #: front/src/components/manage/library/FilesTable.vue:5 @@ -1415,142 +1421,146 @@ msgstr "" #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 src/views/playlists/List.vue:13 msgid "Search" -msgstr "" +msgstr "Zoeken" #: front/src/views/content/remote/ScanForm.vue:9 msgid "Search a remote library" -msgstr "" +msgstr "Zoeken naar externe verzameling" #: front/src/components/audio/Search.vue:2 msgid "Search for some music" -msgstr "" +msgstr "Zoeken naar muziek" #: front/src/components/library/Track.vue:116 msgid "Search on lyrics.wikia.com" -msgstr "" +msgstr "Zoeken op lyrics.wikia.com" #: front/src/components/library/Album.vue:33 src/components/library/Artist.vue:31 #: front/src/components/library/Track.vue:40 msgid "Search on Wikipedia" -msgstr "" +msgstr "Zoeken op Wikipedia" #: front/src/views/admin/Settings.vue:15 msgid "Sections" -msgstr "" +msgstr "Secties" #: front/src/components/library/radios/Builder.vue:45 msgid "Select a filter" -msgstr "" +msgstr "Filter kiezen" #: front/src/components/common/ActionTable.vue:64 msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Selecteer %{ total } element" +msgstr[1] "Selecteer alle %{ total } elementen" #: front/src/components/common/ActionTable.vue:73 msgid "Select only current page" -msgstr "" +msgstr "Alleen deze pagina kiezen" #: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:88 #: front/src/components/manage/users/UsersTable.vue:185 #: front/src/views/admin/users/UsersDetail.vue:189 msgid "Settings" -msgstr "" +msgstr "Instellingen" #: front/src/components/auth/Settings.vue:10 msgid "Settings updated" -msgstr "" +msgstr "Instellingen bijgewerkt" #: front/src/components/admin/SettingsGroup.vue:11 msgid "Settings updated successfully." -msgstr "" +msgstr "De instellingen zijn bijgewerkt." #: front/src/components/manage/users/InvitationForm.vue:27 msgid "Share link" -msgstr "" +msgstr "Link om te delen" #: front/src/views/content/libraries/Detail.vue:15 msgid "Share this link with other users so they can request an access to your library." msgstr "" +"Deel deze link met anderen zodat ze toegang tot je verzamelingen kunnen " +"aanvragen." #: front/src/views/content/libraries/Detail.vue:14 #: front/src/views/content/remote/Card.vue:73 msgid "Sharing link" -msgstr "" +msgstr "Link om te delen" #: front/src/components/audio/album/Card.vue:40 msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Nog %{ count } nummer tonen" +msgstr[1] "Nog %{ count } nummers tonen" #: front/src/components/audio/artist/Card.vue:30 msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Nog %{ count } album tonen" +msgstr[1] "Nog %{ count } albums tonen" #: front/src/views/Notifications.vue:10 msgid "Show read notifications" -msgstr "" +msgstr "Gelezen meldingen tonen" #: front/src/components/manage/library/FilesTable.vue:97 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 #: front/src/views/content/libraries/FilesTable.vue:111 msgid "Showing results %{ start }-%{ end } on %{ total }" -msgstr "" +msgstr "Resultaten - %{ start }-%{ end } van de %{ total }" #: front/src/components/manage/users/UsersTable.vue:40 #: front/src/views/admin/users/UsersDetail.vue:37 msgid "Sign-up" -msgstr "" +msgstr "Registreren" #: front/src/components/library/FileUpload.vue:84 src/components/library/Track.vue:69 #: front/src/components/manage/library/FilesTable.vue:44 #: front/src/views/content/libraries/FilesTable.vue:57 #: front/src/components/mixins/Translations.vue:27 msgid "Size" -msgstr "" +msgstr "Grootte" #: front/src/views/content/libraries/FilesTable.vue:15 #: front/src/views/content/libraries/FilesTable.vue:229 msgid "Skipped" -msgstr "" +msgstr "Overgeslagen" #: front/src/views/content/libraries/Quota.vue:49 msgid "Skipped files" -msgstr "" +msgstr "Overgeslagen bestanden" #: front/src/components/requests/Form.vue:3 msgid "Something's missing in the library? Let us know what you would like to listen!" msgstr "" +"Ontbreekt er iets in de verzameling? Laat ons weten waar je naar wilt " +"luisteren!" #: front/src/components/audio/Search.vue:25 msgid "Sorry, we did not found any album matching your query" -msgstr "" +msgstr "Sorry, er is geen album dat overeenkomt met de zoekopdracht" #: front/src/components/audio/Search.vue:16 msgid "Sorry, we did not found any artist matching your query" -msgstr "" +msgstr "Sorry, er is geen artiest die overeenkomt met de zoekopdracht" #: front/src/App.vue:40 msgid "Source code" -msgstr "" +msgstr "Broncode" #: front/src/App.vue:39 msgid "Source code (%{version})" -msgstr "" +msgstr "Broncode (%{ version })" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 msgid "Staff member" -msgstr "" +msgstr "Staflid" #: front/src/components/radios/Button.vue:4 msgid "Start" -msgstr "" +msgstr "Starten" #: front/src/components/library/FileUpload.vue:85 #: front/src/components/manage/users/InvitationsTable.vue:17 @@ -1558,111 +1568,122 @@ msgstr "" #: front/src/components/manage/users/UsersTable.vue:43 #: front/src/views/content/libraries/Detail.vue:28 msgid "Status" -msgstr "" +msgstr "Status" #: front/src/components/radios/Button.vue:3 msgid "Stop" -msgstr "" +msgstr "Stoppen" #: front/src/components/Sidebar.vue:150 msgid "Stop radio" -msgstr "" +msgstr "Radio stoppen" #: front/src/App.vue:11 src/components/requests/Form.vue:17 msgid "Submit" -msgstr "" +msgstr "Indienen" #: front/src/components/requests/Form.vue:22 msgid "Submit another request" -msgstr "" +msgstr "Nóg een verzoek indienen" #: front/src/components/auth/SubsonicTokenForm.vue:2 msgid "Subsonic API password" -msgstr "" +msgstr "Wachtwoord voor Subsonic-API" #: front/src/App.vue:13 msgid "Suggested choices" -msgstr "" +msgstr "Aanbevelingen" #: front/src/components/library/FileUpload.vue:3 msgid "Summary" -msgstr "" +msgstr "Samenvatting" #: front/src/components/playlists/Editor.vue:9 msgid "Syncing changes to server..." -msgstr "" +msgstr "Bezig met synchroniseren van wijzigingen naar server..." #: front/src/components/common/CopyInput.vue:3 msgid "Text copied to clipboard!" -msgstr "" +msgstr "Tekst gekopieerd naar het klembord!" #: front/src/components/Home.vue:26 msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "" +"Het zit zo: we waren fan van Grooveshark en wilden iets nóg beters bouwen." #: front/src/App.vue:58 msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "" +"Het Funkwhale-logo is met liefde ontworpen en gemaakt door Francis Gading." #: front/src/views/content/libraries/Form.vue:34 msgid "The library and all its tracks will be deleted. This action is irreversible." msgstr "" +"De verzameling en alle bijbehorende nummers worden verwijderd. Dit kan niet " +"ongedaan worden gemaakt." #: front/src/components/library/FileUpload.vue:39 msgid "The music files you are uploading are tagged properly:" -msgstr "" +msgstr "De muziekbestanden die je uploadt hebben de juiste tags:" #: front/src/components/Home.vue:121 msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "" +"Het platform is vrij en open source; je kunt het zorgeloos installeren en " +"aanpassen" #: front/src/components/auth/SubsonicTokenForm.vue:4 msgid "The Subsonic API is not available on this Funkwhale instance." -msgstr "" +msgstr "De Subsonic-API is niet beschikbaar op deze instantie." #: front/src/components/library/FileUpload.vue:43 msgid "The uploaded music files are in OGG, Flac or MP3 format" -msgstr "" +msgstr "De geüploade bestanden zijn in de formaten OGG, FLAC of MP3" #: front/src/components/library/Album.vue:52 msgid "This album is present in the following libraries:" -msgstr "" +msgstr "Dit album is beschikbaar in de volgende verzamelingen:" #: front/src/components/library/Artist.vue:63 msgid "This artist is present in the following libraries:" -msgstr "" +msgstr "Deze artiest komt voor in de volgende verzamelingen:" #: front/src/views/content/Home.vue:9 msgid "This instance offers up to %{quota} of storage space to every user." -msgstr "" +msgstr "Deze instantie biedt tot %{ quota } opslagruimte voor elke gebruiker." #: front/src/components/auth/Profile.vue:16 msgid "This is you!" -msgstr "" +msgstr "Dit ben jij!" #: front/src/components/common/ActionTable.vue:38 msgid "This may affect a lot of elements, please double check this is really what you want." msgstr "" +"Dit kan gevolgen hebben voor veel elementen. Controleer of je dit écht wilt " +"doen." #: front/src/components/library/FileUpload.vue:52 msgid "This reference will be used to group imported files together." -msgstr "" +msgstr "Deze referentie wordt gebruikt om geïmporteerde bestanden te groeperen." #: front/src/components/library/Track.vue:125 msgid "This track is present in the following libraries:" -msgstr "" +msgstr "Dit nummer komt voor in de volgende verzamelingen:" #: front/src/views/playlists/Detail.vue:37 msgid "This will completely delete this playlist and cannot be undone." msgstr "" +"De afspeellijst wordt volledig verwijderd; dit kan niet ongedaan worden " +"gemaakt." #: front/src/views/radios/Detail.vue:27 msgid "This will completely delete this radio and cannot be undone." msgstr "" +"De radio wordt volledig verwijderd; dit kan niet ongedaan worden gemaakt." #: front/src/components/auth/SubsonicTokenForm.vue:51 msgid "This will completely disable access to the Subsonic API using from account." -msgstr "" +msgstr "De toegang tot de Subsonic-API vanaf dit account wordt uitgeschakeld." #: front/src/App.vue:162 src/components/About.vue:55 src/components/Home.vue:154 #: front/src/components/PageNotFound.vue:33 src/components/Sidebar.vue:203 @@ -1799,160 +1820,176 @@ msgstr "" msgid "This will erase your local data and disconnect you, do you want to continue?" msgid_plural "%{ count } tracks were added to your queue" msgstr[0] "" -msgstr[1] "" +"Dit wist al je lokale gegevens en ontkoppelt je. Weet je zeker dat je wilt " +"doorgaan?" +msgstr[1] "Er zijn %{ count } nummers toegevoegd aan je wachtrij" #: front/src/components/auth/SubsonicTokenForm.vue:36 msgid "This will log you out from existing devices that use the current password." -msgstr "" +msgstr "Je wordt uitgelogd op elk apparaat met het huidige wachtwoord." #: front/src/components/playlists/Editor.vue:44 msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "" +"Alle nummers worden verwijderd uit de afspeellijst; dit kan niet ongedaan " +"worden gemaakt." #: front/src/views/content/libraries/Quota.vue:90 msgid "This will remove tracks that were uploaded but failed to be process by the server. This will remove those files completely and you will regain the corresponding quota." msgstr "" +"Alle nummers die niet konden worden verwerkt, worden verwijderd. De " +"bestanden worden volledig verwijderd en je krijgt het bijbehorende quotum " +"terug." #: front/src/views/content/libraries/Quota.vue:38 msgid "This will remove tracks that were uploaded but not processed yet. This will remove those files completely and you will regain the corresponding quota." msgstr "" +"Alle nummers die nog niet verwerkt zijn, worden verwijderd. De bestanden " +"worden volledig verwijderd en je krijgt het bijbehorende quotum terug." #: front/src/views/content/libraries/Quota.vue:64 msgid "This will remove tracks that were uploaded but skipped during import processes for various reasons. This will remove those files completely and you will regain the corresponding quota." msgstr "" +"Alle nummers die bij het importeren zijn overgeslagen (om wat voor reden dan " +"ook), worden verwijderd. De bestanden worden volledig verwijderd en je " +"krijgt het bijbehorende quotum terug." #: front/src/components/audio/track/Table.vue:6 #: front/src/components/manage/library/FilesTable.vue:37 #: front/src/views/content/libraries/FilesTable.vue:51 #: front/src/components/mixins/Translations.vue:26 msgid "Title" -msgstr "" +msgstr "Titel" #: front/src/components/library/Track.vue:53 msgid "Track information" -msgstr "" +msgstr "Nummerinformatie" #: front/src/components/library/radios/Filter.vue:44 msgid "Track matching filter" -msgstr "" +msgstr "Kloppend nummer" #: front/src/components/instance/Stats.vue:54 msgid "tracks" -msgstr "" +msgstr "nummers" #: front/src/components/library/Album.vue:43 #: front/src/components/playlists/PlaylistModal.vue:33 src/views/content/Base.vue:8 #: front/src/views/content/libraries/Detail.vue:8 src/views/playlists/Detail.vue:50 #: front/src/views/radios/Detail.vue:34 msgid "Tracks" -msgstr "" +msgstr "Nummers" #: front/src/components/library/Artist.vue:54 msgid "Tracks by this artist" -msgstr "" +msgstr "Nummers van deze artiest" #: front/src/components/instance/Stats.vue:25 msgid "Tracks favorited" -msgstr "" +msgstr "Nummers in favorieten" #: front/src/components/instance/Stats.vue:19 msgid "tracks listened" -msgstr "" +msgstr "beluisterde nummers" #: front/src/components/library/Track.vue:91 #: front/src/components/manage/library/FilesTable.vue:41 msgid "Type" -msgstr "" +msgstr "Type" #: front/src/views/content/remote/Card.vue:100 src/views/content/remote/Card.vue:105 msgid "Unfollow" -msgstr "" +msgstr "Ontvolgen" #: front/src/views/content/remote/Card.vue:101 msgid "Unfollow this library?" -msgstr "" +msgstr "Wil je deze verzameling ontvolgen?" #: front/src/components/About.vue:15 msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." msgstr "" +"Helaas is deze pagina nog niet ingevuld door de beheerders van deze " +"instantie." #: front/src/components/Home.vue:37 msgid "Unlimited music" -msgstr "" +msgstr "Ongelimiteerd luisteren naar muziek" #: front/src/components/auth/Settings.vue:50 msgid "Update avatar" -msgstr "" +msgstr "Gebruikersafbeelding bijwerken" #: front/src/views/content/libraries/Form.vue:25 msgid "Update library" -msgstr "" +msgstr "Verzameling bijwerken" #: front/src/components/playlists/Form.vue:33 msgid "Update playlist" -msgstr "" +msgstr "Afspeellijst bijwerken" #: front/src/components/auth/Settings.vue:27 msgid "Update settings" -msgstr "" +msgstr "Instellingen bijwerken" #: front/src/views/auth/PasswordResetConfirm.vue:21 msgid "Update your password" -msgstr "" +msgstr "Wachtwoord bijwerken" #: front/src/views/content/libraries/Card.vue:44 #: front/src/components/manage/users/UsersTable.vue:173 #: front/src/views/content/libraries/DetailArea.vue:24 #: front/src/views/admin/users/UsersDetail.vue:177 msgid "Upload" -msgstr "" +msgstr "Uploaden" #: front/src/components/auth/Settings.vue:45 msgid "Upload a new avatar" -msgstr "" +msgstr "Nieuwe gebruikersafbeelding uploaden" #: front/src/views/content/Home.vue:6 msgid "Upload audio content" -msgstr "" +msgstr "Audio uploaden" #: front/src/views/content/libraries/FilesTable.vue:54 msgid "Upload date" -msgstr "" +msgstr "Uploaddatum" #: front/src/views/content/Home.vue:7 msgid "Upload music files (mp3, ogg, flac, etc.) from your personal library directly from your browser to enjoy them here." msgstr "" +"Upload je eigen muziekbestanden (MP3, FLAC, OGG) via je browser om er hier " +"naar te luisteren." #: front/src/components/library/FileUpload.vue:31 msgid "Upload new tracks" -msgstr "" +msgstr "Nieuwe nummers uploaden" #: front/src/views/admin/users/UsersDetail.vue:82 msgid "Upload quota" -msgstr "" +msgstr "Uploadquotum" #: front/src/components/library/FileUpload.vue:99 msgid "Uploaded" -msgstr "" +msgstr "Geüpload" #: front/src/components/library/FileUpload.vue:5 msgid "Uploading" -msgstr "Aan het uploaden" +msgstr "Bezig met uploaden" #: front/src/components/library/FileUpload.vue:102 msgid "Uploading..." -msgstr "Aan het uploaden..." +msgstr "Bezig met uploaden..." #: front/src/App.vue:44 msgid "Use another instance" -msgstr "Een andere instantiëring gebruiken" +msgstr "Andere instantie gebruiken" #: front/src/views/auth/PasswordReset.vue:12 msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "" "Met dit formulier kun je een nieuw wachtwoord aanvragen. Je ontvangt van ons " -"een e-mail op jouw aangegeven adres met de instructies om je wachtwoord te " -"vervangen." +"een e-mail op het door jou ingevoerde aangegeven adres met de instructies om " +"je wachtwoord te herstellen." #: front/src/components/manage/users/InvitationsTable.vue:49 msgid "Used" @@ -1964,16 +2001,16 @@ msgstr "Gebruiker" #: front/src/components/instance/Stats.vue:5 msgid "User activity" -msgstr "Activiteit van gebruikers" +msgstr "Gebruikersactiviteit" #: front/src/components/library/Album.vue:49 src/components/library/Artist.vue:60 #: front/src/components/library/Track.vue:122 msgid "User libraries" -msgstr "Gebruikersbibliotheken" +msgstr "Gebruikersverzamelingen" #: front/src/components/library/Radios.vue:20 msgid "User radios" -msgstr "Gebruikerradio's" +msgstr "Gebruikersradio's" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 @@ -1999,14 +2036,14 @@ msgstr "Gebruikers" #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 msgid "View files" -msgstr "Bekijk bestanden" +msgstr "Bestanden bekijken" #: front/src/components/library/Album.vue:37 src/components/library/Artist.vue:35 #: front/src/components/library/Track.vue:44 #: front/src/components/metadata/ReleaseCard.vue:53 #: front/src/components/metadata/ArtistCard.vue:49 msgid "View on MusicBrainz" -msgstr "Bekijk in MusicBrainz" +msgstr "Bekijken op MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 msgid "Visibility" @@ -2014,35 +2051,35 @@ msgstr "Zichtbaarheid" #: front/src/components/playlists/PlaylistModal.vue:20 msgid "We cannot add the track to a playlist" -msgstr "Dit nummer kan niet aan een afspeellijst toegevoegd worden" +msgstr "Dit nummer kan niet aan een afspeellijst worden toegevoegd" #: front/src/components/playlists/Form.vue:14 msgid "We cannot create the playlist" -msgstr "De afspeellijst kan niet aangemaakt worden" +msgstr "De afspeellijst kan niet worden gecreëerd" #: front/src/components/auth/Signup.vue:13 msgid "We cannot create your account" -msgstr "Jouw account kan niet aangemaakt worden" +msgstr "Je account kan niet worden gecreëerd" #: front/src/components/auth/Login.vue:7 msgid "We cannot log you in" -msgstr "Fout bij het inloggen" +msgstr "Er is een fout opgetreden tijdens het inloggen" #: front/src/components/auth/Settings.vue:38 msgid "We cannot save your avatar" -msgstr "Je avatar kan niet opgeslagen worden" +msgstr "Je gebruikersafbeelding kan niet worden opgeslagen" #: front/src/components/auth/Settings.vue:14 msgid "We cannot save your settings" -msgstr "Je instellingen kunnen niet opgeslagen worden" +msgstr "Je instellingen kunnen niet worden opgeslagen" #: front/src/components/Home.vue:127 msgid "We do not track you or bother you with ads" -msgstr "We volgen je niet en storen je niet met reclame" +msgstr "We volgen je niet en vallen je niet lastig met reclame" #: front/src/views/Notifications.vue:26 msgid "We don't have any notification to display!" -msgstr "Geen melding om te tonen!" +msgstr "Er zijn geen te tonen meldingen!" #: front/src/views/content/Home.vue:4 msgid "We offer various way to grab new content and make it available here." @@ -2058,486 +2095,517 @@ msgstr "Omdat het makkelijk moet zijn naar muziek te luisteren." #: front/src/components/PageNotFound.vue:10 msgid "We're sorry, the page you asked for does not exist:" -msgstr "Helaas bestaat de opgevraagde pagina niet:" +msgstr "Helaas, de opgevraagde pagina bestaat niet:" #: front/src/components/requests/Form.vue:21 msgid "We've received your request, you'll get some groove soon ;)" -msgstr "" +msgstr "We hebben je verzoek ontvangen. Binnenkort kun je genieten ;)" #: front/src/components/Home.vue:5 msgid "Welcome on Funkwhale" -msgstr "" +msgstr "Welkom op Funkwhale" #: front/src/components/Home.vue:24 msgid "Why funkwhale?" -msgstr "" +msgstr "Waarom Funkwhale?" #: front/src/components/Sidebar.vue:115 msgid "Yes" -msgstr "" +msgstr "Ja" #: front/src/components/auth/Logout.vue:8 msgid "Yes, log me out!" -msgstr "" +msgstr "Ja, ik wil uitloggen!" #: front/src/components/library/FileUpload.vue:33 msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "" +"Je staat op het punt muziek te uploaden. Controleer voordat je doorgaat:" #: front/src/components/auth/Logout.vue:7 msgid "You are currently logged in as %{ username }" -msgstr "" +msgstr "Je bent ingelogd als %{ username }" #: front/src/views/content/Home.vue:17 msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "" +"Je kunt andere verzamelingen volgen voor toegang tot nieuwe muziek. Openbare " +"verzamelingen kun je meteen volgen, maar voor privéverzamelingen heb je " +"toestemming nodig van de beheerder." #: front/src/components/Home.vue:133 msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "" +"Je kunt vrienden en familie uitnodigen zodat ze kunnen genieten van je muziek" #: front/src/components/library/radios/Builder.vue:7 msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "" +"Met dit hulpmiddel kun je je eigen radiostation opzetten. Dit station speelt " +"nummers aan de hand van jouw wensen." #: front/src/components/auth/SubsonicTokenForm.vue:8 msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "" +"Je kunt ze gebruiken om ook offline te genieten van je afspeellijst en " +"muziek, bijvoorbeeld op je smartphone of tablet." #: front/src/components/Sidebar.vue:147 msgid "You have a radio playing" -msgstr "" +msgstr "Er wordt een radiostation afgespeeld" #: front/src/App.vue:8 msgid "You need to select an instance in order to continue" -msgstr "" +msgstr "Kies een instantie om door te gaan" #: front/src/views/content/libraries/Form.vue:19 msgid "You will be able to share your library with other people, regardless of it's visibility." msgstr "" +"Je kunt je verzameling delen met anderen, ongeacht hoe je de zichtbaarheid " +"hebt ingesteld." #: front/src/components/auth/Settings.vue:100 msgid "You will be logged out from this session and have to log in with the new one" -msgstr "" +msgstr "Je wordt uitgelogd uit deze sessie moet inloggen op de nieuwe" #: front/src/components/auth/Settings.vue:71 msgid "You will have to update your password on your clients that use this password." msgstr "" +"Je moet je wachtwoord bijwerken op je clients die ook dit wachtwoord " +"gebruiken." #: front/src/views/auth/EmailConfirm.vue:24 msgid "Your email address was confirmed, you can now use the service without limitations." msgstr "" +"Je e-mailadres is bevestigd. Je kunt nu onbeperkt gebruikmaken van de dienst." #: front/src/components/Home.vue:114 msgid "Your music, your way" -msgstr "" +msgstr "Jouw muziek op jouw manier" #: front/src/views/Notifications.vue:7 msgid "Your notifications" -msgstr "" +msgstr "Je meldingen" #: front/src/views/auth/PasswordResetConfirm.vue:29 msgid "Your password has been updated successfully." -msgstr "" +msgstr "Je wachtwoord is bijgewerkt." #: front/src/components/auth/Settings.vue:101 msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "" +"Je Subsonic-wachtwoord wordt gewijzigd in een nieuw willekeurig wachtwoord. " +"Hierbij wordt je uitgelogd op apparaten die het oude Subsonic-wachtwoord " +"gebruiken." #: front/src/components/mixins/Translations.vue:8 msgid "Activity visibility" -msgstr "" +msgstr "Zichtbaarheid van activiteit" #: front/src/components/mixins/Translations.vue:9 msgid "Determine the visibility level of your activity" -msgstr "" +msgstr "Bepaal de zichtbaarheid van je activiteit" #: front/src/components/mixins/Translations.vue:11 #: front/src/components/playlists/Form.vue:81 src/views/content/libraries/Form.vue:72 msgid "Nobody except me" -msgstr "" +msgstr "Niemand, behalve ik" #: front/src/components/mixins/Translations.vue:12 #: front/src/components/playlists/Form.vue:85 src/views/content/libraries/Form.vue:73 msgid "Everyone on this instance" -msgstr "" +msgstr "Iedereen op deze instantie" #: front/src/components/mixins/Translations.vue:18 msgid "Accessed date" -msgstr "" +msgstr "Benaderd op" #: front/src/components/mixins/Translations.vue:19 msgid "Modification date" -msgstr "" +msgstr "Bewerkt op" #: front/src/components/mixins/Translations.vue:20 msgid "Imported date" -msgstr "" +msgstr "Geïmporteerd op" #: front/src/components/mixins/Translations.vue:22 msgid "Track name" -msgstr "" +msgstr "Naam van nummer" #: front/src/components/mixins/Translations.vue:23 msgid "Album name" -msgstr "" +msgstr "Albumnaam" #: front/src/components/mixins/Translations.vue:30 msgid "Sign-up date" -msgstr "" +msgstr "Geregistreerd op" #: front/src/components/playlists/Editor.vue:163 msgid "Copy tracks from current queue to playlist" -msgstr "" +msgstr "Nummers kopiëren van huidige wachtrij naar afspeellijst" #: front/src/components/playlists/PlaylistModal.vue:116 msgid "Add to this playlist" -msgstr "" +msgstr "Toevoegen aan deze afspeellijst" #: front/src/components/playlists/Form.vue:74 msgid "My awesome playlist" -msgstr "" +msgstr "Mijn geweldige afspeellijst" #: front/src/components/playlists/Form.vue:89 msgid "Everyone" -msgstr "" +msgstr "Iedereen" #: front/src/components/auth/Signup.vue:95 msgid "Sign Up" -msgstr "" +msgstr "Registreren" #: front/src/components/auth/Signup.vue:96 msgid "Enter your invitation code (case insensitive)" -msgstr "" +msgstr "Voer je uitnodigingscode in (hoofdletterongevoelig)" #: front/src/components/auth/Signup.vue:97 msgid "Enter your username" -msgstr "" +msgstr "Voer je gebruikersnaam in" #: front/src/components/auth/Signup.vue:98 msgid "Enter your email" -msgstr "" +msgstr "Voer je e-mailadres in" #: front/src/components/auth/SubsonicTokenForm.vue:95 msgid "Password updated" -msgstr "" +msgstr "Wachtwoord bijgewerkt" #: front/src/components/auth/SubsonicTokenForm.vue:111 msgid "Access disabled" -msgstr "" +msgstr "Toegang uitgeschakeld" #: front/src/components/auth/Login.vue:77 msgid "Enter your username or email" -msgstr "" +msgstr "Voer je gebruikersnaam of e-mailadres in" #: front/src/components/auth/Login.vue:78 msgid "Log In" -msgstr "" +msgstr "Inloggen" #: front/src/components/auth/Profile.vue:47 msgid "%{ username }'s profile" -msgstr "" +msgstr "%{ username }'s profiel" #: front/src/components/auth/Logout.vue:20 msgid "Log Out" -msgstr "" +msgstr "Uitloggen" #: front/src/components/auth/Settings.vue:249 msgid "Account Settings" -msgstr "" +msgstr "Accountinstellingen" #: front/src/components/favorites/TrackFavoriteIcon.vue:19 msgid "Remove from favorites" -msgstr "" +msgstr "Verwijderen uit favorieten" #: front/src/components/favorites/List.vue:110 msgid "Your Favorites" -msgstr "" +msgstr "Je favorieten" #: front/src/components/library/Radios.vue:141 msgid "Enter a radio name..." -msgstr "" +msgstr "Voer een naam in voor de radio..." #: front/src/components/library/radios/Builder.vue:233 msgid "Radio Builder" -msgstr "" +msgstr "Radio opzetten" #: front/src/components/library/radios/Builder.vue:235 msgid "My awesome radio" -msgstr "" +msgstr "Mijn geweldige radio" #: front/src/components/library/radios/Builder.vue:236 msgid "My awesome description" -msgstr "" +msgstr "Mijn geweldige omschrijving" #: front/src/components/library/FileUpload.vue:238 msgid "Upload refused, ensure the file is not too big and you have not reached your quota" msgstr "" +"Upload geweigerd. Zorg ervoor dat het bestand niet te groot is en dat je je " +"quotum nog niet hebt bereikt." #: front/src/components/library/FileUpload.vue:239 msgid "Impossible to upload this file, ensure it is not too big" -msgstr "" +msgstr "Kan dit bestand niet uploaden; zorg ervoor dat het niet te groot is" #: front/src/components/library/FileUpload.vue:240 msgid "A network error occured while uploading this file" -msgstr "" +msgstr "Er is een netwerkfout opgetreden tijdens het uploaden van dit bestand" #: front/src/components/library/FileUpload.vue:241 msgid "Upload timeout, please try again" -msgstr "" +msgstr "Upload verlopen; probeer het opnieuw" #: front/src/components/library/Artists.vue:119 msgid "Enter an artist name..." -msgstr "" +msgstr "Voer een artiestnaam in..." #: front/src/components/library/Track.vue:195 src/components/audio/SearchBar.vue:27 #: front/src/components/metadata/Search.vue:138 msgid "Track" -msgstr "" +msgstr "Nummer" #: front/src/components/library/Home.vue:65 msgid "Home" -msgstr "" +msgstr "Startpagina" #: front/src/components/forms/PasswordInput.vue:25 msgid "Show/hide password" -msgstr "" +msgstr "Wachtwoord tonen/verbergen" #: front/src/components/requests/Form.vue:73 msgid "The Beatles, Mickael Jackson…" -msgstr "" +msgstr "The Beatles, Michael Jackson…" #: front/src/components/requests/Form.vue:74 msgid "The White Album, Thriller…" -msgstr "" +msgstr "The White Album, Thriller…" #: front/src/components/requests/Form.vue:75 msgid "Use this comment box to add details to your request if needed" msgstr "" +"Gebruik, indien nodig, dit opmerkingsveld om iets extra's toe te voegen aan " +"je verzoek" #: front/src/components/audio/PlayButton.vue:158 msgid "%{ count } track was added to your queue" msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%{ count } nummer toegevoegd aan je wachtrij" +msgstr[1] "%{ count } nummers toegevoegd aan je wachtrij" #: front/src/components/audio/Search.vue:65 msgid "Artist, album, track..." -msgstr "" +msgstr "Artiest, album, nummer..." #: front/src/components/audio/SearchBar.vue:20 msgid "Search for artists, albums, tracks..." -msgstr "" +msgstr "Zoek naar artiesten, albums, nummers..." #: front/src/components/audio/Player.vue:216 msgid "Queue shuffled!" -msgstr "" +msgstr "Wachtrij ingesteld op willekeurig!" #: front/src/components/audio/Player.vue:273 msgid "Previous track" -msgstr "" +msgstr "Vorig nummer" #: front/src/components/audio/Player.vue:274 msgid "Play track" -msgstr "" +msgstr "Nummer afspelen" #: front/src/components/audio/Player.vue:275 msgid "Pause track" -msgstr "" +msgstr "Nummer pauzeren" #: front/src/components/audio/Player.vue:276 msgid "Next track" -msgstr "" +msgstr "Volgend nummer" #: front/src/components/audio/Player.vue:277 msgid "Unmute" -msgstr "" +msgstr "Ontdempen" #: front/src/components/audio/Player.vue:278 msgid "Mute" -msgstr "" +msgstr "Dempen" #: front/src/components/audio/Player.vue:279 msgid "Looping disabled. Click to switch to single-track looping." msgstr "" +"Herhalen uitgeschakeld. Klik om over te schakelen naar één nummer-herhaling." #: front/src/components/audio/Player.vue:280 msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "" +"Eén nummer wordt herhaald. Klik om over te schakelen naar wachtrijherhaling." #: front/src/components/audio/Player.vue:281 msgid "Looping on whole queue. Click to disable looping." -msgstr "" +msgstr "Gehele wachtrij wordt herhaald. Klik om herhalen uit te schakelen." #: front/src/components/audio/Player.vue:282 msgid "Shuffle your queue" -msgstr "" +msgstr "Willekeurig" #: front/src/components/audio/Player.vue:283 msgid "Clear your queue" -msgstr "" +msgstr "Wachtrij wissen" #: front/src/components/Sidebar.vue:203 msgid "Pending import requests" -msgstr "" +msgstr "In behandeling zijnde importverzoeken" #: front/src/components/Sidebar.vue:204 msgid "Pending follow requests" -msgstr "" +msgstr "In behandeling zijnde volgverzoeken" #: front/src/components/metadata/Search.vue:114 msgid "Enter your search query..." -msgstr "" +msgstr "Voer een zoekopdracht in..." #: front/src/components/manage/library/FilesTable.vue:176 msgid "Search by title, artist, domain..." -msgstr "" +msgstr "Zoek op titel, artiest, domein..." #: front/src/components/manage/users/InvitationForm.vue:58 msgid "Leave empty for a random code" -msgstr "" +msgstr "Laat leeg om een willekeurig code te krijgen" #: front/src/components/manage/users/InvitationsTable.vue:153 msgid "Search by username, email, code..." -msgstr "" +msgstr "Zoek op gebruikersnaam, e-mailadres, code..." #: front/src/components/manage/users/UsersTable.vue:163 msgid "Search by username, email, name..." -msgstr "" +msgstr "Zoek op naam, gebruikersnaam, e-mailadres..." #: front/src/components/manage/users/UsersTable.vue:181 #: front/src/views/admin/users/UsersDetail.vue:185 src/views/admin/Settings.vue:83 msgid "Federation" -msgstr "" +msgstr "Federatie" #: front/src/components/Home.vue:154 msgid "Welcome" -msgstr "" +msgstr "Welkom" #: front/src/views/content/remote/ScanForm.vue:48 msgid "Enter a library url" -msgstr "" +msgstr "Voer de url in van een verzameling" #: front/src/views/content/remote/Card.vue:165 msgid "Scan launched" -msgstr "" +msgstr "Scan begonnen" #: front/src/views/content/remote/Card.vue:166 msgid "Scan skipped (previous scan is too recent)" -msgstr "" +msgstr "Scan overgeslagen (vorige scan was zeer recent)" #: front/src/views/content/libraries/FilesTable.vue:226 msgid "Search by title, artist, album..." -msgstr "" +msgstr "Zoek op titel, artiest, album..." #: front/src/views/content/libraries/FilesTable.vue:230 msgid "Track was already present in one of your libraries" -msgstr "" +msgstr "Nummer is al aanwezig in één van je verzamelingen" #: front/src/views/content/libraries/FilesTable.vue:234 msgid "Track is uploaded but not processed by the server yet" -msgstr "" +msgstr "Nummer is geüpload, maar nog niet verwerkt door de server" #: front/src/views/content/libraries/FilesTable.vue:238 msgid "An error occured while processing this track, ensure the track is correctly tagged" msgstr "" +"Er is een fout opgetreden tijdens het verwerken van dit nummer. Zorg ervoor " +"dat het juist getagd is." #: front/src/views/content/libraries/FilesTable.vue:242 msgid "Import went on successfully" -msgstr "" +msgstr "Import voltooid" #: front/src/views/content/libraries/FilesTable.vue:259 msgid "Relaunch import" -msgstr "" +msgstr "Opnieuw importeren" #: front/src/views/content/libraries/Card.vue:58 msgid "Visibility: nobody except me" -msgstr "" +msgstr "Zichtbaarheid: niemand, behalve ik" #: front/src/views/content/libraries/Card.vue:59 msgid "Visibility: everyone on this instance" -msgstr "" +msgstr "Zichtbaarheid: iedereen op deze instantie" #: front/src/views/content/libraries/Card.vue:60 msgid "Visibility: everyone, including other instances" -msgstr "" +msgstr "Zichtbaarheid: iedereen, inclusief personen op andere instanties" #: front/src/views/content/libraries/Card.vue:61 msgid "Total size of the files in this library" -msgstr "" +msgstr "Totale grootte van de bestanden in deze verzameling" #: front/src/views/content/libraries/Form.vue:70 msgid "My awesome library" -msgstr "" +msgstr "Mijn geweldige verzameling" #: front/src/views/content/libraries/Form.vue:71 msgid "This library contains my personnal music, I hope you will like it!" msgstr "" +"Deze verzameling bevat mijn persoonlijke muziek. Ik hoop dat je ervan zult " +"genieten!" #: front/src/views/content/libraries/Form.vue:74 msgid "Everyone, including other instances" -msgstr "" +msgstr "Iedereen, inclusief personen op andere instanties" #: front/src/views/content/libraries/Form.vue:106 msgid "Library updated" -msgstr "" +msgstr "Verzameling bijgewerkt" #: front/src/views/content/libraries/Form.vue:109 msgid "Library created" -msgstr "" +msgstr "Verzameling gecreëerd" #: front/src/views/content/Home.vue:35 msgid "Add and manage content" -msgstr "" +msgstr "Inhoud toevoegen en beheren" #: front/src/views/radios/Detail.vue:80 msgid "Radio" -msgstr "" +msgstr "Radio" #: front/src/views/instance/Timeline.vue:57 msgid "Instance Timeline" -msgstr "" +msgstr "Instantietijdlijn" #: front/src/views/playlists/Detail.vue:90 msgid "Playlist" -msgstr "" +msgstr "Afspeellijst" #: front/src/views/playlists/List.vue:105 msgid "Enter an playlist name..." -msgstr "" +msgstr "Voer een naam in voor de afspeellijst..." #: front/src/views/admin/library/Base.vue:16 msgid "Manage library" -msgstr "" +msgstr "Verzameling beheren" #: front/src/views/admin/users/UsersDetail.vue:169 msgid "Determine if the user account is active or not. Inactive users cannot login or use the service." msgstr "" +"Bepaal of het gebruikersaccount actief is of niet. Inactieve gebruikers " +"kunnen niet inloggen of gebruikmaken van de dienst." #: front/src/views/admin/users/UsersDetail.vue:170 msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "" +"Bepaald hoeveel een gebruiker kan uploaden. Laat leeg om de standaardwaarde " +"te gebruiken van deze instantie." #: front/src/views/admin/users/Base.vue:20 msgid "Manage users" -msgstr "" +msgstr "Gebruikers beheren" #: front/src/views/admin/Settings.vue:75 msgid "Instance settings" -msgstr "" +msgstr "Instantie-instellingen" #: front/src/views/admin/Settings.vue:80 msgid "Instance information" -msgstr "" +msgstr "Instantie-informatie" #: front/src/views/admin/Settings.vue:84 msgid "Subsonic" -msgstr "" +msgstr "Subsonic" #: front/src/views/admin/Settings.vue:85 msgid "Statistics" -msgstr "" +msgstr "Statistieken" #: front/src/views/admin/Settings.vue:86 msgid "Error reporting" -msgstr "" +msgstr "Foutrapportage" diff --git a/front/locales/oc/LC_MESSAGES/app.po b/front/locales/oc/LC_MESSAGES/app.po index feffda9dcf57203744d5eff6aa4f986e7404e064..f622786573b4d4b22b982128a260f879e7b7e5b2 100644 --- a/front/locales/oc/LC_MESSAGES/app.po +++ b/front/locales/oc/LC_MESSAGES/app.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" -"PO-Revision-Date: 2019-01-20 16:50+0000\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" +"PO-Revision-Date: 2019-05-07 09:29+0000\n" "Last-Translator: Quentà <quentin_antonin@hotmail.com>\n" "Language-Team: none\n" "Language: oc\n" @@ -19,1871 +19,3116 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", de %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } sus %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(voida)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "%{ app } vòl accedir a vòstre compte Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } sus %{ total } element seleccionat" msgstr[1] "%{ count } sus %{ total } elements seleccionats" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } pista" msgstr[1] "%{ count } pistas" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } pista dins %{ albumsCount } albums" msgstr[1] "%{ count } pistas dins %{ albumsCount } albums" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } pista correspond als filtres seleccionats" msgstr[1] "%{ count } pistas correspondon als filtres seleccionats" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } pista ajustada a la fila" -msgstr[1] "%{ count } pistas ajustadas a la fila" - #: front/src/components/playlists/Card.vue:18 +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} pista" msgstr[1] "%{ count} pistas" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ count } utilizat sus %{ total } autorizat" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" -msgstr "" -"%{ username } acceptèt vòstra demanda de seguiment de la bibliotèca « %{ " -"library } »" +msgstr "%{ username } acceptèt vòstra demanda de seguiment de la bibliotèca « %{ library } »" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } seguÃs vòstra bibliotèca « %{ library } »" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } vòl seguir vòstra bibliotèca « %{ library } »" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Perfil de %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" -msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">Tocant " -"%{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." +msgstr "<strong>%{ track }</strong> es ja dins <strong>%{ playlist }</strong>." #: front/src/components/audio/artist/Card.vue:41 +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 album" msgstr[1] "%{ count } albums" #: front/src/components/favorites/List.vue:10 +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 favorit" msgstr[1] "%{ count } favorits" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Bibliotèca de qualitat" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Una error de ret s’es producha en enviar aqueste fichièr" +#: front/src/components/library/EditForm.vue:145 +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Una descripcion pichona per explicar vòstres cambiaments." + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "A prepaus de %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "A prepaus de %{instanceName}" #: front/src/components/Footer.vue:45 +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "A prepaus de Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Pagina « a prepaus »" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +msgctxt "Content/About/Title" msgid "About this instance" msgstr "A prepaus d’aquesta instà ncia" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Acceptar" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Acceptat" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Accès desactivat" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" -msgstr "Accedissètz a vòstra musica d’una interfà cia afinada estant, adaptada a çò que compta vertadièrament" - -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "Accès als fichièrs à udio, bibliotècas, artistas, albums e pistas" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Accès als filtre de contengut" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Accès a las modificacions" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "Accès als corrièls, nom d’utilizaire e informacions del perfil" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Accès als favorits" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "Accès al seguiment" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "Accès a l’istoric d’escota" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Accès a las notificacions" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Accès a las listas de lectura" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Accès a las rà dios" + +#: front/src/components/Home.vue:101 +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "" +"Accedissètz a vòstra musica d’una interfà cia afinada estant, adaptada a çò " +"que compta vertadièrament" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +msgctxt "Content/*/*/Noun" msgid "Accessed date" msgstr "Data d’accès" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Compte" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +msgctxt "*/*/*" +msgid "Account" +msgstr "Compte" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Donadas del compte" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Paramètres del compte" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Paramètres del compte" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Estat del compte" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Adreça electronica del compte" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Comptes" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Accion" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "L’action %{ action } es estada lançada corrèctament sus %{ count } element" msgstr[1] "L’action %{ action } es estada lançada corrèctament sus %{ count } elements" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Accions" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Actiu" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Activitat" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Visibilitat de l’activitat" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Ajustar" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Ajustar un domeni" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Ajustar una règla de moderacion" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Ajustar una nòva règla de moderacion" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Ajustar e gerir lo contengut" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "Ajustar pr’aquò" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Ajustar de contengut" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" -msgstr "Ajustar de filtres" +msgstr "Ajustar lo filtre" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" -msgstr "Ajustar de filtre per dire de personalizar vòstra rà dio" +msgstr "Ajustar de filtres per dire de personalizar vòstra rà dio" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Ajustar a la lista actuala" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Ajustar als favorits" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Ajustar a la lista de lectura…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Ajustar a la lista" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Ajustar a aquesta lista de lectura" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Ajustar una pista" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administracion" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Album" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Album contenent %{ count } pista, de %{ artist }" -msgstr[1] "Album contenent %{ count } pistas, de %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Album" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 +#: front/src/views/admin/library/TrackDetail.vue:128 +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Artista d’aqueste album" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +msgctxt "Content/Moderation/Title" +msgid "Album data" +msgstr "Donadas de l’album" + +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" msgid "Album name" msgstr "Nom de l’album" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Pagina de l’album" - #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +msgctxt "*/*/*" msgid "Albums" msgstr "Albums" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Albums d’aqueste artista" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Tot" +#: front/src/components/common/ActionTable.vue:59 +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } sus %{ total } element seleccionat" +msgstr[1] "%{ count } sus %{ total } elements seleccionats" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "Autorizar l’aplicacion" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" +"Una error s’es producha pendent lo processús de mandadÃs. Trobaretz mai d’" +"informacion çai-jos." + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Una error s’es producha en enregistrar vòstras modificacions" +#: front/src/components/federation/FetchButton.vue:21 +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Una error s’es producha en actualizar las donadas :" + +#: front/src/components/federation/FetchButton.vue:41 +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Una error HTTP s’es producha en contactar lo servidor alonhat" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Una error desconeguda encontrada, aquò pòt significar que lo servidor es fòra servici o pòt pas èsser atengut" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "Una error desconeguda s’es producha" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Aplicacion" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "Detalhs de l’aplicacion" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "ID de l’aplicacion" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" +"L’ID e lo secrèt de l’aplicacion son de donadas vertadièrament sensiblas e " +"devon èsser consideradas coma de senhals. Partegetz-las pas amb degun." + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "Secret de l’aplicacion" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Validar" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Validat" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "Validada e aplicada" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Volètz vertadièrament vos desconnectar ?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artista" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artista" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Artist data" +msgstr "Donadas de l’artista" + +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" msgid "Artist name" msgstr "Nom de l’artista" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Pagina de l’artista" - #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artista, album, pista…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistas" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistas" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Ascendent" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Demandar un nòu senhal" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Contengut à udio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Acorchis del lector à udio" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "Autorizar %{ app }" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "Autorizar una tèrça aplicacion" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "Aplicacions autorizadas" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Listas de lectura disponiblas" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Tornar a la pagina de connexion" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Tornar als paramètres" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Debit" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "O blocar tot" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"O blocar tot d’aqueste compte estant. Empacharà totas interaccion amb l’" -"entitat e purgarà lo contengut ligat (pistas, bibliotèca, seguiments, etc.)" +msgstr "O blocar tot d’aqueste compte estant. Empacharà totas interaccion amb l’entitat e purgarà lo contengut ligat (pistas, bibliotèca, seguiments, etc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Percórrer" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Percórrer la bibliotèca" +#: front/src/components/library/Albums.vue:4 +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Percórrer los albums" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Percórrer los artistas" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Percórrer las listas de lectura" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Percórrer las rà dios" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Editor" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "De %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." -msgstr "" -"En quitar de seguir aquesta bibliotèca, perdretz l’accès a son contengut." - -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +msgstr "En quitar de seguir aquesta bibliotèca, perdretz l’accès a son contengut." + +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "Talha del cache" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Anullar" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Pistas candidatas" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Lo senhal pòt pas se cambiar" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "MandadÃs del fichièr impossible, asseguratz-vos qu’es pas tròp pesuc" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Cambiar la lenga" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Cambiar lo senhal" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Cambiar lo senhal" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Cambiar lo senhal" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Cambiar lo senhal ?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Cambiament sincronizat amb lo servidor" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "L’actualizacion de vòstra senhal cambiarà tanben lo de l’API Subsonic se n’avètz un." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" -msgstr "Lo cambiament de senhal a las consequéncias seguentas" +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" +msgstr "Lo cambiament de senhal a las consequéncias seguentas :" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Sala de discutida" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" +"Causir l’autorizacion «Lectura» o «Escritura» sul parent implica l’accès a " +"totes los jos elements correspondents." + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Causissètz vòstra instà ncia" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Bibliotèca de qualitat" +#: front/src/components/library/EditForm.vue:75 +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Escafar" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Escafar" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Escafar la lista de lectura" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Voidar la fila" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Amb un clic, escotatz d’oras de musica a la rà dio" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" +"Clicar per mostrar mai d’informacion tocant lo processús d’import d’aqueste " +"mandadÃs" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Clicatz per causir los fichièrs d’enviar o lisatz los fichièrs o repertòris" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Tampar" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Tampar" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "Tampar e tornar cargar la pagina" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Còdi" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Plegar" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configuracion" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Confirmar" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Confirmar vòstra adreça electronica" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Còdi de confirmacion" -#: front/src/components/common/ActionTable.vue:7 -msgid "Content have been updated, click refresh to see up-to-date content" +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "Filtre de contengut corrèctament ajustat" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Filtres de contengut" + +#: front/src/components/auth/Settings.vue:116 +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Filtres de contengut" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." msgstr "" -"Lo contengut es estat actualizat, clicar per tornar cargar e veire lo " -"contengut a jorn" +"Los filtres de contengut vos ajudan a amagar los contenguts que volètz pas " +"veire sus aqueste servici." + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "Lo contengut es estat actualizat, clicar per tornar cargar e veire lo contengut a jorn" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Contribuir" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Copiar" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "Copiar las pistas de la fila a la lista de lectura" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "Copiatz-pegatz lo còdi seguent dins l’aplicacion :" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "Copiatz / Pegatz aqueste còdi al vòstre site HTML" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Copyright" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Confirmacion vòstra adreça electronica impossibla" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Error en recuperar la bibliotèca alonhada" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Una error s’es producha en tractar aquesta pista, asseguratz-vos qu’es " -"corrèctament etiquetada" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Jaqueta d’albums, paraulas, nòstra tòca es d’o aver tot ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Crear" #: front/src/components/auth/Signup.vue:4 +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Crear un compte funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Crear una nòva aplicacion" + +#: front/src/components/auth/Settings.vue:220 +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Crear una nòva aplicacion" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Crear una nòva bibliotèca" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Crear una nòva lista de lectura" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Crear un compte" +#: front/src/components/auth/ApplicationForm.vue:65 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Crear una aplicacion" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Crear una bibliotèca" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Crear mon compte" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "Creatz-ne un per integrar Funkwhale amb de tèrças aplicacions." + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Crear la lista de lectura" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Crear vòstra pròpria rà dio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Data de creacion" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar actual" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Bibliotèca actuala" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Pista actuala" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Utilizacion actuala" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" +"Las donadas tornadas pel servidor alonhat an d’atributs mancants o invalids" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" +"Las donadas son estadas actualizadas corrèctament del servidors alonhat " +"estant." + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Data" +#: front/src/components/library/ImportStatusModal.vue:64 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Informacions de debug" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Reduire lo volum" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Suprimir" +#: front/src/components/auth/Settings.vue:254 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Suprimir l’aplicacion" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "Suprimir l’aplicacion « %{ application } » ?" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Suprimir la bibliotèca" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Suprimir la règla de moderacion" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Suprimir la lista de lectura" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Suprimir la rà dio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Suprimir aqueste album ?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Suprimir aqueste artista ?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Suprimir aquesta bibliotèca ?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Suprimir aquesta règla de moderacion ?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Suprimir aquesta règla de moderacion ?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Suprimir aqueste mandadÃs ?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Descendent" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Descripcion" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detalhs" +#: front/src/views/admin/library/LibraryDetail.vue:123 +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Descripcion" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Detalhs" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Definissètz la quantitat de contengut que l’utilizaire pòt enviar. Daissatz void per emplegar las valors per defaut de l’instà ncia." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Determinatz lo nivèl de visibilitat de vòstra activitat" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Desactivar l’accès" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Desactivar l’accès via Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Desactivar l’accès a l’API Subsonic ?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Desactivat" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "Numèro del disc" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Aprenètz a utilizar Funkwhale amb d’autras aplicacions" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Nom public" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Mostrar publicament" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Telecargar pas cap de mèdia (à udio, album, tampa, avatar de compte…) d’" -"aqueste compte o domeni. Purgarà tanben lo contengut existent." +msgstr "Telecargar pas cap de mèdia (à udio, album, tampa, avatar de compte…) d’aqueste compte o domeni. Purgarà tanben lo contengut existent." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Volètz voidar la lista de lectura « %{ playlist } »  ?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Volètz confirmar aquesta accion ?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Volètz suprimir la lista de lectura « %{ playlist } »  ?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Volètz suprimir la rà dio « %{ playlist } »  ?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Volètz rescondre lo contengut de l’artista « %{ name } »  ?" + +#: front/src/components/common/ActionTable.vue:37 +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" -msgstr[0] "Volètz lançar l’accion « %{ action } » sus %{ count } element ?" -msgstr[1] "Volètz lançar l’accion « %{ action } » sus %{ count } elements ?" +msgstr[0] "Volètz lançar l’accion « %{ action } » sus %{ count } element ?" +msgstr[1] "Volètz lançar l’accion « %{ action } » sus %{ count } elements ?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Volètz restablir vòstra fila precedenta ?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentacion" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Domenu" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "Domenis" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Telecargar" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Lisatz las linhas per triar las pistas de la lista de lectura" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Durada" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Corrièl confirmat" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Simple d’utilizar" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Modificar" + +#: front/src/components/auth/Settings.vue:246 +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Modificar" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Modificar l’aplicacion" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Editrar las informacions d’aquesta instà ncia" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Modificar…" - -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Actualizar las règlas de moderacion" + +#: front/src/components/library/AlbumEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Modificar aqueste album" + +#: front/src/components/library/ArtistEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Modificar aqueste artista" + +#: front/src/components/library/TrackEdit.vue:4 +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Modificar aquesta pista" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Modificacions" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Modificacions" + +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Corrièl" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Adreça electronica" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Integrar" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Còdi d’integracion" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Integrar aqueste album a un site web" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Integrar aquesta pista a vòstre site web" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Integrar aquesta pista a un site web" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "Seguiments de bibliotècas enviats" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Messatge emés" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Activat" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Acabar l’edicion" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Picatz l’URL d’una bibliotèca" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Escrivètz un nom de rà dio…" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "Picatz lo tÃtol de l’album…" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Escrivètz un nom d’artista…" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Escrivètz un nom de lista de lectura…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Picatz l’adreça de corrièl ligada a vòstre compte" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Escrivètz vòstre adreça electronica" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Escrivètz vòstre còdi d’invitacion (pas sensible a la cassa)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Escrivètz vòstra recèrca…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Escrivètz vòstre nom d’utilizaire" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Escrivètz vòstre nom d’utilizaire o corrièl" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Error" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Detalhs de l’error" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Rapòrt d’error" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Tip d’error" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Error en tractar l’accion" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Error en demandar un novèl senhal" +#: front/src/components/auth/Authorize.vue:6 +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Error en autorizar l’aplicacion" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Error en cambiar lo senhal" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Error en crear lo domeni" +#: front/src/components/moderation/FilterModal.vue:13 +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Error en crear lo filtre" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Error en crear l’invitacion" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Error en crear la règla" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Error en recuperar las donadas de l’aplicacion" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Error en recuperar las informacions del nos" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Error en enregistrar los paramètres" + +#: front/src/components/federation/FetchButton.vue:73 +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Error en enregistrar los paramètres" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Error en enviar la modificacion" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Perturbat" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Fichièrs amb errors" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Tot lo monde" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Lo monde d’aquesta instà ncia" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Tot lo monde, per totas las instà ncias" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Exclure" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Data d’expiracion" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Expirada" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Expirada/utilizada" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" +msgstr "Explicatz perque aplicatz aquesta politica. Segon la configuracion de l’instà ncia, aquò vos ajudarà a vos rapelar perque avètz agit sus aqueste compte o domeni, e aquò pòt èsser mostrat publicament per ajudar als utilizaires a comprendre qualas règlas de moderacion son en plaça." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Fracà s" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Pistas en error :" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Pistas en favorit" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favorits" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favorits" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federacion" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "ID Federacion" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "Camp" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nom del fichièr" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Fichièrs" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Nom del filtre" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +msgctxt "Content/Library/*" msgid "Finished" msgstr "Acabat" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "Primièra aparicion" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Data de primièra aparicion" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Seguir" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Seguir de bibliotècas alonhadas" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Abonament en espèra de validacion" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Seguidors" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Seguidors" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Abonat" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "De l’album %{ album } per %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Seguir" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "De l’album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> per <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" + +#: front/src/components/auth/Authorize.vue:28 +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Accès complèt" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale es compatible amb d’autres lectors de musica compatibles amb l’API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale es simple d’utilizar." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale es concebut per facilitar l’escota de las musicas que vos agradan e descobrir de novèls artistas." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale es a gratÃs e vos dòna lo contròla de vòstra musica." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale prend cura de vòstra musica" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Acrochis generals" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Obténer una novèla invitacion" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Menatz-me a la bibliotèca" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" -msgstr "Obtenètz de metadonadas de qualitat per vòstra musica grà cia a <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" +msgstr "" +"Obtenètz de metadonadas de qualitat per vòstra musica grà cia a <a href=\"%{ " +"url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Començar" +#: front/src/components/library/ImportStatusModal.vue:45 +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Obténer d’ajuda" + #: front/src/components/Footer.vue:37 +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "Obténer d’ajuda" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Zo" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Tornar a l’acuèlh" +#: front/src/components/auth/Settings.vue:128 +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Artistas amagats" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "Amagar lo contengut del compte o del demoni, levat pels seguidors." +#: front/src/components/moderation/FilterModal.vue:40 +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Amagar lo contengut" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "Amagar lo contengut d’aqueste artista" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "Amagar lo contengut d’aqueste artista…" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Acuèlh" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Oras de musica" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Pr’aquò, accedir a Funkwhale d’un client estant demanda un senhal diferent que podètz configurar çai-jos." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Se l’adreça qu’avètz provesida es valida e associada a un compte utilizaire, sètz per recebre un messatge amb las consignas de reïnicializacion d’aquà una estona." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Data d’import" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" +"S’autorizatz de tèrças aplicacions a accedir a vòstras donadas, serà n " +"listadas aquÃ." -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importatz la musica de diferentas plataforma, coma YouTube o Soundcloud" +#: front/src/components/library/ImportStatusModal.vue:3 +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Detalhs de l’import" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Importar la referéncia" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Estatut de l’import" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Estatut de l’import" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importat" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Data d’import" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "Connexion impossibla al servidor alonhat" + +#: front/src/components/moderation/FilterModal.vue:26 +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Dins lo widget « Ajustada i a res »" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "Dins las listas d’artistas e d’albums" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Als favorits" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "Dins los favorits e istorics d’escota d’autres utilizaires" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "Dins las suggestions de rà dios" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Actiu" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Aumentar lo volum" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Picatz l’adreça de corrièl ligada a vòstre compte" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Inserir de la fila (%{ count } pista)" msgstr[1] "Inserir de la fila (%{ count } pistas)" +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Instà ncia" + #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Donadas de l’instà ncia" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Info. de l’instà ncia" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Rà dios de l’instà ncia" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Paramètres de l’intà ncia" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "URL de l’instà ncia" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" -msgstr "" -"Tipe de fichièr pas valid, asseguratz-vos d’enviar un fichièr à udio. Las " -"extensions compatiblas son %{ extensions }" +msgstr "Tipe de fichièr pas valid, asseguratz-vos d’enviar un fichièr à udio. Las extensions compatiblas son %{ extensions }" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" +msgstr "Metadonada invalida" + +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Còdi d’invitacion" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Còdi d’invitacion (opcional)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Invitacions" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Traçador de problèmas" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "Connexion impossibla a l’URL donada" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Gardatz una traça de vòstras cançons favoritas" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "Acorchis clavièr" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Comptes coneguts" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Bibliotècas conegudas" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Darrièra activitat" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Darrièra verificacion" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Darrièra modificacion" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Darrièra visita" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Darrièra visita" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Darrièra actualizacion :" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Aviar" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Ne saber mai tocant aquesta instà ncia" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Daissar void per obténer un còdi aleatòri" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Daissar void per un widget adaptatiu" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +msgctxt "*/*/*/Noun" msgid "Libraries" msgstr "Bibliotècas" +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +msgctxt "*/*/*" +msgid "Libraries" +msgstr "Bibliotècas" + +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Bibliotèca e mandadÃs" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Las bibliotècas vos ajudan a organizar e partejar vòstras colleccions de musica. Podètz enviar vòstra pròpria collecion musicala a Funkwhale e la partejar amb vòstres amics e vòstra familha." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +msgctxt "*/*/*" msgid "Library" msgstr "Bibliotèca" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Bibliotèca creada" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Donadas de bibliotèca" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Bibliotèca suprimida" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" -msgstr "Fichièrs de la bibliotèca" +#: front/src/views/admin/library/EditsList.vue:4 +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" +msgstr "Modificacions de la bibliotèca" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Bibliotèca actualizada" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Licéncia" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "Escotas" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "Escotas" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Ne cargar mai…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Cargament dels seguidors…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Cargament de las bibliotècas…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Cargament de las donadas de la bibliotèca…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Cargament de las notificacions…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." -msgstr "Cargament de las bibliotècas alonhadas..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" +msgstr "Cargament de las bibliotècas alonhadas…" #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Cargament de l’utilizacion de las donadas…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Cargament dels favorits…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "Local" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Compte local" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Connexion" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Connectatz-vos a vòstre compte Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Desconnexion" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Connectat coma %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Connexion" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Estat del compte" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Desconnexion" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." -msgstr "" -"Sembla qu’avètz pas cap de bibliotèca pel moment, es ora de ne crear una." +msgstr "Sembla qu’avètz pas cap de bibliotèca pel moment, es ora de ne crear una." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Repeticion desactivada. Clicatz per activar la repeticion de la pista actuala." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Repeticion de la pista actuala. Clicatz per activar la repeticion de tota la fila." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Repeticion de tota la fila, clicatz per desactivar la repeticion." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Paraulas" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Menú mà ger" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Gerir la bibliotèca" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Gerir las listas de lectura" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Gerir los utilizaires" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Gerir vòstras listas de lectura" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Las marcar totas coma legidas" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Marcar coma legidas" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Marcar coma pas legidas" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "Mo" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "Lector mèdia" +#: front/src/components/auth/Profile.vue:12 +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Membre dempuèi %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "Aplicacions mobil e de burèu" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Moderacion" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"Las politicas de moderacion vos ajudan a contrarotlar cossà vòstra instà ncia " -"deu interagir amb un compte o domeni donat." +msgstr "Las politicas de moderacion vos ajudan a contrarotlar cossà vòstra instà ncia deu interagir amb un compte o domeni donat." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Modificacion %{ id }" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Data de modificacion" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "Mai…" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Musica" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Copar lo son" -#: front/src/components/Sidebar.vue:34 -msgid "My account" -msgstr "Mon compte" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Amagar l’activitat" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Amagar las notificacions" + +#: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" +msgid "My account" +msgstr "Mon compte" + +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Ma descripcion tròp crana" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Ma bibliotèca tròp crana" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Ma lista de lectura tròp crana" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Ma rà dio tròp crana" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Mas bibliotècas" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "ND" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +msgctxt "*/*/*" +msgid "Name" +msgstr "Nom" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nom" + +#: front/src/components/auth/ApplicationForm.vue:9 +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nom" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nòu senhal" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Las novèlas pistas serà n automaticament ajustadas aquÃ." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "Nòva valor" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Pista seguenta" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Non" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Cap d’extension d’installar, vos cal pas qu’una bibliotèca sul web" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Cap d’album correspond pas a vòstra recèrca" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Cap d’artista correspond pas a vòstra recèrca" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." -msgstr "Cap de paraulas pas disponiblas per aquesta pista." +#: front/src/components/library/TrackDetail.vue:14 +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" +msgstr "" +"Cap d’informacions pas disponiblas per aquesta pista tocant los dreches " +"d’autor" + +#: front/src/components/library/TrackDetail.vue:25 +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Avèm pas cap d’informacion de licéncia per aquesta pista" #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Cap de bibliotèca correspondenta." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." -msgstr "Cap de notificacion pel moment." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." +msgstr "Cap de notificacion de mostrar." + +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "Cap de resultat pas trobat." #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Degun fòra ieu" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Degun sèc pas aquesta bibliotèca" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Pas utilizat" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Notificacions" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Notificacions" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Site oficial" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Senhal precedent" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "Anciana valor" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Accès liure" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Dobrir dins l’interfà cia de moderacion" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Dobrir lo perfil local" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Veire sus MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Dobrir lo perfil" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Dobrir lo perfil alonhat" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Dobrir lo site web" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "O personalizatz aquesta règla" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Ã’rdre" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Ã’rdre" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Direccion" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Proprietari" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Pagina pas trobada" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Pagina pas trobada !" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "Paginacion" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Senhal" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Senhal actualizat" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Senhal corrèctament modificat" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Pausar" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "Pausar/Legir la pista actuala" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "En pausa" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "En espèra" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "En espèra de validacion" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Fichièrs en espèra" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Demandas d’abonament en espèra" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "En espèra de validacion" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Fichièrs en espèra" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Autorizacions" + +#: front/src/components/auth/Settings.vue:176 +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Autorizacions" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Legir" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "O legir tot" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Legir totes los albums" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Legir en seguida" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Legir la pista seguenta" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Legir ara" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Legir la pista precedenta" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "Legir de cançons similaras" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Legir aquesta pista" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Legir" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Legir…" + +#: front/src/views/playlists/Detail.vue:91 +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Lista de lectura" #: front/src/views/playlists/Detail.vue:12 +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Lista de lectura contenent %{ count } pista, per %{ username }" msgstr[1] "Lista de lectura contenent %{ count } pistas, per %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Lista de lectura creada" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Editor de lista de lectura" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nom de la lista de lectura" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Lista de lectura actualizada" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilitat de la lista de lectura" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Listas de lectura" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Listas de lectura" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" -msgstr "Las listas de lectura ? Son aicÃ !" +msgstr "Las listas de lectura ? Las avèm" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Mercés de verificar que lo senhal es corrèct" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Mercés de verificar que lo nom d’utilizaire e lo senhal son corrèctes" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF o JPG. 2Mo al maximum. L’imatge serà retalhat en 400×400 pixèls." +#: front/src/views/admin/library/TrackDetail.vue:137 +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Posicions" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" -"Empacha lo compte o domeni de far veire de notificacions, levat pels " -"seguidors." +msgstr "Empacha lo compte o domeni de far veire de notificacions, levat pels seguidors." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "Apercebut" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Pista precedenta" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "Privat" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Error en explorant" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Contunhar" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Contunhar cap a la pagina de connexion" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Tractament" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Perfil" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1892,1088 +3137,1901 @@ msgstr "Tractament" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Purgar" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "Purgar los fichièrs amb errors ?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "Purgar los fichièrs en espèra ?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "Purgar los fichièrs ignorats ?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Fila" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "La fila es estada mesclada !" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Rà dio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Editor de rà dio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Nom de la rà dio" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nom de la rà dio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Rà dio actualizada" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +msgctxt "*/*/*" +msgid "Radios" +msgstr "Rà dios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Rà dios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "Lectura" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "Legissètz nòstra documentacion per aquesta error" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "Lectura sola" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "Accès lectura sola de las donadas utilizaire" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "Rason" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "Seguiments de bibliotècas recebuts" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Messatges recebuts" +#: front/src/components/library/EditForm.vue:27 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Modificacions recentas" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "Modificacions recentas en espèra de relectura" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Ajustats i a res" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Ajustadas als favorits i a res" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Escotadas i a res" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "URI de redireccion" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Actualizar" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Error en actualizar" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "Actualizar del servidor alonhat estant" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Actualizar las info del nos" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Actualizar las info del nos" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "Actualizacion reüssida" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "Actualizar lo contengut de la tabla" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Marcat dempuèi %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "L’actualizada es estada passada" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "Las inscripcions son tampadas sus aquestà instà ncia, aurètz de téner un còdi d’invitacion per vos marcar." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" -msgstr "utilizaire estandard" +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" +msgstr "Utilizaire estandard" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Regetar" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Regetar lo mèdia" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Regetat" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Relançar l’import" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Data de sortida" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "Espaci liure" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Bibliotècas alonhadas" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Las bibliotècas alonhadas apertenon a d’autres utilizaires del malhum. I podètz accedir tant que sián publicas o qu’ajatz l’autorizacion." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Tirar" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Suprimir l’avatar" +#: front/src/components/library/ArtistDetail.vue:12 +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Levar lo filtre" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Tirar dels favorits" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Las pistas importadas que lo servidor a pas encara tractadas serà n " -"complètament suprimidas. Lo quòta correspondent vos serà tornat." +msgstr "Las pistas importadas que lo servidor a pas encara tractadas serà n complètament suprimidas. Lo quòta correspondent vos serà tornat." #: front/src/views/content/libraries/Quota.vue:64 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "Aquò escafarà las pistas enviadas mas ignoradas pendent lo processús d’importacion per mantun rasons. Aquò tirarà complètament los fichièrs e vos donarà de nòu lo quòta escafat." +msgstr "Las pistas enviadas mas ignoradas pendent lo processús d’importacion per mantun rasons serà n complètament suprimidas. Vos donarà de nòu lo quòta escafat." #: front/src/views/content/libraries/Quota.vue:90 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "Aquò escafarà las pistas enviadas mas pas tractadas pel moment. Aquò tirarà los complètament los fichièrs e vos donarà de nòu lo quòta escafat." +msgstr "Las pistas enviadas mas pas complètament tractadas pel servidor serà n complètament suprimidas. Vos donarà de nòu lo quòta escafat." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Demandar un nòu senhal" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Demandar un nòu senhal per l’API Subsonic ?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Demandar un senhal" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "Tornar a la valor iniciala : %{ value }" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Reïnicializar lo senhal" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Relançar l’import" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Resultats per pagina" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "Restrénher a la modifications pas repassadas" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Tornar a la pagina de connexion" +#: front/src/components/library/ArtistDetail.vue:9 +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Validar mos filtres" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "Revocar" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "Revocar l’accès" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "Revocar l’accès a l’aplicacion « %{ application } » ?" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Règla" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Enregistrar" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Exploracion lançada" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Explorar ara" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Analisi en espèra" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Exploracion sautada (la darrièra es tròp recenta)" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Exploracion en espèra" - -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Explorat" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Explorada amb d’errors" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Exploracion… (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "Visibilitat" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "Visibilitats" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Recercar" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Cercar una bibliotèca alonhada" +#: front/src/components/manage/library/EditsCardList.vue:211 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Recercar per tÃtol, artista, domeni…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Recercar per domeni, actor, nom, descripcion…" + +#: front/src/components/manage/library/UploadsTable.vue:241 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Recercar per domeni, actor, nom, referéncia, sorga…" + +#: front/src/components/manage/library/ArtistsTable.vue:164 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Recercar per domeni, nom, ID MusicBrainz,…" + +#: front/src/components/manage/library/TracksTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Recercar per tÃtol, artista, album, ID MusicBrainz…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Recercar per tÃtol, artista, ID MusicBrainz…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." -msgstr "Recercar per domenu, nom d’utilizaire, bio…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Recercar per domeni, nom d’utilizaire, bio…" #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." -msgstr "Recercar per nom d’utilizaire…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Recercar per nom…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "Recercar per tÃtol, artista, album…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Recercar per tÃtol, artista, domeni…" - #: front/src/components/manage/users/InvitationsTable.vue:153 +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Recercar per nom d’utilizaire, corrièl, còdi…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Recercar per nom d’utilizaire, corrièl, nom…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Recercar d’artistas, d’albums, de pistas…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Recercar de musica" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Recercar sus lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Recercar sus Wikipèdia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "Menú segondari" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Seccions" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Seleccionar un filtre" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Seleccionar l’ensemble dels %{ total } element" msgstr[1] "Seleccionar l’ensemble dels %{ total } elements" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Seleccionar solament la pagina actuala" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Paramètres" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Paramètres actualizats" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Paramètres corrèctament modificats." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Ligam de partatge" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Partajatz aqueste ligam amb d’autres utilizaires per que pòscan accedir a " -"vòstra bibliotèca." +msgstr "Partajatz aqueste ligam amb d’autres utilizaires per que pòscan accedir a vòstra bibliotèca." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Ligam de partatge" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "%{ count } pista mai" msgstr[1] "%{ count } pistas mai" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Mostrar 1 album mai" msgstr[1] "Mostrar %{ count } albums mai" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "Mostrar totas las modificacions" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "Mostrar los acorchis clavièr disponibles" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Mostrar las notificacions legidas" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Mostrar/amagar lo senhal" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Afichatge dels resultats %{ start }-%{ end } sus %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Mesclar la fila" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Mesclar la fila" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Inscripcion" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Inscripcion" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Data d’inscripcion" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 -msgid "Silence activity" -msgstr "Far venir l’activitat silenciosa" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Far venir las notificacions silenciosas" +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Talha" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Talha" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Ignorat" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Fichièrs ignorats" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Logicial" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "Unas pistas de la fila son ja dins aquesta lista de lectura :" + +#: front/src/components/PageNotFound.vue:10 +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "O planhèm, la pagina demandada existÃs pas :" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Còdi font" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Membre de la còla" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Aviar" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Aviar la rà dio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Estatisticas" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" +msgstr "Las estatisticas son calculadas amb las activitats conegudas e lo contengut de vòstra instà ncia, son pas lo rebat de l’activitat generala d’aqueste compte" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" +msgstr "Las estatisticas son calculadas amb las activitats conegudas e lo contengut de vòstra instà ncia, son pas lo rebat de l’activitat generala d’aqueste domeni" + +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "Las estatisticas son calculadas amb las activitats conegudas e lo contengut de vòstra instà ncia, son pas lo rebat de l’activitat generala d’aqueste compte" + +#: front/src/components/library/FileUpload.vue:95 +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Estatut" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Estatut" + +#: front/src/components/manage/library/EditsCardList.vue:12 +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Estatut" + +#: front/src/components/manage/users/UsersTable.vue:43 +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Estatut" -#: front/src/components/library/FileUpload.vue:86 #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Estatut" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Arrestar" +#: front/src/views/content/libraries/Detail.vue:28 +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Estatut" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Arrestar la rà dio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Validar" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "Enviar e aplicar la modificacion" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "Enviar una modificacion de mai" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "Enviar la suggestion" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Senhal de l’API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "Suggerir un cambiament en utilizar lo formulari çai-jos." + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Podèm pas cargar aquesta pisata" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Podèm pas cargar aquesta pisata" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Podèm pas cargar aquesta pisata" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Suggestions" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Resumit" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "Resumit (opcional)" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Forum d’assisténcia" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Extensions compatiblas : %{ extensions }" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Sincronizacion dels cambiaments amb lo servidor…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Tèxte copiat al quichapapièr !" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Es simple : nos agradava Grooveshark e voliam construire quicòm de melhor." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "L’aplicacion demanda tanben las autorizacions desconegudas seguentas :" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "Lo logotipe de Funkwhale foguèt dessenhat e fornit per Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "L’adreça donada es pas un servidor Funkwhale" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "Aquesta bibliotèca e totas sas pistas serà n suprimidas. Aquesta accion se pòt pas anullar." + +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -"Aquesta bibliotèca e totas sas pistas serà n suprimidas. Aquesta accion se " -"pòt pas anullar." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" -msgstr "Los fichièrs musicals que sètz a enviar son etiquetats coma cal :" +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." -msgstr "La pista seguenta serà legida automaticament dins una estona..." +#: front/src/components/library/FileUpload.vue:38 +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." +msgstr "Los fichièrs musicals que sètz a enviar son etiquetats coma cal." -#: front/src/components/Home.vue:121 +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" +msgstr "La pista seguenta serà legida automaticament dins una estona…" + +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "La plataforma es gratuita e liura, podètz l’installar e la modificar sens cap de limit" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Lista de lectura creada" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "La suggestion serà complètament tirada, aquesta accion es irreversibla." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "Se pòt que lo servidor siá atudat" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "L’API Subsonic es pas disponibla per aquesta instà ncia Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "La suggestion serà complètament tirada, aquesta accion es irreversibla." + +#: front/src/components/playlists/PlaylistModal.vue:34 +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Podèm pas ajustar aquesta pista a una lista de lectura" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "La pista pòt pas èsser cargada" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "La suggestion serà complètament tirada, aquesta accion es irreversibla." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Los fichièrs enviats son al format OGG, Flac o MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "" -"Prepausam mantun biais de recuperar de nòu contengut e de lo far venir " -"disponible aquÃ." +msgstr "Prepausam mantun biais de recuperar de nòu contengut e de lo far venir disponible aquÃ." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Aquesta accion se pòt pas anullar." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Aqueste album es present a las bibliotècas seguentas :" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Aqueste artista es present a las bibliotècas seguentas :" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "Aqueste domeni es sosmés a de règlas de moderacion especificadas" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "Aquesta instà ncia provesÃs fins a %{quota} d’espaci per cada utilizaire." + +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." msgstr "" -"Aquesta instà ncia provesÃs fins a %{quota} d’espaci per cada utilizaire." #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Sètz vos !" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Aquesta bibliotèca conten ma musica personala, espèri que vos agradarà ." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Aquesta bibliotèca es privada e son proprietari deu validar vòstra demanda d’" -"accès per que accediscatz a son contengut" +msgstr "Aquesta bibliotèca es privada e son proprietari deu validar vòstra demanda d’accès per que accediscatz a son contengut" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" -msgstr "" -"Aqueste bibliotèca es publica e podètz accedir a son contengut liurament" +msgstr "Aqueste bibliotèca es publica e podètz accedir a son contengut liurament" -#: front/src/components/common/ActionTable.vue:45 -#, fuzzy +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." -msgstr "Aquesta operacion pòt afectar mantun elements, mercés de verificar s’es ben çò que desiratz." +msgstr "Aquesta operacion pòt afectar mantun elements o pòt pas èsser anullada, mercés de verificar s’es ben çò que desiratz." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Utilizarem aquesta referéncia per gropar los fichièrs importats amassa." -#: front/src/components/audio/PlayButton.vue:73 -msgid "This track is not available in any library you have access to" +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" msgstr "" -"Aquesta pista es pas disponibla dins cap de las bibliotècas qu’avètz accès" +"Una error s’es producha en tractar aquesta pista, asseguratz-vos qu’es " +"corrèctament etiquetada" -#: front/src/components/library/Track.vue:171 +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "La pista es enviada mas pas encara tractada pel servidor" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "La pista ja presenta dins una de vòstras bibliotècas" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" +msgid "This track is not available in any library you have access to" +msgstr "Aquesta pista es pas disponibla dins cap de las bibliotècas qu’avètz accès" + +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Aquesta pista es presenta a las bibliotècas seguentas :" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Aquò escafarà aquesta lista de lectura per totjorn e poirà pas èsser anullat." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Aquò escafarà aquesta rà dio per totjorn e poirà pas èsser anullat." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Aquò desactivarà complètament l’accès a l’API Subsonic de vòstre compte estant." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" -"Aquò escafarà vòstras donadas localas e vos desconnectarà , volètz contunhar ?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Aquò vos desconnectarà de totes los periferics qu’utilizan aqueste senhal." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Aquò escafarà aquesta aplicacion per totjorn e totes los getons ligats." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Aquò escafarà totas las pistas de la lista de lectura e poirà pas èsser anullat." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +msgctxt "*/*/*/Noun" msgid "Title" msgstr "TÃtol" +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "TÃtol" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "TÃtol" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "Per contunhar, seleccionatz una instà ncia Funkwhale que volètz vos i connectar. Picatz l’adreça dirèctament, o seleccionatz-ne una dins las en suggestion." + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "Cambiar lo tipe de lectura de la lista" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Talha totala" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Talha totala dels fichièrs d’aquesta bibliotèca" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Utilizaires totals" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Pista" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "La pista ja presenta dins una de vòstras bibliotècas" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Pista" + +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "Pista #%{ id } - %{ name }" + +#: front/src/views/admin/library/TrackDetail.vue:91 +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Donada de la pista" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Informacions de la pista" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Pista correspondent al filtre" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Nom de la pista" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "La pista es enviada mas pas encara tractada pel servidor" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Pistas" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "pistas" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Pistas" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Pistas d’aqueste artista" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Pistas en favorits" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "pistas escotadas" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Pistas correspondentas al filtre" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipe" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipe" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" msgstr "Jos règla de moderacion" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Quitar de seguir" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Quitar de seguir aquesta bibliotèca ?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." -msgstr "Malurosament, los gestionaris d’aquesta instà ncia completèron pas aquesta pagina." +#: front/src/components/About.vue:17 +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." +msgstr "Malurosament, los gestionaris d’aquesta instà ncia aguèron pas encara lo temps de completar pas aquesta pagina." + +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "Error desconeguda" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "Error desconeguda" #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Musica sens cap de limit" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Restablir lo son" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Actualizar" +#: front/src/components/auth/ApplicationForm.vue:64 +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Actualizar l’aplicacion" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Actualizar l’avatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Actualizar la bibliotèca" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Actualizar la règla de moderacion" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Actualizar la lista de lectura" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Actualizar los paramètres" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Actualizar vòstre senhal" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "MandadÃs" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Enviar un nòu avatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Actualizar lo contengut à udio" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Data de mandadÃs" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Data de mandadÃs" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "MandadÃs refusat, asseguratz-vos que lo fichièr es pas tròp grand e qu’avètz pas atenhut vòstre quòta" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"MandadÃs refusat, asseguratz-vos que lo fichièr es pas tròp grand e qu’avètz " -"pas atenhut vòstre quòta" #: front/src/views/content/Home.vue:7 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "Enviatz vòstres fichièrs musicals (mp3, ogg, flac, etc.) de vòstra bibliotèca personala estant dirèctament amb vòstre navigador per ne profechar aquÃ." +msgstr "Enviatz vòstres fichièrs musicals (MP3, OGG, FLAC, etc.) de vòstra bibliotèca personala estant dirèctament amb vòstre navigador per ne profechar aquÃ." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Enviar nòvas pistas" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Quòta de mandadÃs" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Temps de transferiment expirat, ensajatz tornamai" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Enviat" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "MandadÃs en cors" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "MandadÃs…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "MandadÃs" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +msgctxt "*/*/*" +msgid "Uploads" +msgstr "MandadÃs" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "MandadÃs" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Utilizar una autra instà ncia" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Garnissètz aqueste formulari per demandar un nòu senhal. Auretz un corrièl a vòstra adreça indicada contenent las consignas de reïnicializacion." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Utilizatz aqueste paramètre per activar/desactivar temporà riament la règla " -"sens la suprimir complètament." +msgstr "Utilizatz aqueste paramètre per activar/desactivar temporà riament la règla sens la suprimir complètament." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Utilizat" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Utilizaire" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Activitat dels utilizaires" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Bibliotècas de l’utilizaire" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Rà dios dels utilizaires" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nom d’utilizaire" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nom d’utilizaire o corrièl" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "utilizaires" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Utilizaires" #: front/src/components/Footer.vue:29 +msgctxt "Footer/*/Title" msgid "Using Funkwhale" -msgstr "Utilizar de Funkwhale" +msgstr "Utilizar Funkwhale" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Version %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Veire los fichièrs" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "Veire sul panèl d’admin de Django" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Veire sus MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Visibilitat" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Visibilitat : lo monde d’aquesta instà ncia" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Visibilitat : totes, tanben las autras instà ncias" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Visibilitat : degun levat ieu" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilitat" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Volum %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Podèm pas ajustar aquesta pista a una lista de lectura" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Podèm pas crear aquesta lista de lectura" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Podèm pas crear vòstre compte" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Podèm pas cargar aquesta pisata" +#: front/src/components/federation/FetchButton.vue:69 +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "En espèra dels resultats…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Error pendent la connexion" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Podèm pas enregistrar vòstre avatar" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Podèm pas enregistrar vòstres paramètres" +#: front/src/components/auth/ApplicationForm.vue:3 +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Podèm pas enregistrar vòstras modificacions" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Vos pistam pas e vos mostram pas cap de reclama" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Avèm pas cap d’informacion de copyright per aquesta pista" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Avèm pas cap d’informacion de licéncia per aquesta pista" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Vos recomandam d’utilizar lo logicial Picard per aquò far." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Pensam que l’accès a la musica deuriá èsser simple." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "O planhèm, la pagina demandada existÃs pas :" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "La benvenguda" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "La benvenguda a Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Perque Funkwhale ?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Nautor del widget" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "Largor del widget" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "Escritura" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "Escritura sola" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Ã’c" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Ã’c-ben, desconnectatz-me !" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" -"Poiretz partejar vòstra bibiotèca amb d’autres gents, sens importà ncia de sa " -"visibilitat." +msgstr "Poiretz partejar vòstra bibiotèca amb d’autres gents, sens importà ncia de sa visibilitat." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Sètz a man d’enviar de la musica a vòstra bibliotèca. Abans de començar, mercés de vos assegurar que :" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "Sètz actualament connectat a <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. Se contunhatz, seretz desconnectat de l’instà ncia actuala e totas vòstras donadas localas serà n suprimidas." + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "Amagatz lo contengut ligat a aqueste artista." + #: front/src/components/auth/Logout.vue:7 +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Sètz connectat coma %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "Envietz pas cap de contengut jos dreches a una bibliotèca publica, autrament enfranhètz la lei" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "Utilizatz una instà ncia Funkwhale a %{ url }" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Podètz seguir las bibliotècas d’autres utilizaires per accedir a de nòvas musicas. Las bibliotècas publicas pòdon èsser seguidas còp sec, mentre qu’una bibliotèca privada demanda una aprovacion de sus proprietaris." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Podètz convidar vòstres amics e vòstra familha a aquesta instà ncia per que pòscan profeitar de vòstra musica" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "Podètz gerir e actualizar los filtres quand volgatz dels paramètres de compte estant." + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Podètz ara utilizar lo servici sens cap de limitacions." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Podètz utilizar aquesta interfà cia per realizar vòstra pròpria rà dio personalizada, que jogarà las listas segon los critèris indicats." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Podètz los utilizar per profeitar de vòstras listas de lectura e de vòstra musica en mòde fòra linha sus vòstre mobil, tableta, per exemple." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Avètz pas cap d‘aplicacion connectada a aqueste compte." + +#: front/src/components/auth/Settings.vue:261 +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Avètz pas cap d‘aplicacion configurada pel moment." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Avètz pas cap de règla en plaça per aqueste compte." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Avètz pas cap de règla en plaça per aqueste domeni." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "Avètz pas l’autorizacion de modificar aqueste objècte, mas podètz suggerir de cambiaments. Un còp mandadas, las suggestions serà n repassadas abans validacion." + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Escotatz una rà dio" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "Poiriá arribar qu’ajatz de problèma de connexion." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Vos cal causir una instà ncia per contunhar" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Sètz per èsser desconnectat d’aquesta session e vos caldrà vos connectar amb lo nòu senhal" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "Vos mostrarem un còdi de copiar-pegar dins l’aplicacion." + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Vos caldrà actualizar lo senhal sus totes los clients qu’utilizan aqueste senhal." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "Veiretz pas mai las pistas, albums e las activitats d’utilizaires ligadas a aqueste artista :" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "L’avatar pòt pas èsser enregistrat" + +#: front/src/components/auth/Settings.vue:215 +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Vòstras aplicacions" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "L’avatar pòt pas èsser enregistrat" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "La modificacion es estada corrèctament enviada." + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Vòstres favorits" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Vòstra musica, coma volètz" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Vòstras notificacions" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "Lo senhal pòt pas èsser cambiat" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Vòstre senhal es corrèctament cambiat." +#: front/src/components/auth/Settings.vue:14 +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Impossible d’actualizar vòstres paramètres" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Vòstre senhal Subsonic serà remplaçat per un nòu aleatòri, aquò vos desconnectarà de totes los periferics qu’utilizan l’ancian senhal" + +#: front/src/edits.js:47 +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Posicions" + +#: front/src/edits.js:54 +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Copyright" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Album contenent %{ count } pista, de %{ artist }" +msgstr[1] "Album contenent %{ count } pistas, de %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } pista ajustada a la fila" +msgstr[1] "%{ count } pistas ajustadas a la fila" diff --git a/front/locales/pl/LC_MESSAGES/app.po b/front/locales/pl/LC_MESSAGES/app.po index 7f0047855aae07ee819d5eaaff6b2d0f1b898754..a49742b58262bfa77bac6667925d1aa07b65aa8d 100644 --- a/front/locales/pl/LC_MESSAGES/app.po +++ b/front/locales/pl/LC_MESSAGES/app.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" "PO-Revision-Date: 2018-11-11 08:36+0000\n" "Last-Translator: Marcin MikoÅ‚ajczak <me@m4sk.in>\n" "Language-Team: \n" @@ -19,56 +19,67 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "„%{ title }â€, od %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } z %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(pusta)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Zaloguj siÄ™ na swoje konto Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } z %{ total } zaznaczonego" msgstr[1] "%{ count } z %{ total } zaznaczonych" msgstr[2] "%{ count } z %{ total } zaznaczonych" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } utwór" msgstr[1] "%{ count } utwory" msgstr[2] "%{ count } utworów" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } utwór w %{ albumsCount } albumach" msgstr[1] "%{ count } utwory w %{ albumsCount } albumach" msgstr[2] "%{ count } utworow w %{ albumsCount } albumach" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } utwór zgodny z wybranymi filtrami" msgstr[1] "%{ count } utwory zgodne z wybranymi filtrami" msgstr[2] "%{ count } utworów zgodnych z wybranymi filtrami" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "Dodano %{ count } utwór do kolejki" -msgstr[1] "Dodano %{ count } utwory do kolejki" -msgstr[2] "Dodano %{ count } utworów do kolejki" - #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} utwór" @@ -76,34 +87,48 @@ msgstr[1] "%{ count} utwory" msgstr[2] "%{ count} utworów" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "Wykorzystano %{ count } z dozwolonego %{ total }" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } godz. %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Profil %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 album" @@ -111,78 +136,190 @@ msgstr[1] "%{ count } albumy" msgstr[2] "%{ count } albumów" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 polubienie" msgstr[1] "%{ count} polubienia" msgstr[2] "%{ count} polubieÅ„" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Przejrzysta biblioteka" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d sieci podczas wysyÅ‚ania tego pliku" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania zmian" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "O %{ instance }" #: front/src/components/Footer.vue:6 -#, fuzzy +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "O %{ instance }" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "O Funkwhale" #: front/src/components/Footer.vue:10 -#, fuzzy +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Strona albumu" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "O tej instancji" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Zaakceptuj" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Zaakceptowano" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Brak dostÄ™pu" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Zaznacz filtr" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Brak dostÄ™pu" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Dodaj do ulubionych" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Twoje powiadomienia" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Dodaj do playlisty…" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Brak dostÄ™pu" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" msgstr "Uzyskaj dostÄ™p do swojej muzyki z przejrzystego interfejsu skupionego na tym, co naprawdÄ™ ważne" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Data dostÄ™pu" +msgstr "Brak dostÄ™pu" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Stan konta" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 #, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Stan konta" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Konto aktywne" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Ustawienia konta" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Ustawienia konta" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Stan konta" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Adres e-mail konta" @@ -190,1682 +327,2826 @@ msgstr "Adres e-mail konta" #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 #, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Stan konta" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "DziaÅ‚anie" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "DziaÅ‚anie %{ action } zostaÅ‚o rozpoczÄ™te pomyÅ›lnie na %{ count } elemencie" msgstr[1] "DziaÅ‚anie %{ action } zostaÅ‚o rozpoczÄ™te pomyÅ›lnie na %{ count } elementach" msgstr[2] "DziaÅ‚anie %{ action } zostaÅ‚o rozpoczÄ™te pomyÅ›lnie na %{ count } elementach" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "DziaÅ‚ania" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Aktywny" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Aktywność" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Widoczność aktywnoÅ›ci" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" -msgstr "" +msgstr "UsuÅ„ radio" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Dodaj i zarzÄ…dzaj zawartoÅ›ciÄ…" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Dodaj zawartość" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Dodaj filtr" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Dodaj filtry aby dostosować swoje radio" -#: front/src/components/audio/PlayButton.vue:64 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Dodaj do kolejki" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Dodaj do ulubionych" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 -#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Dodaj do playlisty…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Dodaj do kolejki" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Dodaj do tej playlisty" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Dodaj utwór" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Administrator" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administracja" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Album" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Album zawiera %{ count } utwór od %{ artist }" -msgstr[1] "Album zawiera %{ count } utwory od %{ artist }" -msgstr[2] "Album zawiera %{ count } utworów od %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Album" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Albumy tego wykonawcy" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Nazwa albumu" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Strona albumu" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Nazwa albumu" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Albumy" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Albumy tego wykonawcy" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Wszystkie" +#: front/src/components/common/ActionTable.vue:59 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } z %{ total } zaznaczonego" +msgstr[1] "%{ count } z %{ total } zaznaczonych" +msgstr[2] "%{ count } z %{ total } zaznaczonych" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania zmian" +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania zmian" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania zmian" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "WystÄ…piÅ‚ nieznany bÅ‚Ä…d, może oznaczać to że serwer jest wyÅ‚Ä…czony lub nieosiÄ…galny" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "DziaÅ‚anie" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Czy na pewno chcesz siÄ™ wylogować?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Wykonawca" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Wykonawca" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Nazwa wykonawcy" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Strona wykonawcy" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Nazwa wykonawcy" #: front/src/components/audio/Search.vue:65 -#, fuzzy +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Wykonawca, album, utwór…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Wykonawcy" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Wykonawcy" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "RosnÄ…co" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "PoproÅ› o zresetowanie hasÅ‚a" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 -#, fuzzy +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Dodaj zawartość" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "DostÄ™pne playlisty" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Awatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Wróć do logowania" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Aktualizuj ustawienia" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrate" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" msgstr "" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "PrzeglÄ…daj" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "PrzeglÄ…daj bibliotekÄ™" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "PrzeglÄ…danie radiów" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "PrzeglÄ…danie wykonawców" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "PrzeglÄ…danie list odtwarzania" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "PrzeglÄ…danie radiów" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Budowanie" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Od %{ artist }" -#: front/src/views/content/remote/Card.vue:103 -#, fuzzy +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "Gdy przestaniesz obserwować tÄ™ bibliotekÄ™, stracisz dostÄ™p do jej zawartoÅ›ci." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Anuluj" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Kandydaci" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Nie udaÅ‚o siÄ™ zmienić Twojego hasÅ‚a" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 -#, fuzzy +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "Nie można byÅ‚o zaimportować tego pliku, upewnij siÄ™ że nie jest zbyt duży" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "ZmieÅ„ jÄ™zyk" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "ZmieÅ„ moje hasÅ‚o" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "ZmieÅ„ hasÅ‚o" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "ZmieÅ„ swoje hasÅ‚o" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Zmienić Twoje hasÅ‚o?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Zsynchronizowano zmiany z serwerem" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Zmiana Twojego hasÅ‚a zmieni też Twoje hasÅ‚o API Subsonic, jeżeli uzyskaÅ‚eÅ›(-aÅ›) je." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "Zmiana hasÅ‚a bÄ™dzie miaÅ‚a nastÄ™pujÄ…ce konsekwencje" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Wybierz instancjÄ™" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Przejrzysta biblioteka" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Wyczyść" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Wyczyść" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Wyczyść listÄ™ odtwarzania" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Wyczyść swojÄ… kolejkÄ™" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "NaciÅ›nij raz, sÅ‚uchaj godzinami dziÄ™ki wbudowanemu radio" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "NaciÅ›nij aby wybrać pliki do wysÅ‚ania lub przeciÄ…gnij i upuść pliki lub katalogi" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Kod" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "ZwiÅ„" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Konfiguracja" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Potwierdź" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 -#, fuzzy +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Potwierdź swój e-mail" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Kod potwierdzajÄ…cy" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Zaznacz filtr" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Zaznacz filtr" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Kopiuj" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "Skopiuj utwory z obecnej kolejki do listy odtwarzania" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "" -#: front/src/components/library/Track.vue:91 -#, fuzzy +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Kopiuj" #: front/src/views/auth/EmailConfirm.vue:7 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Potwierdź swój e-mail" #: front/src/views/content/remote/ScanForm.vue:3 -#, fuzzy +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas uzyskiwania zdalnej biblioteki" -#: front/src/views/content/libraries/FilesTable.vue:213 -#, fuzzy -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "WystÄ…piÅ‚ bÅ‚Ä…d w trakcie przetwarzania tego utworu, upewnij siÄ™ że posiada on poprawne metadane" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Covery, teksty, naszym celem jest mieć je wszystkie ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Utwórz importowanie" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Utwórz konto funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Utwórz nowÄ… listÄ™ odtwarzania" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Utwórz nowÄ… listÄ™ odtwarzania" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Utwórz nowÄ… bibliotekÄ™" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Utwórz nowÄ… listÄ™ odtwarzania" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Utwórz konto" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Utwórz listÄ™ odtwarzania" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Utwórz bibliotekÄ™" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Utwórz konto" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Utwórz listÄ™ odtwarzania" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Utwórz wÅ‚asne radio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Data utworzenia" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Obecny awatar" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Obecna biblioteka" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Obecny utwór" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Obecne użycie" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Data" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Informacje o utworze" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "UsuÅ„" -#: front/src/views/content/libraries/Form.vue:39 -msgid "Delete library" -msgstr "UsuÅ„ bibliotekÄ™" - -#: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +#: front/src/components/auth/Settings.vue:254 #, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "UsuÅ„ listÄ™ odtwarzania" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" +msgid "Delete library" +msgstr "UsuÅ„ bibliotekÄ™" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "UsuÅ„ radio" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "UsuÅ„ listÄ™ odtwarzania" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "UsuÅ„ radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 -#, fuzzy +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Czy chcesz usunąć tÄ™ bibliotekÄ™?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "MalejÄ…co" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Opis" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Szczegóły" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Opis" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Szczegóły" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "OkreÅ›l jak dużo zawartoÅ›ci może zaimportować użytkownik. Pozostaw puste, aby użyć domyÅ›lnej wartoÅ›ci instancji" #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Ustal poziom widocznoÅ›ci twojej aktywnoÅ›ci" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "WyÅ‚Ä…cz dostÄ™p" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "WyÅ‚Ä…cz dostÄ™p Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "WyÅ‚Ä…czyć dostÄ™p do API Subsonic?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "WyÅ‚Ä…cz dostÄ™p" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Odkryj, jak korzystać z Funkwhale z innych aplikacji" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Nazwa pliku" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "WyÅ›wietlaj publicznie" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." msgstr "" -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Czy chcesz wyczyÅ›cić listÄ™ odtwarzania „%{ playlist }â€?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Czy chcesz potwierdzić to dziaÅ‚anie?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Czy chcesz usunąć listÄ™ odtwarzania „%{ playlist }â€?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Czy chcesz usunąć radio „%{ radio }â€?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Czy chcesz usunąć radio „%{ radio }â€?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Czy chcesz wykonać dziaÅ‚anie %{ action } na %{ count } elemencie?" msgstr[1] "Czy chcesz wykonać dziaÅ‚anie %{ action } na %{ count } elementach?" msgstr[2] "Czy chcesz wykonać dziaÅ‚anie %{ action } na %{ count } elementach?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Czy chcesz przywrócić poprzedniÄ… kolejkÄ™?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Dokumentacja" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Pobierz" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "PrzeciÄ…gnij i upuść aby zmienić kolejność utworów w liÅ›cie odtwarzania" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "DÅ‚ugość" #: front/src/views/auth/EmailConfirm.vue:23 -#, fuzzy +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "Potwierdzono e-mail" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Prosty w użyciu" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Edytuj" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Edytuj" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "BÅ‚Ä…d podczas zastosowywania dziaÅ‚ania" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Edytuj informacje o instancji" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "UsuÅ„ radio" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Odtwórz utwór" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Odtwórz utwór" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Odtwórz utwór" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Edytuj" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 #, fuzzy -msgid "Edit…" +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" msgstr "Edytuj" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "E-mail" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Adres e-mail" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 -#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "Wprowadź adres URL biblioteki" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +#, fuzzy +msgctxt "*/*/*" msgid "Enabled" -msgstr "" +msgstr "WyÅ‚Ä…cz dostÄ™p" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "ZakoÅ„cz edytowanie" #: front/src/views/content/remote/ScanForm.vue:50 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Wprowadź adres URL biblioteki" -#: front/src/components/library/Radios.vue:140 -#, fuzzy +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Wprowadź nazwÄ™ radia…" -#: front/src/components/library/Artists.vue:118 -#, fuzzy +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Wprowadź nazwÄ™ wykonawcy…" #: front/src/views/playlists/List.vue:107 -#, fuzzy +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Wprowadź nazwÄ™ listy odtwarzania…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Wprowadź swój e-mail" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Wprowadź swój kod zapraszajÄ…cy (wielkość znaków nie ma znaczenia)" #: front/src/components/metadata/Search.vue:114 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Wprowadź swoje kryterium wyszukiwania…" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Wprowadź swojÄ… nazwÄ™ użytkownika" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Wprowadź swojÄ… nazwÄ™ użytkownika lub e-mail" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "BÅ‚Ä…d" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "ZgÅ‚aszanie bÅ‚Ä™dów" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "ZgÅ‚aszanie bÅ‚Ä™dów" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "ZakoÅ„czono bÅ‚Ä™dem" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "BÅ‚Ä…d podczas zastosowywania dziaÅ‚ania" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas proÅ›by o zresetowanie hasÅ‚a" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "BÅ‚Ä…d podczas zastosowywania dziaÅ‚ania" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zmiany hasÅ‚a" #: front/src/views/admin/moderation/DomainsList.vue:6 -#, fuzzy +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas tworzenia zaproszenia" +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas tworzenia zaproszenia" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas tworzenia zaproszenia" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 -#, fuzzy +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas tworzenia zaproszenia" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 #, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas tworzenia zaproszenia" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas uzyskiwania zdalnej biblioteki" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania ustawieÅ„" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania ustawieÅ„" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d podczas zapisywania ustawieÅ„" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "ZakoÅ„czono bÅ‚Ä™dem" #: front/src/views/content/libraries/Quota.vue:75 -#, fuzzy +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "ZakoÅ„czono bÅ‚Ä™dem" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Wszyscy" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Wszyscy na tej instancji" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 #, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Wszyscy na tej instancji" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "WyÅ‚Ä…cz" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Data wygaÅ›niÄ™cia" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Wyczerpany" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Wyczerpany/zużyty" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +#, fuzzy +msgctxt "Content/Library/Dropdown" msgid "Failed" -msgstr "" +msgstr "Sfederowane utwory" -#: front/src/views/content/remote/Card.vue:58 -#, fuzzy +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Sfederowane utwory" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Sfederowane utwory" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Ulubione" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Ulubione" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federacja" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 #, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Federacja" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nazwa pliku" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Pliki" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Filtruj nazwÄ™" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "UkoÅ„czono" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" -msgstr "" +msgstr "Data wygaÅ›niÄ™cia" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 -#, fuzzy +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Data wygaÅ›niÄ™cia" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Åšledź" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Åšledź zdalne biblioteki" -#: front/src/views/content/remote/Card.vue:88 -#, fuzzy +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Åšledzenie oczekuje na zatwierdzenie" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "ÅšledzÄ…cy" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "ÅšledzÄ…cy" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Åšledzisz" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Z albumu %{ album } od %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Åšledź" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "WyÅ‚Ä…cz dostÄ™p" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale jest kompatybilny z innymi odtwarzaczami muzycznymi obsÅ‚ugujÄ…cymi API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale jest niezwykle prosty w użyciu." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale zostaÅ‚ zaprojektowany, aby uczynić sÅ‚uchanie muzyki którÄ… lubisz i poznawanie nowych wykonawców prostym." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale jest wolny i oddaje Ci kontrolÄ™ nad TwojÄ… muzykÄ…." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale dba o TwojÄ… bibliotekÄ™ muzycznÄ…" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Uzyskaj nowe zaproszenie" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Pokaż mi bibliotekÄ™" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Uzyskaj dobrej jakoÅ›ci metadane o Twojej muzyce dziÄ™ki <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Rozpocznij" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Ustawienia" + #: front/src/components/Footer.vue:37 #, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "Ustawienia" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Przejdź" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Przejdź na stronÄ™ głównÄ…" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "PrzeglÄ…danie wykonawców" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "" +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Dodaj zawartość" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Strona główna" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Godziny muzyki" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Korzystanie z Funkwhale z tych klientów wymaga jednak oddzielnego hasÅ‚a, które możesz ustawić poniżej." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Jeżeli adres e-mail podany w poprzednim kroku jest prawidÅ‚owy i przypisany do konta użytkownika, powinieneÅ› dostać wiadomość z instrukcjami resetowania hasÅ‚a w przeciÄ…gu kilku minut." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Data zaimportowania" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importuj muzykÄ™ z różnych platform, takich jak YouTube i SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Stan importu" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Importuj źródÅ‚o" -#: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Import status" msgstr "Stan importu" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/manage/library/UploadsTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:11 +#: front/src/views/content/libraries/FilesTable.vue:59 #, fuzzy +msgctxt "Content/Library/*/Noun" +msgid "Import status" +msgstr "Stan importu" + +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Data zaimportowania" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Data zaimportowania" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Ostatnio dodane" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "W ulubionych" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Nieaktywny" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Dodaj z kolejki (%{ count } utwór)" msgstr[1] "Dodaj z kolejki (%{ count } utwory)" msgstr[2] "Dodaj z kolejki (%{ count } utworów)" -#: front/src/views/admin/moderation/DomainsDetail.vue:71 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 #, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Radia instancji" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Radia instancji" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Informacje o instancji" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Radia instancji" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Ustawienia instancji" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Radia instancji" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" msgstr "" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" +msgstr "" + +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Kod zapraszajÄ…cy" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Kod zapraszajÄ…cy (nieobowiÄ…zkowy)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Zaproszenia" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Åšledzenie bÅ‚Ä™dów" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Zachowaj kontrolÄ™ nad ulubionymi utworami" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "" #: front/src/views/admin/moderation/DomainsDetail.vue:161 -#, fuzzy +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Moje konto" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Znane biblioteki" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Ostatnia aktywność" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#, fuzzy +msgctxt "Content/*/Table.Label" msgid "Last checked" -msgstr "" +msgstr "Ostatnia aktualizacja:" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Ostatnia modyfikacja" #: front/src/components/manage/moderation/AccountsTable.vue:43 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" -msgstr "" +msgstr "Ostatnia aktualizacja:" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 -#, fuzzy +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Ostatnia aktualizacja:" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Ostatnia aktualizacja:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Uruchom" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Dowiedz siÄ™ wiÄ™cej o tej instancji" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Pozostaw puste, aby wygenerować kod" #: front/src/components/audio/EmbedWizard.vue:7 -#, fuzzy +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Pozostaw puste, aby wygenerować kod" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Biblioteki" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Biblioteki" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Zaktualizowano bibliotekÄ™" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "Biblioteki pomagajÄ… organizować i udostÄ™pniać kolekcje muzyki. Możesz wysÅ‚ać wÅ‚asnÄ… kolekcjÄ™ muzyki na Funkwhale i dzielić siÄ™ niÄ… z rodzinÄ… i znajomymi." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Biblioteka" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Utworzono bibliotekÄ™" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 #, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Zaktualizowano bibliotekÄ™" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Zaktualizowano bibliotekÄ™" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Pliki z biblioteki" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Zaktualizowano bibliotekÄ™" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +msgctxt "Content/*/*/Noun" msgid "License" msgstr "" -#: front/src/views/content/libraries/Detail.vue:21 +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 #, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Åadowanie Å›ledzÄ…cych…" + +#: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Åadowanie Å›ledzÄ…cych…" #: front/src/views/content/libraries/Home.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Åadowanie bibliotek…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Åadowanie danych biblioteki…" -#: front/src/views/Notifications.vue:4 -#, fuzzy +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Åadowanie powiadomień…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" msgstr "Åadowanie zdalnych bibliotek…" #: front/src/views/content/libraries/Quota.vue:4 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Åadowanie danych o użyciu…" #: front/src/components/favorites/List.vue:5 -#, fuzzy +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Åadowanie Twoich ulubionych…" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 #, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Moje konto" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Zaloguj siÄ™" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Zaloguj siÄ™ na swoje konto Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Wyloguj siÄ™" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Zalogowano jako %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Logowanie" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Stan konta" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Wyloguj siÄ™" #: front/src/views/content/libraries/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "WyglÄ…da na to, że nie masz jeszcze żadnej biblioteki — czas na jej utworzenie!" -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "ZapÄ™tlanie jest wyÅ‚Ä…czone. NaciÅ›nij, aby przeÅ‚Ä…czyć na powtarzanie jednego utworu." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "ZapÄ™tlanie jednego utworu jest wÅ‚Ä…czone. NaciÅ›nij, aby przeÅ‚Ä…czyć na powtarzanie caÅ‚ej kolejki." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "ZapÄ™tlanie caÅ‚ej kolejki jest wÅ‚Ä…czone. NaciÅ›nij, aby wyÅ‚Ä…czyć zapÄ™tlanie." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Tekst" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "ZarzÄ…dzaj bibliotekÄ…" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "ZarzÄ…dzaj listami odtwarzania" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "ZarzÄ…dzaj użytkownikami" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "ZarzÄ…dzaj swoimi listami odtwarzania" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Oznacz wszystko jako przeczytane" -#: front/src/components/notifications/NotificationRow.vue:44 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Oznacz wszystko jako przeczytane" -#: front/src/components/notifications/NotificationRow.vue:45 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Oznacz wszystko jako przeczytane" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Zarejestrowany od %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 #, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Federacja" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." msgstr "" -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Data modyfikacji" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Data modyfikacji" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Muzyka" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Wycisz" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Ostatnia aktywność" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Twoje powiadomienia" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Moje konto" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Mój wspaniaÅ‚y opis" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Moja wspaniaÅ‚a biblioteka" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Moja wspaniaÅ‚a playlista" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Moje wspaniaÅ‚e radio" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Moje biblioteki" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "N/A" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nazwa" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nazwa" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nazwa" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nowe hasÅ‚o" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Nowe utwory automatycznie pojawiÄ… siÄ™ tutaj." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "NastÄ™pny utwór" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Nie" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Brak wtyczek, brak dodatków: potrzebujesz tylko biblioteki sieciowej" #: front/src/components/audio/Search.vue:25 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Przepraszamy, nie znaleziono albumu speÅ‚niajÄ…cego Twoje kryteria" #: front/src/components/audio/Search.vue:16 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Przepraszamy, nie znaleziono wykonawcy speÅ‚niajÄ…cego Twoje kryteria" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "Tekst nie jest dostÄ™pny dla tego utworu." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Brak pasujÄ…cej biblioteki." -#: front/src/views/Notifications.vue:26 -#, fuzzy -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." msgstr "Twoje powiadomienia" +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" + #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Nikt poza mnÄ…" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Nikt nie Å›ledzi tej biblioteki" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Nie użyty" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Powiadomienia" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Powiadomienia" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Oficjalna strona" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Poprzednie hasÅ‚o" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Otwórz" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "UsuÅ„ radio" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "WyÅ›wietl na MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 -#, fuzzy +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Oficjalna strona" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 -#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "Dodaj filtry aby dostosować swoje radio" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 -#, fuzzy +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "PorzÄ…dkowanie" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "PorzÄ…dkowanie" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Kolejność porzÄ…dkowania" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "WÅ‚aÅ›ciciel" #: front/src/components/PageNotFound.vue:33 -#, fuzzy +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Nie odnaleziono strony!" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Nie odnaleziono strony!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Haslo" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Zmieniono hasÅ‚o" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "PomyÅ›lnie zmieniono hasÅ‚o" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Wstrzymaj utwór" #: front/src/components/ShortcutsModal.vue:59 +#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" -msgstr "" +msgstr "Odtwórz utwór" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "OczekujÄ…ce" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Oczekiwanie na przyjÄ™cie" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "OczekujÄ…ce pliki" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "OczekujÄ…ce proÅ›by o możliwość Å›ledzenia" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "OczekujÄ…ce pliki" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "OczekujÄ…ce pliki" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Uprawnienia" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Uprawnienia" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Odtwórz" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Odtwórz wszystkie" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Odtwórz wszystkie albumy" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Odtwórz nastÄ™pny" #: front/src/components/ShortcutsModal.vue:67 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Odtwórz utwór" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Odtwórz teraz" #: front/src/components/ShortcutsModal.vue:63 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Poprzedni utwór" -#: front/src/components/Sidebar.vue:211 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Odtwórz utwór" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Odtwórz utwór" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Odtwórz" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Lista odtwarzania" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Lista odtwarzania zawierajÄ…ca %{ count } utwór od %{ username }" @@ -1873,77 +3154,122 @@ msgstr[1] "Lista odtwarzania zawierajÄ…ca %{ count } utwory od %{ username }" msgstr[2] "Lista odtwarzania zawierajÄ…ca %{ count } utworów od %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Utworzono listÄ™ odtwarzania" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Edytor list odtwarzania" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nazwa listy odtwarzania" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Zaktualizowano listÄ™ odtwarzania" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Widoczność listy odtwarzania" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Listy odtwarzania" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Listy odtwarzania" #: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "Listy odtwarzania? Mamy je" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Sprawdź dwukrotnie, czy Twoje hasÅ‚o jest poprawne" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Sprawdź dwukrotnie, czy poÅ‚Ä…czenie nazwy użytkownika i hasÅ‚a jest poprawne" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie pomniejszony do 400x400 pikseli." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Opis" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." msgstr "" -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Poprzedni utwór" -#: front/src/views/content/remote/Card.vue:39 -#, fuzzy +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "BÅ‚Ä…d podczas skanowania" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Przejdź" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Przejdź, aby zalogować siÄ™" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Przetwarzanie" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1952,348 +3278,606 @@ msgstr "Przetwarzanie" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Wyczyść" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "WyczyÅ›cić pliki z bÅ‚Ä™dami?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "WyczyÅ›cić oczekujÄ…ce pliki?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "WyczyÅ›cić pominiÄ™te pliki?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Kolejka" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Wymieszano kolejkÄ™!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Radio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Tworzenie radia" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Utworzono radio" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nazwa radia" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Zaktualizowano radio" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Radia" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Radia" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" -msgstr "" +msgstr "Wprowadź adres URL biblioteki" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Ostatnio dodane" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Ostatnio dodane" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Ostatnio dodane do ulubionych" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Ostatnio sÅ‚uchane" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "OdÅ›wież" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "OdÅ›wież" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "MalejÄ…co" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Zarejestrowany od %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "Rejestracja na tej instancji jest wyÅ‚Ä…czona, potrzebujesz kodu zapraszajÄ…cego aby zarejestrować siÄ™." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "zwykÅ‚y użytkownik" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Odrzuć" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 #, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Odrzucono" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Odrzucono" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Uruchom importowanie ponownie" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Ostatnia aktualizacja:" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Zdalne biblioteki" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Zdalne biblioteki należą do innych użytkowników sieci. Możesz uzyskać do nich dostÄ™p jeżeli sÄ… publiczne lub zostaÅ‚ Ci on przyznany." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "UsuÅ„" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "UsuÅ„ awatar" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "UsuÅ„ awatar" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "UsuÅ„ z ulubionych" #: front/src/views/content/libraries/Quota.vue:38 +#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Ta opcja usunie utwory które zostaÅ‚y wysÅ‚ane, ale nie zostaÅ‚y jeszcze przetworzone. Bezpowrotnie usunie te pliki i zostanie Ci przywrócona odpowiednia przestrzeÅ„." #: front/src/views/content/libraries/Quota.vue:64 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." msgstr "Ta opcja usunie utwory które zostaÅ‚y wysÅ‚ane, lecz zostaÅ‚y z jakiegoÅ› powodu pominiÄ™te w procesie importowania. Bezpowrotnie usunie te pliki i zostanie Ci przywrócona odpowiednia przestrzeÅ„." #: front/src/views/content/libraries/Quota.vue:90 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." msgstr "Ta opcja usunie utwory które zostaÅ‚y wysÅ‚ane, ale nie zostaÅ‚y jeszcze przetworzone. Bezpowrotnie usunie te pliki i zostanie Ci przywrócona odpowiednia przestrzeÅ„." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "PoproÅ› o nowe hasÅ‚o" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Poprosić o nowe hasÅ‚o API Subsonic?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "PoproÅ› o nowe hasÅ‚o" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Ustaw nowe hasÅ‚o" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Uruchom importowanie ponownie" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Wyniki na stronÄ™" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 -#, fuzzy +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Przejdź, aby zalogować siÄ™" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Zobacz pliki" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Zapisz" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Uruchomiono skanowanie" -#: front/src/views/content/remote/Card.vue:63 -#, fuzzy +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Odtwórz teraz" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "RosnÄ…co" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Skanowanie pominÄ™te (poprzednie skanowanie byÅ‚o zbyt wczeÅ›nie)" -#: front/src/views/content/remote/Card.vue:31 -#, fuzzy -msgid "Scan waiting" -msgstr "Oczekiwanie na skanowanie" - -#: front/src/views/content/remote/Card.vue:43 -#, fuzzy +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Uruchomiono skanowanie" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Zeskanowano z bÅ‚Ä™dami" -#: front/src/views/content/remote/Card.vue:35 -#, fuzzy +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Skanowanie... (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Szukaj" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Wyszukaj w zdalnej bibliotece" -#: front/src/components/manage/moderation/AccountsTable.vue:171 +#: front/src/components/manage/library/EditsCardList.vue:211 #, fuzzy -msgid "Search by domain, username, bio..." -msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, domeny…" -#: front/src/components/manage/moderation/DomainsTable.vue:151 +#: front/src/components/manage/library/LibrariesTable.vue:191 #, fuzzy -msgid "Search by name..." -msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, nazwy…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/components/manage/library/UploadsTable.vue:241 #, fuzzy -msgid "Search by title, artist, album…" -msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, albumu…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" -#: front/src/components/manage/library/FilesTable.vue:176 +#: front/src/components/manage/library/ArtistsTable.vue:164 #, fuzzy -msgid "Search by title, artist, domain…" -msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, domeny…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, albumu…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, albumu…" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, nazwy…" + +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" +msgid "Search by title, artist, album…" +msgstr "Szukaj wedÅ‚ug tytuÅ‚u, wykonawcy, albumu…" #: front/src/components/manage/users/InvitationsTable.vue:153 #, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, kodu…" #: front/src/components/manage/users/UsersTable.vue:163 -#, fuzzy +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Szukaj wedÅ‚ug nazwy użytkownika, adresu e-mail, nazwy…" #: front/src/components/audio/SearchBar.vue:20 -#, fuzzy +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Szukaj wykonawców, albumów, utworów…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Wyszukaj trochÄ™ muzyki" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Szukaj na lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Szukaj na Wikipedii" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Sekcje" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Zaznacz filtr" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Zaznacz %{ total } element" msgstr[1] "Zaznacz wszystkie %{ total } elementy" msgstr[2] "Zaznacz wszystkie %{ total } elementów" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Zaznacz tylko obecnÄ… stronÄ™" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Ustawienia" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Zaktualizowano ustawienia" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "PomyÅ›lnie zaktualizowano ustawienia." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "UdostÄ™pnij odnoÅ›nik" #: front/src/views/content/libraries/Detail.vue:15 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." msgstr "Podziel siÄ™ tym odnoÅ›nikiem z innymi użytkownikami, aby mogli poprosić od dostÄ™p do Twojej biblioteki." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "OdnoÅ›nik do udostÄ™pnienia" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "Pokaż %{ count } utwór wiÄ™cej" @@ -2301,760 +3885,1372 @@ msgstr[1] "Pokaż %{ count } utwory wiÄ™cej" msgstr[2] "Pokaż %{ count } utworów wiÄ™cej" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Pokaż 1 kolejny album" msgstr[1] "Pokaż %{ count } kolejne albumy" msgstr[2] "Pokaż %{ count } kolejnych albumów" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Pokaż przeczytane powiadomienia" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Pokaż/ukryj hasÅ‚o" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "WyÅ›wietlanie wyników %{ start }-%{ end } z %{ total }" #: front/src/components/ShortcutsModal.vue:83 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Wymieszaj kolejkÄ™" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Wymieszaj kolejkÄ™" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Rejestracja" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Rejestracja" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Data rejestracji" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 #, fuzzy -msgid "Silence activity" -msgstr "Aktywność użytkownika" +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Rozmiar" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 #, fuzzy -msgid "Silence notifications" -msgstr "Pokaż przeczytane powiadomienia" - -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Rozmiar" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "PominiÄ™to" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "PominiÄ™te pliki" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Przepraszamy, strona której szukasz nie istnieje:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "Kod źródÅ‚owy" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "CzÅ‚onek administracji" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Rozpocznij" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Zatrzymaj radio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Statystyki" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" msgstr "" -#: front/src/components/library/FileUpload.vue:86 +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Stan" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Stan" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Stan" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Stan" + #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Stan" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Zatrzymaj" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Stan" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Zatrzymaj radio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "WyÅ›lij" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "HasÅ‚o API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Polecane wybory" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Podsumowanie" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "" #: front/src/components/playlists/Editor.vue:9 -#, fuzzy +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Synchronizowanie zmian z serwerem…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Skopiowano tekst do schowka!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "To proste: kochamy Grooveshark i chcemy utworzyć coÅ› jeszcze lepszego." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "Logo Funkwhale zostaÅ‚o zaprojektowane i dostarczone przez Francisa Gadinga." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." msgstr "Biblioteka i wszystkie utwory z niej zostanÄ… usuniÄ™te. To dziaÅ‚anie jest nieodwracalne." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "Pliki muzyczne które wysyÅ‚asz sÄ… poprawnie otagowane:" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" msgstr "" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "Platforma jest wolna i otwartoźródÅ‚owa, każdy może bez zmartwieÅ„ zainstalować i modyfikować jÄ…" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Utworzono listÄ™ odtwarzania" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "API Subsonic nie jest dostÄ™pne na tej instancji Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "WysyÅ‚ane pliki muzyczne sÄ… w formacie OGG, FLAC lub MP3" #: front/src/views/content/Home.vue:4 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "Oferujemy różne sposoby zdobywania nowej zawartoÅ›ci i udostÄ™pniania jej tutaj" #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "" -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Ten album wystÄ™puje w nastÄ™pujÄ…cych bibliotekach:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Ten artysta wystÄ™puje w nastÄ™pujÄ…cych bibliotekach:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "" #: front/src/views/content/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "Ta instancja oferuje maksymalnie %{quota} przestrzeni dla każdego użytkownika." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "To Ty!" -#: front/src/views/content/libraries/Form.vue:71 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Ta instancja zawiera mojÄ… personalnÄ… bibliotekÄ™, mam nadziejÄ™ że jÄ… polubisz!" -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" msgstr "" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "" -#: front/src/components/common/ActionTable.vue:45 -#, fuzzy +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." msgstr "Może to wpÅ‚ywać na wiele rzeczy, sprawdź dwukrotnie czy to na pewno to, czego chcesz." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Te źródÅ‚a zostanÄ… wykorzystane, aby pogrubować zaimportowane pliki" -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "WystÄ…piÅ‚ bÅ‚Ä…d w trakcie przetwarzania tego utworu, upewnij siÄ™ że posiada on poprawne metadane" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Utwór zostaÅ‚ zaimportowany, ale jeszcze nie jest przetworzony przez serwer" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Utwór jest już w jednej z twoich bibliotek" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Ten utwór wystÄ™puje w nastÄ™pujÄ…cych bibliotekach:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "To caÅ‚kowicie usunie listÄ™ odtwarzania i nie może zostać cofniÄ™te." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "To bezpowrotnie usunie radio." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "To caÅ‚kowicie wyÅ‚Ä…czy dostÄ™p do API Subsonic z tego konta." -#: front/src/App.vue:129 src/components/Footer.vue:72 -#, fuzzy -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "To wyczyÅ›ci Twoje lokalne dane i rozÅ‚Ä…czy CiÄ™, czy chcesz kontynuować?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "To wyloguje CiÄ™ z urzÄ…dzeÅ„ na których jesteÅ› obecnie zalogowany." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "To caÅ‚kowicie usunie listÄ™ odtwarzania i nie może zostać cofniÄ™te." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "To bezpowrotnie usunie wszystkie utwory z tej listy odtwarzania." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "TytuÅ‚" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "TytuÅ‚" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" msgid "Title" msgstr "TytuÅ‚" +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +#, fuzzy +msgctxt "Content/Moderation/Table.Label" msgid "Total size" -msgstr "" +msgstr "Nie użyty" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "ÅÄ…czny rozmiar wszystkich plików w tej bibliotece" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 -#, fuzzy +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Nie użyty" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Utwór" -#: front/src/views/content/libraries/FilesTable.vue:205 +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Utwór" + +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:91 #, fuzzy -msgid "Track already present in one of your libraries" -msgstr "Utwór jest już w jednej z twoich bibliotek" +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "TytuÅ‚ utworu" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Informacje o utworze" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Utwór zgodny z filtrem" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "TytuÅ‚ utworu" -#: front/src/views/content/libraries/FilesTable.vue:209 -#, fuzzy -msgid "Track uploaded, but not processed by the server yet" -msgstr "Utwór zostaÅ‚ zaimportowany, ale jeszcze nie jest przetworzony przez serwer" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Utwory" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "utwory" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Utwory" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Utwory tego wykonawcy" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Ulubione utwory" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "wysÅ‚uchane utwory" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Utwór zgodny z filtrem" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Rodzaj" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Rodzaj" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "UsuÅ„ radio" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "PrzestaÅ„ Å›ledzić" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Czy chcesz przestać Å›ledzić tÄ™ bibliotekÄ™?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Niestety, wÅ‚aÅ›ciciele tej instancji nie znaleźli czasu na wypeÅ‚nienie tej strony." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Nieograniczona muzyka" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Cofnij wyciszenie" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Data wysyÅ‚ania" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Aktualizuj listÄ™ odtwarzania" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Aktualizuj awatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Aktualizuj bibliotekÄ™" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Aktualizuj listÄ™ odtwarzania" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Aktualizuj ustawienia" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Aktualizuj swoje hasÅ‚o" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "WyÅ›lij" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Dodaj nowy awatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "WyÅ›lij zawartość dźwiÄ™kowÄ…" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Data wysyÅ‚ania" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Data wysyÅ‚ania" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 -#, fuzzy +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" msgstr "BÅ‚Ä…d wysyÅ‚ania, upewnij siÄ™ że plik nie jest zbyt duży i że nie przekroczyÅ‚eÅ› swojego limitu" +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." +msgstr "" + #: front/src/views/content/Home.vue:7 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." msgstr "WyÅ›lij pliki muzyczne (mp3, ogg, flac itp.) ze swojej biblioteki bezpoÅ›rednio z przeglÄ…darki, aby cieszyć siÄ™ nimi tutaj." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "WyÅ›lij nowe utwory" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Powierzchnia dyskowa" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Importowanie zajęło zbyt dÅ‚ugo, spróbuj jeszcze raz" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "WysÅ‚ano" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "WysyÅ‚anie" -#: front/src/components/library/FileUpload.vue:103 -#, fuzzy +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "WysyÅ‚anie" -#: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "WyÅ›lij" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 #, fuzzy +msgctxt "*/*/*" msgid "Uploads" msgstr "WyÅ›lij" +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Uploads" +msgstr "WyÅ›lij" + +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Skorzystaj z innej instancji" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Użyj tego formularza aby poprosić o zresetowanie hasÅ‚a. Otrzymasz e-mail z instrukcjami resetowania hasÅ‚a na podany adres." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Zużyty" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Użytkownik" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Aktywność użytkownika" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Biblioteki użytkownika" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Radia użytkownika" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nazwa użytkownika" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nazwa użytkownika lub adres e-mail" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "użytkownicy" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Użytkownicy" #: front/src/components/Footer.vue:29 #, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "O Funkwhale" #: front/src/components/Footer.vue:13 -#, fuzzy +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Kod źródÅ‚owy (%{version})" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Zobacz pliki" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "WyÅ›wietl na MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Widoczność" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Widoczność: wszyscy na tej instancji" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Widoczność: wszyscy, uwzglÄ™dniajÄ…c inne instancje" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Widoczność: nikt poza mnÄ…" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Widoczność" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Nie udaÅ‚o siÄ™ utworzyć listy odtwarzania" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Nie udaÅ‚o siÄ™ utworzyć Ci konta" - -#: front/src/components/audio/Player.vue:64 +#: front/src/components/federation/FetchButton.vue:69 #, fuzzy -msgid "We cannot load this track" -msgstr "Nie udaÅ‚o siÄ™ dodać tego utworu do listy odtwarzania" +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Åadowanie Twoich ulubionych…" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Nie udaÅ‚o siÄ™ zalogować CiÄ™" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Nie udaÅ‚o siÄ™ zapisać awatara" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Nie udaÅ‚o siÄ™ zapisać ustawieÅ„" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Nie udaÅ‚o siÄ™ utworzyć Ci konta" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Nie Å›ledzimy CiÄ™ i nie wyÅ›wietlamy Ci reklam" -#: front/src/components/library/Track.vue:95 -#, fuzzy -msgid "We don't have any copyright information for this track" -msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" - -#: front/src/components/library/Track.vue:106 -#, fuzzy -msgid "We don't have any licensing information for this track" -msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" - -#: front/src/components/library/FileUpload.vue:40 -#, fuzzy +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "polecamy używać do tego Picarda" #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Uważamy, że sÅ‚uchanie muzyki powinno być proste." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Przepraszamy, strona której szukasz nie istnieje:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Witaj" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Witamy na Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Dlaczego funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Tak" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Tak, wyloguj mnie!" #: front/src/views/content/libraries/Form.vue:19 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "BÄ™dziesz mógÅ‚ udostÄ™pniać swojÄ… bibliotekÄ™ innym, nie zważajÄ…c na jej widoczność." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Za chwilÄ™ dodasz utwory do swojej biblioteki. Zanim kontynuujesz, upewnij siÄ™ że:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "JesteÅ› obecnie zalogowany jako %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Możesz zaobserwować biblioteki innych użytkowników aby uzyskać dostÄ™p do nowej muzyki. Publiczne biblioteki mogÄ… być zaobserwowane natychmiast, a do zaobserwowania prywatnej biblioteki bÄ™dziesz potrzebowaÅ‚ zgody jej wÅ‚aÅ›ciciela." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Możesz zaprosić znajomych i rodzinÄ™ na swojÄ… instancjÄ™, aby mogli siÄ™ cieszyć dodanÄ… przez Ciebie muzykÄ…" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Twój adres e-mail zostaÅ‚ potwierdzony, możesz używać usÅ‚ugi bez ograniczeÅ„." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Możesz używać tego interfejsu aby utworzyć wÅ‚asne radio, które bÄ™dzie odtwarzać utwory pasujÄ…ce do Twoich kryteriów." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Możesz używać tego, aby cieszyć siÄ™ muzykÄ… i swojÄ… listÄ… odtwarzania w trybie offline, na przykÅ‚ad na smartfonie i tablecie." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." -msgstr "" +msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" #: front/src/views/admin/moderation/DomainsDetail.vue:39 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." +msgstr "Brak powiadomieÅ„ do wyÅ›wietlenia!" + +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/Sidebar.vue:158 +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Odtwarzasz radio" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "" -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Musisz wybrać instancjÄ™ aby kontynuować" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Zostaniesz wylogowany z tej sesji i musisz zalogować siÄ™ nowym hasÅ‚em" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "BÄ™dziesz musiaÅ‚ zmienić hasÅ‚o na klientach używajÄ…cych tego hasÅ‚a." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Utworzono listÄ™ odtwarzania" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Twoje powiadomienia" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Twoje ulubione" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Twoja muzyka, po Twojemu" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Twoje powiadomienia" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Twoje hasÅ‚o zostaÅ‚o pomyÅ›lnie zmienione." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Zaktualizowano ustawienia" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Twoje hasÅ‚o Subsonic zostanie zmienione na nowe, losowe i zostaniesz wylogowany z urzÄ…dzeÅ„ korzystajÄ…cych ze starego hasÅ‚a Subsonic" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Opis" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Kopiuj" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Album zawiera %{ count } utwór od %{ artist }" +msgstr[1] "Album zawiera %{ count } utwory od %{ artist }" +msgstr[2] "Album zawiera %{ count } utworów od %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "Dodano %{ count } utwór do kolejki" +msgstr[1] "Dodano %{ count } utwory do kolejki" +msgstr[2] "Dodano %{ count } utworów do kolejki" diff --git a/front/locales/pt_BR/LC_MESSAGES/app.po b/front/locales/pt_BR/LC_MESSAGES/app.po new file mode 100644 index 0000000000000000000000000000000000000000..5b02315fabd39257d1734874692d802fae47f3e5 --- /dev/null +++ b/front/locales/pt_BR/LC_MESSAGES/app.po @@ -0,0 +1,2938 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the front package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: front 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-01-11 16:04+0100\n" +"PO-Revision-Date: 2019-04-16 07:46+0000\n" +"Last-Translator: Matroid <matroid@outlook.com.br>\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.2.2\n" + +#: front/src/components/playlists/PlaylistModal.vue:9 +msgid "\"%{ title }\", by %{ artist }" +msgstr "\"%{ title }\", por %{ artist }" + +#: front/src/components/Sidebar.vue:24 +msgid "(%{ index } of %{ length })" +msgstr "(%{ index } de %{ length })" + +#: front/src/components/Sidebar.vue:22 +msgid "(empty)" +msgstr "(vazio)" + +#: front/src/components/common/ActionTable.vue:57 +#: front/src/components/common/ActionTable.vue:66 +msgid "%{ count } on %{ total } selected" +msgid_plural "%{ count } on %{ total } selected" +msgstr[0] "%{ count } de %{ total } selecionado" +msgstr[1] "%{ count } de %{ total } selecionados" + +#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 +#: front/src/views/content/libraries/Card.vue:39 src/views/content/remote/Card.vue:26 +msgid "%{ count } track" +msgid_plural "%{ count } tracks" +msgstr[0] "%{ count } faixa" +msgstr[1] "%{ count } faixas" + +#: front/src/components/library/Artist.vue:13 +msgid "%{ count } track in %{ albumsCount } albums" +msgid_plural "%{ count } tracks in %{ albumsCount } albums" +msgstr[0] "%{ count } faixa em %{ albumsCount } álbuns" +msgstr[1] "%{ count } faixas em %{ albumsCount } álbuns" + +#: front/src/components/library/radios/Builder.vue:80 +msgid "%{ count } track matching combined filters" +msgid_plural "%{ count } tracks matching combined filters" +msgstr[0] "%{ count } faixa encontrada com os filtros" +msgstr[1] "%{ count } faixas encontradas com os filtros" + +#: front/src/components/audio/PlayButton.vue:180 +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } faixa adicionada à fila" +msgstr[1] "%{ count } faixas adicionadas à fila" + +#: front/src/components/playlists/Card.vue:18 +msgid "%{ count} track" +msgid_plural "%{ count } tracks" +msgstr[0] "%{ count } faixa" +msgstr[1] "%{ count } faixas" + +#: front/src/views/content/libraries/Quota.vue:11 +msgid "%{ current } used on %{ max } allowed" +msgstr "%{ current } usados de %{ max } permitidos" + +#: front/src/components/common/Duration.vue:2 +msgid "%{ hours } h %{ minutes } min" +msgstr "%{ hours } h %{ minutes } min" + +#: front/src/components/common/Duration.vue:5 +msgid "%{ minutes } min" +msgstr "%{ minutes } min" + +#: front/src/components/notifications/NotificationRow.vue:40 +msgid "%{ username } accepted your follow on library \"%{ library }\"" +msgstr "" +"%{ username } aceitou seu pedido para seguir a biblioteca \"%{ library }\"" + +#: front/src/components/notifications/NotificationRow.vue:39 +msgid "%{ username } followed your library \"%{ library }\"" +msgstr "%{ username } seguiu sua biblioteca \"%{ library }\"" + +#: front/src/components/auth/Profile.vue:46 +msgid "%{ username }'s profile" +msgstr "Perfil de %{ username }" + +#: front/src/components/Footer.vue:5 +msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +msgstr "" +"<translate :translate-params=\"{instanceName: instanceHostname}\">Sobre " +"%{instanceName}</translate>" + +#: front/src/components/audio/artist/Card.vue:41 +msgid "1 album" +msgid_plural "%{ count } albums" +msgstr[0] "1 álbum" +msgstr[1] "%{ count } álbuns" + +#: front/src/components/favorites/List.vue:10 +msgid "1 favorite" +msgid_plural "%{ count } favorites" +msgstr[0] "1 favorito" +msgstr[1] "%{ count } favoritos" + +#: front/src/components/library/FileUpload.vue:225 +#: front/src/components/library/FileUpload.vue:226 +msgid "A network error occured while uploading this file" +msgstr "Um erro de rede ocorreu ao enviar este arquivo" + +#: front/src/components/About.vue:5 +msgid "About %{ instance }" +msgstr "Sobre %{ instance }" + +#: front/src/components/Footer.vue:6 +msgid "About %{instanceName}" +msgstr "Sobre %{instanceName}" + +#: front/src/components/Footer.vue:45 +msgid "About Funkwhale" +msgstr "Sobre o Funkwhale" + +#: front/src/components/Footer.vue:10 +msgid "About page" +msgstr "Sobre" + +#: front/src/components/About.vue:8 src/components/About.vue:64 +msgid "About this instance" +msgstr "Sobre esta instância" + +#: front/src/views/content/libraries/Detail.vue:48 +msgid "Accept" +msgstr "Aceitar" + +#: front/src/views/content/libraries/Detail.vue:40 +msgid "Accepted" +msgstr "Aceito" + +#: front/src/components/auth/SubsonicTokenForm.vue:111 +msgid "Access disabled" +msgstr "Acesso desabilitado" + +#: front/src/components/Home.vue:106 +msgid "Access your music from a clean interface that focus on what really matters" +msgstr "" +"Ouça suas músicas em uma interface limpa voltada para o que realmente importa" + +#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:20 +msgid "Accessed date" +msgstr "Data de acesso" + +#: front/src/views/admin/moderation/AccountsDetail.vue:78 +msgid "Account data" +msgstr "Dados da conta" + +#: front/src/components/auth/Settings.vue:5 +msgid "Account settings" +msgstr "Configurações da conta" + +#: front/src/components/auth/Settings.vue:264 +msgid "Account Settings" +msgstr "Configurações de Conta" + +#: front/src/components/manage/users/UsersTable.vue:39 +msgid "Account status" +msgstr "Situação da conta" + +#: front/src/views/auth/PasswordReset.vue:14 +msgid "Account's email" +msgstr "Email da conta" + +#: front/src/views/admin/moderation/AccountsList.vue:3 +#: front/src/views/admin/moderation/AccountsList.vue:24 +#: front/src/views/admin/moderation/Base.vue:8 +msgid "Accounts" +msgstr "Contas" + +#: front/src/views/content/libraries/Detail.vue:29 +msgid "Action" +msgstr "Ação" + +#: front/src/components/common/ActionTable.vue:99 +msgid "Action %{ action } was launched successfully on %{ count } element" +msgid_plural "Action %{ action } was launched successfully on %{ count } elements" +msgstr[0] "Ação %{ action } aplicada com sucesso em %{ count } elemento" +msgstr[1] "Ação %{ action } aplicada com sucesso em %{ count } elementos" + +#: front/src/components/common/ActionTable.vue:21 +#: front/src/components/library/radios/Builder.vue:64 +msgid "Actions" +msgstr "Ações" + +#: front/src/components/manage/users/UsersTable.vue:53 +msgid "Active" +msgstr "Ativo" + +#: front/src/views/admin/moderation/AccountsDetail.vue:199 +#: front/src/views/admin/moderation/DomainsDetail.vue:144 +msgid "Activity" +msgstr "Atividade" + +#: front/src/components/mixins/Translations.vue:7 +#: front/src/components/mixins/Translations.vue:8 +msgid "Activity visibility" +msgstr "Visibilidade da atividade" + +#: front/src/views/admin/moderation/DomainsList.vue:18 +msgid "Add" +msgstr "Adicionar" + +#: front/src/views/admin/moderation/DomainsList.vue:13 +msgid "Add a domain" +msgstr "Adicionar domÃnio" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgid "Add a new moderation rule" +msgstr "Adicionar nova regra de moderação" + +#: front/src/views/content/Home.vue:35 +msgid "Add and manage content" +msgstr "Adicionar e administrar conteúdo" + +#: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +msgid "Add content" +msgstr "Adicionar conteúdo" + +#: front/src/components/library/radios/Builder.vue:50 +msgid "Add filter" +msgstr "Adicionar filtro" + +#: front/src/components/library/radios/Builder.vue:40 +msgid "Add filters to customize your radio" +msgstr "Adicionar filtros para personalizar sua rádio" + +#: front/src/components/audio/PlayButton.vue:64 +msgid "Add to current queue" +msgstr "Adicionar à fila atual" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:4 +#: front/src/components/favorites/TrackFavoriteIcon.vue:28 +msgid "Add to favorites" +msgstr "Adicionar aos favoritos" + +#: front/src/components/playlists/TrackPlaylistIcon.vue:6 +#: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgid "Add to playlist…" +msgstr "Adicionar à lista…" + +#: front/src/components/audio/PlayButton.vue:14 +msgid "Add to queue" +msgstr "Adicionar à fila" + +#: front/src/components/playlists/PlaylistModal.vue:116 +msgid "Add to this playlist" +msgstr "Adicionar a esta lista" + +#: front/src/components/playlists/PlaylistModal.vue:54 +msgid "Add track" +msgstr "Adicionar faixa" + +#: front/src/components/manage/users/UsersTable.vue:69 +msgid "Admin" +msgstr "Administrador/a" + +#: front/src/components/Sidebar.vue:79 +msgid "Administration" +msgstr "Administração" + +#: front/src/components/audio/SearchBar.vue:26 src/components/audio/track/Table.vue:8 +#: front/src/components/library/Album.vue:159 +#: front/src/components/manage/library/FilesTable.vue:39 +#: front/src/components/metadata/Search.vue:134 +#: front/src/views/content/libraries/FilesTable.vue:56 +msgid "Album" +msgstr "Ãlbum" + +#: front/src/components/library/Album.vue:12 +msgid "Album containing %{ count } track, by %{ artist }" +msgid_plural "Album containing %{ count } tracks, by %{ artist }" +msgstr[0] "Ãlbum com %{ count } faixa, de %{ artist }" +msgstr[1] "Ãlbum com %{ count } faixas, de %{ artist }" + +#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:25 +msgid "Album name" +msgstr "Nome do álbum" + +#: front/src/components/library/Track.vue:27 +msgid "Album page" +msgstr "Página do álbum" + +#: front/src/components/audio/Search.vue:19 src/components/instance/Stats.vue:48 +#: front/src/views/admin/moderation/AccountsDetail.vue:321 +#: front/src/views/admin/moderation/DomainsDetail.vue:257 +msgid "Albums" +msgstr "Ãlbuns" + +#: front/src/components/library/Artist.vue:44 +msgid "Albums by this artist" +msgstr "Ãlbuns deste artista" + +#: front/src/components/manage/users/InvitationsTable.vue:19 +#: front/src/views/content/libraries/FilesTable.vue:13 +msgid "All" +msgstr "Todos" + +#: front/src/components/playlists/Editor.vue:13 +msgid "An error occured while saving your changes" +msgstr "Um erro ocorreu ao salvar suas alterações" + +#: front/src/components/auth/Login.vue:10 +msgid "An unknown error happend, this can mean the server is down or cannot be reached" +msgstr "" +"Um erro desconhecido ocorreu; o servidor pode estar fora dou ar ou " +"inacessÃvel" + +#: front/src/components/notifications/NotificationRow.vue:62 +msgid "Approve" +msgstr "Aprovar" + +#: front/src/components/auth/Logout.vue:5 +msgid "Are you sure you want to log out?" +msgstr "Tem certeza que deseja sair?" + +#: front/src/components/audio/SearchBar.vue:25 src/components/audio/track/Table.vue:7 +#: front/src/components/library/Artist.vue:137 +#: front/src/components/manage/library/FilesTable.vue:38 +#: front/src/components/metadata/Search.vue:130 +#: front/src/views/content/libraries/FilesTable.vue:55 +msgid "Artist" +msgstr "Artista" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +msgid "Artist name" +msgstr "Nome do/a artista" + +#: front/src/components/library/Album.vue:22 src/components/library/Track.vue:33 +msgid "Artist page" +msgstr "Página do/a artista" + +#: front/src/components/audio/Search.vue:65 +msgid "Artist, album, track…" +msgstr "Artista, álbum, faixa…" + +#: front/src/components/audio/Search.vue:10 src/components/instance/Stats.vue:42 +#: front/src/components/library/Artists.vue:119 src/components/library/Library.vue:7 +#: front/src/views/admin/moderation/AccountsDetail.vue:313 +#: front/src/views/admin/moderation/DomainsDetail.vue:249 +msgid "Artists" +msgstr "Artistas" + +#: front/src/components/favorites/List.vue:33 src/components/library/Artists.vue:25 +#: front/src/components/library/Radios.vue:44 +#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/manage/moderation/AccountsTable.vue:21 +#: front/src/components/manage/moderation/DomainsTable.vue:19 +#: front/src/components/manage/users/UsersTable.vue:19 +#: front/src/views/content/libraries/FilesTable.vue:31 +#: front/src/views/playlists/List.vue:27 +msgid "Ascending" +msgstr "Crescente" + +#: front/src/views/auth/PasswordReset.vue:27 +msgid "Ask for a password reset" +msgstr "Solicitar redefinição de senha" + +#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgid "Audio content" +msgstr "Conteúdo de áudio" + +#: front/src/components/ShortcutsModal.vue:55 +msgid "Audio player shortcuts" +msgstr "Atalhos do reprodutor de som" + +#: front/src/components/playlists/PlaylistModal.vue:26 +msgid "Available playlists" +msgstr "Listas disponÃveis" + +#: front/src/components/auth/Settings.vue:34 +msgid "Avatar" +msgstr "Imagem de perfil" + +#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordResetConfirm.vue:18 +msgid "Back to login" +msgstr "Voltar à página de entrada" + +#: front/src/components/library/Track.vue:129 +#: front/src/components/manage/library/FilesTable.vue:42 +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +msgid "Bitrate" +msgstr "Taxa de bits" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:19 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgid "Block everything" +msgstr "Bloquear tudo" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" +msgstr "" +"Bloquear tudo desta conta ou domÃnio. Isso desabilitará a interação e " +"eliminará conteúdos relacionados (envios, bibliotecas, seguidas, etc.)" + +#: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +msgid "Browse" +msgstr "Explorar" + +#: front/src/components/Sidebar.vue:65 +msgid "Browse library" +msgstr "Explorar biblioteca" + +#: front/src/components/library/Artists.vue:4 +msgid "Browsing artists" +msgstr "Explorando artistas" + +#: front/src/views/playlists/List.vue:3 +msgid "Browsing playlists" +msgstr "Explorando listas" + +#: front/src/components/library/Radios.vue:4 +msgid "Browsing radios" +msgstr "Explorando rádios" + +#: front/src/components/library/radios/Builder.vue:5 +msgid "Builder" +msgstr "Construtor" + +#: front/src/components/audio/album/Card.vue:13 +msgid "By %{ artist }" +msgstr "De %{ artist }" + +#: front/src/views/content/remote/Card.vue:103 +msgid "By unfollowing this library, you loose access to its content." +msgstr "" +"Ao deixar de seguir esta biblioteca você perderá o acesso a seu conteúdo." + +#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgid "Cached size" +msgstr "Tamanho em cache" + +#: front/src/components/common/DangerousButton.vue:17 +#: front/src/components/library/Album.vue:58 src/components/library/Track.vue:76 +#: front/src/components/library/radios/Filter.vue:53 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:63 +msgid "Cancel" +msgstr "Cancelar" + +#: front/src/components/library/radios/Builder.vue:63 +msgid "Candidates" +msgstr "Candidatos/as" + +#: front/src/components/auth/Settings.vue:76 +msgid "Cannot change your password" +msgstr "Não é possÃvel alterar a senha" + +#: front/src/components/library/FileUpload.vue:222 +#: front/src/components/library/FileUpload.vue:223 +msgid "Cannot upload this file, ensure it is not too big" +msgstr "" +"Não é possÃvel enviar este arquivo; tenha certeza de que não é grande demais" + +#: front/src/components/Footer.vue:21 +msgid "Change language" +msgstr "Alterar idioma" + +#: front/src/components/auth/Settings.vue:67 +msgid "Change my password" +msgstr "Alterar minha senha" + +#: front/src/components/auth/Settings.vue:95 +msgid "Change password" +msgstr "Alterar senha" + +#: front/src/views/auth/PasswordResetConfirm.vue:4 +#: front/src/views/auth/PasswordResetConfirm.vue:62 +msgid "Change your password" +msgstr "Alterar sua senha" + +#: front/src/components/auth/Settings.vue:96 +msgid "Change your password?" +msgstr "Alterar sua senha?" + +#: front/src/components/playlists/Editor.vue:21 +msgid "Changes synced with server" +msgstr "Alterações sincronizadas com o servidor" + +#: front/src/components/auth/Settings.vue:70 +msgid "Changing your password will also change your Subsonic API password if you have requested one." +msgstr "" +"A alteração de sua senha também modificará - caso a utilize - a senha da API " +"Subsonic." + +#: front/src/components/auth/Settings.vue:98 +msgid "Changing your password will have the following consequences" +msgstr "A alteração da senha terá as seguintes consequências" + +#: front/src/components/Footer.vue:40 +msgid "Chat room" +msgstr "Sala de chat" + +#: front/src/App.vue:13 +msgid "Choose your instance" +msgstr "Escolha sua instância" + +#: front/src/components/Home.vue:64 +msgid "Clean library" +msgstr "Biblioteca limpa" + +#: front/src/components/manage/users/InvitationForm.vue:37 +msgid "Clear" +msgstr "Limpar" + +#: front/src/components/playlists/Editor.vue:40 +#: front/src/components/playlists/Editor.vue:45 +msgid "Clear playlist" +msgstr "Limpar lista" + +#: front/src/components/audio/Player.vue:363 +msgid "Clear your queue" +msgstr "Limpar sua fila" + +#: front/src/components/Home.vue:44 +msgid "Click once, listen for hours using built-in radios" +msgstr "Clique uma vez e ouça as rádios nativas por horas" + +#: front/src/components/library/FileUpload.vue:75 +msgid "Click to select files to upload or drag and drop files or directories" +msgstr "" +"Clique para selecionar arquivos para enviar ou arraste e solte arquivos ou " +"pastas" + +#: front/src/components/ShortcutsModal.vue:20 +msgid "Close" +msgstr "Fechar" + +#: front/src/components/manage/users/InvitationForm.vue:26 +#: front/src/components/manage/users/InvitationsTable.vue:42 +msgid "Code" +msgstr "Código" + +#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/artist/Card.vue:33 +msgid "Collapse" +msgstr "Esconder" + +#: front/src/components/library/radios/Builder.vue:62 +msgid "Config" +msgstr "Configuração" + +#: front/src/components/common/DangerousButton.vue:21 +msgid "Confirm" +msgstr "Confirmar" + +#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 +#: front/src/views/auth/EmailConfirm.vue:51 +msgid "Confirm your e-mail address" +msgstr "Confirmar seu endereço de email" + +#: front/src/views/auth/EmailConfirm.vue:13 +msgid "Confirmation code" +msgstr "Código de confirmação" + +#: front/src/components/common/ActionTable.vue:7 +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "Conteúdo modificado; atualize a página para ver o conteúdo atual" + +#: front/src/components/Footer.vue:48 +msgid "Contribute" +msgstr "Contribua" + +#: front/src/components/audio/EmbedWizard.vue:19 +#: front/src/components/common/CopyInput.vue:8 +msgid "Copy" +msgstr "Copiar" + +#: front/src/components/playlists/Editor.vue:163 +msgid "Copy tracks from current queue to playlist" +msgstr "Copiar faixas da fila atual para lista" + +#: front/src/components/audio/EmbedWizard.vue:21 +msgid "Copy/paste this code in your website HTML" +msgstr "Copie e cole este código HTML em seu site" + +#: front/src/components/library/Track.vue:91 +msgid "Copyright" +msgstr "Direitos autorais" + +#: front/src/views/auth/EmailConfirm.vue:7 +msgid "Could not confirm your e-mail address" +msgstr "Não foi possÃvel confirmar seu endereço de email" + +#: front/src/views/content/remote/ScanForm.vue:3 +msgid "Could not fetch remote library" +msgstr "Não foi possÃvel obter a biblioteca remota" + +#: front/src/views/content/libraries/FilesTable.vue:213 +msgid "Could not process this track, ensure it is tagged correctly" +msgstr "" +"Não foi possÃvel processar esta faixa; verifique se ela possui as tags " +"corretas" + +#: front/src/components/Home.vue:85 +msgid "Covers, lyrics, our goal is to have them all ;)" +msgstr "Capas, letras, nosso objetivo é tudo isso! ;)" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgid "Create" +msgstr "Criar" + +#: front/src/components/auth/Signup.vue:4 +msgid "Create a funkwhale account" +msgstr "Criar conta funkwhale" + +#: front/src/views/content/libraries/Home.vue:14 +msgid "Create a new library" +msgstr "Criar nova biblioteca" + +#: front/src/components/playlists/Form.vue:2 +msgid "Create a new playlist" +msgstr "Criar nova lista" + +#: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +msgid "Create an account" +msgstr "Criar uma conta" + +#: front/src/views/content/libraries/Form.vue:26 +msgid "Create library" +msgstr "Criar biblioteca" + +#: front/src/components/auth/Signup.vue:51 +msgid "Create my account" +msgstr "Criar minha conta" + +#: front/src/components/playlists/Form.vue:34 +msgid "Create playlist" +msgstr "Criar lista" + +#: front/src/components/library/Radios.vue:23 +msgid "Create your own radio" +msgstr "Criar sua própria rádio" + +#: front/src/components/manage/users/InvitationsTable.vue:40 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +msgid "Creation date" +msgstr "Data de criação" + +#: front/src/components/auth/Settings.vue:54 +msgid "Current avatar" +msgstr "Imagem de perfil atual" + +#: front/src/views/content/libraries/DetailArea.vue:4 +msgid "Current library" +msgstr "Biblioteca atual" + +#: front/src/components/playlists/PlaylistModal.vue:8 +msgid "Current track" +msgstr "Faixa atual" + +#: front/src/views/content/libraries/Quota.vue:2 +msgid "Current usage" +msgstr "Uso atual" + +#: front/src/views/content/libraries/Detail.vue:27 +msgid "Date" +msgstr "Data" + +#: front/src/components/ShortcutsModal.vue:75 +msgid "Decrease volume" +msgstr "Diminuir volume" + +#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:61 +#: front/src/components/manage/users/InvitationsTable.vue:167 +#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/content/libraries/Form.vue:29 src/views/playlists/Detail.vue:33 +msgid "Delete" +msgstr "Excluir" + +#: front/src/views/content/libraries/Form.vue:39 +msgid "Delete library" +msgstr "Excluir biblioteca" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgid "Delete moderation rule" +msgstr "Excluir regra de moderação" + +#: front/src/views/playlists/Detail.vue:38 +msgid "Delete playlist" +msgstr "Excluir lista" + +#: front/src/views/radios/Detail.vue:28 +msgid "Delete radio" +msgstr "Excluir rádio" + +#: front/src/views/content/libraries/Form.vue:31 +msgid "Delete this library?" +msgstr "Excluir esta biblioteca?" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgid "Delete this moderation rule?" +msgstr "Excluir esta regra de moderação?" + +#: front/src/components/favorites/List.vue:34 src/components/library/Artists.vue:26 +#: front/src/components/library/Radios.vue:47 +#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/manage/moderation/AccountsTable.vue:22 +#: front/src/components/manage/moderation/DomainsTable.vue:20 +#: front/src/components/manage/users/UsersTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:32 +#: front/src/views/playlists/List.vue:28 +msgid "Descending" +msgstr "Descendente" + +#: front/src/components/library/radios/Builder.vue:25 +#: front/src/views/content/libraries/Form.vue:14 +msgid "Description" +msgstr "Descrição" + +#: front/src/views/content/libraries/Card.vue:47 +msgid "Detail" +msgstr "Detalhe" + +#: front/src/views/content/remote/Card.vue:50 +msgid "Details" +msgstr "Detalhes" + +#: front/src/views/admin/moderation/AccountsDetail.vue:455 +msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." +msgstr "" +"Determine quanto conteúdo o usuário pode enviar. Deixe em branco para " +"utilizar o valor padrão da instância." + +#: front/src/components/mixins/Translations.vue:8 +#: front/src/components/mixins/Translations.vue:9 +msgid "Determine the visibility level of your activity" +msgstr "Determine o nÃvel de visibilidade de sua atividade" + +#: front/src/components/auth/Settings.vue:104 +#: front/src/components/auth/SubsonicTokenForm.vue:52 +msgid "Disable access" +msgstr "Desabilitar acesso" + +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgid "Disable Subsonic access" +msgstr "Desabilitar acesso Subsonic" + +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgid "Disable Subsonic API access?" +msgstr "Desabilitar acesso da API Subsonic?" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:18 +#: front/src/views/admin/moderation/AccountsDetail.vue:128 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgid "Disabled" +msgstr "Desabilitado" + +#: front/src/components/auth/SubsonicTokenForm.vue:14 +msgid "Discover how to use Funkwhale from other apps" +msgstr "Aprenda como usar o Funkwhale em outros aplicativos" + +#: front/src/views/admin/moderation/AccountsDetail.vue:103 +msgid "Display name" +msgstr "Nome de exibição" + +#: front/src/components/library/radios/Builder.vue:30 +msgid "Display publicly" +msgstr "Mostrar publicamente" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." +msgstr "" +"Não baixar nenhum arquivo de mÃdia (áudio, capa de álbum, imagem de " +"exibição...) desta conta ou domÃnio. Esta ação também deletará o conteúdo já " +"existente." + +#: front/src/components/playlists/Editor.vue:42 +msgid "Do you want to clear the playlist \"%{ playlist }\"?" +msgstr "Você quer limpar a lista \"%{ playlist }\"?" + +#: front/src/components/common/DangerousButton.vue:7 +msgid "Do you want to confirm this action?" +msgstr "Você deseja confirmar esta ação?" + +#: front/src/views/playlists/Detail.vue:35 +msgid "Do you want to delete the playlist \"%{ playlist }\"?" +msgstr "Você deseja excluir a lista \"%{ playlist }\"?" + +#: front/src/views/radios/Detail.vue:26 +msgid "Do you want to delete the radio \"%{ radio }\"?" +msgstr "Você deseja excluir a rádio \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:36 +msgid "Do you want to launch %{ action } on %{ count } element?" +msgid_plural "Do you want to launch %{ action } on %{ count } elements?" +msgstr[0] "Você deseja executar %{ action } em %{ count } elemento?" +msgstr[1] "Você deseja executar %{ action } em %{ count } elementos?" + +#: front/src/components/Sidebar.vue:107 +msgid "Do you want to restore your previous queue?" +msgstr "Você deseja restaurar sua fila anterior?" + +#: front/src/components/Footer.vue:31 +msgid "Documentation" +msgstr "Documentação" + +#: front/src/components/manage/moderation/AccountsTable.vue:40 +#: front/src/components/mixins/Translations.vue:34 +#: front/src/views/admin/moderation/AccountsDetail.vue:93 +#: front/src/components/mixins/Translations.vue:35 +msgid "Domain" +msgstr "DomÃnio" + +#: front/src/views/admin/moderation/Base.vue:5 +#: front/src/views/admin/moderation/DomainsList.vue:3 +#: front/src/views/admin/moderation/DomainsList.vue:48 +msgid "Domains" +msgstr "DomÃnios" + +#: front/src/components/library/Track.vue:55 +msgid "Download" +msgstr "Baixar" + +#: front/src/components/playlists/Editor.vue:49 +msgid "Drag and drop rows to reorder tracks in the playlist" +msgstr "Arraste as colunas para reorganizar as faixas na lista" + +#: front/src/components/audio/track/Table.vue:9 src/components/library/Track.vue:111 +#: front/src/components/manage/library/FilesTable.vue:43 +#: front/src/components/mixins/Translations.vue:30 +#: front/src/views/content/libraries/FilesTable.vue:59 +#: front/src/components/mixins/Translations.vue:31 +msgid "Duration" +msgstr "Duração" + +#: front/src/views/auth/EmailConfirm.vue:23 +msgid "E-mail address confirmed" +msgstr "Endereço de email confirmado" + +#: front/src/components/Home.vue:93 +msgid "Easy to use" +msgstr "Fácil de usar" + +#: front/src/views/content/libraries/Detail.vue:9 +msgid "Edit" +msgstr "Editar" + +#: front/src/components/About.vue:21 +msgid "Edit instance info" +msgstr "Editar informações da instância" + +#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 +msgid "Edit…" +msgstr "Editar…" + +#: front/src/components/auth/Signup.vue:29 +#: front/src/components/manage/users/UsersTable.vue:38 +msgid "Email" +msgstr "Email" + +#: front/src/views/admin/moderation/AccountsDetail.vue:111 +msgid "Email address" +msgstr "Endereço de email" + +#: front/src/components/library/Album.vue:44 src/components/library/Track.vue:62 +msgid "Embed" +msgstr "Incorporar" + +#: front/src/components/audio/EmbedWizard.vue:20 +msgid "Embed code" +msgstr "Código para incorporação" + +#: front/src/components/library/Album.vue:48 +msgid "Embed this album on your website" +msgstr "Incorpore este álbum em seu site" + +#: front/src/components/library/Track.vue:66 +msgid "Embed this track on your website" +msgstr "Incorpore esta faixa em seu site" + +#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgid "Emitted library follows" +msgstr "Seguidas de biblioteca emitidas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgid "Emitted messages" +msgstr "Mensagens emitidas" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:8 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:17 +#: front/src/views/admin/moderation/AccountsDetail.vue:127 +#: front/src/views/admin/moderation/AccountsDetail.vue:131 +msgid "Enabled" +msgstr "Habilitado" + +#: front/src/views/playlists/Detail.vue:29 +msgid "End edition" +msgstr "Finalizar edição" + +#: front/src/views/content/remote/ScanForm.vue:50 +msgid "Enter a library URL" +msgstr "Insira o endereço URL de uma biblioteca" + +#: front/src/components/library/Radios.vue:140 +msgid "Enter a radio name…" +msgstr "Insira o nome de uma rádio…" + +#: front/src/components/library/Artists.vue:118 +msgid "Enter artist name…" +msgstr "Insira o nome do/a artista…" + +#: front/src/views/playlists/List.vue:107 +msgid "Enter playlist name…" +msgstr "Insira o nome da lista…" + +#: front/src/components/auth/Signup.vue:100 +msgid "Enter your email" +msgstr "Insira seu email" + +#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +msgid "Enter your invitation code (case insensitive)" +msgstr "Insira seu código de convite (não diferencia maiúsculas de minúsculas)" + +#: front/src/components/metadata/Search.vue:114 +msgid "Enter your search query…" +msgstr "Entre sua busca…" + +#: front/src/components/auth/Signup.vue:99 +msgid "Enter your username" +msgstr "Entre seu nome de usuário" + +#: front/src/components/auth/Login.vue:77 +msgid "Enter your username or email" +msgstr "Entre seu nome de usuário ou email" + +#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/views/content/libraries/Form.vue:4 +msgid "Error" +msgstr "Erro" + +#: front/src/views/admin/Settings.vue:87 +msgid "Error reporting" +msgstr "Relatos de erro" + +#: front/src/components/common/ActionTable.vue:92 +msgid "Error while applying action" +msgstr "Erro ao executar ação" + +#: front/src/views/auth/PasswordReset.vue:7 +msgid "Error while asking for a password reset" +msgstr "Erro ao solicitar redefinição de senha" + +#: front/src/views/auth/PasswordResetConfirm.vue:7 +msgid "Error while changing your password" +msgstr "Erro ao alterar sua senha" + +#: front/src/views/admin/moderation/DomainsList.vue:6 +msgid "Error while creating domain" +msgstr "Erro ao criar domÃnio" + +#: front/src/components/manage/users/InvitationForm.vue:4 +msgid "Error while creating invitation" +msgstr "Erro ao criar convite" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgid "Error while creating rule" +msgstr "Erro ao criar regra" + +#: front/src/views/admin/moderation/DomainsDetail.vue:126 +msgid "Error while fetching node info" +msgstr "Erro ao solicitar node info" + +#: front/src/components/admin/SettingsGroup.vue:5 +msgid "Error while saving settings" +msgstr "Erro ao salvar configurações" + +#: front/src/views/content/libraries/FilesTable.vue:212 +msgid "Errored" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:75 +msgid "Errored files" +msgstr "" + +#: front/src/components/playlists/Form.vue:89 +msgid "Everyone" +msgstr "Todo mundo" + +#: front/src/components/mixins/Translations.vue:11 +#: front/src/components/playlists/Form.vue:85 src/views/content/libraries/Form.vue:73 +#: front/src/components/mixins/Translations.vue:12 +msgid "Everyone on this instance" +msgstr "Todo mundo nesta instância" + +#: front/src/views/content/libraries/Form.vue:74 +msgid "Everyone, across all instances" +msgstr "Todo mundo, em todas as instâncias" + +#: front/src/components/library/radios/Builder.vue:61 +msgid "Exclude" +msgstr "Excluir" + +#: front/src/components/manage/users/InvitationsTable.vue:41 +#: front/src/components/mixins/Translations.vue:22 +#: front/src/components/mixins/Translations.vue:23 +msgid "Expiration date" +msgstr "Data de expiração" + +#: front/src/components/manage/users/InvitationsTable.vue:50 +msgid "Expired" +msgstr "Expirado" + +#: front/src/components/manage/users/InvitationsTable.vue:21 +msgid "Expired/used" +msgstr "Expirado/utilizado" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." +msgstr "" +"Explique por que está aplicando esta regra. Dependendo da configuração de " +"sua instância, a descrição te ajudará a se lembrar o motivo da ação e também " +"será exposta publicamente para que os usuários possam saber quais regras de " +"moderação estão ativas." + +#: front/src/views/content/libraries/FilesTable.vue:16 +msgid "Failed" +msgstr "Falhou" + +#: front/src/views/content/remote/Card.vue:58 +msgid "Failed tracks:" +msgstr "Faixas que falharam:" + +#: front/src/components/Sidebar.vue:66 +msgid "Favorites" +msgstr "Favoritos" + +#: front/src/views/admin/Settings.vue:84 +msgid "Federation" +msgstr "Federação" + +#: front/src/components/library/FileUpload.vue:84 +msgid "Filename" +msgstr "Nome do arquivo" + +#: front/src/views/admin/library/Base.vue:5 src/views/admin/library/FilesList.vue:21 +msgid "Files" +msgstr "Arquivos" + +#: front/src/components/library/radios/Builder.vue:60 +msgid "Filter name" +msgstr "Nome do filtro" + +#: front/src/views/content/libraries/FilesTable.vue:17 +#: front/src/views/content/libraries/FilesTable.vue:216 +msgid "Finished" +msgstr "Finalizado" + +#: front/src/components/manage/moderation/AccountsTable.vue:42 +#: front/src/components/manage/moderation/DomainsTable.vue:41 +#: front/src/views/admin/moderation/AccountsDetail.vue:159 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgid "First seen" +msgstr "Visto primeiro" + +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +msgid "First seen date" +msgstr "Data do primeiro acesso" + +#: front/src/views/content/remote/Card.vue:83 +msgid "Follow" +msgstr "Seguir" + +#: front/src/views/content/Home.vue:16 +msgid "Follow remote libraries" +msgstr "Seguir bibliotecas remotas" + +#: front/src/views/content/remote/Card.vue:88 +msgid "Follow request pending approval" +msgstr "Solicitação de seguidor pendente" + +#: front/src/components/mixins/Translations.vue:38 +#: front/src/views/content/libraries/Detail.vue:7 +#: front/src/components/mixins/Translations.vue:39 +msgid "Followers" +msgstr "Seguidores" + +#: front/src/views/content/remote/Card.vue:93 +msgid "Following" +msgstr "Seguindo" + +#: front/src/components/library/Track.vue:17 +msgid "From album %{ album } by %{ artist }" +msgstr "Do álbum %{ album } de %{ artist }" + +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgid "Funkwhale is compatible with other music players that support the Subsonic API." +msgstr "" +"O Funkwhale funciona em outros reprodutores de música compatÃveis com a " +"Subsonic API." + +#: front/src/components/Home.vue:95 +msgid "Funkwhale is dead simple to use." +msgstr "O Funkwhale é muito fácil de usar." + +#: front/src/components/Home.vue:39 +msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." +msgstr "" +"O Funkwhale foi feito para ser fácil ouvir as músicas que você curte ou " +"conhecer novos/as artistas." + +#: front/src/components/Home.vue:116 +msgid "Funkwhale is free and gives you control on your music." +msgstr "O Funkwhale é gratuito e te coloca no controle de suas músicas." + +#: front/src/components/Home.vue:66 +msgid "Funkwhale takes care of handling your music" +msgstr "O Funkwhale cuida da organização de suas músicas" + +#: front/src/components/ShortcutsModal.vue:38 +msgid "General shortcuts" +msgstr "Atalhos gerais" + +#: front/src/components/manage/users/InvitationForm.vue:16 +msgid "Get a new invitation" +msgstr "Solicitar novo convite" + +#: front/src/components/Home.vue:13 +msgid "Get me to the library" +msgstr "Me leve à biblioteca" + +#: front/src/components/Home.vue:76 +msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" +msgstr "" +"Tenha metadados de qualidade sobre suas músicas graças ao <a href=\"%{ url }" +"\" target=\"_blank\">MusicBrainz</a>" + +#: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgid "Get started" +msgstr "Comece" + +#: front/src/components/Footer.vue:37 +msgid "Getting help" +msgstr "Procurando ajuda" + +#: front/src/components/common/ActionTable.vue:34 +#: front/src/components/common/ActionTable.vue:54 +msgid "Go" +msgstr "Ir" + +#: front/src/components/PageNotFound.vue:14 +msgid "Go to home page" +msgstr "Ir à página inicial" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgid "Hide account or domain content, except from followers." +msgstr "Esconder conteúdo de conta ou domÃnio, exceto de seguidores." + +#: front/src/components/library/Home.vue:65 +msgid "Home" +msgstr "InÃcio" + +#: front/src/components/instance/Stats.vue:36 +msgid "Hours of music" +msgstr "Horas de música" + +#: front/src/components/auth/SubsonicTokenForm.vue:11 +msgid "However, accessing Funkwhale from those clients require a separate password you can set below." +msgstr "" +"Entretanto, para acessar o Funkwhale a partir desses clientes você precisa " +"de uma senha que pode ser configurada abaixo." + +#: front/src/views/auth/PasswordResetConfirm.vue:24 +msgid "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." +msgstr "" +"Se o endereço de email inserido anteriormente for válido e associado a uma " +"conta de usuário, você receberá um email com as instruções de redefinição " +"nos próximos minutos." + +#: front/src/components/manage/library/FilesTable.vue:40 +#, fuzzy +msgid "Import date" +msgstr "Importar data" + +#: front/src/components/Home.vue:71 +msgid "Import music from various platforms, such as YouTube or SoundCloud" +msgstr "Importação de músicas de várias plataformas, como YouTube e SoundCloud" + +#: front/src/components/library/FileUpload.vue:51 +msgid "Import reference" +msgstr "Importar referência" + +#: front/src/views/content/libraries/FilesTable.vue:11 +#: front/src/views/content/libraries/FilesTable.vue:58 +msgid "Import status" +msgstr "Importar status" + +#: front/src/views/content/libraries/FilesTable.vue:217 +msgid "Imported" +msgstr "Importado" + +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgid "Imported date" +msgstr "Data importada" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgid "In favorites" +msgstr "Nos favoritos" + +#: front/src/components/manage/users/UsersTable.vue:54 +msgid "Inactive" +msgstr "Inativo" + +#: front/src/components/ShortcutsModal.vue:71 +msgid "Increase volume" +msgstr "Aumentar volume" + +#: front/src/views/auth/PasswordReset.vue:53 +msgid "Input the email address binded to your account" +msgstr "Insira o endereço de email associado a sua conta" + +#: front/src/components/playlists/Editor.vue:31 +msgid "Insert from queue (%{ count } track)" +msgid_plural "Insert from queue (%{ count } tracks)" +msgstr[0] "Inserir da fila (%{ count } faixa)" +msgstr[1] "Inserir da fila (%{ count } faixas)" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgid "Instance data" +msgstr "Dados da instância" + +#: front/src/views/admin/Settings.vue:80 +msgid "Instance information" +msgstr "Informação da instância" + +#: front/src/components/library/Radios.vue:9 +msgid "Instance radios" +msgstr "Rádios da instância" + +#: front/src/views/admin/Settings.vue:75 +msgid "Instance settings" +msgstr "Configurações da instância" + +#: front/src/components/library/FileUpload.vue:229 +#: front/src/components/library/FileUpload.vue:230 +msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "" +"Formato de arquivo inválido. Certifique-se de estar enviando um arquivo de " +"áudio. As extensões permitidas são %{ extensions }" + +#: front/src/components/auth/Signup.vue:42 +#: front/src/components/manage/users/InvitationForm.vue:11 +msgid "Invitation code" +msgstr "Código de convite" + +#: front/src/components/auth/Signup.vue:43 +msgid "Invitation code (optional)" +msgstr "Código de convite (opcional)" + +#: front/src/views/admin/users/Base.vue:8 src/views/admin/users/InvitationsList.vue:3 +#: front/src/views/admin/users/InvitationsList.vue:24 +msgid "Invitations" +msgstr "Convites" + +#: front/src/components/Footer.vue:41 +msgid "Issue tracker" +msgstr "" + +#: front/src/components/Home.vue:50 +msgid "Keep a track of your favorite songs" +msgstr "" + +#: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgid "Keyboard shortcuts" +msgstr "Atalhos de teclado" + +#: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgid "Known accounts" +msgstr "Contas conhecidas" + +#: front/src/views/content/remote/Home.vue:14 +msgid "Known libraries" +msgstr "" + +#: front/src/components/manage/users/UsersTable.vue:41 +#: front/src/components/mixins/Translations.vue:32 +#: front/src/views/admin/moderation/AccountsDetail.vue:184 +#: front/src/components/mixins/Translations.vue:33 +msgid "Last activity" +msgstr "Última atividade" + +#: front/src/views/admin/moderation/AccountsDetail.vue:167 +#: front/src/views/admin/moderation/DomainsDetail.vue:86 +msgid "Last checked" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:32 +msgid "Last modification" +msgstr "Última modificação" + +#: front/src/components/manage/moderation/AccountsTable.vue:43 +msgid "Last seen" +msgstr "" + +#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:19 +msgid "Last seen date" +msgstr "" + +#: front/src/views/content/remote/Card.vue:56 +msgid "Last update:" +msgstr "Última atualização:" + +#: front/src/components/common/ActionTable.vue:47 +msgid "Launch" +msgstr "" + +#: front/src/components/Home.vue:10 +msgid "Learn more about this instance" +msgstr "" + +#: front/src/components/manage/users/InvitationForm.vue:58 +msgid "Leave empty for a random code" +msgstr "" + +#: front/src/components/audio/EmbedWizard.vue:7 +msgid "Leave empty for a responsive widget" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:297 +#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/content/Base.vue:5 +msgid "Libraries" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:2 +msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." +msgstr "" + +#: front/src/components/instance/Stats.vue:30 +#: front/src/components/manage/users/UsersTable.vue:173 +#: front/src/views/admin/moderation/AccountsDetail.vue:464 +msgid "Library" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:109 +msgid "Library created" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:129 +msgid "Library deleted" +msgstr "" + +#: front/src/views/admin/library/FilesList.vue:3 +msgid "Library files" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:106 +msgid "Library updated" +msgstr "" + +#: front/src/components/library/Track.vue:100 +msgid "License" +msgstr "Licença" + +#: front/src/views/content/libraries/Detail.vue:21 +msgid "Loading followers…" +msgstr "Carregando seguidores…" + +#: front/src/views/content/libraries/Home.vue:3 +msgid "Loading Libraries…" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:3 +#: front/src/views/content/libraries/Upload.vue:3 +msgid "Loading library data…" +msgstr "" + +#: front/src/views/Notifications.vue:4 +msgid "Loading notifications…" +msgstr "Carregando notificações…" + +#: front/src/views/content/remote/Home.vue:3 +msgid "Loading remote libraries..." +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:4 +msgid "Loading usage data…" +msgstr "" + +#: front/src/components/favorites/List.vue:5 +msgid "Loading your favorites…" +msgstr "" + +#: front/src/components/manage/moderation/AccountsTable.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:18 +msgid "Local account" +msgstr "Conta local" + +#: front/src/components/auth/Login.vue:78 +msgid "Log In" +msgstr "Entrar" + +#: front/src/components/auth/Login.vue:4 +msgid "Log in to your Funkwhale account" +msgstr "" + +#: front/src/components/auth/Logout.vue:20 +msgid "Log Out" +msgstr "Sair" + +#: front/src/components/Sidebar.vue:38 +msgid "Logged in as %{ username }" +msgstr "" + +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +msgid "Login" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:119 +msgid "Login status" +msgstr "" + +#: front/src/components/Sidebar.vue:52 +msgid "Logout" +msgstr "Sair" + +#: front/src/views/content/libraries/Home.vue:9 +msgid "Looks like you don't have a library, it's time to create one." +msgstr "" + +#: front/src/components/audio/Player.vue:353 src/components/audio/Player.vue:354 +msgid "Looping disabled. Click to switch to single-track looping." +msgstr "" + +#: front/src/components/audio/Player.vue:356 src/components/audio/Player.vue:357 +msgid "Looping on a single track. Click to switch to whole queue looping." +msgstr "" + +#: front/src/components/audio/Player.vue:359 src/components/audio/Player.vue:360 +msgid "Looping on whole queue. Click to disable looping." +msgstr "" + +#: front/src/components/library/Track.vue:150 +msgid "Lyrics" +msgstr "Letra" + +#: front/src/components/Sidebar.vue:210 +msgid "Main menu" +msgstr "Menu principal" + +#: front/src/views/admin/library/Base.vue:16 +msgid "Manage library" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:3 +msgid "Manage playlists" +msgstr "Gerenciar listas de reprodução" + +#: front/src/views/admin/users/Base.vue:20 +msgid "Manage users" +msgstr "Gerenciar usuários" + +#: front/src/views/playlists/List.vue:8 +msgid "Manage your playlists" +msgstr "" + +#: front/src/views/Notifications.vue:17 +msgid "Mark all as read" +msgstr "" + +#: front/src/components/notifications/NotificationRow.vue:44 +msgid "Mark as read" +msgstr "" + +#: front/src/components/notifications/NotificationRow.vue:45 +msgid "Mark as unread" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#, fuzzy +msgid "MB" +msgstr "MB" + +#: front/src/components/audio/Player.vue:346 +msgid "Media player" +msgstr "Reprodutor de mÃdia" + +#: front/src/components/Footer.vue:32 +msgid "Mobile and desktop apps" +msgstr "" + +#: front/src/components/Sidebar.vue:97 src/components/manage/users/UsersTable.vue:177 +#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/Base.vue:21 +msgid "Moderation" +msgstr "Moderação" + +#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgid "Moderation policies help you control how your instance interact with a given domain or account." +msgstr "" + +#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:21 +msgid "Modification date" +msgstr "Data de modificação" + +#: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +msgid "Music" +msgstr "Música" + +#: front/src/components/audio/Player.vue:352 +msgid "Mute" +msgstr "" + +#: front/src/components/Sidebar.vue:34 +msgid "My account" +msgstr "Minha conta" + +#: front/src/components/library/radios/Builder.vue:236 +msgid "My awesome description" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:70 +msgid "My awesome library" +msgstr "" + +#: front/src/components/playlists/Form.vue:74 +msgid "My awesome playlist" +msgstr "" + +#: front/src/components/library/radios/Builder.vue:235 +msgid "My awesome radio" +msgstr "" + +#: front/src/views/content/libraries/Home.vue:6 +msgid "My libraries" +msgstr "" + +#: front/src/components/audio/track/Row.vue:40 src/components/library/Track.vue:115 +#: front/src/components/library/Track.vue:124 src/components/library/Track.vue:133 +#: front/src/components/library/Track.vue:142 +#: front/src/components/manage/library/FilesTable.vue:63 +#: front/src/components/manage/library/FilesTable.vue:69 +#: front/src/components/manage/library/FilesTable.vue:75 +#: front/src/components/manage/library/FilesTable.vue:81 +#: front/src/components/manage/users/UsersTable.vue:61 +#: front/src/views/admin/moderation/AccountsDetail.vue:171 +#: front/src/views/admin/moderation/DomainsDetail.vue:90 +#: front/src/views/content/libraries/FilesTable.vue:92 +#: front/src/views/content/libraries/FilesTable.vue:98 +#: front/src/views/admin/moderation/DomainsDetail.vue:109 +#: front/src/views/admin/moderation/DomainsDetail.vue:117 +msgid "N/A" +msgstr "" + +#: front/src/components/manage/moderation/AccountsTable.vue:39 +#: front/src/components/manage/moderation/DomainsTable.vue:38 +#: front/src/components/mixins/Translations.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:31 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +#: front/src/views/content/libraries/Form.vue:10 +#: front/src/components/mixins/Translations.vue:27 +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/Settings.vue:88 +#: front/src/views/auth/PasswordResetConfirm.vue:14 +msgid "New password" +msgstr "Nova senha" + +#: front/src/components/Sidebar.vue:160 +msgid "New tracks will be appended here automatically." +msgstr "" + +#: front/src/components/audio/Player.vue:350 +msgid "Next track" +msgstr "" + +#: front/src/components/Sidebar.vue:119 +msgid "No" +msgstr "Não" + +#: front/src/components/Home.vue:100 +msgid "No add-ons, no plugins : you only need a web library" +msgstr "" + +#: front/src/components/audio/Search.vue:25 +msgid "No album matched your query" +msgstr "" + +#: front/src/components/audio/Search.vue:16 +msgid "No artist matched your query" +msgstr "" + +#: front/src/components/library/Track.vue:158 +msgid "No lyrics available for this track." +msgstr "" + +#: front/src/components/federation/LibraryWidget.vue:6 +msgid "No matching library." +msgstr "" + +#: front/src/views/Notifications.vue:26 +msgid "No notifications yet." +msgstr "" + +#: front/src/components/mixins/Translations.vue:10 +#: front/src/components/playlists/Form.vue:81 src/views/content/libraries/Form.vue:72 +#: front/src/components/mixins/Translations.vue:11 +msgid "Nobody except me" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:57 +msgid "Nobody is following this library" +msgstr "" + +#: front/src/components/manage/users/InvitationsTable.vue:51 +msgid "Not used" +msgstr "" + +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +msgid "Notifications" +msgstr "Notificações" + +#: front/src/components/Footer.vue:47 +msgid "Official website" +msgstr "" + +#: front/src/components/auth/Settings.vue:83 +msgid "Old password" +msgstr "Senha antiga" + +#: front/src/components/manage/users/InvitationsTable.vue:20 +msgid "Open" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgid "Open profile" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgid "Open website" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgid "Or customize your rule" +msgstr "" + +#: front/src/components/favorites/List.vue:31 src/components/library/Radios.vue:41 +#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/users/UsersTable.vue:17 +#: front/src/views/playlists/List.vue:25 +msgid "Order" +msgstr "" + +#: front/src/components/favorites/List.vue:23 src/components/library/Artists.vue:15 +#: front/src/components/library/Radios.vue:33 +#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/manage/moderation/AccountsTable.vue:11 +#: front/src/components/manage/moderation/DomainsTable.vue:9 +#: front/src/components/manage/users/InvitationsTable.vue:9 +#: front/src/components/manage/users/UsersTable.vue:9 +#: front/src/views/content/libraries/FilesTable.vue:21 +#: front/src/views/playlists/List.vue:17 +msgid "Ordering" +msgstr "" + +#: front/src/components/library/Artists.vue:23 +#: front/src/components/manage/moderation/AccountsTable.vue:19 +#: front/src/components/manage/moderation/DomainsTable.vue:17 +#: front/src/views/content/libraries/FilesTable.vue:29 +msgid "Ordering direction" +msgstr "" + +#: front/src/components/manage/users/InvitationsTable.vue:38 +msgid "Owner" +msgstr "Proprietário" + +#: front/src/components/PageNotFound.vue:33 +msgid "Page Not Found" +msgstr "Página Não Encontrada" + +#: front/src/components/PageNotFound.vue:7 +msgid "Page not found!" +msgstr "Página não encontrada!" + +#: front/src/components/Pagination.vue:39 +msgid "Pagination" +msgstr "Paginação" + +#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +msgid "Password" +msgstr "Senha" + +#: front/src/components/auth/SubsonicTokenForm.vue:95 +msgid "Password updated" +msgstr "Senha atualizada" + +#: front/src/views/auth/PasswordResetConfirm.vue:28 +msgid "Password updated successfully" +msgstr "Senha atualizada com sucesso" + +#: front/src/components/audio/Player.vue:349 +msgid "Pause track" +msgstr "" + +#: front/src/components/ShortcutsModal.vue:59 +msgid "Pause/play the current track" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgid "Paused" +msgstr "" + +#: front/src/components/library/FileUpload.vue:106 +#: front/src/views/content/libraries/FilesTable.vue:14 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgid "Pending" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:37 +msgid "Pending approval" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:22 +msgid "Pending files" +msgstr "" + +#: front/src/components/Sidebar.vue:212 +msgid "Pending follow requests" +msgstr "" + +#: front/src/components/manage/users/UsersTable.vue:42 +#: front/src/views/admin/moderation/AccountsDetail.vue:137 +msgid "Permissions" +msgstr "Permissões" + +#: front/src/components/audio/PlayButton.vue:9 src/components/library/Track.vue:40 +msgid "Play" +msgstr "Reproduzir" + +#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/artist/Card.vue:44 src/components/library/Album.vue:28 +#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +msgid "Play all" +msgstr "Reproduzir tudo" + +#: front/src/components/library/Artist.vue:26 +msgid "Play all albums" +msgstr "Reproduzir todos os álbuns" + +#: front/src/components/audio/PlayButton.vue:15 +#: front/src/components/audio/PlayButton.vue:65 +msgid "Play next" +msgstr "" + +#: front/src/components/ShortcutsModal.vue:67 +msgid "Play next track" +msgstr "" + +#: front/src/components/audio/PlayButton.vue:16 +#: front/src/components/audio/PlayButton.vue:63 +#: front/src/components/audio/PlayButton.vue:70 +msgid "Play now" +msgstr "" + +#: front/src/components/ShortcutsModal.vue:63 +msgid "Play previous track" +msgstr "Reproduzir faixa anterior" + +#: front/src/components/Sidebar.vue:211 +msgid "Play this track" +msgstr "Reproduzir esta faixa" + +#: front/src/components/audio/Player.vue:348 +msgid "Play track" +msgstr "Reproduzir faixa" + +#: front/src/views/playlists/Detail.vue:90 +msgid "Playlist" +msgstr "Lista de reprodução" + +#: front/src/views/playlists/Detail.vue:12 +msgid "Playlist containing %{ count } track, by %{ username }" +msgid_plural "Playlist containing %{ count } tracks, by %{ username }" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/playlists/Form.vue:9 +msgid "Playlist created" +msgstr "Lista de reprodução criada" + +#: front/src/components/playlists/Editor.vue:4 +msgid "Playlist editor" +msgstr "" + +#: front/src/components/playlists/Form.vue:21 +msgid "Playlist name" +msgstr "Nome da lista de reprodução" + +#: front/src/components/playlists/Form.vue:6 +msgid "Playlist updated" +msgstr "Lista de reprodução atualizada" + +#: front/src/components/playlists/Form.vue:25 +msgid "Playlist visibility" +msgstr "Visibilidade da lista de reprodução" + +#: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 +#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 +#: front/src/views/playlists/List.vue:106 +msgid "Playlists" +msgstr "Listas de reprodução" + +#: front/src/components/Home.vue:56 +msgid "Playlists? We got them" +msgstr "" + +#: front/src/components/auth/Settings.vue:79 +msgid "Please double-check your password is correct" +msgstr "" + +#: front/src/components/auth/Login.vue:9 +msgid "Please double-check your username/password couple is correct" +msgstr "" + +#: front/src/components/auth/Settings.vue:46 +msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgid "Prevent account or domain from triggering notifications, except from followers." +msgstr "" + +#: front/src/components/audio/EmbedWizard.vue:29 +msgid "Preview" +msgstr "Prévia" + +#: front/src/components/audio/Player.vue:347 +msgid "Previous track" +msgstr "Faixa anterior" + +#: front/src/views/content/remote/Card.vue:39 +msgid "Problem during scanning" +msgstr "" + +#: front/src/components/library/FileUpload.vue:58 +msgid "Proceed" +msgstr "" + +#: front/src/views/auth/EmailConfirm.vue:26 +#: front/src/views/auth/PasswordResetConfirm.vue:31 +msgid "Proceed to login" +msgstr "" + +#: front/src/components/library/FileUpload.vue:17 +msgid "Processing" +msgstr "Processando" + +#: front/src/components/manage/moderation/AccountsTable.vue:188 +#: front/src/components/manage/moderation/DomainsTable.vue:168 +#: front/src/views/content/libraries/Quota.vue:36 +#: front/src/views/content/libraries/Quota.vue:39 +#: front/src/views/content/libraries/Quota.vue:62 +#: front/src/views/content/libraries/Quota.vue:65 +#: front/src/views/content/libraries/Quota.vue:88 +#: front/src/views/content/libraries/Quota.vue:91 +msgid "Purge" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:89 +msgid "Purge errored files?" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:37 +msgid "Purge pending files?" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:63 +msgid "Purge skipped files?" +msgstr "" + +#: front/src/components/Sidebar.vue:20 +msgid "Queue" +msgstr "Fila" + +#: front/src/components/audio/Player.vue:282 +msgid "Queue shuffled!" +msgstr "" + +#: front/src/views/radios/Detail.vue:80 +msgid "Radio" +msgstr "Rádio" + +#: front/src/components/library/radios/Builder.vue:233 +msgid "Radio Builder" +msgstr "" + +#: front/src/components/library/radios/Builder.vue:15 +msgid "Radio created" +msgstr "" + +#: front/src/components/library/radios/Builder.vue:21 +msgid "Radio name" +msgstr "" + +#: front/src/components/library/radios/Builder.vue:12 +msgid "Radio updated" +msgstr "" + +#: front/src/components/library/Library.vue:10 src/components/library/Radios.vue:141 +msgid "Radios" +msgstr "Rádios" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:39 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgid "Reason" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgid "Received library follows" +msgstr "" + +#: front/src/components/manage/moderation/DomainsTable.vue:40 +#: front/src/components/mixins/Translations.vue:36 +#: front/src/components/mixins/Translations.vue:37 +msgid "Received messages" +msgstr "Mensagens recebidas" + +#: front/src/components/library/Home.vue:24 +msgid "Recently added" +msgstr "" + +#: front/src/components/library/Home.vue:11 +msgid "Recently favorited" +msgstr "" + +#: front/src/components/library/Home.vue:6 +msgid "Recently listened" +msgstr "" + +#: front/src/views/content/remote/Home.vue:15 +msgid "Refresh" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:135 +msgid "Refresh node info" +msgstr "" + +#: front/src/components/common/ActionTable.vue:272 +msgid "Refresh table content" +msgstr "" + +#: front/src/components/auth/Profile.vue:12 +msgid "Registered since %{ date }" +msgstr "" + +#: front/src/components/auth/Signup.vue:9 +msgid "Registration are closed on this instance, you will need an invitation code to signup." +msgstr "" + +#: front/src/components/manage/users/UsersTable.vue:71 +msgid "regular user" +msgstr "usuário regular" + +#: front/src/views/content/libraries/Detail.vue:51 +msgid "Reject" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:32 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +msgid "Reject media" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:43 +msgid "Rejected" +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:234 +msgid "Relaunch import" +msgstr "" + +#: front/src/views/content/remote/Home.vue:6 +msgid "Remote libraries" +msgstr "" + +#: front/src/views/content/remote/Home.vue:7 +msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." +msgstr "" + +#: front/src/components/library/radios/Filter.vue:59 +msgid "Remove" +msgstr "" + +#: front/src/components/auth/Settings.vue:58 +msgid "Remove avatar" +msgstr "" + +#: front/src/components/favorites/TrackFavoriteIcon.vue:26 +msgid "Remove from favorites" +msgstr "Remover dos favoritos" + +#: front/src/views/content/libraries/Quota.vue:38 +msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:64 +msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:90 +msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:34 +#: front/src/components/auth/SubsonicTokenForm.vue:37 +msgid "Request a new password" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgid "Request a new Subsonic API password?" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:43 +msgid "Request a password" +msgstr "" + +#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:52 +msgid "Reset your password" +msgstr "Redefinir sua senha" + +#: front/src/components/favorites/List.vue:38 src/components/library/Artists.vue:30 +#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +msgid "Results per page" +msgstr "Resultados por página" + +#: front/src/views/auth/EmailConfirm.vue:17 +msgid "Return to login" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgid "Rule" +msgstr "Regra" + +#: front/src/components/admin/SettingsGroup.vue:63 +#: front/src/components/library/radios/Builder.vue:33 +msgid "Save" +msgstr "Salvar" + +#: front/src/views/content/remote/Card.vue:165 +msgid "Scan launched" +msgstr "" + +#: front/src/views/content/remote/Card.vue:63 +msgid "Scan now" +msgstr "" + +#: front/src/views/content/remote/Card.vue:166 +msgid "Scan skipped (previous scan is too recent)" +msgstr "" + +#: front/src/views/content/remote/Card.vue:31 +msgid "Scan waiting" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgid "Scanned" +msgstr "" + +#: front/src/views/content/remote/Card.vue:47 +msgid "Scanned with errors" +msgstr "" + +#: front/src/views/content/remote/Card.vue:35 +msgid "Scanning… (%{ progress }%)" +msgstr "" + +#: front/src/components/library/Artists.vue:10 src/components/library/Radios.vue:29 +#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/manage/moderation/AccountsTable.vue:5 +#: front/src/components/manage/moderation/DomainsTable.vue:5 +#: front/src/components/manage/users/InvitationsTable.vue:5 +#: front/src/components/manage/users/UsersTable.vue:5 +#: front/src/views/content/libraries/FilesTable.vue:5 src/views/playlists/List.vue:13 +msgid "Search" +msgstr "Pesquisar" + +#: front/src/views/content/remote/ScanForm.vue:9 +msgid "Search a remote library" +msgstr "" + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgid "Search by domain, username, bio..." +msgstr "" + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgid "Search by name..." +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:201 +msgid "Search by title, artist, album…" +msgstr "" + +#: front/src/components/manage/library/FilesTable.vue:176 +msgid "Search by title, artist, domain…" +msgstr "" + +#: front/src/components/manage/users/InvitationsTable.vue:153 +msgid "Search by username, e-mail address, code…" +msgstr "" + +#: front/src/components/manage/users/UsersTable.vue:163 +msgid "Search by username, e-mail address, name…" +msgstr "" + +#: front/src/components/audio/SearchBar.vue:20 +msgid "Search for artists, albums, tracks…" +msgstr "" + +#: front/src/components/audio/Search.vue:2 +msgid "Search for some music" +msgstr "" + +#: front/src/components/library/Track.vue:162 +msgid "Search on lyrics.wikia.com" +msgstr "" + +#: front/src/components/library/Album.vue:33 src/components/library/Artist.vue:31 +#: front/src/components/library/Track.vue:47 +msgid "Search on Wikipedia" +msgstr "" + +#: front/src/components/library/Library.vue:32 src/views/admin/library/Base.vue:17 +#: front/src/views/admin/moderation/Base.vue:22 src/views/admin/users/Base.vue:21 +#: front/src/views/content/Base.vue:19 +msgid "Secondary menu" +msgstr "Menu secundário" + +#: front/src/views/admin/Settings.vue:15 +msgid "Sections" +msgstr "Seções" + +#: front/src/components/library/radios/Builder.vue:45 +msgid "Select a filter" +msgstr "Selecione um filtro" + +#: front/src/components/common/ActionTable.vue:77 +msgid "Select all %{ total } elements" +msgid_plural "Select all %{ total } elements" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/common/ActionTable.vue:86 +msgid "Select only current page" +msgstr "" + +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/manage/users/UsersTable.vue:181 +#: front/src/views/admin/moderation/AccountsDetail.vue:472 +msgid "Settings" +msgstr "Configurações" + +#: front/src/components/auth/Settings.vue:10 +msgid "Settings updated" +msgstr "Configurações atualizadas" + +#: front/src/components/admin/SettingsGroup.vue:11 +msgid "Settings updated successfully." +msgstr "" + +#: front/src/components/manage/users/InvitationForm.vue:27 +msgid "Share link" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:15 +msgid "Share this link with other users so they can request access to your library." +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:14 +#: front/src/views/content/remote/Card.vue:73 +msgid "Sharing link" +msgstr "" + +#: front/src/components/audio/album/Card.vue:40 +msgid "Show %{ count } more track" +msgid_plural "Show %{ count } more tracks" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/audio/artist/Card.vue:30 +msgid "Show 1 more album" +msgid_plural "Show %{ count } more albums" +msgstr[0] "" +msgstr[1] "" + +#: front/src/components/ShortcutsModal.vue:42 +msgid "Show available keyboard shortcuts" +msgstr "" + +#: front/src/views/Notifications.vue:10 +msgid "Show read notifications" +msgstr "" + +#: front/src/components/forms/PasswordInput.vue:25 +msgid "Show/hide password" +msgstr "" + +#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/moderation/AccountsTable.vue:88 +#: front/src/components/manage/moderation/DomainsTable.vue:74 +#: front/src/components/manage/users/InvitationsTable.vue:76 +#: front/src/components/manage/users/UsersTable.vue:87 +#: front/src/views/content/libraries/FilesTable.vue:114 +msgid "Showing results %{ start }-%{ end } on %{ total }" +msgstr "Mostrando resultados %{ start }-%{ end } de %{ total }" + +#: front/src/components/ShortcutsModal.vue:83 +msgid "Shuffle queue" +msgstr "" + +#: front/src/components/audio/Player.vue:362 +msgid "Shuffle your queue" +msgstr "Embaralhar sua fila" + +#: front/src/components/auth/Signup.vue:95 +msgid "Sign Up" +msgstr "Registrar" + +#: front/src/components/manage/users/UsersTable.vue:40 +msgid "Sign-up" +msgstr "" + +#: front/src/components/mixins/Translations.vue:31 +#: front/src/views/admin/moderation/AccountsDetail.vue:176 +#: front/src/components/mixins/Translations.vue:32 +msgid "Sign-up date" +msgstr "Data de registro" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +msgid "Silence activity" +msgstr "Silenciar atividade" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +msgid "Silence notifications" +msgstr "Silenciar notificações" + +#: front/src/components/library/FileUpload.vue:85 +#: front/src/components/library/Track.vue:120 +#: front/src/components/manage/library/FilesTable.vue:44 +#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:29 +msgid "Size" +msgstr "Tamanho" + +#: front/src/views/content/libraries/FilesTable.vue:15 +#: front/src/views/content/libraries/FilesTable.vue:204 +msgid "Skipped" +msgstr "" + +#: front/src/views/content/libraries/Quota.vue:49 +msgid "Skipped files" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +msgid "Software" +msgstr "Software" + +#: front/src/components/Footer.vue:49 +msgid "Source code" +msgstr "Código fonte" + +#: front/src/components/auth/Profile.vue:23 +#: front/src/components/manage/users/UsersTable.vue:70 +msgid "Staff member" +msgstr "" + +#: front/src/components/radios/Button.vue:4 +msgid "Start" +msgstr "" + +#: front/src/views/admin/Settings.vue:86 +msgid "Statistics" +msgstr "EstatÃsticas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:454 +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:358 +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" +msgstr "" + +#: front/src/components/library/FileUpload.vue:86 +#: front/src/components/manage/users/InvitationsTable.vue:17 +#: front/src/components/manage/users/InvitationsTable.vue:39 +#: front/src/components/manage/users/UsersTable.vue:43 +#: front/src/views/admin/moderation/DomainsDetail.vue:123 +#: front/src/views/content/libraries/Detail.vue:28 +msgid "Status" +msgstr "" + +#: front/src/components/radios/Button.vue:3 +msgid "Stop" +msgstr "" + +#: front/src/components/Sidebar.vue:161 +msgid "Stop radio" +msgstr "Parar rádio" + +#: front/src/App.vue:22 +msgid "Submit" +msgstr "Enviar" + +#: front/src/views/admin/Settings.vue:85 +#, fuzzy +msgid "Subsonic" +msgstr "Subsonic" + +#: front/src/components/auth/SubsonicTokenForm.vue:2 +msgid "Subsonic API password" +msgstr "Senha da API Subsonic" + +#: front/src/App.vue:26 +msgid "Suggested choices" +msgstr "" + +#: front/src/components/library/FileUpload.vue:3 +msgid "Summary" +msgstr "" + +#: front/src/components/Footer.vue:39 +msgid "Support forum" +msgstr "" + +#: front/src/components/library/FileUpload.vue:78 +msgid "Supported extensions: %{ extensions }" +msgstr "" + +#: front/src/components/playlists/Editor.vue:9 +msgid "Syncing changes to server…" +msgstr "" + +#: front/src/components/common/CopyInput.vue:3 +msgid "Text copied to clipboard!" +msgstr "Texto copiado para a área de transferência!" + +#: front/src/components/Home.vue:26 +msgid "That's simple: we loved Grooveshark and we want to build something even better." +msgstr "" + +#: front/src/components/Footer.vue:53 +msgid "The funkwhale logo was kindly designed and provided by Francis Gading." +msgstr "" + +#: front/src/views/content/libraries/Form.vue:34 +msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "" + +#: front/src/components/library/FileUpload.vue:39 +msgid "The music files you are uploading are tagged properly:" +msgstr "" + +#: front/src/components/audio/Player.vue:67 +msgid "The next track will play automatically in a few seconds..." +msgstr "A próxima faixa será reproduzida automaticamente em alguns segundos..." + +#: front/src/components/Home.vue:121 +msgid "The plaform is free and open-source, you can install it and modify it without worries" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:4 +msgid "The Subsonic API is not available on this Funkwhale instance." +msgstr "" + +#: front/src/components/library/FileUpload.vue:43 +msgid "The uploaded music files are in OGG, Flac or MP3 format" +msgstr "" + +#: front/src/views/content/Home.vue:4 +msgid "There are various ways to grab new content and make it available here." +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgid "This action is irreversible." +msgstr "Esta ação é irreversÃvel." + +#: front/src/components/library/Album.vue:91 +msgid "This album is present in the following libraries:" +msgstr "" + +#: front/src/components/library/Artist.vue:63 +msgid "This artist is present in the following libraries:" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgid "This domain is subject to specific moderation rules" +msgstr "Este domÃnio está sujeito a regras especÃficas de moderação" + +#: front/src/views/content/Home.vue:9 +msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "" + +#: front/src/components/auth/Profile.vue:16 +msgid "This is you!" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:71 +msgid "This library contains my personal music, I hope you like it." +msgstr "" + +#: front/src/views/content/remote/Card.vue:131 +msgid "This library is private and your approval from its owner is needed to access its content" +msgstr "" + +#: front/src/views/content/remote/Card.vue:132 +msgid "This library is public and you can access its content freely" +msgstr "" + +#: front/src/components/common/ActionTable.vue:45 +msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "" + +#: front/src/components/library/FileUpload.vue:52 +msgid "This reference will be used to group imported files together." +msgstr "" + +#: front/src/components/audio/PlayButton.vue:73 +msgid "This track is not available in any library you have access to" +msgstr "" + +#: front/src/components/library/Track.vue:171 +msgid "This track is present in the following libraries:" +msgstr "" + +#: front/src/views/playlists/Detail.vue:37 +msgid "This will completely delete this playlist and cannot be undone." +msgstr "" + +#: front/src/views/radios/Detail.vue:27 +msgid "This will completely delete this radio and cannot be undone." +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgid "This will completely disable access to the Subsonic API using from account." +msgstr "" + +#: front/src/App.vue:129 src/components/Footer.vue:72 +msgid "This will erase your local data and disconnect you, do you want to continue?" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:36 +msgid "This will log you out from existing devices that use the current password." +msgstr "" + +#: front/src/components/playlists/Editor.vue:44 +msgid "This will remove all tracks from this playlist and cannot be undone." +msgstr "" + +#: front/src/components/audio/track/Table.vue:6 +#: front/src/components/manage/library/FilesTable.vue:37 +#: front/src/components/mixins/Translations.vue:27 +#: front/src/views/content/libraries/FilesTable.vue:54 +#: front/src/components/mixins/Translations.vue:28 +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/ShortcutsModal.vue:79 +msgid "Toggle queue looping" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgid "Total size" +msgstr "Tamanho total" + +#: front/src/views/content/libraries/Card.vue:61 +msgid "Total size of the files in this library" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:113 +msgid "Total users" +msgstr "" + +#: front/src/components/audio/SearchBar.vue:27 src/components/library/Track.vue:262 +#: front/src/components/metadata/Search.vue:138 +msgid "Track" +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:205 +msgid "Track already present in one of your libraries" +msgstr "" + +#: front/src/components/library/Track.vue:85 +msgid "Track information" +msgstr "" + +#: front/src/components/library/radios/Filter.vue:44 +msgid "Track matching filter" +msgstr "" + +#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:24 +msgid "Track name" +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:209 +msgid "Track uploaded, but not processed by the server yet" +msgstr "" + +#: front/src/components/instance/Stats.vue:54 +msgid "tracks" +msgstr "" + +#: front/src/components/library/Album.vue:81 +#: front/src/components/playlists/PlaylistModal.vue:33 +#: front/src/views/admin/moderation/AccountsDetail.vue:329 +#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 +#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +msgid "Tracks" +msgstr "" + +#: front/src/components/library/Artist.vue:54 +msgid "Tracks by this artist" +msgstr "" + +#: front/src/components/instance/Stats.vue:25 +msgid "Tracks favorited" +msgstr "" + +#: front/src/components/instance/Stats.vue:19 +msgid "tracks listened" +msgstr "" + +#: front/src/components/library/Track.vue:138 +#: front/src/components/manage/library/FilesTable.vue:41 +#: front/src/views/admin/moderation/AccountsDetail.vue:151 +msgid "Type" +msgstr "Tipo" + +#: front/src/components/manage/moderation/AccountsTable.vue:44 +#: front/src/components/manage/moderation/DomainsTable.vue:42 +msgid "Under moderation rule" +msgstr "" + +#: front/src/views/content/remote/Card.vue:100 src/views/content/remote/Card.vue:105 +msgid "Unfollow" +msgstr "" + +#: front/src/views/content/remote/Card.vue:101 +msgid "Unfollow this library?" +msgstr "" + +#: front/src/components/About.vue:15 +msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +msgstr "" + +#: front/src/components/Home.vue:37 +msgid "Unlimited music" +msgstr "" + +#: front/src/components/audio/Player.vue:351 +msgid "Unmute" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgid "Update" +msgstr "" + +#: front/src/components/auth/Settings.vue:50 +msgid "Update avatar" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:25 +msgid "Update library" +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +msgid "Update moderation rule" +msgstr "" + +#: front/src/components/playlists/Form.vue:33 +msgid "Update playlist" +msgstr "" + +#: front/src/components/auth/Settings.vue:27 +msgid "Update settings" +msgstr "" + +#: front/src/views/auth/PasswordResetConfirm.vue:21 +msgid "Update your password" +msgstr "" + +#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/DetailArea.vue:24 +msgid "Upload" +msgstr "Enviar" + +#: front/src/components/auth/Settings.vue:45 +msgid "Upload a new avatar" +msgstr "" + +#: front/src/views/content/Home.vue:6 +msgid "Upload audio content" +msgstr "" + +#: front/src/views/content/libraries/FilesTable.vue:57 +msgid "Upload date" +msgstr "" + +#: front/src/components/library/FileUpload.vue:219 +#: front/src/components/library/FileUpload.vue:220 +msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "" + +#: front/src/views/content/Home.vue:7 +msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." +msgstr "" + +#: front/src/components/library/FileUpload.vue:31 +msgid "Upload new tracks" +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:269 +msgid "Upload quota" +msgstr "" + +#: front/src/components/library/FileUpload.vue:228 +msgid "Upload timeout, please try again" +msgstr "" + +#: front/src/components/library/FileUpload.vue:100 +msgid "Uploaded" +msgstr "Enviado" + +#: front/src/components/library/FileUpload.vue:5 +msgid "Uploading" +msgstr "Enviando" + +#: front/src/components/library/FileUpload.vue:103 +msgid "Uploading…" +msgstr "Enviando…" + +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:305 +#: front/src/views/admin/moderation/DomainsDetail.vue:241 +#: front/src/components/mixins/Translations.vue:38 +msgid "Uploads" +msgstr "Envios" + +#: front/src/components/Footer.vue:16 +msgid "Use another instance" +msgstr "" + +#: front/src/views/auth/PasswordReset.vue:12 +msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." +msgstr "" + +#: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgid "Use this setting to temporarily enable/disable the policy without completely removing it." +msgstr "" + +#: front/src/components/manage/users/InvitationsTable.vue:49 +msgid "Used" +msgstr "" + +#: front/src/views/content/libraries/Detail.vue:26 +msgid "User" +msgstr "Usuário" + +#: front/src/components/instance/Stats.vue:5 +msgid "User activity" +msgstr "Atividade de usuário" + +#: front/src/components/library/Album.vue:88 src/components/library/Artist.vue:60 +#: front/src/components/library/Track.vue:168 +msgid "User libraries" +msgstr "" + +#: front/src/components/library/Radios.vue:20 +msgid "User radios" +msgstr "" + +#: front/src/components/auth/Signup.vue:19 +#: front/src/components/manage/users/UsersTable.vue:37 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/views/admin/moderation/AccountsDetail.vue:85 +#: front/src/components/mixins/Translations.vue:34 +msgid "Username" +msgstr "Nome de usuário" + +#: front/src/components/auth/Login.vue:15 +msgid "Username or email" +msgstr "Nome de usuário ou email" + +#: front/src/components/instance/Stats.vue:13 +msgid "users" +msgstr "usuários" + +#: front/src/components/Sidebar.vue:91 +#: front/src/components/manage/moderation/DomainsTable.vue:39 +#: front/src/components/mixins/Translations.vue:35 src/views/admin/Settings.vue:81 +#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:3 +#: front/src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:36 +msgid "Users" +msgstr "Usuários" + +#: front/src/components/Footer.vue:29 +msgid "Using Funkwhale" +msgstr "Usando Funkwhale" + +#: front/src/components/Footer.vue:13 +msgid "Version %{version}" +msgstr "Versão %{version}" + +#: front/src/views/content/libraries/Quota.vue:29 +#: front/src/views/content/libraries/Quota.vue:56 +#: front/src/views/content/libraries/Quota.vue:82 +msgid "View files" +msgstr "" + +#: front/src/components/library/Album.vue:37 src/components/library/Artist.vue:35 +#: front/src/components/library/Track.vue:51 +#: front/src/components/metadata/ArtistCard.vue:49 +#: front/src/components/metadata/ReleaseCard.vue:53 +msgid "View on MusicBrainz" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:18 +msgid "Visibility" +msgstr "Visibilidade" + +#: front/src/views/content/libraries/Card.vue:59 +msgid "Visibility: everyone on this instance" +msgstr "Visibilidade: todos nesta instância" + +#: front/src/views/content/libraries/Card.vue:60 +msgid "Visibility: everyone, including other instances" +msgstr "Visibilidade: todos, incluindo outras instâncias" + +#: front/src/views/content/libraries/Card.vue:58 +msgid "Visibility: nobody except me" +msgstr "" + +#: front/src/components/library/Album.vue:67 +msgid "Volume %{ number }" +msgstr "Volume %{ number }" + +#: front/src/components/playlists/PlaylistModal.vue:20 +msgid "We cannot add the track to a playlist" +msgstr "" + +#: front/src/components/playlists/Form.vue:14 +msgid "We cannot create the playlist" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +msgid "We cannot create your account" +msgstr "" + +#: front/src/components/audio/Player.vue:64 +msgid "We cannot load this track" +msgstr "" + +#: front/src/components/auth/Login.vue:7 +msgid "We cannot log you in" +msgstr "" + +#: front/src/components/auth/Settings.vue:38 +msgid "We cannot save your avatar" +msgstr "" + +#: front/src/components/auth/Settings.vue:14 +msgid "We cannot save your settings" +msgstr "" + +#: front/src/components/Home.vue:127 +msgid "We do not track you or bother you with ads" +msgstr "" + +#: front/src/components/library/Track.vue:95 +msgid "We don't have any copyright information for this track" +msgstr "" + +#: front/src/components/library/Track.vue:106 +msgid "We don't have any licensing information for this track" +msgstr "" + +#: front/src/components/library/FileUpload.vue:40 +msgid "We recommend using Picard for that purpose." +msgstr "" + +#: front/src/components/Home.vue:7 +msgid "We think listening to music should be simple." +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +msgid "We're sorry, the page you asked for does not exist:" +msgstr "" + +#: front/src/components/Home.vue:153 +msgid "Welcome" +msgstr "" + +#: front/src/components/Home.vue:5 +msgid "Welcome on Funkwhale" +msgstr "" + +#: front/src/components/Home.vue:24 +msgid "Why funkwhale?" +msgstr "" + +#: front/src/components/audio/EmbedWizard.vue:13 +msgid "Widget height" +msgstr "" + +#: front/src/components/audio/EmbedWizard.vue:6 +msgid "Widget width" +msgstr "" + +#: front/src/components/Sidebar.vue:118 +#: front/src/components/manage/moderation/AccountsTable.vue:72 +#: front/src/components/manage/moderation/DomainsTable.vue:58 +msgid "Yes" +msgstr "Sim" + +#: front/src/components/auth/Logout.vue:8 +msgid "Yes, log me out!" +msgstr "" + +#: front/src/views/content/libraries/Form.vue:19 +msgid "You are able to share your library with other people, regardless of its visibility." +msgstr "" + +#: front/src/components/library/FileUpload.vue:33 +msgid "You are about to upload music to your library. Before proceeding, please ensure that:" +msgstr "" + +#: front/src/components/auth/Logout.vue:7 +msgid "You are currently logged in as %{ username }" +msgstr "" + +#: front/src/views/content/Home.vue:17 +msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." +msgstr "" + +#: front/src/components/Home.vue:133 +msgid "You can invite friends and family to your instance so they can enjoy your music" +msgstr "" + +#: front/src/views/auth/EmailConfirm.vue:24 +msgid "You can now use the service without limitations." +msgstr "" + +#: front/src/components/library/radios/Builder.vue:7 +msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:8 +msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." +msgstr "" + +#: front/src/views/admin/moderation/AccountsDetail.vue:46 +msgid "You don't have any rule in place for this account." +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgid "You don't have any rule in place for this domain." +msgstr "" + +#: front/src/components/Sidebar.vue:158 +msgid "You have a radio playing" +msgstr "" + +#: front/src/components/audio/Player.vue:71 +msgid "You may have a connectivity issue." +msgstr "" + +#: front/src/App.vue:17 +msgid "You need to select an instance in order to continue" +msgstr "" + +#: front/src/components/auth/Settings.vue:100 +msgid "You will be logged out from this session and have to log in with the new one" +msgstr "" + +#: front/src/components/auth/Settings.vue:71 +msgid "You will have to update your password on your clients that use this password." +msgstr "" + +#: front/src/components/favorites/List.vue:112 +msgid "Your Favorites" +msgstr "" + +#: front/src/components/Home.vue:114 +msgid "Your music, your way" +msgstr "" + +#: front/src/views/Notifications.vue:7 +msgid "Your notifications" +msgstr "Suas notificações" + +#: front/src/views/auth/PasswordResetConfirm.vue:29 +msgid "Your password has been updated successfully." +msgstr "Sua senha foi atualizada com sucesso." + +#: front/src/components/auth/Settings.vue:101 +msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" +msgstr "" diff --git a/front/locales/pt_PT/LC_MESSAGES/app.po b/front/locales/pt_PT/LC_MESSAGES/app.po index 221a833c43f42505b907efe49f3c99d656837ce1..1f7ab2acdc32b41d2e338f7d429131284a7b65cf 100644 --- a/front/locales/pt_PT/LC_MESSAGES/app.po +++ b/front/locales/pt_PT/LC_MESSAGES/app.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: front 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 15:55+0100\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" "PO-Revision-Date: 2019-01-18 21:50+0000\n" "Last-Translator: troll <my_name_is_troll@protonmail.com>\n" "Language-Team: none\n" @@ -19,1875 +19,3253 @@ msgstr "" "X-Generator: Weblate 3.2.2\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\", de %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } de %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(vazio)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 +#, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Logar na sua conta Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "%{ count } em %{ total } selecionado" msgstr[1] "%{ count } em %{ total } selecionados" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } pista" msgstr[1] "%{ count } pistas" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } tema em %{ albumsCount } álbuns" msgstr[1] "%{ count } temas em %{ albumsCount } álbuns" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } pista corresponde aos filtros selecionados" msgstr[1] "%{ count } pistas correspondem aos filtros selecionados" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } pista foi adicionada à sua fila" -msgstr[1] "%{ count } pistas foram adicionadas à sua fila" - #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} pista" msgstr[1] "%{ count } pistas" #: front/src/views/content/libraries/Quota.vue:11 +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } utilizado em %{ max } permitido" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } h %{ minutes } min" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } min" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "%{ username } aceitou seu seguimento na biblioteca \"%{ library }\"" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "%{ username } seguiu a sua biblioteca \"%{ library }\"" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "%{ username } seguiu a sua biblioteca \"%{ library }\"" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "%{ username } perfil" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" -"<translate :translate-params=\"{instanceName: instanceHostname}\">Sobre " -"%{instanceName}</translate>" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 álbum" msgstr[1] "%{ count } álbums" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 preferida" msgstr[1] "%{ count } preferidas" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "Biblioteca limpa" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "Ocorreu um erro durante o upload deste ficheiro" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "Ocorreu um erro ao salvar suas mudanças" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "Sobre %{ instance }" #: front/src/components/Footer.vue:6 +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "Sobre %{instanceName}" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "Sobre Funkwhale" #: front/src/components/Footer.vue:10 +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Sobre a página" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "Sobre esta instância" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "Aceitar" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Aceitado" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "Acesso desativado" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Selecione um filtro" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "Acesso desativado" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Adicionar aos favoritos" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" msgstr "" -"Aceda à sua música a partir de uma interface limpa que se concentra no que " -"realmente importa" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Suas notificações" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Adicionar à Playlist…" + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "Acesso desativado" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" +msgstr "Aceda à sua música a partir de uma interface limpa que se concentra no que realmente importa" + +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "Data de acesso" +msgstr "Acesso desativado" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Contas" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Contas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Dados da conta" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "Configurações da conta" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "Configurações da Conta" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Status da conta" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "Email da conta" #: front/src/views/admin/moderation/AccountsList.vue:3 #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 +#, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Contas" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "Açao" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "A ação %{ action } foi lançada com sucesso em %{ count } item" msgstr[1] "A ação %{ action } foi lançada com sucesso em %{ count } itens" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "Ações" #: front/src/components/manage/users/UsersTable.vue:53 +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Ativo" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "Atividade" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "Visibilidade da atividade" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "Adicionar" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "Adicionar um domÃnio" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +#, fuzzy +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "Criar uma nova regra de moderação" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" msgstr "Criar uma nova regra de moderação" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Adicionar e gerenciar conteúdo" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Adicionar conteúdo" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Adicionar um filtro" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Adicione filtros para personalizar seu rádio" -#: front/src/components/audio/PlayButton.vue:64 +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Adicionar à fila atual" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Adicionar aos favoritos" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Adicionar à Playlist…" -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Adicionar à fila" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Adicionar a esta playlist" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Adicionar música" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "Admin" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "Administração" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Ãlbum" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Ãlbum contendo %{ count } pista, por %{ artist }" -msgstr[1] "Ãlbum contendo %{ count } pistas, por %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Ãlbum" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Ãlbuns deste artista" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Nome do álbum" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Página do álbum" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Nome do álbum" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Ãlbuns" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Ãlbuns deste artista" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Tudo" +#: front/src/components/common/ActionTable.vue:59 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "%{ count } em %{ total } selecionado" +msgstr[1] "%{ count } em %{ total } selecionados" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "Ocorreu um erro ao salvar suas mudanças" +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "Ocorreu um erro ao salvar suas mudanças" + +#: front/src/components/federation/FetchButton.vue:41 +#, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "Ocorreu um erro ao salvar suas mudanças" + #: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Um erro desconhecido aconteceu, isso pode significar que o servidor está inoperante ou não pode ser alcançado" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "Açao" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "Aprovar" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +#, fuzzy +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "Aprovar" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Tem certeza que deseja sair?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "Artista" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "Artista" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Nome do artista" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Página do artista" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Nome do artista" #: front/src/components/audio/Search.vue:65 +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "Artista, álbum, música…" +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "Artistas" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "Artistas" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "Ascendente" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "Peça uma redefinição de senha" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Conteúdo áudio" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "Atalhos do leitor de áudio" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "Playlists disponÃveis" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Avatar" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Volte ao login" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Atualizar configurações" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Bitrate" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "Bloquear tudo" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" -msgstr "" -"Bloquear tudo a partir desta conta ou domÃnio. Isso evitará qualquer " -"interação com a entidade e eliminará conteúdo relacionado (uploads, " -"bibliotecas, segue, etc.)" +msgstr "Bloquear tudo a partir desta conta ou domÃnio. Isso evitará qualquer interação com a entidade e eliminará conteúdo relacionado (uploads, bibliotecas, segue, etc.)" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "Procurar" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "Navegar pela biblioteca" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "Procure radios" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "Procurando artistas" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "Procure playlists" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "Procure radios" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "Construtor" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "Por %{ artist }" -#: front/src/views/content/remote/Card.vue:103 +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." -msgstr "" -"Ao deixar de seguir esta biblioteca, você perderá o acesso ao seu conteúdo." - -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +msgstr "Ao deixar de seguir esta biblioteca, você perderá o acesso ao seu conteúdo." + +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "Tamanho em cache" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Cancelar" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Candidatos" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Não é possÃvel alterar sua senha" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" -msgstr "" -"Não é possÃvel carregar este arquivo, assegure-se de que não é muito grande" +msgstr "Não é possÃvel carregar este arquivo, assegure-se de que não é muito grande" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Mudar idioma" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Mudar minha senha" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Mudar senha" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Mude sua senha" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Mude sua senha?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Modificações sincronizadas com o servidor" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Mudar sua senha também muda sua senha da API Subsonic se você tiver solicitado uma." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "Alterar sua senha terá as seguintes consequências" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "Sala de bate-papo" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Escolha sua instância" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "Biblioteca limpa" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "Claro" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "Claro" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "Limpar playlist" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "Limpar sua fila" #: front/src/components/Home.vue:44 +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Clique uma vez, ouça por horas usando rádios" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Clique para selecionar arquivos para carregar ou arrastar e soltar arquivos ou diretórios" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "Fechar" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +#, fuzzy +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "Fechar" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Código" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Colapso" -#: front/src/components/library/radios/Builder.vue:62 +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "Configuração" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Confirme" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Confirme seu email" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Código de confirmação" -#: front/src/components/common/ActionTable.vue:7 -msgid "Content have been updated, click refresh to see up-to-date content" +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Selecione um filtro" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Selecione um filtro" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." msgstr "" -"O conteúdo foi atualizado, clique em atualizar para ver o conteúdo atualizado" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" +msgid "Content have been updated, click refresh to see up-to-date content" +msgstr "O conteúdo foi atualizado, clique em atualizar para ver o conteúdo atualizado" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "Contribuir" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Cópia" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "Copiar músicas da fila atual para a playlist" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "Copie/colar este código no seu website HTML" -#: front/src/components/library/Track.vue:91 +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Direitos Autorais" #: front/src/views/auth/EmailConfirm.vue:7 +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Não foi possÃvel confirmar o seu e-mail" #: front/src/views/content/remote/ScanForm.vue:3 +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Não foi possÃvel buscar a biblioteca remota" -#: front/src/views/content/libraries/FilesTable.vue:213 -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "" -"Não foi possÃvel processar esta pista, certifique-se de que está " -"correctamente etiquetada" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Capas, letras, nosso objetivo é tê-los todos ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Criar" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Crie uma conta funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Crie uma nova playlist" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Crie uma nova playlist" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Criar uma nova biblioteca" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Crie uma nova playlist" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Crie a sua conta" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Crie uma playlist" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Criar biblioteca" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Criar a minha conta" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Crie uma playlist" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Crie seu próprio rádio" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Data de criação" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Avatar atual" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Biblioteca atual" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Música atual" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Uso atual" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Data" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Informação da música" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "Diminuir o volume" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Suprimir" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Suprimir Playlist" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Excluir biblioteca" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Eliminar regra de moderação" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Suprimir Playlist" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Suprimir radio" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Excluir esta biblioteca?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Excluir esta biblioteca?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Excluir esta biblioteca?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Eliminar esta regra de moderação?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 -#: front/src/components/manage/moderation/AccountsTable.vue:22 -#: front/src/components/manage/moderation/DomainsTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Eliminar esta regra de moderação?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Excluir esta biblioteca?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 +#: front/src/components/manage/moderation/AccountsTable.vue:22 +#: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "Descendente" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "Descrição" -#: front/src/views/content/libraries/Card.vue:47 -msgid "Detail" -msgstr "Detalhe" +#: front/src/views/admin/library/LibraryDetail.vue:123 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "Descrição" -#: front/src/views/content/remote/Card.vue:50 +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "Detalhes" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Determine quanto conteúdo o usuário pode enviar. Deixe em branco para usar o valor padrão da instância." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Determinar o nÃvel de visibilidade de sua atividade" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Desativar acesso" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Desativar o acesso a Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Desativar o acesso o API Subsonic?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Desativado" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Descubra como usar o Funkwhale em outros aplicativos" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Mostrar nome" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Exibir publicamente" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." -msgstr "" -"Não faça o download de nenhum arquivo de mÃdia (áudio, capa do álbum, avatar " -"da conta ...) dessa conta ou domÃnio. Isso também limpará o conteúdo " -"existente." +msgstr "Não faça o download de nenhum arquivo de mÃdia (áudio, capa do álbum, avatar da conta ...) dessa conta ou domÃnio. Isso também limpará o conteúdo existente." -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Você quer limpar a playlist \"%{ playlist }\"?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Você quer confirmar esta ação?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Você deseja excluir a playlist \"%{ playlist }\"?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Você deseja excluir a radio \"%{ radio }\"?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Você deseja excluir a radio \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Deseja lançar %{ action } no %{ count } elemento?" msgstr[1] "Deseja lançar %{ action } nos %{ count } elementos?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Você quer restaurar sua fila anterior?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "Documentação" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "Dominio" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +#, fuzzy +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "DomÃnios" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Descarregar" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "Arraste e solte as linhas para reordenar as músicas da playlist" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "Duração" #: front/src/views/auth/EmailConfirm.vue:23 +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "E-mail confirmado" -#: front/src/components/Home.vue:93 +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Fácil de usar" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" +msgid "Edit" +msgstr "Editar" + +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" msgid "Edit" msgstr "Editar" -#: front/src/components/About.vue:21 +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Erro ao aplicar a ação" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Editar informações da instância" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 -msgid "Edit…" -msgstr "Modificar…" +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Atualizar a regra de moderação" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Jogar esta pista" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Jogar esta pista" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Jogar esta pista" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Editar" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" +msgstr "Editar" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "Email" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "Endereço de e-mail" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "Incorporar" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "Código de incorporação" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "Incorpore este álbum no seu website" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +#, fuzzy +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "Incorpore esta pista no seu website" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "Incorpore esta pista no seu website" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "A biblioteca emitida segue" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "Mensagens emitidas" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +msgctxt "*/*/*" msgid "Enabled" msgstr "Ativado" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Finalizar edição" #: front/src/views/content/remote/ScanForm.vue:50 +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "Insira um URL de biblioteca" -#: front/src/components/library/Radios.vue:140 +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Indicar um nome de rádio …" -#: front/src/components/library/Artists.vue:118 +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Indicar um nome de artista …" #: front/src/views/playlists/List.vue:107 +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Indicar um nome de playlist…" -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +#, fuzzy +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "Insira o endereço de e-mail associado à sua conta" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Insira seu email" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Indicar seu código de convite (não diferencia maiúsculas de minúsculas)" #: front/src/components/metadata/Search.vue:114 +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Insira sua consulta de pesquisa …" -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Indicar seu nome de usuário" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Indicar seu nome de usuário ou email" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Erro" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Relatório de erros" + #: front/src/views/admin/Settings.vue:87 +msgctxt "Content/Admin/Menu" msgid "Error reporting" msgstr "Relatório de erros" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Errado" + +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Erro ao aplicar a ação" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Erro ao solicitar uma redefinição de senha" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Erro ao aplicar a ação" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Erro ao alterar sua senha" #: front/src/views/admin/moderation/DomainsList.vue:6 +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Erro na criação do domÃnio" +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Erro na criação da regra" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Erro ao criar convite" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Erro na criação da regra" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Erro ao criar convite" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Erro ao buscar informações do nó" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Erro ao salvar configurações" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Erro ao salvar configurações" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 +#, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Erro ao salvar configurações" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Errado" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Arquivos errados" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Todo o mundo" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Todos nessa instância" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 +#, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Todos, em todas as instâncias" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "Excluir" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Data de vencimento" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "Expirado" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "Expirado / usados" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." -msgstr "" -"Explique porque está a aplicar esta polÃtica. Dependendo da configuração da " -"sua instância, isso o ajudará a lembrar por que você agiu nessa conta ou " -"domÃnio e poderá ser exibido publicamente para ajudar os usuários a entender " -"quais regras de moderação estão em vigor." +msgstr "Explique porque está a aplicar esta polÃtica. Dependendo da configuração da sua instância, isso o ajudará a lembrar por que você agiu nessa conta ou domÃnio e poderá ser exibido publicamente para ajudar os usuários a entender quais regras de moderação estão em vigor." +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +msgctxt "Content/Library/Dropdown" msgid "Failed" msgstr "Falhou" -#: front/src/views/content/remote/Card.vue:58 +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Pistas falhadas:" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Pistas falhadas:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Favoritas" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Favoritas" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "Federação" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "Federação" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Nome do ficheiro" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Ficheiros" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Nome do filtro" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Acabado" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" msgstr "Visto pela primeira vez" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Data da primeira vista" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "Segue" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "Siga as bibliotecas remotas" -#: front/src/views/content/remote/Card.vue:88 +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "Solicitação de seguir pendente" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "Seguidores" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "Seguidores" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +msgctxt "Content/Library/Card.Paragraph" msgid "Following" msgstr "Seguir" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Do álbum %{ album } por %{ artist }" +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "Segue" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Desativar acesso" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "O Funkwhale é compatÃvel com outros players de música que suportam a API Subsonic." -#: front/src/components/Home.vue:95 +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale é muito simples de usar." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "O Funkwhale foi projetado para facilitar a escuta da música que você gosta ou descobrir novos artistas." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "O Funkwhale é gratuito e permite controlar sua música." #: front/src/components/Home.vue:66 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale cuida da sua música" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "Atalhos gerais" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Receba um novo convite" #: front/src/components/Home.vue:13 +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Me leve para a biblioteca" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Obtenha metadados de qualidade sobre sua música graças ao <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Começar" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "Obter ajuda" + #: front/src/components/Footer.vue:37 +#, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "Obter ajuda" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "Vá" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Vá para a página inicial" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "Procurando artistas" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "Ocultar conta ou conteúdo de domÃnio, exceto de seguidores." +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Adicionar conteúdo" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Casa" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "Horas de música" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "No entanto, acessar o Funkwhale desses clientes requer uma senha separada que você pode definir abaixo." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "Se o endereço de e-mail fornecido na etapa anterior for válido e vinculado a uma conta de usuário, você deverá receber um e-mail com instruções de redefinição nos próximos minutos." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Data de importação" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Importe músicas de várias plataformas, como o YouTube ou o SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Status de Importação" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "Referência de importação" +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Import status" +msgstr "Status de Importação" + +#: front/src/components/manage/library/UploadsTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/views/content/libraries/FilesTable.vue:59 +#, fuzzy +msgctxt "Content/Library/*/Noun" msgid "Import status" msgstr "Status de Importação" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Importado" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Data de importação" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Adicionado recentemente" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Nos favoritos" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Inativo" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "Aumentar o volume" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "Insira o endereço de e-mail associado à sua conta" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Inserir da fila (%{ count } música)" msgstr[1] "Inserir da fila (%{ count } músicas)" +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Dados da instância" + #: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Dados da instância" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Informação da instância" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Rádios da instância" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "Configurações da instância" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Dados da instância" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" +msgstr "Tipo de ficheiro inválido, certifique-se de que está a carregar um ficheiro de áudio. As extensões de arquivo suportadas são %{ extensions }" + +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" msgstr "" -"Tipo de ficheiro inválido, certifique-se de que está a carregar um ficheiro " -"de áudio. As extensões de arquivo suportadas são %{ extensions }" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Código de Convite" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Código de convite (opcional)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "Convites" #: front/src/components/Footer.vue:41 +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Rastreador de problemas" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "Guardar suas músicas favoritas" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "Atalhos do teclado" #: front/src/views/admin/moderation/DomainsDetail.vue:161 +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Contas conhecidas" #: front/src/views/content/remote/Home.vue:14 +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "Bibliotecas conhecidas" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "Ultima atividade" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +msgctxt "Content/*/Table.Label" msgid "Last checked" msgstr "Última verificação" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "Última modificação" #: front/src/components/manage/moderation/AccountsTable.vue:43 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" msgstr "Visto pela última vez" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "Data da última vista" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "Última atualização:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "Lançamento" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Saiba mais sobre esta instância" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "Deixar vazio para um código aleatório" #: front/src/components/audio/EmbedWizard.vue:7 +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "Deixe vazio para um widget responsivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Libraries" +msgstr "Bibliotecas" + +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" msgid "Libraries" msgstr "Bibliotecas" +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Biblioteca atualizada" + #: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." msgstr "As bibliotecas ajudam você a organizar e compartilhar suas coleções de músicas. Você pode enviar sua própria coleção de músicas para o Funkwhale e compartilhá-la com seus amigos e familiares." -#: front/src/components/instance/Stats.vue:30 +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Biblioteca" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Biblioteca criada" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Biblioteca atualizada" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Biblioteca suprimida" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Ficheiros da biblioteca" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Biblioteca atualizada" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "License" msgstr "Licença" +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 +#, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Carregando seguidores…" + #: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Carregando seguidores…" #: front/src/views/content/libraries/Home.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Carregando bibliotecas…" #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Carregando dados da biblioteca…" -#: front/src/views/Notifications.vue:4 +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Carregando notificações…" #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" msgstr "Carregando bibliotecas remotas..." #: front/src/views/content/libraries/Quota.vue:4 +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Carregando dados de uso…" #: front/src/components/favorites/List.vue:5 +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Carregando seus favoritos …" +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 +#, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Conta local" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Autenticação" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Logar na sua conta Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Sair" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Conectado como %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Entrar" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Status do login" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Sair" #: front/src/views/content/libraries/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "Parece que você ainda não tem biblioteca, é hora de criar uma." -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Looping desativado. Clique para alternar para um loop de musica única." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." msgstr "Looping em uma única música. Clique para alternar para todo o loop da fila." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." msgstr "Looping na fila inteira. Clique para desativar o loop." -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "Letras" - -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "Menu principal" -#: front/src/views/admin/library/Base.vue:16 +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Gerenciar biblioteca" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "Gerenciar playlists" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "Gerenciar usuários" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "Gerenciar suas playlists" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Marque tudo como lido" -#: front/src/components/notifications/NotificationRow.vue:44 +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Marcar como lido" -#: front/src/components/notifications/NotificationRow.vue:45 +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Marcar como não lido" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "MB" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "Leitor de mÃdia" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "Registrado desde %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "Aplicativos móveis e de computador" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 +#, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "Moderação" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." -msgstr "" -"As polÃticas de moderação ajudam a controlar como sua instância interage com " -"um determinado domÃnio ou conta." +msgstr "As polÃticas de moderação ajudam a controlar como sua instância interage com um determinado domÃnio ou conta." -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Data de modificação" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Data de modificação" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Música" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Mudo" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "Ultima atividade" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Suas notificações" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Minha conta" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Minha descrição incrÃvel" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "Minha biblioteca incrÃvel" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Minha playlist incrÃvel" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Meu rádio incrÃvel" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Minhas bibliotecas" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "Nome" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "Nome" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Nova senha" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Novas músicas serão adicionadas automaticamente aqui." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Música seguinte" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Não" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "Sem add-ons, sem plugins: você só precisa de uma biblioteca da Web" #: front/src/components/audio/Search.vue:25 +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Nenhum álbum correspondeu à sua consulta" #: front/src/components/audio/Search.vue:16 +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Nenhum artista correspondeu à sua consulta" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "Nenhuma letra disponÃvel para esta música." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "Não temos nenhuma informação de licença para esta pista" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Nenhuma biblioteca correspondente." -#: front/src/views/Notifications.vue:26 -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." msgstr "Ainda não há notificações." +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" + #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Ninguém, exceto eu" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Ninguém está seguindo esta biblioteca" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Não usado" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "Notificações" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "Notificações" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Website oficial" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Senha Antiga" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Aberto" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Atualizar a regra de moderação" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "Abrir o perfil" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Ver no MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "Abrir o perfil" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "Abrir o perfil" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Abrir o website" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "Ou personalize sua regra" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "Ordenar" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "Ordenar" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "Direção de ordenar" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Proprietário" #: front/src/components/PageNotFound.vue:33 +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Página não encontrada" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Página não encontrada!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "Paginação" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Palavra-passe" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Senha atualizada" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Senha atualizada com sucesso" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "Parar música" #: front/src/components/ShortcutsModal.vue:59 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" msgstr "Pausa/leitura da pista actual" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "Pausa" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "Pendente" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Aprovação pendente" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Ficheiros pendentes" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" msgstr "Solicitações pendentes" +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Ficheiros pendentes" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Ficheiros pendentes" + #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "Permissões" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "Permissões" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" msgstr "Jogar" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Jogar tudo" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Jogar todos os álbuns" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Jogar seguinte" #: front/src/components/ShortcutsModal.vue:67 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Jogar pista seguinte" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Jogue agora" #: front/src/components/ShortcutsModal.vue:63 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Jogar pista anterior" -#: front/src/components/Sidebar.vue:211 +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Jogar esta pista" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Jogar mÅ©sica" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "Jogar" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "Playlist" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "Playlist contendo%{ count } música, por %{ username }" msgstr[1] "Playlist contendo %{ count } músicas, por %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "Playlist criada" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Editor de playlist" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Nome da playlist" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "Playlist atualizada" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "Visibilidade da playlist" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" +msgid "Playlists" +msgstr "Playlists" + +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Playlists" msgstr "Playlists" #: front/src/components/Home.vue:56 +#, fuzzy +msgctxt "Content/Home/List item" msgid "Playlists? We got them" -msgstr "" +msgstr "Playlist criada" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "Por favor, verifique novamente se sua senha está correta" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "Por favor, verifique se o seu nome de usuário e senha estão corretos" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF ou JPG. No máximo 2MB. Será reduzido para 400x400px." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "Paginação" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." -msgstr "" -"Evite que uma conta ou domÃnio acione notificações, exceto de seguidores." +msgstr "Evite que uma conta ou domÃnio acione notificações, exceto de seguidores." -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "Pré-visualização" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Música anterior" -#: front/src/views/content/remote/Card.vue:39 +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Erro durante a análise" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Prosseguir" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" msgstr "Continuar com o login" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Em tratamento" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "Abrir o perfil" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1896,1096 +3274,1975 @@ msgstr "Em tratamento" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "Purga" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "Limpar arquivos com erros?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "Remover arquivos pendentes?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "Limpar arquivos ignorados?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Fila" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Fila embaralhada!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Rádio" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "Construtor de rádio" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Rádio criado" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Nome do rádio" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Rádio atualizado" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Rádios" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Rádios" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "Motivo" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" -msgstr "" +msgstr "A biblioteca emitida segue" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +#, fuzzy +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "Mensagens recebidas" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Adicionado recentemente" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Adicionado recentemente" #: front/src/components/library/Home.vue:11 +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Recentemente adicionado aos favoritos" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Escutado recentemente" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Atualizar" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Atualizar" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "Atualizar informações do nó" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "Atualizar informações do nó" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "Atualizar o conteúdo da tabela" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "Registrado desde %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "As inscrições estão fechadas nesta instância, você precisará de um código de convite para inscrição." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "usuário regular" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Rejeitar" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Rejeitar mÃdia" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Rejeitado" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "Reinicie a importação" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "Data da última vista" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Bibliotecas Remotas" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Bibliotecas remotas são de propriedade de outros usuários na rede. Você pode acessá-los desde que sejam públicos ou tenha acesso." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Remover" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Remover avatar" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Remover avatar" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Remover dos favoritos" #: front/src/views/content/libraries/Quota.vue:38 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" -"Remove as pistas carregadas mas ainda a serem processadas completamente, " -"adicionando os dados correspondentes à sua quota." +msgstr "Remove as pistas carregadas mas ainda a serem processadas completamente, adicionando os dados correspondentes à sua quota." #: front/src/views/content/libraries/Quota.vue:64 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" -"Remove as pistas carregadas ignoradas durante os processos de importação " -"completamente, adicionando os dados correspondentes à sua quota." +msgstr "Remove as pistas carregadas ignoradas durante os processos de importação completamente, adicionando os dados correspondentes à sua quota." #: front/src/views/content/libraries/Quota.vue:90 +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." -msgstr "" -"Remove as pistas carregadas que não puderam ser processadas completamente " -"pelo servidor, adicionando os dados correspondentes à sua quota." +msgstr "Remove as pistas carregadas que não puderam ser processadas completamente pelo servidor, adicionando os dados correspondentes à sua quota." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "Solicite uma nova senha" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "Solicitar uma nova senha da Subsonic API?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "Solicite uma senha" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "Redefinir sua senha" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "Reinicie a importação" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Resultados por página" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Voltar ao login" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "Ver ficheiros" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "Regra" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Salvar" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Análise iniciada" -#: front/src/views/content/remote/Card.vue:63 +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Scanar agora" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "Ascendente" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Análise ignorada (a análise anterior é muito recente)" -#: front/src/views/content/remote/Card.vue:31 -msgid "Scan waiting" -msgstr "Scan em espera" - -#: front/src/views/content/remote/Card.vue:43 +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Analisado" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "Analisado com erros" -#: front/src/views/content/remote/Card.vue:35 +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "Buscar" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "Pesquisar uma biblioteca remota" +#: front/src/components/manage/library/EditsCardList.vue:211 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "Pesquisa por tÃtulo, artista, domÃnio…" + +#: front/src/components/manage/library/LibrariesTable.vue:191 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" +msgstr "Pesquisa por domÃnio, nome de utilizador, biografia..." + +#: front/src/components/manage/library/UploadsTable.vue:241 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "Pesquisa por domÃnio, nome de utilizador, biografia..." + +#: front/src/components/manage/library/ArtistsTable.vue:164 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "Pesquisa por domÃnio, nome de utilizador, biografia..." + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" +msgstr "Pesquisa por tÃtulo, artista, álbum…" + +#: front/src/components/manage/library/AlbumsTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "Pesquisa por tÃtulo, artista, álbum…" + #: front/src/components/manage/moderation/AccountsTable.vue:171 -msgid "Search by domain, username, bio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" msgstr "Pesquisa por domÃnio, nome de utilizador, biografia..." #: front/src/components/manage/moderation/DomainsTable.vue:151 -msgid "Search by name..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" msgstr "Pesquisar por nome..." -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" msgid "Search by title, artist, album…" msgstr "Pesquisa por tÃtulo, artista, álbum…" -#: front/src/components/manage/library/FilesTable.vue:176 -msgid "Search by title, artist, domain…" -msgstr "Pesquisa por tÃtulo, artista, domÃnio…" - #: front/src/components/manage/users/InvitationsTable.vue:153 +#, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "Pesquisa por nome de utilizador, endereço de e-mail, código…" #: front/src/components/manage/users/UsersTable.vue:163 +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "Pesquisa por nome de utilizador, endereço de e-mail, nome…" #: front/src/components/audio/SearchBar.vue:20 +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "Procure por artistas, álbuns, pistas…" #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "Procure alguma música" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "Procurar em lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "Procurar em Wikipedia" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "Menu secundário" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Secções" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Selecione um filtro" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%{ count } em %{ total } selecionado" +msgstr[1] "%{ count } em %{ total } selecionados" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Selecione apenas a página atual" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "Configurações" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "Configurações atualizadas" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "Configurações atualizadas com sucesso." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "Compartilhe o link" #: front/src/views/content/libraries/Detail.vue:15 +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." -msgstr "" -"Compartilhe este link com outros usuários para que eles possam solicitar " -"acesso à sua biblioteca." +msgstr "Compartilhe este link com outros usuários para que eles possam solicitar acesso à sua biblioteca." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +msgctxt "Content/Library/Title" msgid "Sharing link" msgstr "Link de compartilhamento" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 #, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "%{ count } tema" msgstr[1] "%{ count } temas" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Mostrar mais 1 álbum" msgstr[1] "Mostrar mais %{ count } albums" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "Mostrar atalhos de teclado disponÃveis" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Mostrar notificações antigas" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Mostrar/ocultar senha" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Mostrando resultados %{ start }-%{ end } em %{ total }" #: front/src/components/ShortcutsModal.vue:83 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Fila de embaralhamento" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Embaralhe sua fila" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "Inscrever-se" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "Inscrever-se" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Data de inscrição" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 #, fuzzy -msgid "Silence activity" -msgstr "Atividade do usuário" - -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 -msgid "Silence notifications" -msgstr "Notificações silenciosas" +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Tamanho" -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Tamanho" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Ignorado" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Arquivos ignorados" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "Software" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Desculpe, a página que você pediu não existe:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +#, fuzzy +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "Membro da equipe" -#: front/src/components/radios/Button.vue:4 -msgid "Start" -msgstr "Iniciar" +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "Pare o rádio" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "Estatisticas" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" -msgstr "" -"As estatÃsticas são calculadas a partir de atividade e conteúdo conhecidos " -"em sua instância, e não refletem a atividade geral para esta conta" +msgstr "As estatÃsticas são calculadas a partir de atividade e conteúdo conhecidos em sua instância, e não refletem a atividade geral para esta conta" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" -msgstr "" -"As estatÃsticas são calculadas a partir da actividade e conteúdo conhecidos " -"na sua instância e não reflectem a actividade geral para este domÃnio" +msgstr "As estatÃsticas são calculadas a partir da actividade e conteúdo conhecidos na sua instância e não reflectem a actividade geral para este domÃnio" -#: front/src/components/library/FileUpload.vue:86 -#: front/src/components/manage/users/InvitationsTable.vue:17 -#: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +#, fuzzy +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "As estatÃsticas são calculadas a partir de atividade e conteúdo conhecidos em sua instância, e não refletem a atividade geral para esta conta" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "Estado" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "Estado" + +#: front/src/components/manage/users/InvitationsTable.vue:17 +#: front/src/components/manage/users/InvitationsTable.vue:39 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "Estado" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "Pare" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "Estado" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "Pare o rádio" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Enviar" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Senha da API Subsonic" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Não podemos carregar esta pista" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Não podemos carregar esta pista" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Não podemos carregar esta pista" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Escolhas sugeridas" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "Sumário" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "Fórum de apoio" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "Extensões suportadas: %{ extensions }" #: front/src/components/playlists/Editor.vue:9 +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Sincronizar mudanças no servidor…" +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "Texto copiado para a área de transferência!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Isso é simples: nós amamos o Grooveshark e queremos construir algo ainda melhor." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "O logótipo funkwhale foi gentilmente projetado e fornecido por Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." +msgstr "A biblioteca e todas as suas pistas serão removidas. Isto não pode ser desfeito." + +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." msgstr "" -"A biblioteca e todas as suas pistas serão removidas. Isto não pode ser " -"desfeito." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +#, fuzzy +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "Os arquivos de música que você está enviando são marcados corretamente:" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" msgstr "A próxima pista será jogada automaticamente em poucos segundos..." -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "A plataforma é gratuita e open-source, você pode instalá-lo e modificá-lo sem preocupações" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "Playlist criada" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +#, fuzzy +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "Esta acção é irreversÃvel." + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "A API Subsonic não está disponÃvel nesta instância do Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Não podemos adicionar a música a uma playlist" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +#, fuzzy +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "Esta acção é irreversÃvel." + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Os arquivos de música enviados estão no formato OGG, Flac ou MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." -msgstr "" -"Existem várias maneiras de obter novos conteúdos e torná-los disponÃveis " -"aqui." +msgstr "Existem várias maneiras de obter novos conteúdos e torná-los disponÃveis aqui." #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "Esta acção é irreversÃvel." -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Este álbum está presente nas seguintes bibliotecas:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "Este artista está presente nas seguintes bibliotecas:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "Este domÃnio está sujeito a regras de moderação especÃficas" #: front/src/views/content/Home.vue:9 +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." +msgstr "Esta instância oferece até %{quota} de espaço de memória para cada usuário." + +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." msgstr "" -"Esta instância oferece até %{quota} de espaço de memória para cada usuário." #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "É você!" -#: front/src/views/content/libraries/Form.vue:71 +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Esta biblioteca contém minha música pessoal, espero que você goste." -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" -msgstr "" -"Esta biblioteca é privada e sua aprovação do proprietário é necessária para " -"acessar seu conteúdo" +msgstr "Esta biblioteca é privada e sua aprovação do proprietário é necessária para acessar seu conteúdo" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "Esta biblioteca é pública e você pode acessar seu conteúdo livremente" -#: front/src/components/common/ActionTable.vue:45 +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." +msgstr "Isso pode afetar muitos elementos ou ter consequências irreversÃveis, por favor, verifique se isso é realmente o que você quer." + +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." msgstr "" -"Isso pode afetar muitos elementos ou ter consequências irreversÃveis, por " -"favor, verifique se isso é realmente o que você quer." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "Esta referência será usada para agrupar arquivos importados." -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Não foi possÃvel processar esta pista, certifique-se de que está correctamente etiquetada" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Pista carregada, mas ainda não tratada pelo servidor" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Pista já disponÃvel em uma de suas bibliotecas" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "Esta pista não está disponÃvel em nenhuma biblioteca a que tenha acesso" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Esta música está presente nas seguintes bibliotecas:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Isso excluirá completamente essa playlist e não poderá ser desfeito." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Isto irá remover completamente o rádio e não pode ser cancelado." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Isso desativará completamente o acesso à API do Subsonic usando a conta." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "Isso removerá seus dados locais e o desconectará, você quer continuar?" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Isto irá desconectá-lo a partir de dispositivos existentes que usam a senha atual." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Isso excluirá completamente essa playlist e não poderá ser desfeito." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Isso removerá todas as músicas dessa playlist e não poderá ser desfeito." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Title" +msgstr "TÃtulo" + +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" msgid "Title" msgstr "TÃtulo" +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "Alternar looping de filas" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +msgctxt "Content/Moderation/Table.Label" msgid "Total size" msgstr "Tamanho total" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Tamanho total dos arquivos nesta biblioteca" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Usuários totais" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Música" -#: front/src/views/content/libraries/FilesTable.vue:205 -msgid "Track already present in one of your libraries" -msgstr "Pista já disponÃvel em uma de suas bibliotecas" +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Música" -#: front/src/components/library/Track.vue:85 -msgid "Track information" -msgstr "Informação da música" +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" -#: front/src/components/library/radios/Filter.vue:44 +#: front/src/views/admin/library/TrackDetail.vue:91 #, fuzzy -msgid "Track matching filter" -msgstr "Filtro de correspondência de músicas" +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "TÃtulo da música" -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" +msgid "Track information" +msgstr "Informação da música" + +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "TÃtulo da música" -#: front/src/views/content/libraries/FilesTable.vue:209 -msgid "Track uploaded, but not processed by the server yet" -msgstr "Pista carregada, mas ainda não tratada pelo servidor" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Músicas" #: front/src/components/instance/Stats.vue:54 -msgid "tracks" -msgstr "músicas" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Músicas" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Musicas deste artista" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Músicas favoritas" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "músicas escutadas" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Filtro de correspondência de músicas" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Tipo" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Tipo" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "Atualizar a regra de moderação" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "Deixar de seguir" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "Deixar de seguir esta biblioteca?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "Infelizmente, os proprietários desta instância não ainda ter o tempo para preencher esta página." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Música ilimitada" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "Cancelar mudo" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Atualizar" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Atualizar playlist" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Atualizar avatar" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Atualizar biblioteca" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "Atualizar a regra de moderação" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Atualizar playlist" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Atualizar configurações" #: front/src/views/auth/PasswordResetConfirm.vue:21 +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Atualize sua senha" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Carregar" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Carregar um novo avatar" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Carregar conteúdo de áudio" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Data de upload" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Data de upload" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" +msgstr "Carregamento recusado, garantir que o arquivo não é muito grande e que você não atingiu sua cota" + +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." msgstr "" -"Carregamento recusado, garantir que o arquivo não é muito grande e que você " -"não atingiu sua cota" #: front/src/views/content/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." -msgstr "" -"Carregue arquivos de música (mp3, ogg, flac, etc.) da sua biblioteca pessoal " -"diretamente de seu navegador para apreciá-los aqui." +msgstr "Carregue arquivos de música (mp3, ogg, flac, etc.) da sua biblioteca pessoal diretamente de seu navegador para apreciá-los aqui." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Carregar novas músicas" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Carregar quota" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "Tempo limite de upload, por favor, tente novamente" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Carregado" #: front/src/components/library/FileUpload.vue:5 +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "Carregamento" -#: front/src/components/library/FileUpload.vue:103 +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "Carregando…" +#: front/src/components/manage/library/LibrariesTable.vue:52 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "Envios" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Envios" + #: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "Envios" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "Use outra instância" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "Use este formulário para solicitar uma redefinição de senha. Enviaremos um email para o endereço fornecido com instruções para redefinir sua senha." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." -msgstr "" -"Use esta configuração para ativar/desativar temporariamente a polÃtica sem " -"removê-la completamente." +msgstr "Use esta configuração para ativar/desativar temporariamente a polÃtica sem removê-la completamente." #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "Usado" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Usuário" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "Atividade do usuário" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "Bibliotecas do usuário" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "Rádios do usuário" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Nome de usuário" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Nome de usuário ou email" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "usuários" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Usuários" #: front/src/components/Footer.vue:29 +#, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "Usando Funkwhale" #: front/src/components/Footer.vue:13 +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "Versão %{version}" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "Ver ficheiros" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Ver no MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "Visibilidade" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "Visibilidade: todos nesta instância" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "Visibilidade: todos, incluindo outras instâncias" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "Visibilidade: ninguém exceto eu" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "Visibilidade" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "Volume %{ number }" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Não podemos adicionar a música a uma playlist" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Não podemos criar a playlist" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Não podemos criar sua conta" - -#: front/src/components/audio/Player.vue:64 -msgid "We cannot load this track" -msgstr "Não podemos carregar esta pista" +#: front/src/components/federation/FetchButton.vue:69 +#, fuzzy +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Carregando seus favoritos …" #: front/src/components/auth/Login.vue:7 +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" msgstr "Nós não podemos te logar" -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Não podemos salvar seu avatar" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" -msgstr "Não podemos salvar suas configurações" +#: front/src/components/auth/ApplicationForm.vue:3 +#, fuzzy +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Não podemos criar sua conta" -#: front/src/components/Home.vue:127 +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" msgid "We do not track you or bother you with ads" msgstr "Nós não rastreamos ou incomodamos você com anúncios" -#: front/src/components/library/Track.vue:95 -msgid "We don't have any copyright information for this track" -msgstr "Nós não temos nenhuma informação de copyright para esta pista" - -#: front/src/components/library/Track.vue:106 -msgid "We don't have any licensing information for this track" -msgstr "Não temos nenhuma informação de licença para esta pista" - -#: front/src/components/library/FileUpload.vue:40 +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "Recomendamos usar o Picard para esse fim." #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Nós achamos que ouvir música deveria ser simples." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Desculpe, a página que você pediu não existe:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Bem-vindo" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Bem-vindo ao Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Por que o funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "Altura do widget" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "Largura do widget" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Sim" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "Sim, me desconecte!" #: front/src/views/content/libraries/Form.vue:19 +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." -msgstr "" -"Você pode compartilhar sua biblioteca com outras pessoas, independentemente " -"de sua visibilidade." +msgstr "Você pode compartilhar sua biblioteca com outras pessoas, independentemente de sua visibilidade." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Você está prestes a fazer o upload de músicas para sua biblioteca. Antes de prosseguir, certifique-se de que:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Você está logado como %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Você pode seguir bibliotecas de outros usuários para obter acesso a novas músicas. Bibliotecas públicas podem ser seguidas imediatamente, enquanto seguir uma biblioteca privada requer aprovação de seu proprietário." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Você pode convidar amigos e familia para sua instância para que eles possam curtir sua música" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Agora você pode usar o serviço sem limitações." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Você pode usar essa interface para construir a sua própria rádio que vai jogar músicas de acordo com seus critérios." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Você pode usá-los para curtir sua playlist e música no modo off-line, em seu smartphone ou tablet, por exemplo." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "Não tem nenhuma regra em vigor para esta conta." + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "Não tem nenhuma regra em vigor para esta conta." + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." msgstr "Não tem nenhuma regra em vigor para esta conta." #: front/src/views/admin/moderation/DomainsDetail.vue:39 +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." msgstr "Você não tem nenhuma regra em vigor para este domÃnio." -#: front/src/components/Sidebar.vue:158 +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." +msgstr "" + +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "Você tem um rádio tocando" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "Você pode ter um problema de conectividade." -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Você precisa selecionar uma instância para continuar" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "Você será desconectado desta sessão e precisará fazer login com o novo" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Você terá que atualizar sua senha em seus clientes que usam essa senha." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "Playlist criada" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Suas notificações" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Seus favoritos" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "Sua música, seu jeito" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Suas notificações" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Sua senha foi atualizada com sucesso." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "Configurações atualizadas" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Sua senha do Subsonic será alterada para uma nova, aleatória, efetuando o logout de dispositivos que usaram a senha antiga do Subsonic" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "Paginação" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Direitos Autorais" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Ãlbum contendo %{ count } pista, por %{ artist }" +msgstr[1] "Ãlbum contendo %{ count } pistas, por %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } pista foi adicionada à sua fila" +msgstr[1] "%{ count } pistas foram adicionadas à sua fila" diff --git a/front/locales/ru/LC_MESSAGES/app.po b/front/locales/ru/LC_MESSAGES/app.po index c45d8ba5a7e06776b2a75cdc863c7e50c32ea8df..11dbede454beab7efaef8f5e3b837d4bb85f0119 100644 --- a/front/locales/ru/LC_MESSAGES/app.po +++ b/front/locales/ru/LC_MESSAGES/app.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: front 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-01-11 16:04+0100\n" +"POT-Creation-Date: 2019-05-02 14:06+0200\n" "PO-Revision-Date: 2018-10-20 14:22+0000\n" "Last-Translator: noname <noname@inventati.org>\n" "Language-Team: none\n" @@ -19,57 +19,67 @@ msgstr "" "X-Generator: Weblate 2.20\n" #: front/src/components/playlists/PlaylistModal.vue:9 +msgctxt "Popup/Playlist/Paragraph" msgid "\"%{ title }\", by %{ artist }" msgstr "\"%{ title }\" от %{ artist }" #: front/src/components/Sidebar.vue:24 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(%{ index } of %{ length })" msgstr "(%{ index } из %{ length })" #: front/src/components/Sidebar.vue:22 +msgctxt "Sidebar/Queue/Tab.Title" msgid "(empty)" msgstr "(пуÑто)" -#: front/src/components/common/ActionTable.vue:57 -#: front/src/components/common/ActionTable.vue:66 +#: front/src/components/auth/Authorize.vue:16 #, fuzzy +msgctxt "Content/Auth/Title" +msgid "%{ app } wants to access your Funkwhale account" +msgstr "Войти в ваш аккаунт Funkwhale" + +#: front/src/components/common/ActionTable.vue:68 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "%{ count } on %{ total } selected" msgid_plural "%{ count } on %{ total } selected" msgstr[0] "выбран %{ count } из %{ total }" msgstr[1] "выбрано %{ count } из %{ total }" msgstr[2] "выбрано %{ count } из %{ total }" -#: front/src/components/Sidebar.vue:110 src/components/audio/album/Card.vue:54 -#: front/src/views/content/libraries/Card.vue:39 -#: src/views/content/remote/Card.vue:26 +#: front/src/components/Sidebar.vue:121 src/components/audio/album/Card.vue:52 +#: front/src/views/content/libraries/Card.vue:40 +#: src/views/content/remote/Card.vue:30 +#, fuzzy +msgctxt "*/*/*" msgid "%{ count } track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count } трек" msgstr[1] "%{ count } трека" msgstr[2] "%{ count } треков" -#: front/src/components/library/Artist.vue:13 +#: front/src/components/library/ArtistBase.vue:13 +#, fuzzy +msgctxt "Content/Artist/Paragraph" msgid "%{ count } track in %{ albumsCount } albums" msgid_plural "%{ count } tracks in %{ albumsCount } albums" msgstr[0] "%{ count } трек из %{ albumsCount } альбомов" msgstr[1] "%{ count } трека из %{ albumsCount } альбомов" msgstr[2] "%{ count } треков из %{ albumsCount } альбомов" -#: front/src/components/library/radios/Builder.vue:80 +#: front/src/components/library/radios/Builder.vue:81 +#, fuzzy +msgctxt "Content/Radio/Table.Paragraph/Short" msgid "%{ count } track matching combined filters" msgid_plural "%{ count } tracks matching combined filters" msgstr[0] "%{ count } подходÑщий трек" msgstr[1] "%{ count } подходÑщих трека" msgstr[2] "%{ count } подходÑщих треков" -#: front/src/components/audio/PlayButton.vue:180 -msgid "%{ count } track was added to your queue" -msgid_plural "%{ count } tracks were added to your queue" -msgstr[0] "%{ count } трек добавлен в вашу очередь" -msgstr[1] "%{ count } трека добавлено в вашу очередь" -msgstr[2] "%{ count } треков добавлено в вашу очередь" - #: front/src/components/playlists/Card.vue:18 +#, fuzzy +msgctxt "Content/*/Card/List item" msgid "%{ count} track" msgid_plural "%{ count } tracks" msgstr[0] "%{ count} трек" @@ -77,35 +87,48 @@ msgstr[1] "%{ count } трека" msgstr[2] "%{ count } треков" #: front/src/views/content/libraries/Quota.vue:11 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "%{ current } used on %{ max } allowed" msgstr "%{ current } иÑпользовано из доÑтупных %{ max }" #: front/src/components/common/Duration.vue:2 +msgctxt "Content/*/Paragraph" msgid "%{ hours } h %{ minutes } min" msgstr "%{ hours } ч %{ minutes } мин" #: front/src/components/common/Duration.vue:5 +msgctxt "Content/*/Paragraph" msgid "%{ minutes } min" msgstr "%{ minutes } мин" #: front/src/components/notifications/NotificationRow.vue:40 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } accepted your follow on library \"%{ library }\"" msgstr "" #: front/src/components/notifications/NotificationRow.vue:39 +msgctxt "Content/Notifications/Paragraph" msgid "%{ username } followed your library \"%{ library }\"" msgstr "" +#: front/src/components/notifications/NotificationRow.vue:41 +msgctxt "Content/Notifications/Paragraph" +msgid "%{ username } wants to follow your library \"%{ library }\"" +msgstr "" + #: front/src/components/auth/Profile.vue:46 +msgctxt "Head/Profile/Title" msgid "%{ username }'s profile" msgstr "Профиль %{ username }" -#: front/src/components/Footer.vue:5 -msgid "<translate :translate-params=\"{instanceName: instanceHostname}\">About %{instanceName}</translate>" +#: front/src/components/playlists/PlaylistModal.vue:21 +msgctxt "Popup/Playlist/Paragraph" +msgid "<strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>." msgstr "" #: front/src/components/audio/artist/Card.vue:41 +#, fuzzy +msgctxt "Content/Artist/Card" msgid "1 album" msgid_plural "%{ count } albums" msgstr[0] "1 альбом" @@ -113,78 +136,190 @@ msgstr[1] "%{ count } альбома" msgstr[2] "%{ count } альбомов" #: front/src/components/favorites/List.vue:10 +#, fuzzy +msgctxt "Content/Favorites/Title" msgid "1 favorite" msgid_plural "%{ count } favorites" msgstr[0] "1 избранный" msgstr[1] "%{ count } избранных" msgstr[2] "%{ count } избранных" -#: front/src/components/library/FileUpload.vue:225 -#: front/src/components/library/FileUpload.vue:226 +#: front/src/components/Home.vue:64 +#, fuzzy +msgctxt "Content/Home/Title" +msgid "A clean library" +msgstr "ОчиÑтить библиотеку" + +#: front/src/components/library/FileUpload.vue:264 +msgctxt "Content/Library/Help text" msgid "A network error occured while uploading this file" msgstr "При загрузке Ñтого файла произошла ÑÐµÑ‚ÐµÐ²Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°" +#: front/src/components/library/EditForm.vue:145 +#, fuzzy +msgctxt "*/*/Placeholder" +msgid "A short summary describing your changes." +msgstr "При Ñохранении ваших изменений произошла ошибка" + #: front/src/components/About.vue:5 +msgctxt "Content/About/Title/Short, Noun" msgid "About %{ instance }" msgstr "О %{ instance }" #: front/src/components/Footer.vue:6 -#, fuzzy +msgctxt "Footer/About/Title" msgid "About %{instanceName}" msgstr "О %{ instance }" #: front/src/components/Footer.vue:45 +#, fuzzy +msgctxt "Footer/*/Title/Short" msgid "About Funkwhale" msgstr "О Funkwhale" #: front/src/components/Footer.vue:10 -#, fuzzy +msgctxt "Footer/About/List item.Link" msgid "About page" msgstr "Страница альбома" -#: front/src/components/About.vue:8 src/components/About.vue:64 +#: front/src/components/About.vue:8 src/components/About.vue:67 +#, fuzzy +msgctxt "Content/About/Title" msgid "About this instance" msgstr "Об Ñтом узле" #: front/src/views/content/libraries/Detail.vue:48 +msgctxt "Content/Library/Button.Label" msgid "Accept" msgstr "ПринÑÑ‚ÑŒ" #: front/src/views/content/libraries/Detail.vue:40 +msgctxt "Content/Library/Table/Short" msgid "Accepted" msgstr "Подтверждено" -#: front/src/components/auth/SubsonicTokenForm.vue:111 +#: front/src/components/auth/SubsonicTokenForm.vue:110 +msgctxt "Content/Settings/Message" msgid "Access disabled" msgstr "ДоÑтуп отключен" -#: front/src/components/Home.vue:106 -msgid "Access your music from a clean interface that focus on what really matters" +#: front/src/components/mixins/Translations.vue:73 +#: front/src/components/mixins/Translations.vue:74 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to audio files, libraries, artists, albums and tracks" +msgstr "" + +#: front/src/components/mixins/Translations.vue:97 +#: front/src/components/mixins/Translations.vue:98 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to content filters" +msgstr "Выберите фильтр" + +#: front/src/components/mixins/Translations.vue:105 +#: front/src/components/mixins/Translations.vue:106 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to edits" +msgstr "ДоÑтуп отключен" + +#: front/src/components/mixins/Translations.vue:69 +#: front/src/components/mixins/Translations.vue:70 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to email, username, and profile information" +msgstr "" + +#: front/src/components/mixins/Translations.vue:77 +#: front/src/components/mixins/Translations.vue:78 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to favorites" +msgstr "Добавить в избранное" + +#: front/src/components/mixins/Translations.vue:85 +#: front/src/components/mixins/Translations.vue:86 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to follows" +msgstr "" + +#: front/src/components/mixins/Translations.vue:81 +#: front/src/components/mixins/Translations.vue:82 +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to listening history" +msgstr "" + +#: front/src/components/mixins/Translations.vue:101 +#: front/src/components/mixins/Translations.vue:102 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to notifications" +msgstr "Ваши уведомлениÑ" + +#: front/src/components/mixins/Translations.vue:89 +#: front/src/components/mixins/Translations.vue:90 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to playlists" +msgstr "Добавить в ÑпиÑок воÑпроизведениÑ..." + +#: front/src/components/mixins/Translations.vue:93 +#: front/src/components/mixins/Translations.vue:94 +#, fuzzy +msgctxt "Content/OAuth Scopes/Paragraph" +msgid "Access to radios" +msgstr "ДоÑтуп отключен" + +#: front/src/components/Home.vue:101 +#, fuzzy +msgctxt "Content/Home/List item" +msgid "Access your music from a clean interface that focuses on what really matters" msgstr "Получайте доÑтуп к вашей музыке из проÑтого интерфейÑа который ÑфокуÑирован на том что дейÑтвительно важно" -#: front/src/components/mixins/Translations.vue:19 -#: front/src/components/mixins/Translations.vue:20 +#: front/src/components/manage/library/UploadsTable.vue:67 +#: front/src/components/mixins/Translations.vue:45 +#: front/src/views/admin/library/UploadDetail.vue:175 +#: front/src/components/mixins/Translations.vue:46 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Accessed date" -msgstr "" +msgstr "ДоÑтуп отключен" -#: front/src/views/admin/moderation/AccountsDetail.vue:78 +#: front/src/views/admin/library/LibraryDetail.vue:104 +#: front/src/views/admin/library/UploadDetail.vue:111 #, fuzzy +msgctxt "*/*/*/Noun" +msgid "Account" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°" + +#: front/src/components/manage/library/LibrariesTable.vue:49 +#: front/src/components/manage/library/UploadsTable.vue:61 +#, fuzzy +msgctxt "*/*/*" +msgid "Account" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°" + +#: front/src/views/admin/moderation/AccountsDetail.vue:107 +msgctxt "Content/Moderation/Title" msgid "Account data" msgstr "Ðккаунт активен" #: front/src/components/auth/Settings.vue:5 +msgctxt "Content/Settings/Title" msgid "Account settings" msgstr "ÐаÑтройки аккаунта" -#: front/src/components/auth/Settings.vue:264 +#: front/src/components/auth/Settings.vue:479 +msgctxt "Head/Settings/Title" msgid "Account Settings" msgstr "ÐаÑтройки аккаунта" #: front/src/components/manage/users/UsersTable.vue:39 +msgctxt "Content/Admin/Table.Label/Short, Noun" msgid "Account status" msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°" #: front/src/views/auth/PasswordReset.vue:14 +msgctxt "Content/Signup/Input.Label" msgid "Account's email" msgstr "ÐÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° аккаунта" @@ -192,1697 +327,2832 @@ msgstr "ÐÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð° аккаунта" #: front/src/views/admin/moderation/AccountsList.vue:24 #: front/src/views/admin/moderation/Base.vue:8 #, fuzzy +msgctxt "*/Moderation/Title" msgid "Accounts" msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°" #: front/src/views/content/libraries/Detail.vue:29 +msgctxt "Content/Library/Table.Label" msgid "Action" msgstr "ДейÑтвие" -#: front/src/components/common/ActionTable.vue:99 +#: front/src/components/common/ActionTable.vue:101 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Action %{ action } was launched successfully on %{ count } element" msgid_plural "Action %{ action } was launched successfully on %{ count } elements" msgstr[0] "ДейÑтвие %{ action } было уÑпешно запущено на %{ count } Ñлементе" msgstr[1] "ДейÑтвие %{ action } было уÑпешно запущено на %{ count } Ñлементах" msgstr[2] "ДейÑтвие %{ action } было уÑпешно запущено на %{ count } Ñлементах" -#: front/src/components/common/ActionTable.vue:21 -#: front/src/components/library/radios/Builder.vue:64 +#: front/src/components/common/ActionTable.vue:22 +#: front/src/components/library/radios/Builder.vue:65 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Actions" msgstr "ДейÑтвиÑ" #: front/src/components/manage/users/UsersTable.vue:53 -#, fuzzy +msgctxt "Content/Admin/Table" msgid "Active" msgstr "Ðктивен(на)" -#: front/src/views/admin/moderation/AccountsDetail.vue:199 -#: front/src/views/admin/moderation/DomainsDetail.vue:144 +#: front/src/views/admin/library/AlbumDetail.vue:134 +#: front/src/views/admin/library/ArtistDetail.vue:123 +#: front/src/views/admin/library/LibraryDetail.vue:138 +#: front/src/views/admin/library/TrackDetail.vue:186 +#: front/src/views/admin/library/UploadDetail.vue:160 +#: front/src/views/admin/moderation/AccountsDetail.vue:220 +#: front/src/views/admin/moderation/DomainsDetail.vue:136 +msgctxt "Content/Moderation/Title" msgid "Activity" msgstr "ÐктивноÑÑ‚ÑŒ" #: front/src/components/mixins/Translations.vue:7 #: front/src/components/mixins/Translations.vue:8 +msgctxt "Content/Settings/Dropdown.Label/Noun" msgid "Activity visibility" msgstr "ВидимоÑÑ‚ÑŒ активноÑти" #: front/src/views/admin/moderation/DomainsList.vue:18 +msgctxt "Content/Moderation/Button/Verb" msgid "Add" msgstr "" #: front/src/views/admin/moderation/DomainsList.vue:13 +msgctxt "Content/Moderation/Form.Label/Verb" msgid "Add a domain" msgstr "" +#: front/src/views/admin/moderation/AccountsDetail.vue:79 +msgctxt "Content/Moderation/Button/Verb" +msgid "Add a moderation policy" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:4 +#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Add a new moderation rule" -msgstr "" +msgstr "Удалить радио" #: front/src/views/content/Home.vue:35 +msgctxt "Content/Library/Title/Verb" msgid "Add and manage content" msgstr "Добавить или управлÑÑ‚ÑŒ Ñодержимым" +#: front/src/components/playlists/Editor.vue:28 +#: front/src/components/playlists/PlaylistModal.vue:31 +msgctxt "*/Playlist/Button.Label/Verb" +msgid "Add anyways" +msgstr "" + #: front/src/components/Sidebar.vue:75 src/views/content/Base.vue:18 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Add content" msgstr "Добавить Ñодержимое" -#: front/src/components/library/radios/Builder.vue:50 +#: front/src/components/library/radios/Builder.vue:51 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Add filter" msgstr "Добавить фильтр" -#: front/src/components/library/radios/Builder.vue:40 +#: front/src/components/library/radios/Builder.vue:41 +msgctxt "Content/Radio/Paragraph" msgid "Add filters to customize your radio" msgstr "Добавить фильтры чтобы каÑтомизировать Ñвоё радио" -#: front/src/components/audio/PlayButton.vue:64 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:75 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Add to current queue" msgstr "Добавить в очередь" #: front/src/components/favorites/TrackFavoriteIcon.vue:4 #: front/src/components/favorites/TrackFavoriteIcon.vue:28 +#, fuzzy +msgctxt "Content/Track/*/Verb" msgid "Add to favorites" msgstr "Добавить в избранное" #: front/src/components/playlists/TrackPlaylistIcon.vue:6 #: front/src/components/playlists/TrackPlaylistIcon.vue:34 -#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Add to playlist…" msgstr "Добавить в ÑпиÑок воÑпроизведениÑ..." -#: front/src/components/audio/PlayButton.vue:14 +#: front/src/components/audio/PlayButton.vue:15 +msgctxt "*/Queue/Dropdown/Button/Label/Short" msgid "Add to queue" msgstr "Добавить в очередь" -#: front/src/components/playlists/PlaylistModal.vue:116 +#: front/src/components/playlists/PlaylistModal.vue:142 +msgctxt "Popup/Playlist/Table.Button.Tooltip/Verb" msgid "Add to this playlist" msgstr "Добавить в Ñтот ÑпиÑок воÑпроизведениÑ" -#: front/src/components/playlists/PlaylistModal.vue:54 +#: front/src/components/playlists/PlaylistModal.vue:68 +msgctxt "Popup/Playlist/Table.Button.Label/Verb" msgid "Add track" msgstr "Добавить трек" #: front/src/components/manage/users/UsersTable.vue:69 +msgctxt "Content/Admin/Table.User role" msgid "Admin" msgstr "ÐдминиÑтратор" #: front/src/components/Sidebar.vue:79 +msgctxt "Sidebar/Admin/Title/Noun" msgid "Administration" msgstr "ÐдминиÑтрирование" #: front/src/components/audio/SearchBar.vue:26 -#: src/components/audio/track/Table.vue:8 -#: front/src/components/library/Album.vue:159 -#: front/src/components/manage/library/FilesTable.vue:39 +#: src/components/audio/track/Table.vue:9 +#: front/src/components/library/AlbumBase.vue:152 +#: front/src/components/library/ArtistBase.vue:194 +#: front/src/components/manage/library/TracksTable.vue:40 #: front/src/components/metadata/Search.vue:134 -#: front/src/views/content/libraries/FilesTable.vue:56 +#: front/src/views/content/libraries/FilesTable.vue:57 +msgctxt "*/*/*" msgid "Album" msgstr "Ðльбом" -#: front/src/components/library/Album.vue:12 -msgid "Album containing %{ count } track, by %{ artist }" -msgid_plural "Album containing %{ count } tracks, by %{ artist }" -msgstr[0] "Ðльбом Ñодержит %{ count } трек от %{ artist }" -msgstr[1] "Ðльбом Ñодержит %{ count } трека от %{ artist }" -msgstr[2] "Ðльбом Ñодержит %{ count } треков от %{ artist }" +#: front/src/views/admin/library/TrackDetail.vue:107 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album" +msgstr "Ðльбом" -#: front/src/components/mixins/Translations.vue:24 -#: front/src/components/mixins/Translations.vue:25 -msgid "Album name" +#: front/src/views/admin/library/TrackDetail.vue:128 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Album artist" +msgstr "Ðльбомы Ñтого иÑполнителÑ" + +#: front/src/views/admin/library/AlbumDetail.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Album data" msgstr "Ðазвание альбома" -#: front/src/components/library/Track.vue:27 -msgid "Album page" -msgstr "Страница альбома" +#: front/src/components/mixins/Translations.vue:51 +#: front/src/components/mixins/Translations.vue:52 +msgctxt "Content/*/Dropdown/Noun" +msgid "Album name" +msgstr "Ðазвание альбома" #: front/src/components/audio/Search.vue:19 #: src/components/instance/Stats.vue:48 -#: front/src/views/admin/moderation/AccountsDetail.vue:321 -#: front/src/views/admin/moderation/DomainsDetail.vue:257 +#: front/src/components/library/Albums.vue:120 +#: src/components/library/Library.vue:7 +#: front/src/components/manage/library/ArtistsTable.vue:41 +#: front/src/views/admin/library/AlbumsList.vue:24 +#: front/src/views/admin/library/ArtistDetail.vue:241 +#: front/src/views/admin/library/Base.vue:11 +#: front/src/views/admin/library/LibraryDetail.vue:219 +#: front/src/views/admin/moderation/AccountsDetail.vue:354 +#: front/src/views/admin/moderation/DomainsDetail.vue:264 +#, fuzzy +msgctxt "*/*/*" msgid "Albums" msgstr "Ðльбомы" -#: front/src/components/library/Artist.vue:44 +#: front/src/components/library/ArtistDetail.vue:21 +msgctxt "Content/Artist/Title" msgid "Albums by this artist" msgstr "Ðльбомы Ñтого иÑполнителÑ" +#: front/src/components/manage/library/EditsCardList.vue:15 +#: front/src/components/manage/library/LibrariesTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:13 +#: front/src/components/manage/library/UploadsTable.vue:22 #: front/src/components/manage/users/InvitationsTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:13 +#, fuzzy +msgctxt "Content/*/Dropdown" msgid "All" msgstr "Ð’Ñе" +#: front/src/components/common/ActionTable.vue:59 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "All %{ count } element selected" +msgid_plural "All %{ count } elements selected" +msgstr[0] "выбран %{ count } из %{ total }" +msgstr[1] "выбрано %{ count } из %{ total }" +msgstr[2] "выбрано %{ count } из %{ total }" + +#: front/src/components/auth/Authorize.vue:107 +msgctxt "Head/Authorize/Title" +msgid "Allow application" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:17 +msgctxt "Popup/Import/Message" +msgid "An error occured during upload processing. You will find more information below." +msgstr "" + #: front/src/components/playlists/Editor.vue:13 +msgctxt "Content/Playlist/Error message.Title" msgid "An error occured while saving your changes" msgstr "При Ñохранении ваших изменений произошла ошибка" -#: front/src/components/auth/Login.vue:10 +#: front/src/components/federation/FetchButton.vue:21 +#, fuzzy +msgctxt "Popup/*/Message.Content" +msgid "An error occured while trying to refresh data:" +msgstr "При Ñохранении ваших изменений произошла ошибка" + +#: front/src/components/federation/FetchButton.vue:41 #, fuzzy +msgctxt "*/*/Error" +msgid "An HTTP error occured while contacting the remote server" +msgstr "При Ñохранении ваших изменений произошла ошибка" + +#: front/src/components/auth/Login.vue:10 +msgctxt "Content/Login/Error message/List item" msgid "An unknown error happend, this can mean the server is down or cannot be reached" msgstr "Произошла неизвеÑÑ‚Ð½Ð°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°, возможно Ñервер перегружен или недоÑтупен" -#: front/src/components/notifications/NotificationRow.vue:62 +#: front/src/components/library/ImportStatusModal.vue:145 +msgctxt "Popup/Import/Error.Label" +msgid "An unkwown error occured" +msgstr "" + +#: front/src/components/auth/Settings.vue:175 +#: src/components/auth/Settings.vue:225 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Application" +msgstr "ДейÑтвие" + +#: front/src/components/auth/ApplicationEdit.vue:12 +msgctxt "Content/Applications/Title" +msgid "Application details" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:21 +msgctxt "Content/Applications/Label" +msgid "Application ID" +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:16 +msgctxt "Content/Application/Paragraph/" +msgid "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else." +msgstr "" + +#: front/src/components/auth/ApplicationEdit.vue:25 +msgctxt "Content/Applications/Label" +msgid "Application secret" +msgstr "" + +#: front/src/components/library/EditCard.vue:81 +#: front/src/components/notifications/NotificationRow.vue:66 +msgctxt "Content/*/Button.Label/Verb" msgid "Approve" msgstr "" +#: front/src/components/library/EditCard.vue:25 +#: front/src/components/manage/library/EditsCardList.vue:21 +msgctxt "Content/*/*/Short" +msgid "Approved" +msgstr "" + +#: front/src/components/library/EditCard.vue:21 +msgctxt "Content/Library/Card/Short" +msgid "Approved and applied" +msgstr "" + #: front/src/components/auth/Logout.vue:5 +msgctxt "Content/Login/Title" msgid "Are you sure you want to log out?" msgstr "Ð’Ñ‹ уверены что хотите выйти?" #: front/src/components/audio/SearchBar.vue:25 -#: src/components/audio/track/Table.vue:7 -#: front/src/components/library/Artist.vue:137 -#: front/src/components/manage/library/FilesTable.vue:38 +#: src/components/audio/track/Table.vue:8 #: front/src/components/metadata/Search.vue:130 -#: front/src/views/content/libraries/FilesTable.vue:55 +#: front/src/views/admin/library/AlbumDetail.vue:108 +#: front/src/views/admin/library/TrackDetail.vue:118 +#: front/src/views/content/libraries/FilesTable.vue:56 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artist" msgstr "ИÑполнитель" -#: front/src/components/mixins/Translations.vue:25 -#: front/src/components/mixins/Translations.vue:26 -msgid "Artist name" +#: front/src/components/manage/library/AlbumsTable.vue:40 +#: front/src/components/manage/library/TracksTable.vue:41 +msgctxt "*/*/*" +msgid "Artist" +msgstr "ИÑполнитель" + +#: front/src/views/admin/library/ArtistDetail.vue:91 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Artist data" msgstr "Ð˜Ð¼Ñ Ð¸ÑполнителÑ" -#: front/src/components/library/Album.vue:22 -#: src/components/library/Track.vue:33 -msgid "Artist page" -msgstr "Страница иÑполнителÑ" +#: front/src/components/mixins/Translations.vue:52 +#: front/src/components/mixins/Translations.vue:53 +msgctxt "Content/*/Dropdown/Noun" +msgid "Artist name" +msgstr "Ð˜Ð¼Ñ Ð¸ÑполнителÑ" #: front/src/components/audio/Search.vue:65 -#, fuzzy +msgctxt "*/Search/Input.Placeholder" msgid "Artist, album, track…" msgstr "ИÑполнитель, альбом, трек..." +#: front/src/views/admin/library/ArtistsList.vue:24 +#: front/src/views/admin/library/Base.vue:8 +#: front/src/views/admin/library/LibraryDetail.vue:209 +#, fuzzy +msgctxt "*/*/*" +msgid "Artists" +msgstr "ИÑполнители" + #: front/src/components/audio/Search.vue:10 #: src/components/instance/Stats.vue:42 -#: front/src/components/library/Artists.vue:119 -#: src/components/library/Library.vue:7 -#: front/src/views/admin/moderation/AccountsDetail.vue:313 -#: front/src/views/admin/moderation/DomainsDetail.vue:249 +#: front/src/components/library/Artists.vue:117 +#: src/components/library/Library.vue:10 +#: front/src/views/admin/moderation/AccountsDetail.vue:346 +#: front/src/views/admin/moderation/DomainsDetail.vue:254 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Artists" msgstr "ИÑполнители" -#: front/src/components/favorites/List.vue:33 -#: src/components/library/Artists.vue:25 -#: front/src/components/library/Radios.vue:44 -#: front/src/components/manage/library/FilesTable.vue:19 +#: front/src/components/favorites/List.vue:34 +#: src/components/library/Albums.vue:25 +#: front/src/components/library/Artists.vue:25 +#: src/components/library/Radios.vue:44 +#: front/src/components/manage/library/AlbumsTable.vue:21 +#: front/src/components/manage/library/ArtistsTable.vue:21 +#: front/src/components/manage/library/EditsCardList.vue:39 +#: front/src/components/manage/library/LibrariesTable.vue:30 +#: front/src/components/manage/library/TracksTable.vue:21 +#: front/src/components/manage/library/UploadsTable.vue:40 #: front/src/components/manage/moderation/AccountsTable.vue:21 #: front/src/components/manage/moderation/DomainsTable.vue:19 #: front/src/components/manage/users/UsersTable.vue:19 #: front/src/views/content/libraries/FilesTable.vue:31 #: front/src/views/playlists/List.vue:27 +msgctxt "Content/Search/Dropdown" msgid "Ascending" msgstr "По возраÑтанию" -#: front/src/views/auth/PasswordReset.vue:27 +#: front/src/views/auth/PasswordReset.vue:28 +msgctxt "Content/Signup/Button.Label/Verb" msgid "Ask for a password reset" msgstr "СброÑить пароль" -#: front/src/views/admin/moderation/AccountsDetail.vue:245 +#: front/src/views/admin/library/AlbumDetail.vue:198 +#: front/src/views/admin/library/ArtistDetail.vue:187 +#: front/src/views/admin/library/LibraryDetail.vue:176 +#: front/src/views/admin/library/TrackDetail.vue:250 +#: front/src/views/admin/library/UploadDetail.vue:191 +#: front/src/views/admin/moderation/AccountsDetail.vue:274 #: front/src/views/admin/moderation/DomainsDetail.vue:202 -#, fuzzy +msgctxt "Content/Moderation/Title" msgid "Audio content" msgstr "Добавить Ñодержимое" #: front/src/components/ShortcutsModal.vue:55 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "Audio player shortcuts" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/auth/Authorize.vue:47 +msgctxt "Content/Signup/Button.Label/Verb" +msgid "Authorize %{ app }" +msgstr "" + +#: front/src/components/auth/Authorize.vue:4 +msgctxt "Content/Auth/Title/Verb" +msgid "Authorize third-party app" +msgstr "" + +#: front/src/components/auth/Settings.vue:162 +msgctxt "Content/Settings/Title/Noun" +msgid "Authorized apps" +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:40 +msgctxt "Popup/Playlist/Title" msgid "Available playlists" msgstr "ДоÑтупные ÑпиÑки воÑпроизведениÑ" #: front/src/components/auth/Settings.vue:34 +msgctxt "Content/Settings/Title" msgid "Avatar" msgstr "Ðватар" -#: front/src/views/auth/PasswordReset.vue:24 +#: front/src/views/auth/PasswordReset.vue:25 #: front/src/views/auth/PasswordResetConfirm.vue:18 +msgctxt "Content/Signup/Link" msgid "Back to login" msgstr "Ðазад ко входу" -#: front/src/components/library/Track.vue:129 -#: front/src/components/manage/library/FilesTable.vue:42 -#: front/src/components/mixins/Translations.vue:29 -#: front/src/components/mixins/Translations.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:9 +#: front/src/components/auth/ApplicationNew.vue:5 +#, fuzzy +msgctxt "Content/Applications/Link" +msgid "Back to settings" +msgstr "Обновить наÑтройки" + +#: front/src/components/library/TrackDetail.vue:48 +#: front/src/components/mixins/Translations.vue:55 +#: front/src/views/admin/library/UploadDetail.vue:227 +#: front/src/components/mixins/Translations.vue:56 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Bitrate" msgstr "Битрейт" #: front/src/components/manage/moderation/InstancePolicyCard.vue:19 #: front/src/components/manage/moderation/InstancePolicyForm.vue:34 +msgctxt "Content/Moderation/*/Verb" msgid "Block everything" msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:112 +msgctxt "Content/Moderation/Help text" msgid "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)" msgstr "" #: front/src/components/Sidebar.vue:18 src/components/library/Library.vue:4 +#, fuzzy +msgctxt "*/Library/*/Verb" msgid "Browse" msgstr "ПроÑмотр" #: front/src/components/Sidebar.vue:65 +msgctxt "Sidebar/Library/List item.Link/Verb" msgid "Browse library" msgstr "ПроÑмотр библиотеки" +#: front/src/components/library/Albums.vue:4 +#, fuzzy +msgctxt "Content/Album/Title" +msgid "Browsing albums" +msgstr "ПроÑмотр радио" + #: front/src/components/library/Artists.vue:4 +msgctxt "Content/Artist/Title" msgid "Browsing artists" msgstr "ПроÑмотр иÑполнителей" #: front/src/views/playlists/List.vue:3 +msgctxt "Content/Playlist/Title" msgid "Browsing playlists" msgstr "ПроÑмотр ÑпиÑков воÑпроизведениÑ" #: front/src/components/library/Radios.vue:4 +msgctxt "Content/Radio/Title" msgid "Browsing radios" msgstr "ПроÑмотр радио" #: front/src/components/library/radios/Builder.vue:5 +msgctxt "Content/Radio/Title" msgid "Builder" msgstr "КонÑтруктор" #: front/src/components/audio/album/Card.vue:13 +msgctxt "Content/Album/Card" msgid "By %{ artist }" msgstr "От %{ artist }" -#: front/src/views/content/remote/Card.vue:103 -#, fuzzy +#: front/src/views/content/remote/Card.vue:107 +msgctxt "Popup/Library/Paragraph" msgid "By unfollowing this library, you loose access to its content." msgstr "ОтпиÑавшиÑÑŒ от Ñтой библиотеки, вы потерÑете доÑтуп к её Ñодержимому." -#: front/src/views/admin/moderation/AccountsDetail.vue:261 +#: front/src/views/admin/library/AlbumDetail.vue:214 +#: front/src/views/admin/library/ArtistDetail.vue:203 +#: front/src/views/admin/library/LibraryDetail.vue:192 +#: front/src/views/admin/library/TrackDetail.vue:266 +#: front/src/views/admin/library/UploadDetail.vue:208 +#: front/src/views/admin/moderation/AccountsDetail.vue:290 #: front/src/views/admin/moderation/DomainsDetail.vue:217 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Cached size" msgstr "" +#: front/src/components/SetInstanceModal.vue:37 #: front/src/components/common/DangerousButton.vue:17 -#: front/src/components/library/Album.vue:58 -#: src/components/library/Track.vue:76 +#: front/src/components/library/AlbumBase.vue:36 +#: front/src/components/library/ArtistBase.vue:47 +#: front/src/components/library/EditForm.vue:95 +#: front/src/components/library/TrackBase.vue:55 #: front/src/components/library/radios/Filter.vue:53 #: front/src/components/manage/moderation/InstancePolicyForm.vue:54 -#: front/src/components/playlists/PlaylistModal.vue:63 +#: front/src/components/moderation/FilterModal.vue:39 +#: front/src/components/playlists/PlaylistModal.vue:26 +#: front/src/components/playlists/PlaylistModal.vue:77 +msgctxt "*/*/Button.Label/Verb" msgid "Cancel" msgstr "Отмена" -#: front/src/components/library/radios/Builder.vue:63 +#: front/src/components/library/radios/Builder.vue:64 +msgctxt "Content/Radio/Table.Label/Noun (Value is a number of Tracks)" msgid "Candidates" msgstr "Кандидаты" -#: front/src/components/auth/Settings.vue:76 -msgid "Cannot change your password" -msgstr "Ðе удалоÑÑŒ изменить ваш пароль" - -#: front/src/components/library/FileUpload.vue:222 -#: front/src/components/library/FileUpload.vue:223 -#, fuzzy +#: front/src/components/library/FileUpload.vue:261 +msgctxt "Content/Library/Help text" msgid "Cannot upload this file, ensure it is not too big" msgstr "Ðевозможно загрузить файл, убедитеÑÑŒ что он не Ñлишком большой" #: front/src/components/Footer.vue:21 +msgctxt "Footer/Settings/Dropdown.Label/Short, Verb" msgid "Change language" msgstr "Сменить Ñзык" -#: front/src/components/auth/Settings.vue:67 +#: front/src/components/auth/Settings.vue:68 +msgctxt "Content/Settings/Title/Verb" msgid "Change my password" msgstr "Сменить мой пароль" #: front/src/components/auth/Settings.vue:95 +msgctxt "Content/Settings/Button.Label" msgid "Change password" msgstr "Сменить пароль" -#: front/src/views/auth/PasswordResetConfirm.vue:4 #: front/src/views/auth/PasswordResetConfirm.vue:62 +#, fuzzy +msgctxt "*/Signup/Title" msgid "Change your password" msgstr "Сменить ваш пароль" #: front/src/components/auth/Settings.vue:96 +msgctxt "Popup/Settings/Title" msgid "Change your password?" msgstr "Сменить ваш пароль?" -#: front/src/components/playlists/Editor.vue:21 +#: front/src/components/playlists/Editor.vue:31 +msgctxt "Content/Playlist/Paragraph" msgid "Changes synced with server" msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñинхронизированы Ñ Ñервером" -#: front/src/components/auth/Settings.vue:70 +#: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph'" msgid "Changing your password will also change your Subsonic API password if you have requested one." msgstr "Изменение вашего Ð¿Ð°Ñ€Ð¾Ð»Ñ Ñ‚Ð°ÐºÐ¶Ðµ изменит ваш пароль Subsonic API еÑли вы запрашивали его." #: front/src/components/auth/Settings.vue:98 -msgid "Changing your password will have the following consequences" +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "Changing your password will have the following consequences:" msgstr "Изменение вашего Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´Ñ‘Ñ‚ к Ñледующим поÑледÑтвиÑм" #: front/src/components/Footer.vue:40 +msgctxt "Footer/*/List item.Link" msgid "Chat room" msgstr "" -#: front/src/App.vue:13 +#: front/src/components/auth/ApplicationForm.vue:24 +msgctxt "Content/Applications/Paragraph/" +msgid "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:2 +msgctxt "Popup/Instance/Title" msgid "Choose your instance" msgstr "Выберите Ñвой узел" -#: front/src/components/Home.vue:64 -msgid "Clean library" -msgstr "ОчиÑтить библиотеку" +#: front/src/components/library/EditForm.vue:75 +#, fuzzy +msgctxt "Content/Library/Button.Label" +msgid "Clear" +msgstr "ОчиÑтить" #: front/src/components/manage/users/InvitationForm.vue:37 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Clear" msgstr "ОчиÑтить" -#: front/src/components/playlists/Editor.vue:40 -#: front/src/components/playlists/Editor.vue:45 +#: front/src/components/playlists/Editor.vue:50 +#: front/src/components/playlists/Editor.vue:55 +#, fuzzy +msgctxt "*/Playlist/Button.Label/Verb" msgid "Clear playlist" msgstr "ОчиÑтить ÑпиÑок воÑпроизведениÑ" -#: front/src/components/audio/Player.vue:363 +#: front/src/components/audio/Player.vue:614 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Clear your queue" msgstr "ОчиÑтить вашу очередь" #: front/src/components/Home.vue:44 -#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Click once, listen for hours using built-in radios" msgstr "Ðажмите один раз, Ñлушайте чаÑами Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ вÑтроенного радио" -#: front/src/components/library/FileUpload.vue:75 +#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/mixins/Translations.vue:22 +msgctxt "Content/Library/Link.Title" +msgid "Click to display more information about the import process for this upload" +msgstr "" + +#: front/src/components/library/FileUpload.vue:82 +msgctxt "Content/Library/Paragraph/Call to action" msgid "Click to select files to upload or drag and drop files or directories" msgstr "Ðажмите чтобы выбрать файлы Ð´Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ или перетащите файлы или директории" #: front/src/components/ShortcutsModal.vue:20 +msgctxt "Popup/Keyboard shortcuts/Button.Label/Verb" +msgid "Close" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:85 +#: front/src/components/library/ImportStatusModal.vue:79 +msgctxt "*/*/Button.Label/Verb" msgid "Close" msgstr "" +#: front/src/components/federation/FetchButton.vue:88 +msgctxt "*/*/Button.Label/Verb" +msgid "Close and reload page" +msgstr "" + #: front/src/components/manage/users/InvitationForm.vue:26 #: front/src/components/manage/users/InvitationsTable.vue:42 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Code" msgstr "Код" -#: front/src/components/audio/album/Card.vue:43 +#: front/src/components/audio/album/Card.vue:41 #: front/src/components/audio/artist/Card.vue:33 +#, fuzzy +msgctxt "Content/*/Card.Link/Verb" msgid "Collapse" msgstr "Свернуть" -#: front/src/components/library/radios/Builder.vue:62 -#, fuzzy +#: front/src/components/library/radios/Builder.vue:63 +msgctxt "Content/Radio/Table.Label/Verb (Value is a List of Parameters)" msgid "Config" msgstr "КонфигурациÑ" #: front/src/components/common/DangerousButton.vue:21 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Confirm" msgstr "Подтвердить" -#: front/src/views/auth/EmailConfirm.vue:4 src/views/auth/EmailConfirm.vue:20 #: front/src/views/auth/EmailConfirm.vue:51 -#, fuzzy +msgctxt "Head/Signup/Title" msgid "Confirm your e-mail address" msgstr "Подтвердить ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" #: front/src/views/auth/EmailConfirm.vue:13 +msgctxt "Content/Signup/Form.Label" msgid "Confirmation code" msgstr "Код подтверждениÑ" -#: front/src/components/common/ActionTable.vue:7 +#: front/src/components/moderation/FilterModal.vue:90 +msgctxt "*/Moderation/Message" +msgid "Content filter successfully added" +msgstr "" + +#: front/src/components/mixins/Translations.vue:96 +#: front/src/components/mixins/Translations.vue:97 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Content filters" +msgstr "Выберите фильтр" + +#: front/src/components/auth/Settings.vue:116 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Content filters" +msgstr "Выберите фильтр" + +#: front/src/components/auth/Settings.vue:119 +msgctxt "Content/Settings/Paragraph" +msgid "Content filters help you hide content you don't want to see on the service." +msgstr "" + +#: front/src/components/common/ActionTable.vue:8 +msgctxt "Content/*/Button.Help text.Paragraph" msgid "Content have been updated, click refresh to see up-to-date content" msgstr "" #: front/src/components/Footer.vue:48 +msgctxt "Footer/*/List item.Link" msgid "Contribute" msgstr "" #: front/src/components/audio/EmbedWizard.vue:19 #: front/src/components/common/CopyInput.vue:8 +#, fuzzy +msgctxt "*/*/Button.Label/Short, Verb" msgid "Copy" msgstr "Копировать" -#: front/src/components/playlists/Editor.vue:163 -msgid "Copy tracks from current queue to playlist" +#: front/src/components/playlists/Editor.vue:194 +msgctxt "Content/Playlist/Button.Tooltip/Verb" +msgid "Copy queued tracks to playlist" msgstr "Копировать треки из текущей очереди в ÑпиÑок воÑпроизведениÑ" +#: front/src/components/auth/Authorize.vue:55 +msgctxt "Content/Auth/Paragraph" +msgid "Copy-paste the following code in the application:" +msgstr "" + #: front/src/components/audio/EmbedWizard.vue:21 +msgctxt "Popup/Embed/Paragraph" msgid "Copy/paste this code in your website HTML" msgstr "" -#: front/src/components/library/Track.vue:91 -#, fuzzy +#: front/src/components/library/TrackDetail.vue:10 +#: front/src/views/admin/library/TrackDetail.vue:153 +msgctxt "Content/Track/Table.Label/Noun" msgid "Copyright" msgstr "Копировать" #: front/src/views/auth/EmailConfirm.vue:7 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "Could not confirm your e-mail address" msgstr "Подтвердить ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" #: front/src/views/content/remote/ScanForm.vue:3 -#, fuzzy +msgctxt "Content/Library/Error message.Title" msgid "Could not fetch remote library" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ удалённой библиотеки" -#: front/src/views/content/libraries/FilesTable.vue:213 -#, fuzzy -msgid "Could not process this track, ensure it is tagged correctly" -msgstr "Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ Ñтого трека, убедитеÑÑŒ что у него корректные теги" - -#: front/src/components/Home.vue:85 +#: front/src/components/Home.vue:80 +msgctxt "Content/Home/List item" msgid "Covers, lyrics, our goal is to have them all ;)" msgstr "Обложки, текÑÑ‚Ñ‹, наша цель Ñобрать их вÑе ;)" #: front/src/components/manage/moderation/InstancePolicyForm.vue:58 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Create" msgstr "Создать импорт" #: front/src/components/auth/Signup.vue:4 +#, fuzzy +msgctxt "Content/Signup/Title" msgid "Create a funkwhale account" msgstr "Создать аккаунт funkwhale" +#: front/src/components/auth/ApplicationNew.vue:8 +#: front/src/components/auth/ApplicationNew.vue:34 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Create a new application" +msgstr "Создать новый ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/auth/Settings.vue:220 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Create a new application" +msgstr "Создать новый ÑпиÑок воÑпроизведениÑ" + #: front/src/views/content/libraries/Home.vue:14 +msgctxt "Content/Library/Link/Verb" msgid "Create a new library" msgstr "Создать новую библиотеку" #: front/src/components/playlists/Form.vue:2 +msgctxt "Popup/Playlist/Title/Verb" msgid "Create a new playlist" msgstr "Создать новый ÑпиÑок воÑпроизведениÑ" #: front/src/components/Sidebar.vue:57 src/components/auth/Login.vue:17 +#, fuzzy +msgctxt "*/Signup/Link/Verb" msgid "Create an account" msgstr "Создать новый аккаунт" +#: front/src/components/auth/ApplicationForm.vue:65 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Create application" +msgstr "Создать ÑпиÑок воÑпроизведениÑ" + #: front/src/views/content/libraries/Form.vue:26 +msgctxt "Content/Library/Button.Label/Verb" msgid "Create library" msgstr "Создать библиотеку" -#: front/src/components/auth/Signup.vue:51 +#: front/src/components/auth/Signup.vue:53 +#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Create my account" msgstr "Создать мой аккаунт" +#: front/src/components/auth/Settings.vue:264 +msgctxt "Content/Applications/Paragraph" +msgid "Create one to integrate Funkwhale with third-party applications." +msgstr "" + #: front/src/components/playlists/Form.vue:34 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Create playlist" msgstr "Создать ÑпиÑок воÑпроизведениÑ" #: front/src/components/library/Radios.vue:23 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Create your own radio" msgstr "Создать ваше ÑобÑтвенное радио" +#: front/src/components/auth/Settings.vue:134 +#: src/components/auth/Settings.vue:227 +#: front/src/components/manage/library/AlbumsTable.vue:44 +#: front/src/components/manage/library/ArtistsTable.vue:43 +#: front/src/components/manage/library/LibrariesTable.vue:54 +#: front/src/components/manage/library/TracksTable.vue:44 +#: front/src/components/manage/library/UploadsTable.vue:66 #: front/src/components/manage/users/InvitationsTable.vue:40 -#: front/src/components/mixins/Translations.vue:16 -#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:43 +#: front/src/components/mixins/Translations.vue:44 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Creation date" msgstr "Дата ÑозданиÑ" #: front/src/components/auth/Settings.vue:54 +msgctxt "Content/Settings/Title/Noun" msgid "Current avatar" msgstr "Текущий аватар" #: front/src/views/content/libraries/DetailArea.vue:4 +msgctxt "Content/Library/Title" msgid "Current library" msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñ‚ÐµÐºÐ°" #: front/src/components/playlists/PlaylistModal.vue:8 +msgctxt "Popup/Playlist/Title" msgid "Current track" msgstr "Текущий трек" #: front/src/views/content/libraries/Quota.vue:2 +msgctxt "Content/Library/Title" msgid "Current usage" msgstr "Текущее иÑпользование" +#: front/src/components/federation/FetchButton.vue:53 +msgctxt "*/*/Error" +msgid "Data returned by the remote server had invalid or missing attributes" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:17 +msgctxt "Popup/*/Message.Content" +msgid "Data was refreshed successfully from remote server." +msgstr "" + #: front/src/views/content/libraries/Detail.vue:27 +msgctxt "Content/Library/Table.Label" msgid "Date" msgstr "Дата" +#: front/src/components/library/ImportStatusModal.vue:64 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Debug information" +msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ треке" + #: front/src/components/ShortcutsModal.vue:75 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Decrease volume" msgstr "" -#: front/src/components/manage/library/FilesTable.vue:190 +#: front/src/components/auth/Settings.vue:150 +#: src/components/auth/Settings.vue:251 +#: front/src/components/library/EditCard.vue:93 +#: front/src/components/library/EditCard.vue:98 +#: front/src/components/manage/library/AlbumsTable.vue:188 +#: front/src/components/manage/library/ArtistsTable.vue:178 +#: front/src/components/manage/library/LibrariesTable.vue:205 +#: front/src/components/manage/library/TracksTable.vue:188 +#: front/src/components/manage/library/UploadsTable.vue:255 #: front/src/components/manage/moderation/InstancePolicyForm.vue:61 #: front/src/components/manage/users/InvitationsTable.vue:167 -#: front/src/views/content/libraries/FilesTable.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:72 +#: front/src/views/admin/library/AlbumDetail.vue:77 +#: front/src/views/admin/library/ArtistDetail.vue:71 +#: front/src/views/admin/library/ArtistDetail.vue:76 +#: front/src/views/admin/library/LibraryDetail.vue:58 +#: front/src/views/admin/library/LibraryDetail.vue:63 +#: front/src/views/admin/library/TrackDetail.vue:71 +#: front/src/views/admin/library/TrackDetail.vue:76 +#: front/src/views/admin/library/UploadDetail.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:70 +#: front/src/views/content/libraries/FilesTable.vue:222 #: front/src/views/content/libraries/Form.vue:29 -#: src/views/playlists/Detail.vue:33 +#: src/views/playlists/Detail.vue:34 +msgctxt "*/*/*/Verb" msgid "Delete" msgstr "Удалить" +#: front/src/components/auth/Settings.vue:254 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" +msgid "Delete application" +msgstr "Удалить ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/auth/Settings.vue:252 +msgctxt "Popup/Settings/Title" +msgid "Delete application \"%{ application }\"?" +msgstr "" + #: front/src/views/content/libraries/Form.vue:39 +msgctxt "Popup/Library/Button.Label/Verb" msgid "Delete library" msgstr "Удалить библиотеку" #: front/src/components/manage/moderation/InstancePolicyForm.vue:69 -#, fuzzy +msgctxt "Popup/Moderation/Button.Label/Verb" msgid "Delete moderation rule" msgstr "Удалить радио" -#: front/src/views/playlists/Detail.vue:38 +#: front/src/views/playlists/Detail.vue:39 +msgctxt "Popup/Playlist/Button.Label/Verb" msgid "Delete playlist" msgstr "Удалить ÑпиÑок воÑпроизведениÑ" #: front/src/views/radios/Detail.vue:28 +msgctxt "Popup/Radio/Button.Label/Verb" msgid "Delete radio" msgstr "Удалить радио" +#: front/src/views/admin/library/AlbumDetail.vue:73 +#: front/src/views/admin/library/TrackDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this album?" +msgstr "Удалить Ñту библиотеку?" + +#: front/src/views/admin/library/ArtistDetail.vue:72 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this artist?" +msgstr "Удалить Ñту библиотеку?" + +#: front/src/views/admin/library/LibraryDetail.vue:59 #: front/src/views/content/libraries/Form.vue:31 +msgctxt "Popup/Library/Title" msgid "Delete this library?" msgstr "Удалить Ñту библиотеку?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:63 -#, fuzzy +msgctxt "Popup/Moderation/Title" msgid "Delete this moderation rule?" msgstr "Удалить Ñту библиотеку?" -#: front/src/components/favorites/List.vue:34 -#: src/components/library/Artists.vue:26 -#: front/src/components/library/Radios.vue:47 -#: front/src/components/manage/library/FilesTable.vue:20 +#: front/src/components/library/EditCard.vue:94 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this suggestion?" +msgstr "Удалить Ñту библиотеку?" + +#: front/src/views/admin/library/UploadDetail.vue:66 +#, fuzzy +msgctxt "Popup/Library/Title" +msgid "Delete this upload?" +msgstr "Удалить Ñту библиотеку?" + +#: front/src/components/favorites/List.vue:35 +#: src/components/library/Albums.vue:26 +#: front/src/components/library/Artists.vue:26 +#: src/components/library/Radios.vue:47 +#: front/src/components/manage/library/AlbumsTable.vue:22 +#: front/src/components/manage/library/ArtistsTable.vue:22 +#: front/src/components/manage/library/EditsCardList.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:31 +#: front/src/components/manage/library/TracksTable.vue:22 +#: front/src/components/manage/library/UploadsTable.vue:41 #: front/src/components/manage/moderation/AccountsTable.vue:22 #: front/src/components/manage/moderation/DomainsTable.vue:20 #: front/src/components/manage/users/UsersTable.vue:20 #: front/src/views/content/libraries/FilesTable.vue:32 #: front/src/views/playlists/List.vue:28 +msgctxt "Content/Search/Dropdown" msgid "Descending" msgstr "По убыванию" #: front/src/components/library/radios/Builder.vue:25 #: front/src/views/content/libraries/Form.vue:14 +#, fuzzy +msgctxt "Content/*/Input.Label/Noun" msgid "Description" msgstr "ОпиÑание" -#: front/src/views/content/libraries/Card.vue:47 +#: front/src/views/admin/library/LibraryDetail.vue:123 #, fuzzy -msgid "Detail" -msgstr "ПодробноÑÑ‚ÑŒ" +msgctxt "*/*/*/Noun" +msgid "Description" +msgstr "ОпиÑание" -#: front/src/views/content/remote/Card.vue:50 -#, fuzzy +#: front/src/views/content/libraries/Card.vue:48 +#: src/views/content/remote/Card.vue:54 +msgctxt "Content/Library/Card.Button.Label/Noun" msgid "Details" msgstr "ПодробноÑти" -#: front/src/views/admin/moderation/AccountsDetail.vue:455 +#: front/src/views/admin/moderation/AccountsDetail.vue:491 +msgctxt "Content/Moderation/Help text" msgid "Determine how much content the user can upload. Leave empty to use the default value of the instance." msgstr "Выберите как много Ñодержимого пользователь может загрузить. ОÑтавьте пуÑтым чтобы иÑпользовать значение по умолчанию Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ узла." #: front/src/components/mixins/Translations.vue:8 #: front/src/components/mixins/Translations.vue:9 +msgctxt "Content/Settings/Dropdown.Help text" msgid "Determine the visibility level of your activity" msgstr "Определить уровень видимоÑти вашей активноÑти" #: front/src/components/auth/Settings.vue:104 -#: front/src/components/auth/SubsonicTokenForm.vue:52 +#: front/src/components/auth/SubsonicTokenForm.vue:51 +msgctxt "Popup/Settings/Button.Label" msgid "Disable access" msgstr "Отключить доÑтуп" -#: front/src/components/auth/SubsonicTokenForm.vue:49 +#: front/src/components/auth/SubsonicTokenForm.vue:48 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Disable Subsonic access" msgstr "Отключить доÑтуп Subsonic" -#: front/src/components/auth/SubsonicTokenForm.vue:50 +#: front/src/components/auth/SubsonicTokenForm.vue:49 +msgctxt "Popup/Settings/Title" msgid "Disable Subsonic API access?" msgstr "Отключить доÑтуп Subsonic API?" #: front/src/components/manage/moderation/InstancePolicyForm.vue:18 -#: front/src/views/admin/moderation/AccountsDetail.vue:128 -#: front/src/views/admin/moderation/AccountsDetail.vue:132 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:157 +#: front/src/views/admin/moderation/AccountsDetail.vue:161 +msgctxt "*/*/*" msgid "Disabled" msgstr "Отключить доÑтуп" -#: front/src/components/auth/SubsonicTokenForm.vue:14 +#: front/src/views/admin/library/TrackDetail.vue:145 +msgctxt "*/*/*/Noun" +msgid "Disc number" +msgstr "" + +#: front/src/components/auth/SubsonicTokenForm.vue:13 +msgctxt "Content/Settings/Link" msgid "Discover how to use Funkwhale from other apps" msgstr "Узнайте как иÑпользовать Funkwhale из других приложений" -#: front/src/views/admin/moderation/AccountsDetail.vue:103 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:132 +msgctxt "'Content/*/*/Noun'" msgid "Display name" msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" #: front/src/components/library/radios/Builder.vue:30 +msgctxt "Content/Radio/Checkbox.Label/Verb" msgid "Display publicly" msgstr "Публично отображать" #: front/src/components/manage/moderation/InstancePolicyForm.vue:122 +msgctxt "Content/Moderation/Help text" msgid "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well." msgstr "" -#: front/src/components/playlists/Editor.vue:42 +#: front/src/components/playlists/Editor.vue:51 +msgctxt "Popup/Playlist/Title" msgid "Do you want to clear the playlist \"%{ playlist }\"?" msgstr "Ð’Ñ‹ хотите очиÑтить ÑпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ \"%{ playlist }\"?" #: front/src/components/common/DangerousButton.vue:7 +msgctxt "Modal/*/Title" msgid "Do you want to confirm this action?" msgstr "Ð’Ñ‹ хотите подтвердить Ñто дейÑтвие?" #: front/src/views/playlists/Detail.vue:35 +msgctxt "Popup/Playlist/Title/Call to action" msgid "Do you want to delete the playlist \"%{ playlist }\"?" msgstr "Ð’Ñ‹ хотите удалить ÑпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ \"%{ playlist }\"?" #: front/src/views/radios/Detail.vue:26 +msgctxt "Popup/Radio/Title" msgid "Do you want to delete the radio \"%{ radio }\"?" msgstr "Ð’Ñ‹ хотите удалить радио \"%{ radio }\"?" -#: front/src/components/common/ActionTable.vue:36 +#: front/src/components/moderation/FilterModal.vue:3 +#, fuzzy +msgctxt "Popup/Moderation/Title/Verb" +msgid "Do you want to hide content from artist \"%{ name }\"?" +msgstr "Ð’Ñ‹ хотите удалить радио \"%{ radio }\"?" + +#: front/src/components/common/ActionTable.vue:37 +#, fuzzy +msgctxt "Modal/*/Title" msgid "Do you want to launch %{ action } on %{ count } element?" msgid_plural "Do you want to launch %{ action } on %{ count } elements?" msgstr[0] "Ð’Ñ‹ хотите запуÑтить %{ action } на %{ count } Ñлементе?" msgstr[1] "Ð’Ñ‹ хотите запуÑтить %{ action } на %{ count } Ñлементах?" msgstr[2] "Ð’Ñ‹ хотите запуÑтить %{ action } на %{ count } Ñлементах?" -#: front/src/components/Sidebar.vue:107 +#: front/src/components/Sidebar.vue:118 +msgctxt "Sidebar/Queue/Message" msgid "Do you want to restore your previous queue?" msgstr "Ð’Ñ‹ хотите воÑÑтановить вашу предыдущую очередь?" #: front/src/components/Footer.vue:31 +msgctxt "Footer/*/List item.Link/Short, Noun" msgid "Documentation" msgstr "ДокументациÑ" +#: front/src/components/manage/library/AlbumsTable.vue:41 +#: front/src/components/manage/library/ArtistsTable.vue:40 +#: front/src/components/manage/library/LibrariesTable.vue:50 +#: front/src/components/manage/library/TracksTable.vue:42 +#: front/src/components/manage/library/UploadsTable.vue:62 #: front/src/components/manage/moderation/AccountsTable.vue:40 -#: front/src/components/mixins/Translations.vue:34 -#: front/src/views/admin/moderation/AccountsDetail.vue:93 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:60 +#: front/src/views/admin/library/AlbumDetail.vue:118 +#: front/src/views/admin/library/ArtistDetail.vue:107 +#: front/src/views/admin/library/LibraryDetail.vue:114 +#: front/src/views/admin/library/TrackDetail.vue:170 +#: front/src/views/admin/library/UploadDetail.vue:121 +#: front/src/views/admin/moderation/AccountsDetail.vue:123 +#: front/src/components/mixins/Translations.vue:61 +msgctxt "Content/Moderation/*/Noun" msgid "Domain" msgstr "" #: front/src/views/admin/moderation/Base.vue:5 #: front/src/views/admin/moderation/DomainsList.vue:3 #: front/src/views/admin/moderation/DomainsList.vue:48 +msgctxt "*/Moderation/*/Noun" msgid "Domains" msgstr "" -#: front/src/components/library/Track.vue:55 +#: front/src/components/library/TrackBase.vue:39 +#: front/src/views/admin/library/UploadDetail.vue:58 +msgctxt "Content/Track/Link/Verb" msgid "Download" msgstr "Скачать" -#: front/src/components/playlists/Editor.vue:49 +#: front/src/components/playlists/Editor.vue:59 +msgctxt "Content/Playlist/Paragraph/Call to action" msgid "Drag and drop rows to reorder tracks in the playlist" msgstr "ПеретаÑкивайте Ñтроки чтобы упорÑдочить треки в ÑпиÑке воÑпроизведениÑ" -#: front/src/components/audio/track/Table.vue:9 -#: src/components/library/Track.vue:111 -#: front/src/components/manage/library/FilesTable.vue:43 -#: front/src/components/mixins/Translations.vue:30 -#: front/src/views/content/libraries/FilesTable.vue:59 -#: front/src/components/mixins/Translations.vue:31 +#: front/src/components/audio/track/Table.vue:10 +#: front/src/components/library/TrackDetail.vue:30 +#: front/src/components/mixins/Translations.vue:56 +#: front/src/views/admin/library/UploadDetail.vue:238 +#: front/src/views/content/libraries/FilesTable.vue:60 +#: front/src/components/mixins/Translations.vue:57 +msgctxt "Content/*/*" msgid "Duration" msgstr "ДлительноÑÑ‚ÑŒ" #: front/src/views/auth/EmailConfirm.vue:23 -#, fuzzy +msgctxt "Content/Signup/Message" msgid "E-mail address confirmed" msgstr "ÐÐ´Ñ€ÐµÑ Ñлектронной почты подтверждён" -#: front/src/components/Home.vue:93 -#, fuzzy +#: front/src/components/Home.vue:88 +msgctxt "Content/Home/Title" msgid "Easy to use" msgstr "Легко в иÑпользовании" +#: front/src/components/library/AlbumBase.vue:68 +#: front/src/components/library/ArtistBase.vue:79 +#: front/src/components/library/TrackBase.vue:87 +#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 +#: front/src/components/radios/Card.vue:23 +#: src/views/admin/library/AlbumDetail.vue:65 +#: front/src/views/admin/library/ArtistDetail.vue:64 +#: front/src/views/admin/library/TrackDetail.vue:64 #: front/src/views/content/libraries/Detail.vue:9 +#: src/views/playlists/Detail.vue:31 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Edit" msgstr "Редактировать" -#: front/src/components/About.vue:21 +#: front/src/components/auth/Settings.vue:246 +#, fuzzy +msgctxt "Content/Settings/Button.Label" +msgid "Edit" +msgstr "Редактировать" + +#: front/src/components/auth/ApplicationEdit.vue:30 +#: front/src/components/auth/ApplicationEdit.vue:75 +#, fuzzy +msgctxt "Content/Applications/Title" +msgid "Edit application" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹ÑтвиÑ" + +#: front/src/components/About.vue:22 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Edit instance info" msgstr "Редактировать информацию об узле" -#: front/src/components/radios/Card.vue:22 src/views/playlists/Detail.vue:30 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 +#, fuzzy +msgctxt "Content/Moderation/Card.Title/Verb" +msgid "Edit moderation rule" +msgstr "Удалить радио" + +#: front/src/components/library/AlbumEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this album" +msgstr "Проиграть трек" + +#: front/src/components/library/ArtistEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this artist" +msgstr "Проиграть трек" + +#: front/src/components/library/TrackEdit.vue:4 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Edit this track" +msgstr "Проиграть трек" + +#: front/src/views/admin/library/AlbumDetail.vue:182 +#: front/src/views/admin/library/ArtistDetail.vue:171 +#: front/src/views/admin/library/Base.vue:5 +#: src/views/admin/library/EditsList.vue:24 +#: front/src/views/admin/library/TrackDetail.vue:234 +#, fuzzy +msgctxt "*/Admin/*/Noun" +msgid "Edits" +msgstr "Редактировать" + +#: front/src/components/mixins/Translations.vue:104 +#: front/src/components/mixins/Translations.vue:105 #, fuzzy -msgid "Edit…" +msgctxt "Content/OAuth Scopes/Label" +msgid "Edits" msgstr "Редактировать" -#: front/src/components/auth/Signup.vue:29 +#: front/src/components/auth/Signup.vue:30 #: front/src/components/manage/users/UsersTable.vue:38 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Email" msgstr "ÐÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°" -#: front/src/views/admin/moderation/AccountsDetail.vue:111 +#: front/src/views/admin/moderation/AccountsDetail.vue:140 +msgctxt "Content/*/*" msgid "Email address" msgstr "ÐÐ´Ñ€ÐµÑ Ñлектронной почты" -#: front/src/components/library/Album.vue:44 -#: src/components/library/Track.vue:62 +#: front/src/components/library/AlbumBase.vue:53 +#: front/src/components/library/ArtistBase.vue:64 +#: front/src/components/library/TrackBase.vue:72 +msgctxt "Content/*/Button.Label/Verb" msgid "Embed" msgstr "" #: front/src/components/audio/EmbedWizard.vue:20 +msgctxt "Popup/Embed/Input.Label/Noun" msgid "Embed code" msgstr "" -#: front/src/components/library/Album.vue:48 +#: front/src/components/library/AlbumBase.vue:26 +msgctxt "Popup/Album/Title/Verb" msgid "Embed this album on your website" msgstr "" -#: front/src/components/library/Track.vue:66 +#: front/src/components/library/ArtistBase.vue:37 +msgctxt "Popup/Artist/Title/Verb" +msgid "Embed this artist work on your website" +msgstr "" + +#: front/src/components/library/TrackBase.vue:45 +msgctxt "Popup/Track/Title" msgid "Embed this track on your website" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:230 +#: front/src/views/admin/moderation/AccountsDetail.vue:259 #: front/src/views/admin/moderation/DomainsDetail.vue:187 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted library follows" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:214 +#: front/src/views/admin/moderation/AccountsDetail.vue:243 #: front/src/views/admin/moderation/DomainsDetail.vue:171 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Emitted messages" msgstr "" #: front/src/components/manage/moderation/InstancePolicyCard.vue:8 #: front/src/components/manage/moderation/InstancePolicyForm.vue:17 -#: front/src/views/admin/moderation/AccountsDetail.vue:127 -#: front/src/views/admin/moderation/AccountsDetail.vue:131 +#: front/src/views/admin/moderation/AccountsDetail.vue:156 +#: front/src/views/admin/moderation/AccountsDetail.vue:160 +#, fuzzy +msgctxt "*/*/*" msgid "Enabled" -msgstr "" +msgstr "Отключить доÑтуп" -#: front/src/views/playlists/Detail.vue:29 +#: front/src/views/playlists/Detail.vue:30 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "End edition" msgstr "Закончить редактирование" #: front/src/views/content/remote/ScanForm.vue:50 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder" msgid "Enter a library URL" msgstr "ОчиÑтить библиотеку" -#: front/src/components/library/Radios.vue:140 -#, fuzzy +#: front/src/components/library/Radios.vue:141 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter a radio name…" msgstr "Введите название радио..." -#: front/src/components/library/Artists.vue:118 -#, fuzzy +#: front/src/components/library/Albums.vue:119 +msgctxt "Content/Search/Input.Placeholder" +msgid "Enter album title..." +msgstr "" + +#: front/src/components/library/Artists.vue:116 +msgctxt "Content/Search/Input.Placeholder" msgid "Enter artist name…" msgstr "Введите Ð¸Ð¼Ñ Ð¸ÑполнителÑ..." #: front/src/views/playlists/List.vue:107 -#, fuzzy +msgctxt "Content/Playlist/Placeholder/Call to action" msgid "Enter playlist name…" msgstr "Введите название ÑпиÑка воÑпроизведениÑ..." -#: front/src/components/auth/Signup.vue:100 +#: front/src/views/auth/PasswordReset.vue:54 +msgctxt "Content/Signup/Input.Placeholder" +msgid "Enter the email address binded to your account" +msgstr "" + +#: front/src/components/auth/Signup.vue:103 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your email" msgstr "Введите ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" -#: front/src/components/auth/Signup.vue:96 src/components/auth/Signup.vue:97 +#: front/src/components/auth/Signup.vue:98 src/components/auth/Signup.vue:100 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your invitation code (case insensitive)" msgstr "Введите ваш код Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ (нечувÑтвительно к региÑтру)" #: front/src/components/metadata/Search.vue:114 -#, fuzzy +msgctxt "Content/Library/Input.Placeholder/Verb" msgid "Enter your search query…" msgstr "Введите ваш поиÑковый запроÑ..." -#: front/src/components/auth/Signup.vue:99 +#: front/src/components/auth/Signup.vue:102 +msgctxt "Content/Signup/Form/Placeholder" msgid "Enter your username" msgstr "Введите ваше Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" -#: front/src/components/auth/Login.vue:77 +#: front/src/components/auth/Login.vue:83 +msgctxt "Content/Login/Input.Placeholder" msgid "Enter your username or email" msgstr "Введите ваше Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" -#: front/src/components/auth/SubsonicTokenForm.vue:20 +#: front/src/components/auth/SubsonicTokenForm.vue:19 #: front/src/views/content/libraries/Form.vue:4 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error" msgstr "Ошибка" +#: front/src/components/federation/FetchButton.vue:34 +#: front/src/components/library/ImportStatusModal.vue:32 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error detail" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" + #: front/src/views/admin/Settings.vue:87 +#, fuzzy +msgctxt "Content/Admin/Menu" msgid "Error reporting" -msgstr "" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" + +#: front/src/components/federation/FetchButton.vue:26 +#: front/src/components/library/ImportStatusModal.vue:24 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Error type" +msgstr "Ошибочно" -#: front/src/components/common/ActionTable.vue:92 +#: front/src/components/common/ActionTable.vue:94 +msgctxt "Content/*/Error message/Header" msgid "Error while applying action" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹ÑтвиÑ" #: front/src/views/auth/PasswordReset.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while asking for a password reset" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа ÑброÑа паролÑ" +#: front/src/components/auth/Authorize.vue:6 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while authorizing application" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´ÐµÐ¹ÑтвиÑ" + #: front/src/views/auth/PasswordResetConfirm.vue:7 +msgctxt "Content/Signup/Card.Title" msgid "Error while changing your password" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ паролÑ" #: front/src/views/admin/moderation/DomainsList.vue:6 -#, fuzzy +msgctxt "Content/Moderation/Message.Title" msgid "Error while creating domain" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" +#: front/src/components/moderation/FilterModal.vue:13 +#, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while creating filter" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" + #: front/src/components/manage/users/InvitationForm.vue:4 +msgctxt "Content/Admin/Error message.Title" msgid "Error while creating invitation" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" #: front/src/components/manage/moderation/InstancePolicyForm.vue:7 -#, fuzzy +msgctxt "Content/Moderation/Error message.Title" msgid "Error while creating rule" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" -#: front/src/views/admin/moderation/DomainsDetail.vue:126 +#: front/src/components/auth/Authorize.vue:7 #, fuzzy +msgctxt "Popup/Moderation/Error message" +msgid "Error while fetching application data" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ" + +#: front/src/views/admin/moderation/DomainsDetail.vue:118 +msgctxt "Content/Moderation/Table" msgid "Error while fetching node info" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ удалённой библиотеки" #: front/src/components/admin/SettingsGroup.vue:5 +msgctxt "Content/Settings/Error message.Title" +msgid "Error while saving settings" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ð°Ñтроек" + +#: front/src/components/federation/FetchButton.vue:73 +#, fuzzy +msgctxt "Content/*/Error message.Title" msgid "Error while saving settings" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ð°Ñтроек" -#: front/src/views/content/libraries/FilesTable.vue:212 +#: front/src/components/library/EditForm.vue:46 #, fuzzy +msgctxt "Content/Library/Error message.Title" +msgid "Error while submitting edit" +msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ð°Ñтроек" + +#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:33 +msgctxt "Content/Library/Table/Short" msgid "Errored" msgstr "Ошибочно" #: front/src/views/content/libraries/Quota.vue:75 +msgctxt "Content/Library/Label" msgid "Errored files" msgstr "Ошибочные файлы" -#: front/src/components/playlists/Form.vue:89 +#: front/src/components/mixins/Translations.vue:17 +#: front/src/components/mixins/Translations.vue:18 +#, fuzzy +msgctxt "Content/Settings/Dropdown/Short" msgid "Everyone" msgstr "Ð’Ñе" #: front/src/components/mixins/Translations.vue:11 -#: front/src/components/playlists/Form.vue:85 -#: src/views/content/libraries/Form.vue:73 #: front/src/components/mixins/Translations.vue:12 +msgctxt "Content/Settings/Dropdown" msgid "Everyone on this instance" msgstr "Ð’Ñе на Ñтом узле" -#: front/src/views/content/libraries/Form.vue:74 +#: front/src/components/mixins/Translations.vue:12 +#: front/src/components/mixins/Translations.vue:13 #, fuzzy +msgctxt "Content/Settings/Dropdown" msgid "Everyone, across all instances" msgstr "Ð’Ñе на Ñтом узле" -#: front/src/components/library/radios/Builder.vue:61 +#: front/src/components/library/radios/Builder.vue:62 +msgctxt "Content/Radio/Table.Label/Verb" msgid "Exclude" msgstr "ИÑключить" #: front/src/components/manage/users/InvitationsTable.vue:41 -#: front/src/components/mixins/Translations.vue:22 -#: front/src/components/mixins/Translations.vue:23 +#: front/src/components/mixins/Translations.vue:49 +#: front/src/components/mixins/Translations.vue:50 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Expiration date" msgstr "Дата иÑтечениÑ" #: front/src/components/manage/users/InvitationsTable.vue:50 +msgctxt "Content/Admin/Table" msgid "Expired" msgstr "ИÑтёк" #: front/src/components/manage/users/InvitationsTable.vue:21 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Expired/used" msgstr "ИÑтёк/иÑпользован" #: front/src/components/manage/moderation/InstancePolicyForm.vue:110 +msgctxt "Content/Moderation/Help text" msgid "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place." msgstr "" +#: front/src/components/manage/library/UploadsTable.vue:25 #: front/src/views/content/libraries/FilesTable.vue:16 +#, fuzzy +msgctxt "Content/Library/Dropdown" msgid "Failed" -msgstr "" +msgstr "Ошибочные треки:" -#: front/src/views/content/remote/Card.vue:58 -#, fuzzy +#: front/src/views/content/remote/Card.vue:62 +msgctxt "Content/Library/Card.List item/Noun" msgid "Failed tracks:" msgstr "Ошибочные треки:" +#: front/src/views/admin/library/AlbumDetail.vue:165 +#: front/src/views/admin/library/ArtistDetail.vue:154 +#: front/src/views/admin/library/TrackDetail.vue:217 +#, fuzzy +msgctxt "*/*/*" +msgid "Favorited tracks" +msgstr "Ошибочные треки:" + +#: front/src/components/mixins/Translations.vue:76 +#: front/src/components/mixins/Translations.vue:77 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Favorites" +msgstr "Избранное" + #: front/src/components/Sidebar.vue:66 +msgctxt "Sidebar/Favorites/List item.Link/Noun" msgid "Favorites" msgstr "Избранное" #: front/src/views/admin/Settings.vue:84 +msgctxt "Content/Admin/Menu" msgid "Federation" msgstr "ФедерациÑ" -#: front/src/components/library/FileUpload.vue:84 +#: front/src/components/library/TrackDetail.vue:66 #, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Federation ID" +msgstr "ФедерациÑ" + +#: front/src/components/library/EditCard.vue:45 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Field" +msgstr "" + +#: front/src/components/library/FileUpload.vue:93 +msgctxt "Content/Library/Table.Label" msgid "Filename" msgstr "Ð˜Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" -#: front/src/views/admin/library/Base.vue:5 -#: src/views/admin/library/FilesList.vue:21 -msgid "Files" -msgstr "Файлы" - -#: front/src/components/library/radios/Builder.vue:60 +#: front/src/components/library/radios/Builder.vue:61 +msgctxt "Content/Radio/Table.Label/Noun" msgid "Filter name" msgstr "Ð˜Ð¼Ñ Ñ„Ð¸Ð»ÑŒÑ‚Ñ€Ð°" +#: front/src/components/manage/library/UploadsTable.vue:26 +#: front/src/components/mixins/Translations.vue:36 #: front/src/views/content/libraries/FilesTable.vue:17 -#: front/src/views/content/libraries/FilesTable.vue:216 +#: front/src/components/mixins/Translations.vue:37 +#, fuzzy +msgctxt "Content/Library/*" msgid "Finished" msgstr "Завершено" #: front/src/components/manage/moderation/AccountsTable.vue:42 #: front/src/components/manage/moderation/DomainsTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:159 -#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#: front/src/views/admin/library/AlbumDetail.vue:149 +#: front/src/views/admin/library/ArtistDetail.vue:138 +#: front/src/views/admin/library/LibraryDetail.vue:153 +#: front/src/views/admin/library/TrackDetail.vue:201 +#: front/src/views/admin/library/UploadDetail.vue:167 +#: front/src/views/admin/moderation/AccountsDetail.vue:235 +#: front/src/views/admin/moderation/DomainsDetail.vue:151 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short (Value is a date)" msgid "First seen" -msgstr "" +msgstr "Дата иÑтечениÑ" -#: front/src/components/mixins/Translations.vue:17 -#: front/src/components/mixins/Translations.vue:18 -#, fuzzy +#: front/src/components/mixins/Translations.vue:46 +#: front/src/components/mixins/Translations.vue:47 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "First seen date" msgstr "Дата иÑтечениÑ" -#: front/src/views/content/remote/Card.vue:83 +#: front/src/views/content/remote/Card.vue:87 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Follow" msgstr "ПодпиÑатьÑÑ" #: front/src/views/content/Home.vue:16 +msgctxt "Content/Library/Title/Verb" msgid "Follow remote libraries" msgstr "ПодпиÑатьÑÑ Ð½Ð° удалённые библиотеки" -#: front/src/views/content/remote/Card.vue:88 -#, fuzzy +#: front/src/views/content/remote/Card.vue:92 +msgctxt "Content/Library/Card.Paragraph" msgid "Follow request pending approval" msgstr "ПодпиÑка требует подтверждениÑ" -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/mixins/Translations.vue:64 +#: front/src/views/admin/library/LibraryDetail.vue:161 #: front/src/views/content/libraries/Detail.vue:7 -#: front/src/components/mixins/Translations.vue:39 +#: front/src/components/mixins/Translations.vue:65 +msgctxt "Content/Federation/*/Noun" +msgid "Followers" +msgstr "ПодпиÑчики" + +#: front/src/components/manage/library/LibrariesTable.vue:53 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Followers" msgstr "ПодпиÑчики" -#: front/src/views/content/remote/Card.vue:93 +#: front/src/views/content/remote/Card.vue:97 +#, fuzzy +msgctxt "Content/Library/Card.Paragraph" msgid "Following" +msgstr "ПодпиÑатьÑÑ" + +#: front/src/components/mixins/Translations.vue:84 +#: front/src/components/mixins/Translations.vue:85 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Follows" +msgstr "ПодпиÑатьÑÑ" + +#: front/src/components/library/TrackBase.vue:17 +msgctxt "Content/Track/Paragraph" +msgid "From album <a class=\"internal\" href=\"%{ albumUrl }\">%{ album }</a> by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" msgstr "" -#: front/src/components/library/Track.vue:17 -msgid "From album %{ album } by %{ artist }" -msgstr "Из альбома %{ album } от %{ artist }" +#: front/src/components/auth/Authorize.vue:28 +#, fuzzy +msgctxt "Content/Auth/Label/Noun" +msgid "Full access" +msgstr "Отключить доÑтуп" #: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph'" msgid "Funkwhale is compatible with other music players that support the Subsonic API." msgstr "Funkwhale ÑовмеÑтим Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ музыкальными плеерами которые поддерживают Subsonic API." -#: front/src/components/Home.vue:95 -#, fuzzy +#: front/src/components/Home.vue:90 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is dead simple to use." msgstr "Funkwhale чрезвычайно проÑÑ‚ в иÑпользовании." #: front/src/components/Home.vue:39 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists." msgstr "Funkwhale Ñоздан чтобы упроÑтить проÑлушивание любимой музыки и поиÑк новых иÑполнителей." -#: front/src/components/Home.vue:116 +#: front/src/components/Home.vue:111 +msgctxt "Content/Home/Paragraph" msgid "Funkwhale is free and gives you control on your music." msgstr "Funkwhale Ñвободен и предоÑтавлÑет вам контроль над вашей музыкой." #: front/src/components/Home.vue:66 -#, fuzzy +msgctxt "Content/Home/Paragraph" msgid "Funkwhale takes care of handling your music" msgstr "Funkwhale заботитÑÑ Ð¾Ð± управлении вашей музыкой" #: front/src/components/ShortcutsModal.vue:38 +msgctxt "Popup/Keyboard shortcuts/Title" msgid "General shortcuts" msgstr "" #: front/src/components/manage/users/InvitationForm.vue:16 +msgctxt "Content/Admin/Button.Label/Verb" msgid "Get a new invitation" msgstr "Получить приглашение" #: front/src/components/Home.vue:13 -#, fuzzy +msgctxt "Content/Home/Button.Label/Verb" msgid "Get me to the library" msgstr "Перейти в библиотеку" -#: front/src/components/Home.vue:76 +#: front/src/components/Home.vue:70 +#, fuzzy +msgctxt "Content/Home/List item/Verb" msgid "Get quality metadata about your music thanks to <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" msgstr "Получайте качеÑтвенные метаданные о вашей музыке Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ <a href=\"%{ url }\" target=\"_blank\">MusicBrainz</a>" #: front/src/views/content/Home.vue:12 src/views/content/Home.vue:19 -#, fuzzy +msgctxt "Content/Library/Button.Label/Verb" msgid "Get started" msgstr "Ðачать" +#: front/src/components/library/ImportStatusModal.vue:45 +#, fuzzy +msgctxt "Popup/Import/Table.Label/Noun" +msgid "Getting help" +msgstr "ÐаÑтройки" + #: front/src/components/Footer.vue:37 #, fuzzy +msgctxt "Footer/*/Link" msgid "Getting help" msgstr "ÐаÑтройки" -#: front/src/components/common/ActionTable.vue:34 -#: front/src/components/common/ActionTable.vue:54 +#: front/src/components/common/ActionTable.vue:35 +#: front/src/components/common/ActionTable.vue:56 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Go" msgstr "" #: front/src/components/PageNotFound.vue:14 +msgctxt "Content/*/Button.Label/Verb" msgid "Go to home page" msgstr "Перейти на домашнюю Ñтраницу" +#: front/src/components/auth/Settings.vue:128 +#, fuzzy +msgctxt "Content/Settings/Title" +msgid "Hidden artists" +msgstr "ПроÑмотр иÑполнителей" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:114 +msgctxt "Content/Moderation/Help text" msgid "Hide account or domain content, except from followers." msgstr "" +#: front/src/components/moderation/FilterModal.vue:40 +#, fuzzy +msgctxt "Popup/*/Button.Label" +msgid "Hide content" +msgstr "Добавить Ñодержимое" + +#: front/src/components/audio/PlayButton.vue:26 +msgctxt "*/Queue/Dropdown/Button/Label/Short" +msgid "Hide content from this artist" +msgstr "" + +#: front/src/components/audio/Player.vue:615 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" +msgid "Hide content from this artist…" +msgstr "" + #: front/src/components/library/Home.vue:65 +msgctxt "Head/Home/Title" msgid "Home" msgstr "Домой" #: front/src/components/instance/Stats.vue:36 +msgctxt "Content/About/Paragraph/Unit" msgid "Hours of music" msgstr "ЧаÑов музыки" -#: front/src/components/auth/SubsonicTokenForm.vue:11 +#: front/src/components/auth/SubsonicTokenForm.vue:10 +msgctxt "Content/Settings/Paragraph" msgid "However, accessing Funkwhale from those clients require a separate password you can set below." msgstr "Однако, получение доÑтупа к Freewhale из Ñтих клиентов требует отдельного Ð¿Ð°Ñ€Ð¾Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¹ может быть уÑтановлен ниже." #: front/src/views/auth/PasswordResetConfirm.vue:24 +msgctxt "Content/Signup/Paragraph" msgid "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." msgstr "ЕÑли указанный на предыдущем шаге Ð°Ð´Ñ€ÐµÑ Ñлектронной почты правильный и привÑзан к пользовательÑкому аккаунту, то вы должны получить пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми по ÑброÑу в течение неÑкольких минут." -#: front/src/components/manage/library/FilesTable.vue:40 -msgid "Import date" -msgstr "Дата импорта" +#: front/src/components/auth/Settings.vue:205 +msgctxt "Content/Applications/Paragraph" +msgid "If you authorize third-party applications to access your data, those applications will be listed here." +msgstr "" -#: front/src/components/Home.vue:71 -msgid "Import music from various platforms, such as YouTube or SoundCloud" -msgstr "Импортируйте музыку из разных платформ, таких как YouTube и SoundCloud" +#: front/src/components/library/ImportStatusModal.vue:3 +#, fuzzy +msgctxt "Popup/Import/Title" +msgid "Import detail" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð°" -#: front/src/components/library/FileUpload.vue:51 +#: front/src/components/library/FileUpload.vue:50 +msgctxt "Content/Library/Input.Label/Noun" msgid "Import reference" msgstr "" -#: front/src/views/content/libraries/FilesTable.vue:11 -#: front/src/views/content/libraries/FilesTable.vue:58 +#: front/src/components/manage/library/UploadsTable.vue:64 +#: front/src/views/admin/library/UploadDetail.vue:131 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Import status" msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð°" -#: front/src/views/content/libraries/FilesTable.vue:217 +#: front/src/components/manage/library/UploadsTable.vue:20 +#: front/src/views/content/libraries/FilesTable.vue:11 +#: front/src/views/content/libraries/FilesTable.vue:59 #, fuzzy +msgctxt "Content/Library/*/Noun" +msgid "Import status" +msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð°" + +#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:38 +msgctxt "Content/Library/Help text" msgid "Imported" msgstr "Дата импорта" -#: front/src/components/mixins/Translations.vue:21 -#: front/src/components/mixins/Translations.vue:22 -msgid "Imported date" -msgstr "Дата импорта" +#: front/src/components/federation/FetchButton.vue:47 +msgctxt "*/*/Error" +msgid "Impossible to connect to the remote server" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:26 +#, fuzzy +msgctxt "Popup/Moderation/List item" +msgid "In \"Recently added\" widget" +msgstr "Ðедавно добавленные" + +#: front/src/components/moderation/FilterModal.vue:27 +msgctxt "Popup/Moderation/List item" +msgid "In artists and album listings" +msgstr "" #: front/src/components/favorites/TrackFavoriteIcon.vue:3 +msgctxt "Content/Track/Button.Message" msgid "In favorites" msgstr "Ð’ избранном" +#: front/src/components/moderation/FilterModal.vue:25 +msgctxt "Popup/Moderation/List item" +msgid "In other users favorites and listening history" +msgstr "" + +#: front/src/components/moderation/FilterModal.vue:28 +msgctxt "Popup/Moderation/List item" +msgid "In radio suggestions" +msgstr "" + #: front/src/components/manage/users/UsersTable.vue:54 -#, fuzzy +msgctxt "Content/Admin/Table" msgid "Inactive" msgstr "Ðеактивен(на)" #: front/src/components/ShortcutsModal.vue:71 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Increase volume" msgstr "" -#: front/src/views/auth/PasswordReset.vue:53 -msgid "Input the email address binded to your account" -msgstr "" - -#: front/src/components/playlists/Editor.vue:31 +#: front/src/components/playlists/Editor.vue:41 +#, fuzzy +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Insert from queue (%{ count } track)" msgid_plural "Insert from queue (%{ count } tracks)" msgstr[0] "Ð’Ñтавить из очереди (%{ count } трек)" msgstr[1] "Ð’Ñтавить из очереди (%{ count } трека)" msgstr[2] "Ð’Ñтавить из очереди (%{ count } треков)" -#: front/src/views/admin/moderation/DomainsDetail.vue:71 +#: front/src/components/mixins/Translations.vue:16 +#: front/src/components/mixins/Translations.vue:17 #, fuzzy +msgctxt "Content/Settings/Dropdown/Short" +msgid "Instance" +msgstr "Радио узла" + +#: front/src/views/admin/moderation/DomainsDetail.vue:71 +msgctxt "Content/Moderation/Title" msgid "Instance data" msgstr "Радио узла" #: front/src/views/admin/Settings.vue:80 +msgctxt "Content/Admin/Menu" msgid "Instance information" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± узле" #: front/src/components/library/Radios.vue:9 +msgctxt "Content/Radio/Title" msgid "Instance radios" msgstr "Радио узла" #: front/src/views/admin/Settings.vue:75 +msgctxt "Head/Admin/Title" msgid "Instance settings" msgstr "ÐаÑтройки узла" -#: front/src/components/library/FileUpload.vue:229 -#: front/src/components/library/FileUpload.vue:230 +#: front/src/components/SetInstanceModal.vue:19 +#, fuzzy +msgctxt "Popup/Instance/Input.Label/Noun" +msgid "Instance URL" +msgstr "Радио узла" + +#: front/src/components/library/FileUpload.vue:268 +msgctxt "Content/Library/Help text" msgid "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" msgstr "" -#: front/src/components/auth/Signup.vue:42 +#: front/src/components/library/ImportStatusModal.vue:139 +msgctxt "Popup/Import/Error.Label" +msgid "Invalid metadata" +msgstr "" + +#: front/src/components/auth/Signup.vue:44 #: front/src/components/manage/users/InvitationForm.vue:11 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Invitation code" msgstr "Код приглашениÑ" -#: front/src/components/auth/Signup.vue:43 -msgid "Invitation code (optional)" -msgstr "Код Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ (не обÑзательно)" - #: front/src/views/admin/users/Base.vue:8 -#: src/views/admin/users/InvitationsList.vue:3 #: front/src/views/admin/users/InvitationsList.vue:24 +#, fuzzy +msgctxt "*/Admin/*/Noun" msgid "Invitations" msgstr "ПриглашениÑ" #: front/src/components/Footer.vue:41 -#, fuzzy +msgctxt "Footer/*/List item.Link" msgid "Issue tracker" msgstr "Багтрекер" +#: front/src/components/SetInstanceModal.vue:5 +msgctxt "Popup/Instance/Error message.Title" +msgid "It is not possible to connect to the given URL" +msgstr "" + #: front/src/components/Home.vue:50 +msgctxt "Content/Home/List item/Verb" msgid "Keep a track of your favorite songs" msgstr "" #: front/src/components/Footer.vue:33 src/components/ShortcutsModal.vue:3 +msgctxt "*/*/*/Noun" msgid "Keyboard shortcuts" msgstr "" #: front/src/views/admin/moderation/DomainsDetail.vue:161 -#, fuzzy +msgctxt "Content/Moderation/Table.Label.Link" msgid "Known accounts" msgstr "Мой аккаунт" #: front/src/views/content/remote/Home.vue:14 -#, fuzzy +msgctxt "Content/Library/Title" msgid "Known libraries" msgstr "ИзвеÑтные библиотеки" #: front/src/components/manage/users/UsersTable.vue:41 -#: front/src/components/mixins/Translations.vue:32 -#: front/src/views/admin/moderation/AccountsDetail.vue:184 -#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:58 +#: front/src/views/admin/moderation/AccountsDetail.vue:205 +#: front/src/components/mixins/Translations.vue:59 +#, fuzzy +msgctxt "Content/Profile/Table.Label/Short, Noun (Value is a date)" msgid "Last activity" msgstr "ПоÑледнÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ" -#: front/src/views/admin/moderation/AccountsDetail.vue:167 -#: front/src/views/admin/moderation/DomainsDetail.vue:86 +#: front/src/views/admin/moderation/AccountsDetail.vue:188 +#: front/src/views/admin/moderation/DomainsDetail.vue:78 +#, fuzzy +msgctxt "Content/*/Table.Label" msgid "Last checked" -msgstr "" +msgstr "ПоÑледнее обновление:" -#: front/src/components/playlists/PlaylistModal.vue:32 +#: front/src/components/playlists/PlaylistModal.vue:46 +msgctxt "Popup/Playlist/Table.Label/Short" msgid "Last modification" msgstr "ПоÑледнее изменение" #: front/src/components/manage/moderation/AccountsTable.vue:43 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Last seen" -msgstr "" +msgstr "ПоÑледнее обновление:" -#: front/src/components/mixins/Translations.vue:18 -#: front/src/components/mixins/Translations.vue:19 -#, fuzzy +#: front/src/components/mixins/Translations.vue:47 +#: front/src/components/mixins/Translations.vue:48 +msgctxt "Content/Moderation/Dropdown/Noun" msgid "Last seen date" msgstr "ПоÑледнее обновление:" -#: front/src/views/content/remote/Card.vue:56 +#: front/src/views/content/remote/Card.vue:60 +msgctxt "Content/Library/Card.List item/Noun" msgid "Last update:" msgstr "ПоÑледнее обновление:" -#: front/src/components/common/ActionTable.vue:47 +#: front/src/components/common/ActionTable.vue:49 +msgctxt "Modal/*/Button.Label/Short, Verb" msgid "Launch" msgstr "ЗапуÑтить" #: front/src/components/Home.vue:10 +msgctxt "Content/Home/Button.Label/Verb" msgid "Learn more about this instance" msgstr "Узнать больше об Ñтом узле" #: front/src/components/manage/users/InvitationForm.vue:58 +msgctxt "Content/Admin/Input.Placeholder" msgid "Leave empty for a random code" msgstr "ОÑтавьте пуÑтым Ð´Ð»Ñ Ñлучайного кода" #: front/src/components/audio/EmbedWizard.vue:7 -#, fuzzy +msgctxt "Popup/Embed/Paragraph" msgid "Leave empty for a responsive widget" msgstr "ОÑтавьте пуÑтым Ð´Ð»Ñ Ñлучайного кода" -#: front/src/views/admin/moderation/AccountsDetail.vue:297 -#: front/src/views/admin/moderation/DomainsDetail.vue:233 +#: front/src/views/admin/library/AlbumDetail.vue:232 +#: front/src/views/admin/library/ArtistDetail.vue:221 +#: front/src/views/admin/library/TrackDetail.vue:284 +#: front/src/views/admin/moderation/AccountsDetail.vue:327 +#: front/src/views/admin/moderation/DomainsDetail.vue:234 #: front/src/views/content/Base.vue:5 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Libraries" msgstr "Библиотеки" -#: front/src/views/content/libraries/Form.vue:2 -msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." -msgstr "Библиотеки помогают организовывать ваши музыкальные коллекции и делитьÑÑ Ð¸Ð¼Ð¸. Ð’Ñ‹ можете загружать вашу музыку на Funkwhale и делитьÑÑ ÐµÑŽ Ñ Ð²Ð°ÑˆÐ¸Ð¼Ð¸ друзьÑми и Ñемьёй." - -#: front/src/components/instance/Stats.vue:30 +#: front/src/views/admin/library/Base.vue:17 +#: front/src/views/admin/library/LibrariesList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Libraries" +msgstr "Библиотеки" + +#: front/src/components/mixins/Translations.vue:72 +#: front/src/components/mixins/Translations.vue:73 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Libraries and uploads" +msgstr "Библиотека обновлена" + +#: front/src/views/content/libraries/Form.vue:2 +msgctxt "Content/Library/Paragraph" +msgid "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family." +msgstr "Библиотеки помогают организовывать ваши музыкальные коллекции и делитьÑÑ Ð¸Ð¼Ð¸. Ð’Ñ‹ можете загружать вашу музыку на Funkwhale и делитьÑÑ ÐµÑŽ Ñ Ð²Ð°ÑˆÐ¸Ð¼Ð¸ друзьÑми и Ñемьёй." + +#: front/src/components/Sidebar.vue:85 src/components/instance/Stats.vue:30 +#: front/src/components/manage/library/UploadsTable.vue:60 #: front/src/components/manage/users/UsersTable.vue:173 -#: front/src/views/admin/moderation/AccountsDetail.vue:464 +#: front/src/views/admin/library/UploadDetail.vue:144 +#: front/src/views/admin/moderation/AccountsDetail.vue:498 +#, fuzzy +msgctxt "*/*/*" msgid "Library" msgstr "Библиотека" -#: front/src/views/content/libraries/Form.vue:109 +#: front/src/views/content/libraries/Form.vue:103 +msgctxt "Content/Library/Message" msgid "Library created" msgstr "Библиотека Ñоздана" -#: front/src/views/content/libraries/Form.vue:129 +#: front/src/views/admin/library/LibraryDetail.vue:78 #, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Library data" +msgstr "Библиотека обновлена" + +#: front/src/views/content/libraries/Form.vue:123 +msgctxt "Content/Library/Message" msgid "Library deleted" msgstr "Библиотека обновлена" -#: front/src/views/admin/library/FilesList.vue:3 -msgid "Library files" +#: front/src/views/admin/library/EditsList.vue:4 +#, fuzzy +msgctxt "Content/Admin/Title/Noun" +msgid "Library edits" msgstr "Файлы библиотеки" -#: front/src/views/content/libraries/Form.vue:106 +#: front/src/views/content/libraries/Form.vue:100 +msgctxt "Content/Library/Message" msgid "Library updated" msgstr "Библиотека обновлена" -#: front/src/components/library/Track.vue:100 +#: front/src/components/library/TrackDetail.vue:19 +#: front/src/components/manage/library/TracksTable.vue:43 +#: front/src/views/admin/library/TrackDetail.vue:159 src/edits.js:61 +msgctxt "Content/*/*/Noun" msgid "License" msgstr "" -#: front/src/views/content/libraries/Detail.vue:21 +#: front/src/components/mixins/Translations.vue:80 +#: front/src/components/mixins/Translations.vue:81 +msgctxt "Content/OAuth Scopes/Label" +msgid "Listenings" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:157 +#: front/src/views/admin/library/ArtistDetail.vue:146 +#: front/src/views/admin/library/TrackDetail.vue:209 +msgctxt "*/*/*/Noun" +msgid "Listenings" +msgstr "" + +#: front/src/components/audio/track/Table.vue:25 +#: front/src/components/library/ArtistDetail.vue:28 #, fuzzy +msgctxt "Content/*/Button.Label" +msgid "Load more…" +msgstr "Загружаем подпиÑчиков..." + +#: front/src/views/content/libraries/Detail.vue:21 +msgctxt "Content/Library/Paragraph" msgid "Loading followers…" msgstr "Загружаем подпиÑчиков..." #: front/src/views/content/libraries/Home.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading Libraries…" msgstr "Загружаем Библиотеки..." #: front/src/views/content/libraries/Detail.vue:3 #: front/src/views/content/libraries/Upload.vue:3 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading library data…" msgstr "Загружаем данные библиотеки..." -#: front/src/views/Notifications.vue:4 -#, fuzzy +#: front/src/views/Notifications.vue:19 +msgctxt "Content/Notifications/Paragraph" msgid "Loading notifications…" msgstr "Загружаем уведомлениÑ..." #: front/src/views/content/remote/Home.vue:3 -msgid "Loading remote libraries..." +msgctxt "Content/Library/Paragraph" +msgid "Loading remote libraries…" msgstr "Загружаем удалённые библиотеки..." #: front/src/views/content/libraries/Quota.vue:4 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Loading usage data…" msgstr "Загружаем данные об иÑпользовании..." #: front/src/components/favorites/List.vue:5 -#, fuzzy +msgctxt "Content/Favorites/Message" msgid "Loading your favorites…" msgstr "Загружаем ваше избранное..." +#: front/src/components/manage/library/AlbumsTable.vue:65 +#: front/src/components/manage/library/ArtistsTable.vue:58 +#: front/src/components/manage/library/LibrariesTable.vue:75 +#: front/src/components/manage/library/TracksTable.vue:71 +#: front/src/components/manage/library/UploadsTable.vue:99 +#: front/src/views/admin/library/AlbumDetail.vue:19 +#: front/src/views/admin/library/ArtistDetail.vue:18 +#: front/src/views/admin/library/LibraryDetail.vue:18 +#: front/src/views/admin/library/TrackDetail.vue:18 +#: front/src/views/admin/library/UploadDetail.vue:19 +msgctxt "Content/Moderation/*/Short, Noun" +msgid "Local" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:59 #: front/src/views/admin/moderation/AccountsDetail.vue:18 #, fuzzy +msgctxt "Content/Moderation/*/Short, Noun" msgid "Local account" msgstr "Мой аккаунт" -#: front/src/components/auth/Login.vue:78 +#: front/src/components/auth/Login.vue:84 +msgctxt "Head/Login/Title" msgid "Log In" msgstr "Вход" #: front/src/components/auth/Login.vue:4 +msgctxt "Content/Login/Title/Verb" msgid "Log in to your Funkwhale account" msgstr "Войти в ваш аккаунт Funkwhale" #: front/src/components/auth/Logout.vue:20 +msgctxt "Head/Login/Title" msgid "Log Out" msgstr "Выход" #: front/src/components/Sidebar.vue:38 +msgctxt "Sidebar/Profile/List item.Link" msgid "Logged in as %{ username }" msgstr "Вошли как %{ username }" -#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:41 +#: front/src/components/Sidebar.vue:54 src/components/auth/Login.vue:42 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Login" msgstr "Войти" -#: front/src/views/admin/moderation/AccountsDetail.vue:119 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:148 +msgctxt "Content/*/*/Noun" msgid "Login status" msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð°" #: front/src/components/Sidebar.vue:52 +msgctxt "Sidebar/Login/List item.Link/Verb" msgid "Logout" msgstr "Выйти" #: front/src/views/content/libraries/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Looks like you don't have a library, it's time to create one." msgstr "Похоже у Ð²Ð°Ñ ÐµÑ‰Ñ‘ нет ни одной библиотеки, Ñамое Ð²Ñ€ÐµÐ¼Ñ Ñоздать её!" -#: front/src/components/audio/Player.vue:353 -#: src/components/audio/Player.vue:354 +#: front/src/components/audio/Player.vue:604 +#: src/components/audio/Player.vue:605 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping disabled. Click to switch to single-track looping." msgstr "Зацикливание отключено. Ðажмите чтобы включить цикличное проигрывание трека." -#: front/src/components/audio/Player.vue:356 -#: src/components/audio/Player.vue:357 +#: front/src/components/audio/Player.vue:607 +#: src/components/audio/Player.vue:608 +#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on a single track. Click to switch to whole queue looping." -msgstr "" +msgstr "Зацикливание отключено. Ðажмите чтобы включить цикличное проигрывание трека." -#: front/src/components/audio/Player.vue:359 -#: src/components/audio/Player.vue:360 +#: front/src/components/audio/Player.vue:610 +#: src/components/audio/Player.vue:611 +#, fuzzy +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Looping on whole queue. Click to disable looping." -msgstr "" - -#: front/src/components/library/Track.vue:150 -msgid "Lyrics" -msgstr "ТекÑÑ‚Ñ‹" +msgstr "Зацикливание отключено. Ðажмите чтобы включить цикличное проигрывание трека." -#: front/src/components/Sidebar.vue:210 +#: front/src/components/Sidebar.vue:223 +msgctxt "Sidebar/*/Hidden text" msgid "Main menu" msgstr "" -#: front/src/views/admin/library/Base.vue:16 -#, fuzzy +#: front/src/views/admin/library/Base.vue:31 +msgctxt "Head/Admin/Title" msgid "Manage library" msgstr "Управление библиотекой" #: front/src/components/playlists/PlaylistModal.vue:3 +msgctxt "Popup/Playlist/Title/Verb" msgid "Manage playlists" msgstr "УправлÑÑ‚ÑŒ ÑпиÑками воÑпроизведениÑ" #: front/src/views/admin/users/Base.vue:20 +msgctxt "Head/Admin/Title" msgid "Manage users" msgstr "УправлÑÑ‚ÑŒ пользователÑми" #: front/src/views/playlists/List.vue:8 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Manage your playlists" msgstr "УправлÑÑ‚ÑŒ вашими ÑпиÑками воÑпроизведениÑ" -#: front/src/views/Notifications.vue:17 +#: front/src/views/Notifications.vue:14 +msgctxt "Content/Notifications/Button.Label/Verb" msgid "Mark all as read" msgstr "Отметить вÑе как прочитанные" -#: front/src/components/notifications/NotificationRow.vue:44 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:46 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as read" msgstr "Отметить вÑе как прочитанные" -#: front/src/components/notifications/NotificationRow.vue:45 -#, fuzzy +#: front/src/components/notifications/NotificationRow.vue:47 +msgctxt "Content/Notifications/Button.Tooltip/Verb" msgid "Mark as unread" msgstr "Отметить вÑе как прочитанные" -#: front/src/views/admin/moderation/AccountsDetail.vue:281 +#: front/src/views/admin/moderation/AccountsDetail.vue:310 +msgctxt "Content/*/*/Unit" msgid "MB" msgstr "МБ" -#: front/src/components/audio/Player.vue:346 +#: front/src/components/audio/Player.vue:597 +msgctxt "Sidebar/Player/Hidden text" msgid "Media player" msgstr "" +#: front/src/components/auth/Profile.vue:12 +#, fuzzy +msgctxt "Content/Profile/Paragraph" +msgid "Member since %{ date }" +msgstr "ЗарегиÑтрировано Ñ %{ date }" + #: front/src/components/Footer.vue:32 +msgctxt "Footer/*/List item.Link" msgid "Mobile and desktop apps" msgstr "" -#: front/src/components/Sidebar.vue:97 +#: front/src/components/Sidebar.vue:96 #: src/components/manage/users/UsersTable.vue:177 -#: front/src/views/admin/moderation/AccountsDetail.vue:468 +#: front/src/views/admin/moderation/AccountsDetail.vue:502 #: front/src/views/admin/moderation/Base.vue:21 #, fuzzy +msgctxt "*/Moderation/*" msgid "Moderation" msgstr "ФедерациÑ" -#: front/src/views/admin/moderation/AccountsDetail.vue:49 +#: front/src/views/admin/moderation/AccountsDetail.vue:78 #: front/src/views/admin/moderation/DomainsDetail.vue:42 +msgctxt "Content/Moderation/Card.Paragraph" msgid "Moderation policies help you control how your instance interact with a given domain or account." msgstr "" -#: front/src/components/mixins/Translations.vue:20 -#: front/src/components/mixins/Translations.vue:21 +#: front/src/components/library/EditCard.vue:5 +#, fuzzy +msgctxt "Content/Library/Card/Short" +msgid "Modification %{ id }" +msgstr "Дата поÑледнего изменениÑ" + +#: front/src/components/mixins/Translations.vue:48 +#: front/src/components/mixins/Translations.vue:49 +msgctxt "Content/Playlist/Dropdown/Noun" msgid "Modification date" msgstr "Дата поÑледнего изменениÑ" +#: front/src/components/library/AlbumBase.vue:42 +#: front/src/components/library/ArtistBase.vue:53 +#: front/src/components/library/TrackBase.vue:61 +msgctxt "*/*/Button.Label/Noun" +msgid "More…" +msgstr "" + #: front/src/components/Sidebar.vue:63 src/views/admin/Settings.vue:82 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Music" msgstr "Музыка" -#: front/src/components/audio/Player.vue:352 +#: front/src/components/audio/Player.vue:603 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Mute" msgstr "Приглушить" +#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute activity" +msgstr "ПоÑледнÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ" + +#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 +#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#, fuzzy +msgctxt "Content/Moderation/*/Verb" +msgid "Mute notifications" +msgstr "Ваши уведомлениÑ" + #: front/src/components/Sidebar.vue:34 +msgctxt "Sidebar/Profile/Title" msgid "My account" msgstr "Мой аккаунт" -#: front/src/components/library/radios/Builder.vue:236 +#: front/src/components/library/radios/Builder.vue:238 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome description" msgstr "Моё замечательное опиÑание" -#: front/src/views/content/libraries/Form.vue:70 +#: front/src/views/content/libraries/Form.vue:72 +msgctxt "Content/Library/Input.Placeholder" msgid "My awesome library" msgstr "ÐœÐ¾Ñ Ð·Ð°Ð¼ÐµÑ‡Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð±Ð¸Ð±Ð»Ð¸Ð¾Ñ‚ÐµÐºÐ°" -#: front/src/components/playlists/Form.vue:74 +#: front/src/components/playlists/Form.vue:76 +msgctxt "Content/Playlist/Input.Placeholder" msgid "My awesome playlist" msgstr "Мой замечательный ÑпиÑок воÑпроизведениÑ" -#: front/src/components/library/radios/Builder.vue:235 +#: front/src/components/library/radios/Builder.vue:237 +msgctxt "Content/Radio/Input.Placeholder" msgid "My awesome radio" msgstr "Моё замечательное радио" #: front/src/views/content/libraries/Home.vue:6 +msgctxt "Content/Library/Title" msgid "My libraries" msgstr "Мои библиотеки" #: front/src/components/audio/track/Row.vue:40 -#: src/components/library/Track.vue:115 -#: front/src/components/library/Track.vue:124 -#: src/components/library/Track.vue:133 -#: front/src/components/library/Track.vue:142 -#: front/src/components/manage/library/FilesTable.vue:63 -#: front/src/components/manage/library/FilesTable.vue:69 -#: front/src/components/manage/library/FilesTable.vue:75 -#: front/src/components/manage/library/FilesTable.vue:81 +#: src/components/library/EditCard.vue:60 +#: front/src/components/library/EditForm.vue:70 +#: front/src/components/library/TrackDetail.vue:34 +#: front/src/components/library/TrackDetail.vue:43 +#: front/src/components/library/TrackDetail.vue:52 +#: front/src/components/library/TrackDetail.vue:61 +#: front/src/components/manage/library/AlbumsTable.vue:73 +#: front/src/components/manage/library/TracksTable.vue:76 +#: front/src/components/manage/library/UploadsTable.vue:121 +#: front/src/components/manage/library/UploadsTable.vue:128 #: front/src/components/manage/users/UsersTable.vue:61 -#: front/src/views/admin/moderation/AccountsDetail.vue:171 -#: front/src/views/admin/moderation/DomainsDetail.vue:90 -#: front/src/views/content/libraries/FilesTable.vue:92 -#: front/src/views/content/libraries/FilesTable.vue:98 -#: front/src/views/admin/moderation/DomainsDetail.vue:109 -#: front/src/views/admin/moderation/DomainsDetail.vue:117 +#: front/src/views/admin/library/UploadDetail.vue:179 +#: front/src/views/admin/library/UploadDetail.vue:214 +#: front/src/views/admin/library/UploadDetail.vue:233 +#: front/src/views/admin/library/UploadDetail.vue:244 +#: front/src/views/admin/library/UploadDetail.vue:257 +#: front/src/views/admin/moderation/AccountsDetail.vue:192 +#: front/src/views/admin/moderation/DomainsDetail.vue:82 +#: front/src/views/content/libraries/FilesTable.vue:95 +#: front/src/views/content/libraries/FilesTable.vue:101 +msgctxt "*/*/*" msgid "N/A" msgstr "Ð/Д" +#: front/src/components/manage/library/LibrariesTable.vue:48 +#: front/src/components/manage/library/UploadsTable.vue:59 +#, fuzzy +msgctxt "*/*/*" +msgid "Name" +msgstr "ИмÑ" + +#: front/src/components/auth/Settings.vue:133 +#: front/src/components/manage/library/ArtistsTable.vue:39 #: front/src/components/manage/moderation/AccountsTable.vue:39 #: front/src/components/manage/moderation/DomainsTable.vue:38 -#: front/src/components/mixins/Translations.vue:26 -#: front/src/components/playlists/PlaylistModal.vue:31 -#: front/src/views/admin/moderation/DomainsDetail.vue:105 -#: front/src/views/content/libraries/Form.vue:10 -#: front/src/components/mixins/Translations.vue:27 +#: front/src/components/mixins/Translations.vue:53 +#: front/src/components/playlists/PlaylistModal.vue:45 +#: front/src/views/admin/library/ArtistDetail.vue:98 +#: front/src/views/admin/library/LibraryDetail.vue:85 +#: front/src/views/admin/library/UploadDetail.vue:92 +#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/content/libraries/Form.vue:10 src/edits.js:10 +#: front/src/components/mixins/Translations.vue:54 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Name" +msgstr "ИмÑ" + +#: front/src/components/auth/ApplicationForm.vue:9 +#, fuzzy +msgctxt "Content/Applications/Input.Label/Noun" msgid "Name" msgstr "ИмÑ" #: front/src/components/auth/Settings.vue:88 #: front/src/views/auth/PasswordResetConfirm.vue:14 +msgctxt "Content/Settings/Input.Label" msgid "New password" msgstr "Ðовый пароль" -#: front/src/components/Sidebar.vue:160 +#: front/src/components/Sidebar.vue:173 +msgctxt "Sidebar/Player/Paragraph" msgid "New tracks will be appended here automatically." msgstr "Ðовые треки будут добавлены Ñюда автоматичеÑки." -#: front/src/components/audio/Player.vue:350 +#: front/src/components/library/EditCard.vue:47 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "New value" +msgstr "" + +#: front/src/components/audio/Player.vue:601 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Next track" msgstr "Следующий трек" -#: front/src/components/Sidebar.vue:119 +#: front/src/components/Sidebar.vue:130 +msgctxt "*/*/*" msgid "No" msgstr "Ðет" -#: front/src/components/Home.vue:100 +#: front/src/components/Home.vue:95 +msgctxt "Content/Home/List item" msgid "No add-ons, no plugins : you only need a web library" msgstr "" #: front/src/components/audio/Search.vue:25 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No album matched your query" msgstr "Извините, мы не нашли никакого альбома подходÑщего под ваш запроÑ" #: front/src/components/audio/Search.vue:16 -#, fuzzy +msgctxt "Content/Search/Paragraph" msgid "No artist matched your query" msgstr "Извините, мы не нашли ни одного иÑÐ¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»Ñ Ð¿Ð¾Ð´Ñ…Ð¾Ð´Ñщего под ваш запроÑ" -#: front/src/components/library/Track.vue:158 -msgid "No lyrics available for this track." +#: front/src/components/library/TrackDetail.vue:14 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No copyright information available for this track" msgstr "ТекÑÑ‚ недоÑтупен Ð´Ð»Ñ Ñтого трека." +#: front/src/components/library/TrackDetail.vue:25 +#, fuzzy +msgctxt "Content/Track/Table.Paragraph" +msgid "No licensing information for this track" +msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" + #: front/src/components/federation/LibraryWidget.vue:6 +msgctxt "Content/Federation/Paragraph" msgid "No matching library." msgstr "Ðет подходÑщей библиотеки." -#: front/src/views/Notifications.vue:26 -#, fuzzy -msgid "No notifications yet." +#: front/src/views/Notifications.vue:28 +msgctxt "Content/Notifications/Paragraph" +msgid "No notification to show." msgstr "Ваши уведомлениÑ" +#: front/src/components/common/EmptyState.vue:7 +msgctxt "Content/*/Paragraph" +msgid "No results were found." +msgstr "" + #: front/src/components/mixins/Translations.vue:10 -#: front/src/components/playlists/Form.vue:81 -#: src/views/content/libraries/Form.vue:72 #: front/src/components/mixins/Translations.vue:11 +msgctxt "Content/Settings/Dropdown" msgid "Nobody except me" msgstr "Ðикто кроме менÑ" #: front/src/views/content/libraries/Detail.vue:57 +msgctxt "Content/Library/Paragraph" msgid "Nobody is following this library" msgstr "Ðикто не подпиÑан на Ñту библиотеку" #: front/src/components/manage/users/InvitationsTable.vue:51 +msgctxt "Content/Admin/Table" msgid "Not used" msgstr "Ðе иÑпользуетÑÑ" -#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:74 +#: front/src/components/Sidebar.vue:46 src/views/Notifications.vue:76 +#, fuzzy +msgctxt "*/Notifications/*" +msgid "Notifications" +msgstr "УведомлениÑ" + +#: front/src/components/mixins/Translations.vue:100 +#: front/src/components/mixins/Translations.vue:101 +#, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Notifications" msgstr "УведомлениÑ" #: front/src/components/Footer.vue:47 +msgctxt "Footer/*/List item.Link" msgid "Official website" msgstr "Официальный веб-Ñайт" #: front/src/components/auth/Settings.vue:83 +msgctxt "Content/Settings/Input.Label" msgid "Old password" msgstr "Старый пароль" +#: front/src/components/library/EditCard.vue:46 +msgctxt "Content/Library/Card.Table.Header/Short" +msgid "Old value" +msgstr "" + #: front/src/components/manage/users/InvitationsTable.vue:20 +msgctxt "Content/Admin/Dropdown/Adjective" msgid "Open" msgstr "Открыть" +#: front/src/components/library/ImportStatusModal.vue:56 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Open a support thread (include the debug information below in your message)" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:73 +#: front/src/components/library/ArtistBase.vue:84 +#: front/src/components/library/TrackBase.vue:92 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Open in moderation interface" +msgstr "Удалить радио" + +#: front/src/views/admin/library/AlbumDetail.vue:31 +#: front/src/views/admin/library/ArtistDetail.vue:30 +#: front/src/views/admin/library/TrackDetail.vue:30 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open local profile" +msgstr "" + +#: front/src/views/admin/library/AlbumDetail.vue:46 +#: front/src/views/admin/library/ArtistDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:45 +#, fuzzy +msgctxt "Content/Moderation/Link/Verb" +msgid "Open on MusicBrainz" +msgstr "Смотреть на MusicBrainz" + #: front/src/views/admin/moderation/AccountsDetail.vue:23 +msgctxt "Content/Moderation/Link/Verb" msgid "Open profile" msgstr "" +#: front/src/views/admin/library/AlbumDetail.vue:54 +#: front/src/views/admin/library/ArtistDetail.vue:53 +#: front/src/views/admin/library/LibraryDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:53 +#: front/src/views/admin/library/UploadDetail.vue:50 +#: front/src/views/admin/moderation/AccountsDetail.vue:52 +msgctxt "Content/Moderation/Link/Verb" +msgid "Open remote profile" +msgstr "" + #: front/src/views/admin/moderation/DomainsDetail.vue:16 -#, fuzzy +msgctxt "Content/Moderation/Link/Verb" msgid "Open website" msgstr "Официальный веб-Ñайт" #: front/src/components/manage/moderation/InstancePolicyForm.vue:40 -#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "Or customize your rule" msgstr "Добавить фильтры чтобы каÑтомизировать Ñвоё радио" -#: front/src/components/favorites/List.vue:31 +#: front/src/components/favorites/List.vue:32 #: src/components/library/Radios.vue:41 -#: front/src/components/manage/library/FilesTable.vue:17 +#: front/src/components/manage/library/EditsCardList.vue:37 #: front/src/components/manage/users/UsersTable.vue:17 #: front/src/views/playlists/List.vue:25 -#, fuzzy +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Order" msgstr "ПорÑдок" -#: front/src/components/favorites/List.vue:23 -#: src/components/library/Artists.vue:15 -#: front/src/components/library/Radios.vue:33 -#: front/src/components/manage/library/FilesTable.vue:9 +#: front/src/components/favorites/List.vue:24 +#: src/components/library/Albums.vue:15 +#: front/src/components/library/Artists.vue:15 +#: src/components/library/Radios.vue:33 +#: front/src/components/manage/library/AlbumsTable.vue:11 +#: front/src/components/manage/library/ArtistsTable.vue:11 +#: front/src/components/manage/library/EditsCardList.vue:29 +#: front/src/components/manage/library/LibrariesTable.vue:20 +#: front/src/components/manage/library/TracksTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:30 #: front/src/components/manage/moderation/AccountsTable.vue:11 #: front/src/components/manage/moderation/DomainsTable.vue:9 #: front/src/components/manage/users/InvitationsTable.vue:9 #: front/src/components/manage/users/UsersTable.vue:9 #: front/src/views/content/libraries/FilesTable.vue:21 #: front/src/views/playlists/List.vue:17 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering" msgstr "ПорÑдок" -#: front/src/components/library/Artists.vue:23 +#: front/src/components/library/Albums.vue:23 +#: src/components/library/Artists.vue:23 +#: front/src/components/manage/library/AlbumsTable.vue:19 +#: front/src/components/manage/library/ArtistsTable.vue:19 +#: front/src/components/manage/library/LibrariesTable.vue:28 +#: front/src/components/manage/library/TracksTable.vue:19 +#: front/src/components/manage/library/UploadsTable.vue:38 #: front/src/components/manage/moderation/AccountsTable.vue:19 #: front/src/components/manage/moderation/DomainsTable.vue:17 #: front/src/views/content/libraries/FilesTable.vue:29 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Ordering direction" msgstr "ПорÑдок" #: front/src/components/manage/users/InvitationsTable.vue:38 +msgctxt "Content/Admin/Table.Label" msgid "Owner" msgstr "Владелец" #: front/src/components/PageNotFound.vue:33 -#, fuzzy +msgctxt "Head/*/Title" msgid "Page Not Found" msgstr "Страница не найдена!" #: front/src/components/PageNotFound.vue:7 +msgctxt "Content/*/Title" msgid "Page not found!" msgstr "Страница не найдена!" #: front/src/components/Pagination.vue:39 +msgctxt "Content/*/Hidden text/Noun" msgid "Pagination" msgstr "" -#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38 +#: front/src/components/auth/Login.vue:33 src/components/auth/Signup.vue:40 +#, fuzzy +msgctxt "Content/*/Input.Label" msgid "Password" msgstr "Пароль" -#: front/src/components/auth/SubsonicTokenForm.vue:95 +#: front/src/components/auth/SubsonicTokenForm.vue:94 +msgctxt "Content/Settings/Message" msgid "Password updated" msgstr "Пароль обновлён" #: front/src/views/auth/PasswordResetConfirm.vue:28 +msgctxt "Content/Signup/Card.Title" msgid "Password updated successfully" msgstr "Пароль уÑпешно обновлён" -#: front/src/components/audio/Player.vue:349 +#: front/src/components/audio/Player.vue:600 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Pause track" msgstr "ПриоÑтановить трек" #: front/src/components/ShortcutsModal.vue:59 +#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Pause/play the current track" -msgstr "" +msgstr "Проиграть трек" #: front/src/components/manage/moderation/InstancePolicyCard.vue:12 +msgctxt "Content/Moderation/Card.List item" msgid "Paused" msgstr "" -#: front/src/components/library/FileUpload.vue:106 +#: front/src/components/library/FileUpload.vue:116 +#: front/src/components/manage/library/UploadsTable.vue:23 +#: front/src/components/mixins/Translations.vue:28 #: front/src/views/content/libraries/FilesTable.vue:14 -#: front/src/views/content/libraries/FilesTable.vue:208 +#: front/src/components/mixins/Translations.vue:29 #, fuzzy +msgctxt "Content/Library/*/Short" msgid "Pending" msgstr "Ожидает" #: front/src/views/content/libraries/Detail.vue:37 +msgctxt "Content/Library/Table/Short" msgid "Pending approval" msgstr "Ожидает подтверждениÑ" #: front/src/views/content/libraries/Quota.vue:22 +msgctxt "Content/Library/Label" msgid "Pending files" msgstr "Ожидающие файлы" -#: front/src/components/Sidebar.vue:212 +#: front/src/components/Sidebar.vue:225 +#, fuzzy +msgctxt "Sidebar/Notifications/Hidden text" msgid "Pending follow requests" -msgstr "" +msgstr "Ожидающие файлы" + +#: front/src/components/library/EditCard.vue:29 +#: front/src/components/manage/library/EditsCardList.vue:18 +#, fuzzy +msgctxt "Content/Admin/*/Noun" +msgid "Pending review" +msgstr "Ожидающие файлы" + +#: front/src/components/Sidebar.vue:226 +#, fuzzy +msgctxt "Sidebar/Moderation/Hidden text" +msgid "Pending review edits" +msgstr "Ожидающие файлы" #: front/src/components/manage/users/UsersTable.vue:42 -#: front/src/views/admin/moderation/AccountsDetail.vue:137 +#: front/src/views/admin/moderation/AccountsDetail.vue:166 +msgctxt "Content/Admin/Table.Label/Noun" +msgid "Permissions" +msgstr "РазрешениÑ" + +#: front/src/components/auth/Settings.vue:176 +#, fuzzy +msgctxt "Content/*/*/Noun" msgid "Permissions" msgstr "РазрешениÑ" #: front/src/components/audio/PlayButton.vue:9 -#: src/components/library/Track.vue:40 +#: front/src/components/library/TrackBase.vue:26 +#, fuzzy +msgctxt "*/Queue/Button.Label/Short, Verb" msgid "Play" -msgstr "" +msgstr "Проиграть вÑÑ‘" -#: front/src/components/audio/album/Card.vue:50 +#: front/src/components/audio/album/Card.vue:48 #: front/src/components/audio/artist/Card.vue:44 -#: src/components/library/Album.vue:28 -#: front/src/components/library/Album.vue:73 src/views/playlists/Detail.vue:23 +#: front/src/components/library/AlbumBase.vue:20 +#: front/src/components/library/AlbumDetail.vue:11 +#: src/views/playlists/Detail.vue:24 +msgctxt "Content/Queue/Button.Label/Short, Verb" msgid "Play all" msgstr "Проиграть вÑÑ‘" -#: front/src/components/library/Artist.vue:26 +#: front/src/components/library/ArtistBase.vue:31 +msgctxt "Content/Artist/Button.Label/Verb" msgid "Play all albums" msgstr "Проиграть вÑе альбомы" -#: front/src/components/audio/PlayButton.vue:15 -#: front/src/components/audio/PlayButton.vue:65 +#: front/src/components/audio/PlayButton.vue:76 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play next" msgstr "Проиграть Ñледующий" #: front/src/components/ShortcutsModal.vue:67 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play next track" msgstr "Проиграть трек" -#: front/src/components/audio/PlayButton.vue:16 -#: front/src/components/audio/PlayButton.vue:63 -#: front/src/components/audio/PlayButton.vue:70 +#: front/src/components/audio/PlayButton.vue:74 +msgctxt "*/Queue/Dropdown/Button/Title" msgid "Play now" msgstr "Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ð³Ñ€Ñ‹Ð²Ð°ÐµÑ‚ÑÑ" #: front/src/components/ShortcutsModal.vue:63 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Play previous track" msgstr "Предыдущий трек" -#: front/src/components/Sidebar.vue:211 -#, fuzzy +#: front/src/components/audio/PlayButton.vue:77 +msgctxt "*/Queue/Dropdown/Button/Title" +msgid "Play similar songs" +msgstr "" + +#: front/src/components/Sidebar.vue:224 +msgctxt "Sidebar/Player/Hidden text" msgid "Play this track" msgstr "Проиграть трек" -#: front/src/components/audio/Player.vue:348 +#: front/src/components/audio/Player.vue:599 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Play track" msgstr "Проиграть трек" -#: front/src/views/playlists/Detail.vue:90 +#: front/src/components/audio/PlayButton.vue:82 +msgctxt "*/Queue/Button/Title" +msgid "Play..." +msgstr "" + +#: front/src/views/playlists/Detail.vue:91 +#, fuzzy +msgctxt "Head/Playlist/Title" msgid "Playlist" msgstr "СпиÑок воÑпроизведениÑ" #: front/src/views/playlists/Detail.vue:12 +#, fuzzy +msgctxt "Content/Playlist/Header.Subtitle" msgid "Playlist containing %{ count } track, by %{ username }" msgid_plural "Playlist containing %{ count } tracks, by %{ username }" msgstr[0] "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñодержит %{ count } трек от %{ username }" @@ -1890,78 +3160,123 @@ msgstr[1] "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñодержит %{ count msgstr[2] "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñодержит %{ count } треков от %{ username }" #: front/src/components/playlists/Form.vue:9 +msgctxt "Content/Playlist/Message" msgid "Playlist created" msgstr "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñоздан" #: front/src/components/playlists/Editor.vue:4 +msgctxt "Content/Playlist/Title" msgid "Playlist editor" msgstr "Редактор ÑпиÑков воÑпроизведениÑ" #: front/src/components/playlists/Form.vue:21 +msgctxt "Content/Playlist/Input.Label" msgid "Playlist name" msgstr "Ðазвание ÑпиÑка воÑпроизведениÑ" #: front/src/components/playlists/Form.vue:6 +msgctxt "Content/Playlist/Message" msgid "Playlist updated" msgstr "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»Ñ‘Ð½" #: front/src/components/playlists/Form.vue:25 +msgctxt "Content/Playlist/Dropdown.Label" msgid "Playlist visibility" msgstr "ВидимоÑÑ‚ÑŒ ÑпиÑка воÑпроизведениÑ" #: front/src/components/Sidebar.vue:71 src/components/library/Home.vue:16 -#: front/src/components/library/Library.vue:13 src/views/admin/Settings.vue:83 -#: front/src/views/playlists/List.vue:106 +#: front/src/components/library/Library.vue:16 src/views/admin/Settings.vue:83 +#: front/src/views/admin/library/AlbumDetail.vue:173 +#: front/src/views/admin/library/ArtistDetail.vue:162 +#: front/src/views/admin/library/TrackDetail.vue:225 +#: src/views/playlists/List.vue:106 +#, fuzzy +msgctxt "*/*/*" msgid "Playlists" msgstr "СпиÑки воÑпроизведениÑ" -#: front/src/components/Home.vue:56 +#: front/src/components/mixins/Translations.vue:88 +#: front/src/components/mixins/Translations.vue:89 #, fuzzy +msgctxt "Content/OAuth Scopes/Label" +msgid "Playlists" +msgstr "СпиÑки воÑпроизведениÑ" + +#: front/src/components/Home.vue:56 +msgctxt "Content/Home/List item" msgid "Playlists? We got them" msgstr "СпиÑки воÑпроизведениÑ? У Ð½Ð°Ñ Ð¾Ð½Ð¸ еÑÑ‚ÑŒ" #: front/src/components/auth/Settings.vue:79 +msgctxt "Content/Settings/Error message.List item/Call to action" msgid "Please double-check your password is correct" msgstr "ПожалуйÑта перепроверьте корректноÑÑ‚ÑŒ вашего паролÑ" #: front/src/components/auth/Login.vue:9 +msgctxt "Content/Login/Error message.List item/Call to action" msgid "Please double-check your username/password couple is correct" msgstr "ПожалуйÑта перепроверьте что ваше Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸ пароль верны" #: front/src/components/auth/Settings.vue:46 +msgctxt "Content/Settings/Paragraph" msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px." msgstr "PNG, GIF или JPG. Ðе больше 2MB. Будет уменьшено до 400x400px." +#: front/src/views/admin/library/TrackDetail.vue:137 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Position" +msgstr "ОпиÑание" + #: front/src/components/manage/moderation/InstancePolicyForm.vue:118 +msgctxt "Content/Moderation/Help text" msgid "Prevent account or domain from triggering notifications, except from followers." msgstr "" -#: front/src/components/audio/EmbedWizard.vue:29 +#: front/src/components/audio/EmbedWizard.vue:33 +msgctxt "Popup/Embed/Title/Noun" msgid "Preview" msgstr "" -#: front/src/components/audio/Player.vue:347 +#: front/src/components/audio/Player.vue:598 +msgctxt "Sidebar/Player/Icon.Tooltip" msgid "Previous track" msgstr "Предыдущий трек" -#: front/src/views/content/remote/Card.vue:39 -#, fuzzy +#: front/src/components/mixins/Translations.vue:15 +#: front/src/components/mixins/Translations.vue:16 +msgctxt "Content/Settings/Dropdown/Short" +msgid "Private" +msgstr "" + +#: front/src/views/content/remote/Card.vue:43 +msgctxt "Content/Library/Card.List item" msgid "Problem during scanning" msgstr "Ошибка во Ð²Ñ€ÐµÐ¼Ñ ÑканированиÑ" -#: front/src/components/library/FileUpload.vue:58 +#: front/src/components/library/FileUpload.vue:57 +msgctxt "Content/Library/Button.Label" msgid "Proceed" msgstr "Продолжить" #: front/src/views/auth/EmailConfirm.vue:26 #: front/src/views/auth/PasswordResetConfirm.vue:31 +#, fuzzy +msgctxt "Content/Signup/Link/Verb" msgid "Proceed to login" -msgstr "" +msgstr "Ðазад ко входу" #: front/src/components/library/FileUpload.vue:17 +msgctxt "Content/Library/Tab.Title/Short" msgid "Processing" msgstr "Обработка" +#: front/src/components/mixins/Translations.vue:68 +#: front/src/components/mixins/Translations.vue:69 +msgctxt "Content/OAuth Scopes/Label" +msgid "Profile" +msgstr "" + #: front/src/components/manage/moderation/AccountsTable.vue:188 #: front/src/components/manage/moderation/DomainsTable.vue:168 #: front/src/views/content/libraries/Quota.vue:36 @@ -1970,349 +3285,607 @@ msgstr "Обработка" #: front/src/views/content/libraries/Quota.vue:65 #: front/src/views/content/libraries/Quota.vue:88 #: front/src/views/content/libraries/Quota.vue:91 +#, fuzzy +msgctxt "*/*/*/Verb" msgid "Purge" msgstr "ОчиÑтить" #: front/src/views/content/libraries/Quota.vue:89 +msgctxt "Popup/Library/Title" msgid "Purge errored files?" msgstr "ОчиÑтить ошибочные файлы?" #: front/src/views/content/libraries/Quota.vue:37 +msgctxt "Popup/Library/Title" msgid "Purge pending files?" msgstr "ОчиÑтить ожидающие файлы?" #: front/src/views/content/libraries/Quota.vue:63 +msgctxt "Popup/Library/Title" msgid "Purge skipped files?" msgstr "ОчиÑтить пропущенные файлы?" #: front/src/components/Sidebar.vue:20 +msgctxt "Sidebar/Queue/Tab.Title/Noun" msgid "Queue" msgstr "Очередь" -#: front/src/components/audio/Player.vue:282 +#: front/src/components/audio/Player.vue:310 +msgctxt "Content/Queue/Message" msgid "Queue shuffled!" msgstr "Очередь перемешана!" #: front/src/views/radios/Detail.vue:80 +msgctxt "Head/Radio/Title" msgid "Radio" msgstr "Радио" -#: front/src/components/library/radios/Builder.vue:233 +#: front/src/components/library/radios/Builder.vue:235 +msgctxt "Head/Radio/Title" msgid "Radio Builder" msgstr "КонÑтруктор радио" #: front/src/components/library/radios/Builder.vue:15 +msgctxt "Content/Radio/Message" msgid "Radio created" msgstr "Радио Ñоздано" #: front/src/components/library/radios/Builder.vue:21 +msgctxt "Content/Radio/Input.Label/Noun" msgid "Radio name" msgstr "Ðазвание радио" #: front/src/components/library/radios/Builder.vue:12 +msgctxt "Content/Radio/Message" msgid "Radio updated" msgstr "Радио обновлено" -#: front/src/components/library/Library.vue:10 -#: src/components/library/Radios.vue:141 +#: front/src/components/library/Library.vue:13 +#: src/components/library/Radios.vue:142 +#, fuzzy +msgctxt "*/*/*" +msgid "Radios" +msgstr "Радио" + +#: front/src/components/mixins/Translations.vue:92 +#: front/src/components/mixins/Translations.vue:93 #, fuzzy +msgctxt "Content/OAuth Scopes/Label" msgid "Radios" msgstr "Радио" +#: front/src/components/auth/ApplicationForm.vue:149 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Read" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:51 +msgctxt "Popup/Import/Table.Label/Value" +msgid "Read our documentation for this error" +msgstr "" + +#: front/src/components/auth/Authorize.vue:24 +msgctxt "Content/Auth/Label/Noun" +msgid "Read-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:150 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Read-only access to user data" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:39 #: front/src/components/manage/moderation/InstancePolicyForm.vue:25 +msgctxt "Content/Moderation/*/Noun" msgid "Reason" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:222 +#: front/src/views/admin/moderation/AccountsDetail.vue:251 #: front/src/views/admin/moderation/DomainsDetail.vue:179 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Received library follows" msgstr "" #: front/src/components/manage/moderation/DomainsTable.vue:40 -#: front/src/components/mixins/Translations.vue:36 -#: front/src/components/mixins/Translations.vue:37 +#: front/src/components/mixins/Translations.vue:62 +#: front/src/components/mixins/Translations.vue:63 +msgctxt "Content/Moderation/*/Noun" msgid "Received messages" msgstr "" +#: front/src/components/library/EditForm.vue:27 +#, fuzzy +msgctxt "Content/Library/Paragraph" +msgid "Recent edits" +msgstr "Ðедавно добавленные" + +#: front/src/components/library/EditForm.vue:17 +msgctxt "Content/Library/Paragraph" +msgid "Recent edits awaiting review" +msgstr "" + #: front/src/components/library/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Recently added" msgstr "Ðедавно добавленные" #: front/src/components/library/Home.vue:11 -#, fuzzy +msgctxt "Content/Home/Title" msgid "Recently favorited" msgstr "Ðедавно добавленные в избранное" #: front/src/components/library/Home.vue:6 +msgctxt "Content/Home/Title" msgid "Recently listened" msgstr "Ðедавно проÑлушанные" -#: front/src/views/content/remote/Home.vue:15 +#: front/src/components/auth/ApplicationForm.vue:13 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Redirect URI" +msgstr "" + +#: front/src/components/auth/Settings.vue:125 +#: src/components/auth/Settings.vue:170 +#: front/src/components/common/EmptyState.vue:16 +#: src/views/content/remote/Home.vue:15 +msgctxt "Content/*/Button.Label/Short, Verb" msgid "Refresh" msgstr "Обновить" -#: front/src/views/admin/moderation/DomainsDetail.vue:135 +#: front/src/components/federation/FetchButton.vue:20 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh error" +msgstr "Обновить" + +#: front/src/views/admin/library/AlbumDetail.vue:50 +#: front/src/views/admin/library/ArtistDetail.vue:49 +#: front/src/views/admin/library/TrackDetail.vue:49 +msgctxt "Content/Moderation/Button/Verb" +msgid "Refresh from remote server" +msgstr "" + +#: front/src/views/admin/moderation/DomainsDetail.vue:127 +msgctxt "Content/Moderation/Button.Label/Verb" msgid "Refresh node info" msgstr "" -#: front/src/components/common/ActionTable.vue:272 +#: front/src/components/federation/FetchButton.vue:79 +#, fuzzy +msgctxt "Popup/*/Message.Title" +msgid "Refresh pending" +msgstr "По убыванию" + +#: front/src/components/federation/FetchButton.vue:80 +msgctxt "Popup/*/Message.Content" +msgid "Refresh request wasn't proceed in time by our server. It will be processed later." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:16 +msgctxt "Popup/*/Message.Title" +msgid "Refresh successful" +msgstr "" + +#: front/src/components/common/ActionTable.vue:275 +msgctxt "Content/*/Button.Tooltip/Verb" msgid "Refresh table content" msgstr "" -#: front/src/components/auth/Profile.vue:12 -msgid "Registered since %{ date }" -msgstr "ЗарегиÑтрировано Ñ %{ date }" +#: front/src/components/federation/FetchButton.vue:12 +msgctxt "Popup/*/Message.Title" +msgid "Refresh was skipped" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:7 +msgctxt "Popup/*/Title" +msgid "Refreshing object from remote…" +msgstr "" #: front/src/components/auth/Signup.vue:9 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" msgid "Registration are closed on this instance, you will need an invitation code to signup." msgstr "РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð½Ð° Ñтом узле закрыта, вам понадобитÑÑ ÐºÐ¾Ð´ Ð¿Ñ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ñ‡Ñ‚Ð¾Ð±Ñ‹ зарегиÑтрироватьÑÑ." #: front/src/components/manage/users/UsersTable.vue:71 -msgid "regular user" +#, fuzzy +msgctxt "Content/Admin/Table, User role" +msgid "Regular user" msgstr "обычный пользователь" +#: front/src/components/library/EditCard.vue:87 #: front/src/views/content/libraries/Detail.vue:51 +msgctxt "Content/Library/Button.Label" msgid "Reject" msgstr "Отклонить" #: front/src/components/manage/moderation/InstancePolicyCard.vue:32 #: front/src/components/manage/moderation/InstancePolicyForm.vue:123 #, fuzzy +msgctxt "Content/Moderation/*/Verb" msgid "Reject media" msgstr "Отклонено" +#: front/src/components/library/EditCard.vue:33 +#: front/src/components/manage/library/EditsCardList.vue:24 #: front/src/views/content/libraries/Detail.vue:43 +#, fuzzy +msgctxt "Content/Library/*/Short" msgid "Rejected" msgstr "Отклонено" -#: front/src/views/content/libraries/FilesTable.vue:234 -msgid "Relaunch import" -msgstr "ПерезапуÑтить импорт" +#: front/src/components/manage/library/AlbumsTable.vue:43 +#: front/src/components/mixins/Translations.vue:44 src/edits.js:28 +#: front/src/components/mixins/Translations.vue:45 +#, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Release date" +msgstr "ПоÑледнее обновление:" + +#: front/src/components/library/FileUpload.vue:63 +msgctxt "Content/Library/Paragraph" +msgid "Remaining storage space" +msgstr "" #: front/src/views/content/remote/Home.vue:6 +msgctxt "Content/Library/Title/Noun" msgid "Remote libraries" msgstr "Удалённые библиотеки" #: front/src/views/content/remote/Home.vue:7 +msgctxt "Content/Library/Paragraph" msgid "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access." msgstr "Удалёнными библиотеками владеют другие пользователи в Ñети. Ð’Ñ‹ можете получить к ним доÑтуп еÑли они публичны или вам предоÑтавлен доÑтуп." #: front/src/components/library/radios/Filter.vue:59 +msgctxt "Content/Radio/Button.Label/Verb" msgid "Remove" msgstr "Удалить" #: front/src/components/auth/Settings.vue:58 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Remove avatar" msgstr "Удалить аватар" +#: front/src/components/library/ArtistDetail.vue:12 +#, fuzzy +msgctxt "Content/Moderation/Button.Label" +msgid "Remove filter" +msgstr "Удалить аватар" + #: front/src/components/favorites/TrackFavoriteIcon.vue:26 +#, fuzzy +msgctxt "Content/Track/Icon.Tooltip/Verb" msgid "Remove from favorites" msgstr "Удалить из избранного" #: front/src/views/content/libraries/Quota.vue:38 +#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Ðто удалит треки которые были загружены но ещё не обработаны. Файлы будут полноÑтью удалены и вы получите ÑоответÑтвующую квоту." #: front/src/views/content/libraries/Quota.vue:64 +#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota." -msgstr "" +msgstr "Ðто удалит треки которые были загружены но ещё не обработаны. Файлы будут полноÑтью удалены и вы получите ÑоответÑтвующую квоту." #: front/src/views/content/libraries/Quota.vue:90 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota." msgstr "Ðто удалит треки которые были загружены но ещё не обработаны. Файлы будут полноÑтью удалены и вы получите ÑоответÑтвующую квоту." -#: front/src/components/auth/SubsonicTokenForm.vue:34 -#: front/src/components/auth/SubsonicTokenForm.vue:37 +#: front/src/components/auth/SubsonicTokenForm.vue:33 +#: front/src/components/auth/SubsonicTokenForm.vue:36 +#, fuzzy +msgctxt "*/Settings/Button.Label/Verb" msgid "Request a new password" msgstr "ЗапроÑить новый пароль" -#: front/src/components/auth/SubsonicTokenForm.vue:35 +#: front/src/components/auth/SubsonicTokenForm.vue:34 +msgctxt "Popup/Settings/Title" msgid "Request a new Subsonic API password?" msgstr "ЗапроÑить новый пароль Subsonic API?" -#: front/src/components/auth/SubsonicTokenForm.vue:43 +#: front/src/components/auth/SubsonicTokenForm.vue:42 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Request a password" msgstr "ЗапроÑить пароль" -#: front/src/components/auth/Login.vue:34 src/views/auth/PasswordReset.vue:4 -#: front/src/views/auth/PasswordReset.vue:52 +#: front/src/components/federation/FetchButton.vue:64 +msgctxt "Popup/*/Loading.Title" +msgid "Requesting a fetch…" +msgstr "" + +#: front/src/components/library/EditForm.vue:82 +msgctxt "Content/Library/Button.Label" +msgid "Reset to initial value: %{ value }" +msgstr "" + +#: front/src/components/auth/Login.vue:35 src/views/auth/PasswordReset.vue:4 +#: front/src/views/auth/PasswordReset.vue:53 +#, fuzzy +msgctxt "*/Login/*/Verb" msgid "Reset your password" msgstr "СброÑить ваш пароль" -#: front/src/components/favorites/List.vue:38 -#: src/components/library/Artists.vue:30 -#: front/src/components/library/Radios.vue:52 src/views/playlists/List.vue:32 +#: front/src/views/content/libraries/FilesTable.vue:223 +#, fuzzy +msgctxt "Content/Library/Dropdown/Verb" +msgid "Restart import" +msgstr "ПерезапуÑтить импорт" + +#: front/src/components/favorites/List.vue:39 +#: src/components/library/Albums.vue:30 +#: front/src/components/library/Artists.vue:30 +#: src/components/library/Radios.vue:52 front/src/views/playlists/List.vue:32 +msgctxt "Content/Search/Dropdown.Label/Noun" msgid "Results per page" msgstr "Результатов на Ñтраницу" +#: front/src/components/library/EditForm.vue:31 +msgctxt "Content/Library/Button.Label" +msgid "Retrict to unreviewed edits" +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:17 -#, fuzzy +msgctxt "Content/Signup/Link/Verb" msgid "Return to login" msgstr "Ðазад ко входу" +#: front/src/components/library/ArtistDetail.vue:9 +#, fuzzy +msgctxt "Content/Moderation/Link" +msgid "Review my filters" +msgstr "ПроÑмотреть файлы" + +#: front/src/components/auth/Settings.vue:192 +msgctxt "*/*/*/Verb" +msgid "Revoke" +msgstr "" + +#: front/src/components/auth/Settings.vue:195 +msgctxt "*/Settings/Button.Label/Verb" +msgid "Revoke access" +msgstr "" + +#: front/src/components/auth/Settings.vue:193 +msgctxt "Popup/Settings/Title" +msgid "Revoke access for application \"%{ application }\"?" +msgstr "" + #: front/src/components/manage/moderation/InstancePolicyCard.vue:16 +msgctxt "Content/Moderation/Card.Title/Noun" msgid "Rule" msgstr "" -#: front/src/components/admin/SettingsGroup.vue:63 -#: front/src/components/library/radios/Builder.vue:33 +#: front/src/components/admin/SettingsGroup.vue:67 +#: front/src/components/library/radios/Builder.vue:34 +#, fuzzy +msgctxt "Content/*/Button.Label/Verb" msgid "Save" msgstr "Сохранить" -#: front/src/views/content/remote/Card.vue:165 +#: front/src/views/content/remote/Card.vue:169 +msgctxt "Content/Library/Message" msgid "Scan launched" msgstr "Сканирование запущено" -#: front/src/views/content/remote/Card.vue:63 -#, fuzzy +#: front/src/views/content/remote/Card.vue:67 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Scan now" msgstr "Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ð³Ñ€Ñ‹Ð²Ð°ÐµÑ‚ÑÑ" -#: front/src/views/content/remote/Card.vue:166 +#: front/src/views/content/remote/Card.vue:35 +#, fuzzy +msgctxt "Content/Library/Card.List item" +msgid "Scan pending" +msgstr "По возраÑтанию" + +#: front/src/views/content/remote/Card.vue:170 +msgctxt "Content/Library/Message" msgid "Scan skipped (previous scan is too recent)" msgstr "Сканирование пропущено (предыдущее Ñканирование было ÑовÑем недавно)" -#: front/src/views/content/remote/Card.vue:31 -#, fuzzy -msgid "Scan waiting" -msgstr "ОжидаетÑÑ Ñканирование" - -#: front/src/views/content/remote/Card.vue:43 -#, fuzzy +#: front/src/views/content/remote/Card.vue:47 +msgctxt "Content/Library/Card.List item" msgid "Scanned" msgstr "Сканирование запущено" -#: front/src/views/content/remote/Card.vue:47 +#: front/src/views/content/remote/Card.vue:51 +msgctxt "Content/Library/Card.List item" msgid "Scanned with errors" msgstr "ПроÑканировано Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°Ð¼Ð¸" -#: front/src/views/content/remote/Card.vue:35 -#, fuzzy +#: front/src/views/content/remote/Card.vue:39 +msgctxt "Content/Library/Card.List item" msgid "Scanning… (%{ progress }%)" msgstr "Сканирование... (%{ progress }%)" -#: front/src/components/library/Artists.vue:10 -#: src/components/library/Radios.vue:29 -#: front/src/components/manage/library/FilesTable.vue:5 +#: front/src/components/auth/ApplicationForm.vue:22 +msgctxt "Content/Applications/Input.Label/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/auth/Settings.vue:226 +msgctxt "Content/*/*/Noun" +msgid "Scopes" +msgstr "" + +#: front/src/components/library/Albums.vue:10 +#: src/components/library/Artists.vue:10 +#: front/src/components/library/Radios.vue:29 +#: front/src/components/manage/library/AlbumsTable.vue:5 +#: front/src/components/manage/library/ArtistsTable.vue:5 +#: front/src/components/manage/library/EditsCardList.vue:6 +#: front/src/components/manage/library/LibrariesTable.vue:5 +#: front/src/components/manage/library/TracksTable.vue:5 +#: front/src/components/manage/library/UploadsTable.vue:5 #: front/src/components/manage/moderation/AccountsTable.vue:5 #: front/src/components/manage/moderation/DomainsTable.vue:5 #: front/src/components/manage/users/InvitationsTable.vue:5 #: front/src/components/manage/users/UsersTable.vue:5 #: front/src/views/content/libraries/FilesTable.vue:5 #: src/views/playlists/List.vue:13 +msgctxt "Content/Search/Input.Label/Noun" msgid "Search" msgstr "ПоиÑк" #: front/src/views/content/remote/ScanForm.vue:9 +msgctxt "Content/Library/Input.Label/Verb" msgid "Search a remote library" msgstr "ИÑкать в удалённой библиотеке" -#: front/src/components/manage/moderation/AccountsTable.vue:171 +#: front/src/components/manage/library/EditsCardList.vue:211 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by account, summary, domain…" +msgstr "ИÑкать по названию, иÑполнителю, домену..." + +#: front/src/components/manage/library/LibrariesTable.vue:191 #, fuzzy -msgid "Search by domain, username, bio..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, description…" msgstr "ИÑкать по пользователю, адреÑу Ñлектронной почты, коду..." -#: front/src/components/manage/moderation/DomainsTable.vue:151 +#: front/src/components/manage/library/UploadsTable.vue:241 #, fuzzy -msgid "Search by name..." -msgstr "ИÑкать по имени пользователÑ, адреÑу Ñлектронной почты, имени..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, actor, name, reference, source…" +msgstr "ИÑкать по пользователю, адреÑу Ñлектронной почты, коду..." -#: front/src/views/content/libraries/FilesTable.vue:201 +#: front/src/components/manage/library/ArtistsTable.vue:164 #, fuzzy -msgid "Search by title, artist, album…" +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, name, MusicBrainz ID…" +msgstr "ИÑкать по пользователю, адреÑу Ñлектронной почты, коду..." + +#: front/src/components/manage/library/TracksTable.vue:174 +#, fuzzy +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, album, MusicBrainz ID…" msgstr "ИÑкать по названию, иÑполнителю, альбому..." -#: front/src/components/manage/library/FilesTable.vue:176 +#: front/src/components/manage/library/AlbumsTable.vue:174 #, fuzzy -msgid "Search by title, artist, domain…" -msgstr "ИÑкать по названию, иÑполнителю, домену..." +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, title, artist, MusicBrainz ID…" +msgstr "ИÑкать по названию, иÑполнителю, альбому..." + +#: front/src/components/manage/moderation/AccountsTable.vue:171 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by domain, username, bio…" +msgstr "ИÑкать по пользователю, адреÑу Ñлектронной почты, коду..." + +#: front/src/components/manage/moderation/DomainsTable.vue:151 +msgctxt "Content/Search/Input.Placeholder" +msgid "Search by name…" +msgstr "ИÑкать по имени пользователÑ, адреÑу Ñлектронной почты, имени..." + +#: front/src/views/content/libraries/FilesTable.vue:208 +msgctxt "Content/Library/Input.Placeholder" +msgid "Search by title, artist, album…" +msgstr "ИÑкать по названию, иÑполнителю, альбому..." #: front/src/components/manage/users/InvitationsTable.vue:153 #, fuzzy +msgctxt "Content/Admin/Input.Placeholder/Verb" msgid "Search by username, e-mail address, code…" msgstr "ИÑкать по пользователю, адреÑу Ñлектронной почты, коду..." #: front/src/components/manage/users/UsersTable.vue:163 -#, fuzzy +msgctxt "Content/Search/Input.Placeholder" msgid "Search by username, e-mail address, name…" msgstr "ИÑкать по имени пользователÑ, адреÑу Ñлектронной почты, имени..." #: front/src/components/audio/SearchBar.vue:20 -#, fuzzy +msgctxt "Sidebar/Search/Input.Placeholder" msgid "Search for artists, albums, tracks…" msgstr "ИÑкать иÑполнителей, альбомы, треки..." #: front/src/components/audio/Search.vue:2 +msgctxt "Content/Search/Title" msgid "Search for some music" msgstr "ПоиÑкать музыку" -#: front/src/components/library/Track.vue:162 -msgid "Search on lyrics.wikia.com" -msgstr "ИÑкать на lyrics.wikia.com" - -#: front/src/components/library/Album.vue:33 -#: src/components/library/Artist.vue:31 -#: front/src/components/library/Track.vue:47 +#: front/src/components/library/AlbumBase.vue:57 +#: front/src/components/library/ArtistBase.vue:68 +#: front/src/components/library/TrackBase.vue:76 +msgctxt "Content/*/Button.Label/Verb" msgid "Search on Wikipedia" msgstr "ИÑкать на Википедии" -#: front/src/components/library/Library.vue:32 -#: src/views/admin/library/Base.vue:17 +#: front/src/components/library/Library.vue:35 +#: src/views/admin/library/Base.vue:32 #: front/src/views/admin/moderation/Base.vue:22 #: src/views/admin/users/Base.vue:21 front/src/views/content/Base.vue:19 +msgctxt "Menu/*/Hidden text" msgid "Secondary menu" msgstr "" #: front/src/views/admin/Settings.vue:15 +msgctxt "Content/Admin/Menu.Title" msgid "Sections" msgstr "Разделы" -#: front/src/components/library/radios/Builder.vue:45 +#: front/src/components/library/radios/Builder.vue:46 +msgctxt "Content/Radio/Dropdown.Placeholder/Verb" msgid "Select a filter" msgstr "Выберите фильтр" -#: front/src/components/common/ActionTable.vue:77 +#: front/src/components/common/ActionTable.vue:79 +#, fuzzy +msgctxt "Content/*/Link/Verb" msgid "Select all %{ total } elements" msgid_plural "Select all %{ total } elements" msgstr[0] "Выделить %{ total } Ñлемент" msgstr[1] "Выделить вÑе %{ total } Ñлемента" msgstr[2] "Выделить вÑе %{ total } Ñлементов" -#: front/src/components/common/ActionTable.vue:86 +#: front/src/components/common/ActionTable.vue:88 +msgctxt "Content/*/Link/Verb" msgid "Select only current page" msgstr "Выбрать только текущую Ñтраницу" -#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:85 +#: front/src/components/Sidebar.vue:43 src/components/Sidebar.vue:108 #: front/src/components/manage/users/UsersTable.vue:181 -#: front/src/views/admin/moderation/AccountsDetail.vue:472 +#: front/src/views/admin/moderation/AccountsDetail.vue:506 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Settings" msgstr "ÐаÑтройки" #: front/src/components/auth/Settings.vue:10 +msgctxt "Content/Settings/Message" msgid "Settings updated" msgstr "ÐаÑтройки обновлены" #: front/src/components/admin/SettingsGroup.vue:11 +msgctxt "Content/Settings/Paragraph" msgid "Settings updated successfully." msgstr "ÐаÑтройки уÑпешно обновлены." #: front/src/components/manage/users/InvitationForm.vue:27 +msgctxt "Content/Admin/Table.Label/Noun" msgid "Share link" msgstr "ПоделитьÑÑ ÑÑылкой" #: front/src/views/content/libraries/Detail.vue:15 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Share this link with other users so they can request access to your library." msgstr "ПоделитеÑÑŒ Ñтой ÑÑылкой Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ пользователÑми чтобы они могли запроÑить доÑтуп к вашей библиотеке." #: front/src/views/content/libraries/Detail.vue:14 -#: front/src/views/content/remote/Card.vue:73 +#: front/src/views/content/remote/Card.vue:77 +#, fuzzy +msgctxt "Content/Library/Title" msgid "Sharing link" -msgstr "" +msgstr "ПоделитьÑÑ ÑÑылкой" -#: front/src/components/audio/album/Card.vue:40 +#: front/src/components/audio/album/Card.vue:38 +#, fuzzy +msgctxt "Content/Album/Card.Link/Verb" msgid "Show %{ count } more track" msgid_plural "Show %{ count } more tracks" msgstr[0] "Показать больше на %{ count } трек" @@ -2320,765 +3893,1371 @@ msgstr[1] "Показать больше на %{ count } трека" msgstr[2] "Показать больше на %{ count } треков" #: front/src/components/audio/artist/Card.vue:30 +#, fuzzy +msgctxt "Content/Artist/Card.Link" msgid "Show 1 more album" msgid_plural "Show %{ count } more albums" msgstr[0] "Показать больше на %{ count } альбом" msgstr[1] "Показать больше на %{ count } альбома" msgstr[2] "Показать больше на %{ count } альбомов" +#: front/src/components/library/EditForm.vue:21 +msgctxt "Content/Library/Button.Label" +msgid "Show all edits" +msgstr "" + #: front/src/components/ShortcutsModal.vue:42 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Show available keyboard shortcuts" msgstr "" -#: front/src/views/Notifications.vue:10 +#: front/src/views/Notifications.vue:7 +msgctxt "Content/Notifications/Form.Label/Verb" msgid "Show read notifications" msgstr "Показывать прочитанные уведомлениÑ" -#: front/src/components/forms/PasswordInput.vue:25 +#: front/src/components/forms/PasswordInput.vue:26 +msgctxt "Content/Settings/Button.Tooltip/Verb" msgid "Show/hide password" msgstr "Показать/Ñкрыть пароль" -#: front/src/components/manage/library/FilesTable.vue:97 +#: front/src/components/manage/library/AlbumsTable.vue:93 +#: front/src/components/manage/library/ArtistsTable.vue:84 +#: front/src/components/manage/library/EditsCardList.vue:72 +#: front/src/components/manage/library/LibrariesTable.vue:110 +#: front/src/components/manage/library/TracksTable.vue:95 +#: front/src/components/manage/library/UploadsTable.vue:144 #: front/src/components/manage/moderation/AccountsTable.vue:88 #: front/src/components/manage/moderation/DomainsTable.vue:74 #: front/src/components/manage/users/InvitationsTable.vue:76 #: front/src/components/manage/users/UsersTable.vue:87 -#: front/src/views/content/libraries/FilesTable.vue:114 +#: front/src/views/content/libraries/FilesTable.vue:117 +#, fuzzy +msgctxt "Content/*/Paragraph" msgid "Showing results %{ start }-%{ end } on %{ total }" msgstr "Показаны результаты %{ start }-%{ end } из %{ total }" #: front/src/components/ShortcutsModal.vue:83 -#, fuzzy +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Shuffle queue" msgstr "Перемешать вашу очередь" -#: front/src/components/audio/Player.vue:362 +#: front/src/components/audio/Player.vue:613 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Shuffle your queue" msgstr "Перемешать вашу очередь" -#: front/src/components/auth/Signup.vue:95 +#: front/src/components/auth/Signup.vue:97 +msgctxt "*/Signup/Title" msgid "Sign Up" msgstr "РегиÑтрациÑ" #: front/src/components/manage/users/UsersTable.vue:40 +msgctxt "Content/Admin/Table.Label/Short, Noun (Value is a date)" msgid "Sign-up" msgstr "РегиÑтрациÑ" -#: front/src/components/mixins/Translations.vue:31 -#: front/src/views/admin/moderation/AccountsDetail.vue:176 -#: front/src/components/mixins/Translations.vue:32 +#: front/src/components/mixins/Translations.vue:57 +#: front/src/views/admin/moderation/AccountsDetail.vue:197 +#: front/src/components/mixins/Translations.vue:58 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun" msgid "Sign-up date" msgstr "Дата региÑтрации" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:24 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:115 +#: front/src/components/library/FileUpload.vue:94 +#: front/src/components/library/TrackDetail.vue:39 +#: front/src/components/mixins/Translations.vue:54 +#: front/src/views/content/libraries/FilesTable.vue:61 +#: front/src/components/mixins/Translations.vue:55 #, fuzzy -msgid "Silence activity" -msgstr "ПользовательÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ" +msgctxt "Content/Library/*/in MB" +msgid "Size" +msgstr "Размер" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:28 -#: front/src/components/manage/moderation/InstancePolicyForm.vue:119 +#: front/src/components/manage/library/UploadsTable.vue:65 +#: front/src/views/admin/library/UploadDetail.vue:219 #, fuzzy -msgid "Silence notifications" -msgstr "Показывать прочитанные уведомлениÑ" - -#: front/src/components/library/FileUpload.vue:85 -#: front/src/components/library/Track.vue:120 -#: front/src/components/manage/library/FilesTable.vue:44 -#: front/src/components/mixins/Translations.vue:28 -#: front/src/views/content/libraries/FilesTable.vue:60 -#: front/src/components/mixins/Translations.vue:29 +msgctxt "Content/*/*/Noun" msgid "Size" msgstr "Размер" +#: front/src/components/manage/library/UploadsTable.vue:24 +#: front/src/components/mixins/Translations.vue:24 #: front/src/views/content/libraries/FilesTable.vue:15 -#: front/src/views/content/libraries/FilesTable.vue:204 +#: front/src/components/mixins/Translations.vue:25 +#, fuzzy +msgctxt "Content/Library/*" msgid "Skipped" msgstr "Пропущено" #: front/src/views/content/libraries/Quota.vue:49 +msgctxt "Content/Library/Label" msgid "Skipped files" msgstr "Пропущенные файлы" -#: front/src/views/admin/moderation/DomainsDetail.vue:97 +#: front/src/views/admin/moderation/DomainsDetail.vue:89 +msgctxt "Content/Moderation/Table.Label" msgid "Software" msgstr "" +#: front/src/components/playlists/Editor.vue:21 +msgctxt "Content/Playlist/Paragraph" +msgid "Some tracks in your queue are already in this playlist:" +msgstr "" + +#: front/src/components/PageNotFound.vue:10 +#, fuzzy +msgctxt "Content/*/Paragraph" +msgid "Sorry, the page you asked for does not exist:" +msgstr "Извините, Ñтраницы которую вы запрашивали не ÑущеÑтвует:" + #: front/src/components/Footer.vue:49 +msgctxt "Footer/*/List item.Link" msgid "Source code" msgstr "ИÑходный код" #: front/src/components/auth/Profile.vue:23 #: front/src/components/manage/users/UsersTable.vue:70 +msgctxt "Content/Profile/User role" msgid "Staff member" msgstr "" -#: front/src/components/radios/Button.vue:4 +#: front/src/components/audio/PlayButton.vue:23 +#: src/components/radios/Button.vue:4 #, fuzzy -msgid "Start" -msgstr "Ðачать" +msgctxt "*/Queue/Button.Label/Short, Verb" +msgid "Start radio" +msgstr "ОÑтановить радио" #: front/src/views/admin/Settings.vue:86 +msgctxt "Content/Admin/Menu" msgid "Statistics" msgstr "СтатиÑтика" -#: front/src/views/admin/moderation/AccountsDetail.vue:454 +#: front/src/views/admin/moderation/AccountsDetail.vue:490 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account" msgstr "" -#: front/src/views/admin/moderation/DomainsDetail.vue:358 +#: front/src/views/admin/moderation/DomainsDetail.vue:371 +msgctxt "Content/Moderation/Help text" msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain" msgstr "" -#: front/src/components/library/FileUpload.vue:86 +#: front/src/views/admin/library/AlbumDetail.vue:329 +#: front/src/views/admin/library/ArtistDetail.vue:328 +#: front/src/views/admin/library/LibraryDetail.vue:316 +#: front/src/views/admin/library/TrackDetail.vue:371 +#: front/src/views/admin/library/UploadDetail.vue:335 +msgctxt "Content/Moderation/Help text" +msgid "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object" +msgstr "" + +#: front/src/components/library/FileUpload.vue:95 +#, fuzzy +msgctxt "Content/Library/Table.Label (Value is Uploading/Uploaded/Error)" +msgid "Status" +msgstr "СтатуÑ" + +#: front/src/views/admin/moderation/DomainsDetail.vue:115 +#, fuzzy +msgctxt "Content/Moderation/Table.Label (Value is Error message)" +msgid "Status" +msgstr "СтатуÑ" + +#: front/src/components/manage/library/EditsCardList.vue:12 +#, fuzzy +msgctxt "Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)" +msgid "Status" +msgstr "СтатуÑ" + +#: front/src/components/manage/users/UsersTable.vue:43 +#, fuzzy +msgctxt "Content/Admin/Table.Label/Noun (Value is Regular user/Admin)" +msgid "Status" +msgstr "СтатуÑ" + #: front/src/components/manage/users/InvitationsTable.vue:17 #: front/src/components/manage/users/InvitationsTable.vue:39 -#: front/src/components/manage/users/UsersTable.vue:43 -#: front/src/views/admin/moderation/DomainsDetail.vue:123 -#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Admin/*/Noun (Value is Used/Not used)" msgid "Status" msgstr "СтатуÑ" -#: front/src/components/radios/Button.vue:3 -msgid "Stop" -msgstr "ОÑтановить" +#: front/src/views/content/libraries/Detail.vue:28 +#, fuzzy +msgctxt "Content/Library.Federation/Table.Label (Value is Approved/Rejected)" +msgid "Status" +msgstr "СтатуÑ" -#: front/src/components/Sidebar.vue:161 +#: front/src/components/Sidebar.vue:174 src/components/radios/Button.vue:3 +#, fuzzy +msgctxt "*/Player/Button.Label/Short, Verb" msgid "Stop radio" msgstr "ОÑтановить радио" -#: front/src/App.vue:22 +#: front/src/components/SetInstanceModal.vue:23 +msgctxt "*/*/Button.Label/Verb" msgid "Submit" msgstr "Отправить" +#: front/src/components/library/EditForm.vue:98 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit and apply edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:7 +msgctxt "Content/Library/Button.Label" +msgid "Submit another edit" +msgstr "" + +#: front/src/components/library/EditForm.vue:99 +msgctxt "Content/Library/Button.Label/Verb" +msgid "Submit suggestion" +msgstr "" + #: front/src/views/admin/Settings.vue:85 +msgctxt "Content/Admin/Menu" msgid "Subsonic" msgstr "Subsonic" #: front/src/components/auth/SubsonicTokenForm.vue:2 +msgctxt "Content/Settings/Title" msgid "Subsonic API password" msgstr "Пароль Subsonic API" -#: front/src/App.vue:26 +#: front/src/components/library/EditForm.vue:38 +msgctxt "Content/Library/Paragraph" +msgid "Suggest a change using the form below." +msgstr "" + +#: front/src/components/library/AlbumEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this album" +msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/library/ArtistEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this artist" +msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/library/TrackEdit.vue:5 +#, fuzzy +msgctxt "Content/*/Title" +msgid "Suggest an edit on this track" +msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/SetInstanceModal.vue:31 +msgctxt "Popup/Instance/List.Label" msgid "Suggested choices" msgstr "Предложенные изменениÑ" #: front/src/components/library/FileUpload.vue:3 +msgctxt "Content/Library/Tab.Title/Short" msgid "Summary" msgstr "" +#: front/src/components/library/EditForm.vue:87 +msgctxt "*/*/*" +msgid "Summary (optional)" +msgstr "" + #: front/src/components/Footer.vue:39 +msgctxt "Footer/*/Listitem.Link" msgid "Support forum" msgstr "" -#: front/src/components/library/FileUpload.vue:78 +#: front/src/components/library/FileUpload.vue:85 +msgctxt "Content/Library/Paragraph" msgid "Supported extensions: %{ extensions }" msgstr "" #: front/src/components/playlists/Editor.vue:9 -#, fuzzy +msgctxt "Content/Playlist/Paragraph" msgid "Syncing changes to server…" msgstr "Синхронизируем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ Ñервером..." +#: front/src/components/audio/EmbedWizard.vue:25 #: front/src/components/common/CopyInput.vue:3 +msgctxt "Content/*/Paragraph" msgid "Text copied to clipboard!" msgstr "ТекÑÑ‚ Ñкопирован в буфер обмена!" #: front/src/components/Home.vue:26 +msgctxt "Content/Home/Paragraph" msgid "That's simple: we loved Grooveshark and we want to build something even better." msgstr "Ðто проÑто: нам нравилÑÑ Grooveshark и мы хотели Ñоздать что-то ещё лучшее." +#: front/src/views/admin/library/AlbumDetail.vue:75 +msgctxt "Content/Moderation/Paragraph" +msgid "The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/auth/Authorize.vue:39 +msgctxt "Content/Auth/Paragraph" +msgid "The application is also requesting the following unknown permissions:" +msgstr "" + +#: front/src/views/admin/library/ArtistDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + #: front/src/components/Footer.vue:53 +msgctxt "Footer/*/List item.Link" msgid "The funkwhale logo was kindly designed and provided by Francis Gading." msgstr "Логотип funkwhale был любезно предоÑтавлен Francis Gading." +#: front/src/components/SetInstanceModal.vue:8 +msgctxt "Popup/Instance/Error message.List item" +msgid "The given address is not a Funkwhale server" +msgstr "" + #: front/src/views/content/libraries/Form.vue:34 -#, fuzzy +msgctxt "Popup/Library/Paragraph" msgid "The library and all its tracks will be deleted. This can not be undone." msgstr "Библиотека и вÑе треки в ней будут удалены. Ðто дейÑтвие необратимо." -#: front/src/components/library/FileUpload.vue:39 -msgid "The music files you are uploading are tagged properly:" +#: front/src/views/admin/library/LibraryDetail.vue:61 +msgctxt "Content/Moderation/Paragraph" +msgid "The library will be removed, as well as associated uploads, and follows. This action is irreversible." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:140 +msgctxt "Popup/Import/Error.Label" +msgid "The metadata included in the file is invalid or some mandatory fields are missing." +msgstr "" + +#: front/src/components/library/FileUpload.vue:38 +msgctxt "Content/Library/List item" +msgid "The music files you are uploading are tagged properly." msgstr "" -#: front/src/components/audio/Player.vue:67 -msgid "The next track will play automatically in a few seconds..." +#: front/src/components/audio/Player.vue:65 +msgctxt "Sidebar/Player/Error message.Paragraph" +msgid "The next track will play automatically in a few seconds…" msgstr "" -#: front/src/components/Home.vue:121 +#: front/src/components/Home.vue:116 +msgctxt "Content/Home/List item" msgid "The plaform is free and open-source, you can install it and modify it without worries" msgstr "" +#: front/src/components/playlists/Form.vue:14 +#, fuzzy +msgctxt "Content/Playlist/Error message.Title" +msgid "The playlist could not be created" +msgstr "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñоздан" + +#: front/src/components/federation/FetchButton.vue:37 +msgctxt "*/*/Error" +msgid "The remote server answered with HTTP %{ status }" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:13 +msgctxt "Popup/*/Message.Content" +msgid "The remote server answered, but returned data was unsupported by Funkwhale." +msgstr "" + +#: front/src/components/federation/FetchButton.vue:44 +msgctxt "*/*/Error" +msgid "The remote server didn't answered fast enough" +msgstr "" + +#: front/src/components/federation/FetchButton.vue:50 +msgctxt "*/*/Error" +msgid "The return server returned invalid JSON or JSON-LD data" +msgstr "" + +#: front/src/components/manage/library/AlbumsTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/ArtistsTable.vue:179 +msgctxt "Popup/*/Paragraph" +msgid "The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/LibrariesTable.vue:206 +msgctxt "Popup/*/Paragraph" +msgid "The selected library will be removed, as well as associated uploads and follows. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/TracksTable.vue:189 +msgctxt "Popup/*/Paragraph" +msgid "The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/components/manage/library/UploadsTable.vue:256 +msgctxt "Popup/*/Paragraph" +msgid "The selected upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/SetInstanceModal.vue:7 +msgctxt "Popup/Instance/Error message.List item" +msgid "The server might be down" +msgstr "" + #: front/src/components/auth/SubsonicTokenForm.vue:4 +msgctxt "Content/Settings/Paragraph" msgid "The Subsonic API is not available on this Funkwhale instance." msgstr "Subsonic API недоÑтупен на Ñтом узле Funkwhale." -#: front/src/components/library/FileUpload.vue:43 +#: front/src/components/library/EditCard.vue:96 +msgctxt "Popup/Library/Paragraph" +msgid "The suggestion will be completely removed, this action is irreversible." +msgstr "" + +#: front/src/components/playlists/PlaylistModal.vue:34 +#, fuzzy +msgctxt "Popup/Playlist/Error message.Title" +msgid "The track can't be added to a playlist" +msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" + +#: front/src/components/audio/Player.vue:62 +msgctxt "Sidebar/Player/Error message.Title" +msgid "The track cannot be loaded" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:74 +msgctxt "Content/Moderation/Paragraph" +msgid "The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible." +msgstr "" + +#: front/src/views/admin/library/UploadDetail.vue:68 +msgctxt "Content/Moderation/Paragraph" +msgid "The upload will be removed. This action is irreversible." +msgstr "" + +#: front/src/components/library/FileUpload.vue:42 +msgctxt "Content/Library/List item" msgid "The uploaded music files are in OGG, Flac or MP3 format" msgstr "Загружаемые музыкальные файлы в форматах OGG, Flac и MP3" #: front/src/views/content/Home.vue:4 +msgctxt "Content/Library/Paragraph" msgid "There are various ways to grab new content and make it available here." msgstr "" #: front/src/components/manage/moderation/InstancePolicyForm.vue:66 +msgctxt "Popup/Moderation/Paragraph" msgid "This action is irreversible." msgstr "" -#: front/src/components/library/Album.vue:91 +#: front/src/components/library/AlbumDetail.vue:29 +msgctxt "Content/Album/Paragraph" msgid "This album is present in the following libraries:" msgstr "Ðльбом предÑтавлен в Ñледующих библиотеках:" -#: front/src/components/library/Artist.vue:63 +#: front/src/components/library/ArtistDetail.vue:42 +msgctxt "Content/Artist/Paragraph" msgid "This artist is present in the following libraries:" msgstr "ИÑполнитель предÑтавлен в Ñледующих библиотеках:" -#: front/src/views/admin/moderation/AccountsDetail.vue:55 +#: front/src/views/admin/moderation/AccountsDetail.vue:84 #: front/src/views/admin/moderation/DomainsDetail.vue:48 +msgctxt "Content/Moderation/Card.Title" msgid "This domain is subject to specific moderation rules" msgstr "" #: front/src/views/content/Home.vue:9 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "This instance offers up to %{quota} of storage space for every user." msgstr "Ðтот узел предоÑтавлÑет до %{quota} диÑкового проÑтранÑтва каждому пользователю." +#: front/src/components/auth/Settings.vue:165 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that have access to your account data." +msgstr "" + +#: front/src/components/auth/Settings.vue:218 +msgctxt "Content/Settings/Paragraph" +msgid "This is the list of applications that you have created." +msgstr "" + #: front/src/components/auth/Profile.vue:16 +msgctxt "Content/Profile/Button.Paragraph" msgid "This is you!" msgstr "Ðто вы!" -#: front/src/views/content/libraries/Form.vue:71 -#, fuzzy +#: front/src/views/content/libraries/Form.vue:73 +msgctxt "Content/Library/Input.Placeholder" msgid "This library contains my personal music, I hope you like it." msgstr "Ðта библиотека Ñодержит мою музыку, надеюÑÑŒ она вам понравитÑÑ!" -#: front/src/views/content/remote/Card.vue:131 +#: front/src/views/content/remote/Card.vue:135 +msgctxt "Content/Library/Card.Help text" msgid "This library is private and your approval from its owner is needed to access its content" msgstr "" -#: front/src/views/content/remote/Card.vue:132 +#: front/src/views/content/remote/Card.vue:136 +msgctxt "Content/Library/Card.Help text" msgid "This library is public and you can access its content freely" msgstr "" -#: front/src/components/common/ActionTable.vue:45 -#, fuzzy +#: front/src/components/common/ActionTable.vue:47 +msgctxt "Modal/*/Paragraph" msgid "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want." msgstr "Ðто может повлиÑÑ‚ÑŒ на многие Ñлементы, пожалуйÑта перепроверьте что вы дейÑтвительно хотите Ñтого." -#: front/src/components/library/FileUpload.vue:52 +#: front/src/components/library/AlbumEdit.vue:8 +#: front/src/components/library/ArtistEdit.vue:8 +#: front/src/components/library/TrackEdit.vue:8 +msgctxt "Content/*/Message" +msgid "This object is managed by another server, you cannot edit it." +msgstr "" + +#: front/src/components/library/FileUpload.vue:51 +msgctxt "Content/Library/Paragraph" msgid "This reference will be used to group imported files together." msgstr "" -#: front/src/components/audio/PlayButton.vue:73 +#: front/src/components/mixins/Translations.vue:33 +#: front/src/components/mixins/Translations.vue:34 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track could not be processed, please it is tagged correctly" +msgstr "Произошла ошибка во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ Ñтого трека, убедитеÑÑŒ что у него корректные теги" + +#: front/src/components/mixins/Translations.vue:29 +#: front/src/components/mixins/Translations.vue:30 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track has been uploaded, but hasn't been processed by the server yet" +msgstr "Трек загружен но ещё не обработан Ñервером" + +#: front/src/components/mixins/Translations.vue:25 +#: front/src/components/mixins/Translations.vue:26 +#, fuzzy +msgctxt "Content/Library/Help text" +msgid "This track is already present in one of your libraries" +msgstr "Трек уже был предÑтавлен в одной из ваших библиотек" + +#: front/src/components/audio/PlayButton.vue:85 +msgctxt "*/Queue/Button/Title" msgid "This track is not available in any library you have access to" msgstr "" -#: front/src/components/library/Track.vue:171 +#: front/src/components/library/TrackDetail.vue:82 +msgctxt "Content/Track/Paragraph" msgid "This track is present in the following libraries:" msgstr "Ðтот трек предÑтавлен в Ñледующих библиотеках:" -#: front/src/views/playlists/Detail.vue:37 +#: front/src/views/playlists/Detail.vue:38 +msgctxt "Popup/Playlist/Paragraph" msgid "This will completely delete this playlist and cannot be undone." msgstr "Ðто необратимо удалит ÑпиÑок воÑпроизведениÑ." #: front/src/views/radios/Detail.vue:27 +msgctxt "Popup/Radio/Paragraph" msgid "This will completely delete this radio and cannot be undone." msgstr "Ðто радио будет необратимо удалено." -#: front/src/components/auth/SubsonicTokenForm.vue:51 +#: front/src/components/auth/SubsonicTokenForm.vue:50 +msgctxt "Popup/Settings/Paragraph" msgid "This will completely disable access to the Subsonic API using from account." msgstr "Ðто полноÑтью отключит доÑтуп к Subsonic API Ð´Ð»Ñ Ñтого аккаунта." -#: front/src/App.vue:129 src/components/Footer.vue:72 -msgid "This will erase your local data and disconnect you, do you want to continue?" -msgstr "" - -#: front/src/components/auth/SubsonicTokenForm.vue:36 +#: front/src/components/auth/SubsonicTokenForm.vue:35 +msgctxt "Popup/Settings/Paragraph" msgid "This will log you out from existing devices that use the current password." msgstr "Ðто приведёт к отключению уÑтройÑтв которые иÑпользуют текущий пароль." -#: front/src/components/playlists/Editor.vue:44 +#: front/src/components/auth/Settings.vue:253 +#, fuzzy +msgctxt "Popup/Settings/Paragraph" +msgid "This will permanently delete the application and all the associated tokens." +msgstr "Ðто необратимо удалит ÑпиÑок воÑпроизведениÑ." + +#: front/src/components/auth/Settings.vue:194 +msgctxt "Popup/Settings/Paragraph" +msgid "This will prevent this application from accessing the service on your behalf." +msgstr "" + +#: front/src/components/playlists/Editor.vue:54 +msgctxt "Popup/Playlist/Paragraph" msgid "This will remove all tracks from this playlist and cannot be undone." msgstr "Ðто необратимо удалит вÑе треки из Ñтого ÑпиÑка воÑпроизведениÑ." -#: front/src/components/audio/track/Table.vue:6 -#: front/src/components/manage/library/FilesTable.vue:37 -#: front/src/components/mixins/Translations.vue:27 -#: front/src/views/content/libraries/FilesTable.vue:54 -#: front/src/components/mixins/Translations.vue:28 +#: front/src/views/admin/library/AlbumDetail.vue:99 +#: front/src/views/admin/library/TrackDetail.vue:98 src/edits.js:21 +#: src/edits.js:39 +#, fuzzy +msgctxt "*/*/*/Noun" +msgid "Title" +msgstr "Ðазвание" + +#: front/src/components/audio/track/Table.vue:7 +#: front/src/views/content/libraries/FilesTable.vue:55 +#, fuzzy +msgctxt "Content/Track/*/Noun" msgid "Title" msgstr "Ðазвание" +#: front/src/components/manage/library/AlbumsTable.vue:39 +#: front/src/components/manage/library/TracksTable.vue:39 +msgctxt "*/*/*" +msgid "Title" +msgstr "Ðазвание" + +#: front/src/components/SetInstanceModal.vue:16 +msgctxt "Popup/Instance/Paragraph" +msgid "To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices." +msgstr "" + #: front/src/components/ShortcutsModal.vue:79 +msgctxt "Popup/Keyboard shortcuts/Table.Label/Verb" msgid "Toggle queue looping" msgstr "" -#: front/src/views/admin/moderation/AccountsDetail.vue:288 +#: front/src/views/admin/library/AlbumDetail.vue:222 +#: front/src/views/admin/library/ArtistDetail.vue:211 +#: front/src/views/admin/library/LibraryDetail.vue:200 +#: front/src/views/admin/library/TrackDetail.vue:274 +#: front/src/views/admin/moderation/AccountsDetail.vue:317 #: front/src/views/admin/moderation/DomainsDetail.vue:225 +#, fuzzy +msgctxt "Content/Moderation/Table.Label" msgid "Total size" -msgstr "" +msgstr "Ðе иÑпользуетÑÑ" -#: front/src/views/content/libraries/Card.vue:61 +#: front/src/views/content/libraries/Card.vue:68 +msgctxt "Content/Library/Card.Help text" msgid "Total size of the files in this library" msgstr "Суммарный размер файлов в Ñтой библиотеке" -#: front/src/views/admin/moderation/DomainsDetail.vue:113 -#, fuzzy +#: front/src/views/admin/moderation/DomainsDetail.vue:105 +msgctxt "Content/*/*" msgid "Total users" msgstr "Ðе иÑпользуетÑÑ" #: front/src/components/audio/SearchBar.vue:27 -#: src/components/library/Track.vue:262 +#: front/src/components/library/TrackBase.vue:173 +#: front/src/components/library/TrackDetail.vue:128 #: front/src/components/metadata/Search.vue:138 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Track" msgstr "Трек" -#: front/src/views/content/libraries/FilesTable.vue:205 +#: front/src/views/admin/library/UploadDetail.vue:199 +msgctxt "*/*/*" +msgid "Track" +msgstr "Трек" + +#: front/src/components/library/EditCard.vue:13 +msgctxt "Content/Library/Card/Short" +msgid "Track #%{ id } - %{ name }" +msgstr "" + +#: front/src/views/admin/library/TrackDetail.vue:91 #, fuzzy -msgid "Track already present in one of your libraries" -msgstr "Трек уже был предÑтавлен в одной из ваших библиотек" +msgctxt "Content/Moderation/Title" +msgid "Track data" +msgstr "Ðазвание трека" -#: front/src/components/library/Track.vue:85 +#: front/src/components/library/TrackDetail.vue:4 +msgctxt "Content/Track/Title/Noun" msgid "Track information" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ треке" -#: front/src/components/library/radios/Filter.vue:44 -msgid "Track matching filter" -msgstr "Трек подходÑщий под фильтр" - -#: front/src/components/mixins/Translations.vue:23 -#: front/src/components/mixins/Translations.vue:24 +#: front/src/components/mixins/Translations.vue:50 +#: front/src/components/mixins/Translations.vue:51 +msgctxt "Content/*/Dropdown/Noun" msgid "Track name" msgstr "Ðазвание трека" -#: front/src/views/content/libraries/FilesTable.vue:209 -#, fuzzy -msgid "Track uploaded, but not processed by the server yet" -msgstr "Трек загружен но ещё не обработан Ñервером" +#: front/src/components/manage/library/AlbumsTable.vue:42 +#: front/src/components/manage/library/ArtistsTable.vue:42 +#: front/src/views/admin/library/AlbumDetail.vue:252 +#: front/src/views/admin/library/ArtistDetail.vue:251 +#: front/src/views/admin/library/Base.vue:14 +#: front/src/views/admin/library/LibraryDetail.vue:229 +#: front/src/views/admin/library/TracksList.vue:24 +msgctxt "*/*/*" +msgid "Tracks" +msgstr "Треки" #: front/src/components/instance/Stats.vue:54 -#, fuzzy -msgid "tracks" -msgstr "треки" - -#: front/src/components/library/Album.vue:81 -#: front/src/components/playlists/PlaylistModal.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:329 -#: front/src/views/admin/moderation/DomainsDetail.vue:265 +#: front/src/components/library/AlbumDetail.vue:19 +#: front/src/components/playlists/PlaylistModal.vue:47 +#: front/src/views/admin/moderation/AccountsDetail.vue:362 +#: front/src/views/admin/moderation/DomainsDetail.vue:274 #: front/src/views/content/Base.vue:8 src/views/content/libraries/Detail.vue:8 -#: front/src/views/playlists/Detail.vue:50 src/views/radios/Detail.vue:34 +#: front/src/views/playlists/Detail.vue:51 src/views/radios/Detail.vue:34 #, fuzzy +msgctxt "*/*/*/Noun" msgid "Tracks" msgstr "Треки" -#: front/src/components/library/Artist.vue:54 +#: front/src/components/library/ArtistDetail.vue:33 +msgctxt "Content/Artist/Title" msgid "Tracks by this artist" msgstr "Треки Ñтого иÑполнителÑ" #: front/src/components/instance/Stats.vue:25 +msgctxt "Content/About/Paragraph/Unit" msgid "Tracks favorited" msgstr "Избранные треки" #: front/src/components/instance/Stats.vue:19 +msgctxt "Content/About/Paragraph/Unit" msgid "tracks listened" msgstr "треков проÑлушано" -#: front/src/components/library/Track.vue:138 -#: front/src/components/manage/library/FilesTable.vue:41 -#: front/src/views/admin/moderation/AccountsDetail.vue:151 +#: front/src/components/library/radios/Filter.vue:44 +#, fuzzy +msgctxt "Popup/Radio/Title/Noun" +msgid "Tracks matching filter" +msgstr "Трек подходÑщий под фильтр" + +#: front/src/views/admin/moderation/AccountsDetail.vue:180 +msgctxt "Content/Moderation/Table.Label/Noun" +msgid "Type" +msgstr "Тип" + +#: front/src/components/library/TrackDetail.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:250 +msgctxt "Content/Track/Table.Label/Noun" msgid "Type" msgstr "Тип" #: front/src/components/manage/moderation/AccountsTable.vue:44 #: front/src/components/manage/moderation/DomainsTable.vue:42 +#, fuzzy +msgctxt "Content/Moderation/Table.Label/Short" msgid "Under moderation rule" -msgstr "" +msgstr "Удалить радио" -#: front/src/views/content/remote/Card.vue:100 -#: src/views/content/remote/Card.vue:105 +#: front/src/views/content/remote/Card.vue:104 +#: src/views/content/remote/Card.vue:109 +#, fuzzy +msgctxt "*/Library/Button.Label/Verb" msgid "Unfollow" msgstr "ОтпиÑатьÑÑ" -#: front/src/views/content/remote/Card.vue:101 +#: front/src/views/content/remote/Card.vue:105 +msgctxt "Popup/Library/Title" msgid "Unfollow this library?" msgstr "ОтпиÑатьÑÑ Ð¾Ñ‚ Ñтой библиотеки?" -#: front/src/components/About.vue:15 -msgid "Unfortunately, owners of this instance did not yet take the time to complete this page." +#: front/src/components/About.vue:17 +#, fuzzy +msgctxt "Content/About/Paragraph" +msgid "Unfortunately, the owners of this instance did not yet take the time to complete this page." msgstr "К Ñожалению, владельцы Ñтого узла ещё не заполнили Ñту Ñтраницу." +#: front/src/components/federation/FetchButton.vue:54 +#: front/src/components/federation/FetchButton.vue:55 +msgctxt "*/*/Error" +msgid "Unknowkn error" +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:144 +msgctxt "Popup/Import/Error.Label" +msgid "Unkwown error" +msgstr "" + #: front/src/components/Home.vue:37 -#, fuzzy +msgctxt "Content/Home/Title" msgid "Unlimited music" msgstr "Ð‘ÐµÐ·Ð³Ñ€Ð°Ð½Ð¸Ñ‡Ð½Ð°Ñ Ð¼ÑƒÐ·Ñ‹ÐºÐ°" -#: front/src/components/audio/Player.vue:351 +#: front/src/components/audio/Player.vue:602 +msgctxt "Sidebar/Player/Icon.Tooltip/Verb" msgid "Unmute" msgstr "" -#: front/src/components/manage/moderation/InstancePolicyCard.vue:45 #: front/src/components/manage/moderation/InstancePolicyForm.vue:57 -#, fuzzy +msgctxt "Content/Moderation/Card.Button.Label/Verb" msgid "Update" msgstr "Дата загрузки" +#: front/src/components/auth/ApplicationForm.vue:64 +#, fuzzy +msgctxt "Content/Applications/Button.Label/Verb" +msgid "Update application" +msgstr "Обновить ÑпиÑок воÑпроизведениÑ" + #: front/src/components/auth/Settings.vue:50 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update avatar" msgstr "Обновить аватар" #: front/src/views/content/libraries/Form.vue:25 +msgctxt "Content/Library/Button.Label/Verb" msgid "Update library" msgstr "Обновить библиотеку" -#: front/src/components/manage/moderation/InstancePolicyForm.vue:3 -msgid "Update moderation rule" -msgstr "" - #: front/src/components/playlists/Form.vue:33 +msgctxt "Content/Playlist/Button.Label/Verb" msgid "Update playlist" msgstr "Обновить ÑпиÑок воÑпроизведениÑ" #: front/src/components/auth/Settings.vue:27 +msgctxt "Content/Settings/Button.Label/Verb" msgid "Update settings" msgstr "Обновить наÑтройки" #: front/src/views/auth/PasswordResetConfirm.vue:21 -#, fuzzy +msgctxt "Content/Signup/Button.Label" msgid "Update your password" msgstr "Обновите ваш пароль" -#: front/src/views/content/libraries/Card.vue:44 +#: front/src/views/content/libraries/Card.vue:45 #: front/src/views/content/libraries/DetailArea.vue:24 +msgctxt "Content/Library/Card.Button.Label/Verb" msgid "Upload" msgstr "Загрузить" #: front/src/components/auth/Settings.vue:45 +msgctxt "Content/Settings/Title/Verb" msgid "Upload a new avatar" msgstr "Загрузить новый аватар" #: front/src/views/content/Home.vue:6 +msgctxt "Content/Library/Title/Verb" msgid "Upload audio content" msgstr "Загрузить аудио" -#: front/src/views/content/libraries/FilesTable.vue:57 +#: front/src/views/admin/library/UploadDetail.vue:85 +#, fuzzy +msgctxt "Content/Moderation/Title" +msgid "Upload data" +msgstr "Дата загрузки" + +#: front/src/views/content/libraries/FilesTable.vue:58 +msgctxt "*/*/*/Noun" msgid "Upload date" msgstr "Дата загрузки" -#: front/src/components/library/FileUpload.vue:219 -#: front/src/components/library/FileUpload.vue:220 -#, fuzzy +#: front/src/components/library/FileUpload.vue:258 +msgctxt "Content/Library/Help text" msgid "Upload denied, ensure the file is not too big and that you have not reached your quota" msgstr "Загрузка отклонена, убедитÑÑŒ что файл не Ñлишком большой и вы не превыÑили квоту" +#: front/src/components/library/ImportStatusModal.vue:8 +msgctxt "Popup/Import/Message" +msgid "Upload is still pending and will soon be processed by the server." +msgstr "" + #: front/src/views/content/Home.vue:7 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here." msgstr "Загрузите музыку (mp3, ogg, flac и Ñ‚.д.) из вашей личной библиотеки прÑмо из вашего браузера и наÑлаждайтеÑÑŒ ей здеÑÑŒ." -#: front/src/components/library/FileUpload.vue:31 +#: front/src/components/library/FileUpload.vue:30 +msgctxt "Content/Library/Title/Verb" msgid "Upload new tracks" msgstr "Загрузить новые треки" -#: front/src/views/admin/moderation/AccountsDetail.vue:269 -#, fuzzy +#: front/src/views/admin/moderation/AccountsDetail.vue:298 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Upload quota" msgstr "Квота загрузки" -#: front/src/components/library/FileUpload.vue:228 +#: front/src/components/library/FileUpload.vue:267 +msgctxt "Content/Library/Help text" msgid "Upload timeout, please try again" msgstr "" -#: front/src/components/library/FileUpload.vue:100 +#: front/src/components/library/ImportStatusModal.vue:14 +msgctxt "Popup/Import/Message" +msgid "Upload was skipped because a similar one is already available in one of your libraries." +msgstr "" + +#: front/src/components/library/ImportStatusModal.vue:11 +msgctxt "Popup/Import/Message" +msgid "Upload was successfully processed by the server." +msgstr "" + +#: front/src/components/library/FileUpload.vue:109 +msgctxt "Content/Library/Table" msgid "Uploaded" msgstr "Загружено" #: front/src/components/library/FileUpload.vue:5 -#, fuzzy +msgctxt "Content/Library/Tab.Title/Short" msgid "Uploading" msgstr "ЗагружаетÑÑ" -#: front/src/components/library/FileUpload.vue:103 -#, fuzzy +#: front/src/components/library/FileUpload.vue:112 +msgctxt "Content/Library/Table" msgid "Uploading…" msgstr "ЗагружаетÑÑ" -#: front/src/components/manage/moderation/AccountsTable.vue:41 -#: front/src/components/mixins/Translations.vue:37 -#: front/src/views/admin/moderation/AccountsDetail.vue:305 -#: front/src/views/admin/moderation/DomainsDetail.vue:241 -#: front/src/components/mixins/Translations.vue:38 +#: front/src/components/manage/library/LibrariesTable.vue:52 #, fuzzy +msgctxt "Content/*/*/Noun" +msgid "Uploads" +msgstr "Загрузить" + +#: front/src/views/admin/library/Base.vue:20 +#: front/src/views/admin/library/UploadsList.vue:24 +#, fuzzy +msgctxt "*/*/*" +msgid "Uploads" +msgstr "Загрузить" + +#: front/src/components/manage/moderation/AccountsTable.vue:41 +#: front/src/components/mixins/Translations.vue:63 +#: front/src/views/admin/library/AlbumDetail.vue:242 +#: front/src/views/admin/library/ArtistDetail.vue:231 +#: front/src/views/admin/library/LibraryDetail.vue:239 +#: front/src/views/admin/library/TrackDetail.vue:294 +#: front/src/views/admin/moderation/AccountsDetail.vue:337 +#: front/src/views/admin/moderation/DomainsDetail.vue:244 +#: front/src/components/mixins/Translations.vue:64 +msgctxt "Content/Moderation/Table.Label/Noun" msgid "Uploads" msgstr "Загрузить" +#: front/src/components/auth/ApplicationForm.vue:16 +msgctxt "Content/Applications/Help Text" +msgid "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web." +msgstr "" + #: front/src/components/Footer.vue:16 +msgctxt "Footer/*/List item.Link" msgid "Use another instance" msgstr "ИÑпользовать другой узел" #: front/src/views/auth/PasswordReset.vue:12 +msgctxt "Content/Signup/Paragraph" msgid "Use this form to request a password reset. We will send an email to the given address with instructions to reset your password." msgstr "ИÑпользуйте Ñту форму чтобы запроÑить ÑÐ±Ñ€Ð¾Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ. Мы вышлем пиÑьмо на указанный Ð°Ð´Ñ€ÐµÑ Ñ Ð¸Ð½ÑтрукциÑми по ÑброÑу вашего паролÑ." #: front/src/components/manage/moderation/InstancePolicyForm.vue:111 +msgctxt "Content/Moderation/Help text" msgid "Use this setting to temporarily enable/disable the policy without completely removing it." msgstr "" #: front/src/components/manage/users/InvitationsTable.vue:49 +msgctxt "Content/Admin/Table" msgid "Used" msgstr "ИÑпользовано" #: front/src/views/content/libraries/Detail.vue:26 +msgctxt "Content/Library/Table.Label" msgid "User" msgstr "Пользователь" #: front/src/components/instance/Stats.vue:5 +msgctxt "Content/About/Title/Noun" msgid "User activity" msgstr "ПользовательÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ" -#: front/src/components/library/Album.vue:88 -#: src/components/library/Artist.vue:60 -#: front/src/components/library/Track.vue:168 +#: front/src/components/library/AlbumDetail.vue:26 +#: front/src/components/library/ArtistDetail.vue:39 +#: front/src/components/library/TrackDetail.vue:79 +#, fuzzy +msgctxt "Content/*/Title/Noun" msgid "User libraries" msgstr "ПользовательÑкие библиотеки" #: front/src/components/library/Radios.vue:20 +msgctxt "Content/Radio/Title" msgid "User radios" msgstr "ПользовательÑкие радио" #: front/src/components/auth/Signup.vue:19 #: front/src/components/manage/users/UsersTable.vue:37 -#: front/src/components/mixins/Translations.vue:33 -#: front/src/views/admin/moderation/AccountsDetail.vue:85 -#: front/src/components/mixins/Translations.vue:34 +#: front/src/components/mixins/Translations.vue:59 +#: front/src/views/admin/moderation/AccountsDetail.vue:114 +#: front/src/components/mixins/Translations.vue:60 +msgctxt "Content/*/*" msgid "Username" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" #: front/src/components/auth/Login.vue:15 +msgctxt "Content/Login/Input.Label/Noun" msgid "Username or email" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль" #: front/src/components/instance/Stats.vue:13 +msgctxt "Content/About/Paragraph/Unit" msgid "users" msgstr "пользователи" -#: front/src/components/Sidebar.vue:91 +#: front/src/components/Sidebar.vue:102 #: front/src/components/manage/moderation/DomainsTable.vue:39 -#: front/src/components/mixins/Translations.vue:35 +#: front/src/components/mixins/Translations.vue:61 #: src/views/admin/Settings.vue:81 front/src/views/admin/users/Base.vue:5 -#: src/views/admin/users/UsersList.vue:3 -#: front/src/views/admin/users/UsersList.vue:21 -#: front/src/components/mixins/Translations.vue:36 +#: src/views/admin/users/UsersList.vue:21 +#: front/src/components/mixins/Translations.vue:62 +#, fuzzy +msgctxt "*/*/*/Noun" msgid "Users" msgstr "Пользователи" #: front/src/components/Footer.vue:29 #, fuzzy +msgctxt "Footer/*/Title" msgid "Using Funkwhale" msgstr "О Funkwhale" #: front/src/components/Footer.vue:13 -#, fuzzy +msgctxt "Footer/*/List item" msgid "Version %{version}" msgstr "ИÑходный код (%{version})" #: front/src/views/content/libraries/Quota.vue:29 #: front/src/views/content/libraries/Quota.vue:56 #: front/src/views/content/libraries/Quota.vue:82 +msgctxt "Content/Library/Link/Verb" msgid "View files" msgstr "ПроÑмотреть файлы" -#: front/src/components/library/Album.vue:37 -#: src/components/library/Artist.vue:35 -#: front/src/components/library/Track.vue:51 +#: front/src/components/library/AlbumBase.vue:81 +#: front/src/components/library/ArtistBase.vue:92 +#: front/src/components/library/TrackBase.vue:100 +#: front/src/views/admin/library/AlbumDetail.vue:42 +#: front/src/views/admin/library/ArtistDetail.vue:41 +#: front/src/views/admin/library/LibraryDetail.vue:34 +#: front/src/views/admin/library/LibraryDetail.vue:45 +#: front/src/views/admin/library/TrackDetail.vue:41 +#: front/src/views/admin/library/UploadDetail.vue:35 +#: front/src/views/admin/library/UploadDetail.vue:46 +#: front/src/views/admin/moderation/AccountsDetail.vue:37 +#: front/src/views/admin/moderation/AccountsDetail.vue:45 +msgctxt "Content/Moderation/Link/Verb" +msgid "View in Django's admin" +msgstr "" + +#: front/src/components/library/AlbumBase.vue:61 +#: front/src/components/library/ArtistBase.vue:72 +#: front/src/components/library/TrackBase.vue:80 #: front/src/components/metadata/ArtistCard.vue:49 #: front/src/components/metadata/ReleaseCard.vue:53 +#, fuzzy +msgctxt "Content/*/*/Clickable, Verb" msgid "View on MusicBrainz" msgstr "Смотреть на MusicBrainz" #: front/src/views/content/libraries/Form.vue:18 +msgctxt "Content/Library/Dropdown.Label" msgid "Visibility" msgstr "ВидимоÑÑ‚ÑŒ" -#: front/src/views/content/libraries/Card.vue:59 -msgid "Visibility: everyone on this instance" -msgstr "ВидимоÑÑ‚ÑŒ: вÑе на Ñтом узле" - -#: front/src/views/content/libraries/Card.vue:60 -msgid "Visibility: everyone, including other instances" -msgstr "ВидимоÑÑ‚ÑŒ: вÑе, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð´Ñ€ÑƒÐ³Ð¸Ðµ узлы" - -#: front/src/views/content/libraries/Card.vue:58 -msgid "Visibility: nobody except me" -msgstr "ВидимоÑÑ‚ÑŒ: никто кроме менÑ" +#: front/src/components/manage/library/LibrariesTable.vue:11 +#: front/src/components/manage/library/LibrariesTable.vue:51 +#: front/src/components/manage/library/UploadsTable.vue:11 +#: front/src/components/manage/library/UploadsTable.vue:63 +#: front/src/views/admin/library/LibraryDetail.vue:94 +#: front/src/views/admin/library/UploadDetail.vue:101 +#, fuzzy +msgctxt "*/*/*" +msgid "Visibility" +msgstr "ВидимоÑÑ‚ÑŒ" -#: front/src/components/library/Album.vue:67 +#: front/src/components/library/AlbumDetail.vue:4 +msgctxt "Content/Album/" msgid "Volume %{ number }" msgstr "" -#: front/src/components/playlists/PlaylistModal.vue:20 -msgid "We cannot add the track to a playlist" -msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" - -#: front/src/components/playlists/Form.vue:14 -msgid "We cannot create the playlist" -msgstr "Ðам не удалоÑÑŒ Ñоздать ÑпиÑок воÑпроизведениÑ" - -#: front/src/components/auth/Signup.vue:13 -msgid "We cannot create your account" -msgstr "Ðам не удалоÑÑŒ Ñоздать ваш аккаунт" - -#: front/src/components/audio/Player.vue:64 +#: front/src/components/federation/FetchButton.vue:69 #, fuzzy -msgid "We cannot load this track" -msgstr "Ðам не удалоÑÑŒ добавить трек в ÑпиÑок воÑпроизведениÑ" +msgctxt "Popup/*/Loading.Title" +msgid "Waiting for result…" +msgstr "Загружаем ваше избранное..." #: front/src/components/auth/Login.vue:7 +#, fuzzy +msgctxt "Content/Login/Error message.Title" msgid "We cannot log you in" -msgstr "" - -#: front/src/components/auth/Settings.vue:38 -msgid "We cannot save your avatar" -msgstr "Мы не Ñмогли Ñохранить ваш аватар" - -#: front/src/components/auth/Settings.vue:14 -msgid "We cannot save your settings" msgstr "Мы не Ñмогли Ñохранить ваши наÑтройки" -#: front/src/components/Home.vue:127 -msgid "We do not track you or bother you with ads" -msgstr "Мы не отÑлеживаем Ð²Ð°Ñ Ð¸ не надоедаем вам рекламой" - -#: front/src/components/library/Track.vue:95 +#: front/src/components/auth/ApplicationForm.vue:3 #, fuzzy -msgid "We don't have any copyright information for this track" -msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" +msgctxt "Content/*/Error message.Title" +msgid "We cannot save your changes" +msgstr "Ðам не удалоÑÑŒ Ñоздать ваш аккаунт" -#: front/src/components/library/Track.vue:106 -#, fuzzy -msgid "We don't have any licensing information for this track" -msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" +#: front/src/components/Home.vue:122 +msgctxt "Content/Home/List item" +msgid "We do not track you or bother you with ads" +msgstr "Мы не отÑлеживаем Ð²Ð°Ñ Ð¸ не надоедаем вам рекламой" -#: front/src/components/library/FileUpload.vue:40 -#, fuzzy +#: front/src/components/library/FileUpload.vue:39 +msgctxt "Content/Library/Link" msgid "We recommend using Picard for that purpose." msgstr "мы рекомендуем иÑпользовать Picard Ð´Ð»Ñ Ñтого" #: front/src/components/Home.vue:7 +msgctxt "Content/Home/Title" msgid "We think listening to music should be simple." msgstr "Мы Ñчитаем что проÑлушивание музыки должно быть проÑтым." -#: front/src/components/PageNotFound.vue:10 -msgid "We're sorry, the page you asked for does not exist:" -msgstr "Извините, Ñтраницы которую вы запрашивали не ÑущеÑтвует:" - -#: front/src/components/Home.vue:153 +#: front/src/components/Home.vue:148 +msgctxt "Head/Home/Title" msgid "Welcome" msgstr "Добро пожаловать" #: front/src/components/Home.vue:5 +msgctxt "Content/Home/Title/Verb" msgid "Welcome on Funkwhale" msgstr "Добро пожаловать в Funkwhale" #: front/src/components/Home.vue:24 +msgctxt "Content/Home/Title" msgid "Why funkwhale?" msgstr "Почему funkwhale?" #: front/src/components/audio/EmbedWizard.vue:13 +msgctxt "Popup/Embed/Input.Label" msgid "Widget height" msgstr "" #: front/src/components/audio/EmbedWizard.vue:6 +msgctxt "Popup/Embed/Input.Label" msgid "Widget width" msgstr "" -#: front/src/components/Sidebar.vue:118 +#: front/src/components/auth/ApplicationForm.vue:155 +msgctxt "Content/OAuth Scopes/Label/Verb" +msgid "Write" +msgstr "" + +#: front/src/components/auth/Authorize.vue:21 +msgctxt "Content/Auth/Label/Noun" +msgid "Write-only" +msgstr "" + +#: front/src/components/auth/ApplicationForm.vue:156 +msgctxt "Content/OAuth Scopes/Help Text" +msgid "Write-only access to user data" +msgstr "" + +#: front/src/components/Sidebar.vue:129 #: front/src/components/manage/moderation/AccountsTable.vue:72 #: front/src/components/manage/moderation/DomainsTable.vue:58 +msgctxt "*/*/*" msgid "Yes" msgstr "Да" #: front/src/components/auth/Logout.vue:8 +msgctxt "Content/Login/Button.Label" msgid "Yes, log me out!" msgstr "" #: front/src/views/content/libraries/Form.vue:19 -#, fuzzy +msgctxt "Content/Library/Paragraph" msgid "You are able to share your library with other people, regardless of its visibility." msgstr "Ð’Ñ‹ Ñможете делитьÑÑ Ð²Ð°ÑˆÐµÐ¹ библиотекой Ñ Ð´Ñ€ÑƒÐ³Ð¸Ð¼Ð¸ людьми незавиÑимо от её видимоÑти." -#: front/src/components/library/FileUpload.vue:33 +#: front/src/components/library/FileUpload.vue:32 +msgctxt "Content/Library/Paragraph" msgid "You are about to upload music to your library. Before proceeding, please ensure that:" msgstr "Ð’Ñ‹ ÑобираетеÑÑŒ загрузить музыку в вашу библиотеку. Перед тем как продолжить, убедитеÑÑŒ что:" +#: front/src/components/SetInstanceModal.vue:12 +msgctxt "Popup/Login/Paragraph" +msgid "You are currently connected to <a href=\"%{ url }\" target=\"_blank\">%{ hostname } <i class=\"external icon\"/></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted." +msgstr "" + +#: front/src/components/library/ArtistDetail.vue:6 +msgctxt "Content/Artist/Paragraph" +msgid "You are currently hiding content related to this artist." +msgstr "" + #: front/src/components/auth/Logout.vue:7 +#, fuzzy +msgctxt "Content/Login/Paragraph" msgid "You are currently logged in as %{ username }" msgstr "Ð’Ñ‹ вошли как %{ username }" +#: front/src/components/library/FileUpload.vue:35 +msgctxt "Content/Library/List item" +msgid "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law" +msgstr "" + +#: front/src/components/SetInstanceModal.vue:98 +msgctxt "*/Instance/Message" +msgid "You are now using the Funkwhale instance at %{ url }" +msgstr "" + #: front/src/views/content/Home.vue:17 +msgctxt "Content/Library/Paragraph" msgid "You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner." msgstr "Ð’Ñ‹ можете подпиÑатьÑÑ Ð½Ð° библиотеки других пользователей чтобы получать доÑтуп к новой музыке. Ðа публичные библиотеки можно подпиÑатьÑÑ Ñразу, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº подпиÑка на приватную библиотеку требует Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ð²Ð»Ð°Ð´ÐµÐ»ÑŒÑ†Ð°." -#: front/src/components/Home.vue:133 +#: front/src/components/Home.vue:128 +msgctxt "Content/Home/List item" msgid "You can invite friends and family to your instance so they can enjoy your music" msgstr "Ð’Ñ‹ можете приглаÑить друзей и Ñемью на ваш узел чтобы они могли наÑлаждатьÑÑ Ð²Ð°ÑˆÐµÐ¹ музыкой" +#: front/src/components/moderation/FilterModal.vue:31 +msgctxt "Popup/Moderation/Paragraph" +msgid "You can manage and update your filters anytime from your account settings." +msgstr "" + #: front/src/views/auth/EmailConfirm.vue:24 -#, fuzzy +msgctxt "Content/Signup/Paragraph" msgid "You can now use the service without limitations." msgstr "Ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты был подтверждён, теперь вы можете пользоватьÑÑ ÑервиÑом без ограничений." #: front/src/components/library/radios/Builder.vue:7 +msgctxt "Content/Radio/Paragraph" msgid "You can use this interface to build your own custom radio, which will play tracks according to your criteria." msgstr "Ð’Ñ‹ можете иÑпользовать Ñтот Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ñ‡Ñ‚Ð¾Ð±Ñ‹ Ñоздать ÑобÑтвенное радио, которое будет проигрывать треки ÑоглаÑно вашим критериÑм." -#: front/src/components/auth/SubsonicTokenForm.vue:8 +#: front/src/components/auth/SubsonicTokenForm.vue:7 +msgctxt "Content/Settings/Paragraph" msgid "You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance." msgstr "Ð’Ñ‹ можете наÑлаждатьÑÑ Ð²Ð°ÑˆÐ¸Ð¼ ÑпиÑком воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¸ музыкой в режиме оффлайн, например Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ Ñмартфона или планшета." -#: front/src/views/admin/moderation/AccountsDetail.vue:46 +#: front/src/components/auth/Settings.vue:202 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any application connected with your account." +msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" + +#: front/src/components/auth/Settings.vue:261 +#, fuzzy +msgctxt "Content/Applications/Paragraph" +msgid "You don't have any configured application yet." +msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" + +#: front/src/views/admin/moderation/AccountsDetail.vue:75 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this account." -msgstr "" +msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" #: front/src/views/admin/moderation/DomainsDetail.vue:39 +#, fuzzy +msgctxt "Content/Moderation/Card.Title" msgid "You don't have any rule in place for this domain." +msgstr "У Ð½Ð°Ñ Ð½ÐµÑ‚ уведомлений Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°!" + +#: front/src/components/library/EditForm.vue:52 +msgctxt "Content/Library/Paragraph" +msgid "You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval." msgstr "" -#: front/src/components/Sidebar.vue:158 +#: front/src/components/Sidebar.vue:171 +msgctxt "Sidebar/Player/Title" msgid "You have a radio playing" msgstr "У Ð²Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ð³Ñ€Ñ‹Ð²Ð°ÐµÑ‚ÑÑ Ñ€Ð°Ð´Ð¸Ð¾" -#: front/src/components/audio/Player.vue:71 +#: front/src/components/audio/Player.vue:69 +msgctxt "Sidebar/Player/Error message.Paragraph" msgid "You may have a connectivity issue." msgstr "" -#: front/src/App.vue:17 -msgid "You need to select an instance in order to continue" -msgstr "Ð’Ñ‹ должны выбрать узел чтобы продолжить" - #: front/src/components/auth/Settings.vue:100 +msgctxt "Popup/Settings/List item" msgid "You will be logged out from this session and have to log in with the new one" msgstr "" +#: front/src/components/auth/Authorize.vue:51 +msgctxt "Content/Auth/Paragraph" +msgid "You will be redirected to <strong>%{ url }</strong>" +msgstr "" + +#: front/src/components/auth/Authorize.vue:49 +msgctxt "Content/Auth/Paragraph" +msgid "You will be shown a code to copy-paste in the application." +msgstr "" + #: front/src/components/auth/Settings.vue:71 +msgctxt "Content/Settings/Paragraph" msgid "You will have to update your password on your clients that use this password." msgstr "Вам потребуетÑÑ Ð¾Ð±Ð½Ð¾Ð²Ð¸Ñ‚ÑŒ пароль на Ñвоих клиентах чтобы иÑпользовать его." -#: front/src/components/favorites/List.vue:112 +#: front/src/components/moderation/FilterModal.vue:20 +msgctxt "Popup/Moderation/Paragraph" +msgid "You will not see tracks, albums and user activity linked to this artist anymore:" +msgstr "" + +#: front/src/components/auth/Signup.vue:13 +#, fuzzy +msgctxt "Content/Signup/Form/Paragraph" +msgid "Your account cannot be created." +msgstr "СпиÑок воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ñоздан" + +#: front/src/components/auth/Settings.vue:215 +#, fuzzy +msgctxt "Content/Settings/Title/Noun" +msgid "Your applications" +msgstr "Ваши уведомлениÑ" + +#: front/src/components/auth/Settings.vue:38 +msgctxt "Content/Settings/Error message.Title" +msgid "Your avatar cannot be saved" +msgstr "" + +#: front/src/components/library/EditForm.vue:3 +msgctxt "Content/Library/Paragraph" +msgid "Your edit was successfully submitted." +msgstr "" + +#: front/src/components/favorites/List.vue:116 +msgctxt "Head/Favorites/Title" msgid "Your Favorites" msgstr "Ваше избранное" -#: front/src/components/Home.vue:114 +#: front/src/components/Home.vue:109 +msgctxt "Content/Home/Title" msgid "Your music, your way" msgstr "" -#: front/src/views/Notifications.vue:7 +#: front/src/views/Notifications.vue:4 +msgctxt "Content/Notifications/Title" msgid "Your notifications" msgstr "Ваши уведомлениÑ" +#: front/src/components/auth/Settings.vue:76 +msgctxt "Content/Settings/Error message.Title" +msgid "Your password cannot be changed" +msgstr "" + #: front/src/views/auth/PasswordResetConfirm.vue:29 +msgctxt "Content/Signup/Card.Paragraph" msgid "Your password has been updated successfully." msgstr "Ваш пароль был уÑпешно обновлён." +#: front/src/components/auth/Settings.vue:14 +#, fuzzy +msgctxt "Content/Settings/Error message.Title" +msgid "Your settings can't be updateds" +msgstr "ÐаÑтройки обновлены" + #: front/src/components/auth/Settings.vue:101 +msgctxt "Popup/Settings/List item" msgid "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password" msgstr "Ваш пароль Subsonic будет изменён на новый Ñлучайный, что приведёт к отключению вÑех уÑтройÑтв, которые иÑпользовали Ñтарый пароль Subsonic" + +#: front/src/edits.js:47 +#, fuzzy +msgctxt "*/*/*/Short, Noun" +msgid "Position" +msgstr "ОпиÑание" + +#: front/src/edits.js:54 +#, fuzzy +msgctxt "Content/Track/*/Noun" +msgid "Copyright" +msgstr "Копировать" + +#: front/src/components/library/AlbumBase.vue:183 +#, fuzzy +msgctxt "Content/Album/Header.Title" +msgid "Album containing %{ count } track, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgid_plural "Album containing %{ count } tracks, by <a class=\"internal\" href=\"%{ artistUrl }\">%{ artist }</a>" +msgstr[0] "Ðльбом Ñодержит %{ count } трек от %{ artist }" +msgstr[1] "Ðльбом Ñодержит %{ count } трека от %{ artist }" +msgstr[2] "Ðльбом Ñодержит %{ count } треков от %{ artist }" + +#: front/src/components/audio/PlayButton.vue:220 +#, fuzzy +msgctxt "*/Queue/Message" +msgid "%{ count } track was added to your queue" +msgid_plural "%{ count } tracks were added to your queue" +msgstr[0] "%{ count } трек добавлен в вашу очередь" +msgstr[1] "%{ count } трека добавлено в вашу очередь" +msgstr[2] "%{ count } треков добавлено в вашу очередь" diff --git a/front/package.json b/front/package.json index adeb1fb19d6bc64a4bee8b1f099245fc285875db..22b5f3bb5ed35aebd5cb562c5f752ec2a65f8533 100644 --- a/front/package.json +++ b/front/package.json @@ -13,6 +13,7 @@ "dependencies": { "axios": "^0.18.0", "dateformat": "^3.0.3", + "diff": "^4.0.1", "django-channels": "^1.1.6", "howler": "^2.0.14", "js-logger": "^1.4.1", diff --git a/front/public/favicon.png b/front/public/favicon.png index 089442fab7cb4e68cec9de87d421b64495e3263a..df1cb56f0ef94c6112fa7a368de8d74ba1041225 100644 Binary files a/front/public/favicon.png and b/front/public/favicon.png differ diff --git a/front/scripts/contextualize.py b/front/scripts/contextualize.py new file mode 100644 index 0000000000000000000000000000000000000000..edff72145a380464f02b022c20ec0bd34dc33256 --- /dev/null +++ b/front/scripts/contextualize.py @@ -0,0 +1,82 @@ +import argparse +import polib + + +def get_missing(entries): + """ + Return a list of entries with: + - a msgcontext + - an empty msgstr + """ + for e in entries: + if e.translated(): + continue + yield e + return [] + + +def match(entries, other_entries): + """ + Given two list of po entries, will return a list of 2-tuples with + match from the second list + """ + + by_id = {} + for e in other_entries: + is_translated = bool(e.msgstr) + if not is_translated: + continue + by_id[e.msgid] = e + + matches = [] + for e in entries: + matches.append((e, by_id.get(e.msgid))) + + return matches + + +def update(new, old): + """ + Update a new po entry with translation from the first one (removing fuzzy if needed) + """ + new.msgstr = old.msgstr + new.flags = [f for f in new.flags if f != "fuzzy"] + + +def contextualize(old_po, new_po, edit=False): + old = polib.pofile(old_po) + new = polib.pofile(new_po) + missing = list(get_missing(new)) + print( + "Found {} entries with contexts and missing translations ({} total)".format( + len(missing), len(new) + ) + ) + matches = match(missing, old) + found = [m for m in matches if m[1] is not None] + print("Found {} matching entries".format(len(found))) + if edit: + print("Applying changes") + for matched, matching in found: + update(matched, matching) + new.save() + else: + print("--no-dry-run not provided, not applying change") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=""" + Given two .po file paths, it will populate empty contextualized messages + in the second one with matching message IDs from the first one, if any. + + This is especially helpful when you add some contexts on existing translated strings + but don't want to have those being retranslated. + """ + ) + parser.add_argument("old_po", help="Path of the po file to use as a source") + parser.add_argument("new_po", help="Path of the po file to populate") + parser.add_argument("--no-dry-run", action="store_true") + args = parser.parse_args() + + contextualize(old_po=args.old_po, new_po=args.new_po, edit=args.no_dry_run) diff --git a/front/scripts/i18n-populate-contextualized-strings.sh b/front/scripts/i18n-populate-contextualized-strings.sh new file mode 100755 index 0000000000000000000000000000000000000000..fa47eb4d6f4b2757bbf608b317a0ecba25f1dd63 --- /dev/null +++ b/front/scripts/i18n-populate-contextualized-strings.sh @@ -0,0 +1,21 @@ +#!/bin/bash -eu + +# Typical use: +# cp -r locales old_locales +# ./scripts/i18n-extract.sh +# ./scripts/i18n-populate-contextualized-strings.sh old_locales locales +# Then review/commit the changes + +old_locales_dir=$1 +new_locales_dir=$2 + +locales=$(tail -n +2 src/locales.js | sed -e 's/export default //' | jq '.locales[].code' | xargs echo) + +# Generate .po files for each available language. +echo $locales +for lang in $locales; do + echo "Fixing contexts for $lang…" + old_po_file=$old_locales_dir/$lang/LC_MESSAGES/app.po + new_po_file=$new_locales_dir/$lang/LC_MESSAGES/app.po + python3 ./scripts/contextualize.py $old_po_file $new_po_file --no-dry-run +done; diff --git a/front/src/App.vue b/front/src/App.vue index 02383e6cfbf41e17e45c5382f57d75e764eadc7f..5711466c5bb9cd29eaec058174a9fe7a4c5376b9 100644 --- a/front/src/App.vue +++ b/front/src/App.vue @@ -1,5 +1,5 @@ <template> - <div id="app"> + <div id="app" :key="String($store.state.instance.instanceUrl)"> <!-- here, we display custom stylesheets, if any --> <link v-for="url in customStylesheets" @@ -8,42 +8,19 @@ :href="url" :key="url" > - <div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl"> - <div class="ui padded segment"> - <h1 class="ui header"> - <translate>Choose your instance</translate> - </h1> - <form class="ui form" @submit.prevent="$store.dispatch('instance/setUrl', instanceUrl)"> - <p> - <translate>You need to select an instance in order to continue</translate> - </p> - <div class="ui action input"> - <input type="text" v-model="instanceUrl"> - <button type="submit" class="ui button"> - <translate>Submit</translate> - </button> - </div> - <p> - <translate>Suggested choices</translate> - </p> - <div class="ui bulleted list"> - <div class="ui item" v-for="url in suggestedInstances"> - <a @click="instanceUrl = url; $store.dispatch('instance/setUrl', url)">{{ url }}</a> - </div> - </div> - </form> - </div> - </div> - <template v-else> + <template> <sidebar></sidebar> + <set-instance-modal @update:show="showSetInstanceModal = $event" :show="showSetInstanceModal"></set-instance-modal> <service-messages v-if="messages.length > 0"/> <router-view :key="$route.fullPath"></router-view> <div class="ui fitted divider"></div> <app-footer :version="version" @show:shortcuts-modal="showShortcutsModal = !showShortcutsModal" + @show:set-instance-modal="showSetInstanceModal = !showSetInstanceModal" ></app-footer> <playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal> + <filter-modal v-if="$store.state.auth.authenticated"></filter-modal> <shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal> <GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal"/> </template> @@ -54,7 +31,7 @@ import Vue from 'vue' import axios from 'axios' import _ from '@/lodash' -import {mapState} from 'vuex' +import {mapState, mapGetters} from 'vuex' import { WebSocketBridge } from 'django-channels' import GlobalEvents from '@/components/utils/global-events' import Sidebar from '@/components/Sidebar' @@ -63,17 +40,21 @@ import ServiceMessages from '@/components/ServiceMessages' import moment from 'moment' import locales from './locales' import PlaylistModal from '@/components/playlists/PlaylistModal' +import FilterModal from '@/components/moderation/FilterModal' import ShortcutsModal from '@/components/ShortcutsModal' +import SetInstanceModal from '@/components/SetInstanceModal' export default { name: 'app', components: { Sidebar, AppFooter, + FilterModal, PlaylistModal, ShortcutsModal, GlobalEvents, - ServiceMessages + ServiceMessages, + SetInstanceModal, }, data () { return { @@ -81,6 +62,7 @@ export default { nodeinfo: null, instanceUrl: null, showShortcutsModal: false, + showSetInstanceModal: false, } }, created () { @@ -110,30 +92,57 @@ export default { id: 'sidebarCount', handler: this.incrementNotificationCountInSidebar }) + this.$store.commit('ui/addWebsocketEventHandler', { + eventName: 'mutation.created', + id: 'sidebarReviewEditCount', + handler: this.incrementReviewEditCountInSidebar + }) + this.$store.commit('ui/addWebsocketEventHandler', { + eventName: 'mutation.updated', + id: 'sidebarReviewEditCount', + handler: this.incrementReviewEditCountInSidebar + }) + }, + mounted () { + let self = this + + // slight hack to allow use to have internal links in <translate> tags + // while preserving router behaviour + document.documentElement.addEventListener('click', function (event) { + if (!event.target.matches('a.internal')) return; + self.$router.push(event.target.getAttribute('href')) + event.preventDefault(); + }, false); + }, destroyed () { this.$store.commit('ui/removeWebsocketEventHandler', { eventName: 'inbox.item_added', id: 'sidebarCount', }) + this.$store.commit('ui/removeWebsocketEventHandler', { + eventName: 'mutation.created', + id: 'sidebarReviewEditCount', + }) + this.$store.commit('ui/removeWebsocketEventHandler', { + eventName: 'mutation.updated', + id: 'sidebarReviewEditCount', + }) this.disconnect() }, methods: { incrementNotificationCountInSidebar (event) { this.$store.commit('ui/incrementNotifications', {type: 'inbox', count: 1}) }, + incrementReviewEditCountInSidebar (event) { + this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewEdits', value: event.pending_review_count}) + }, fetchNodeInfo () { let self = this axios.get('instance/nodeinfo/2.0/').then(response => { self.nodeinfo = response.data }) }, - switchInstance () { - let confirm = window.confirm(this.$gettext('This will erase your local data and disconnect you, do you want to continue?')) - if (confirm) { - this.$store.commit('instance/instanceUrl', null) - } - }, autodetectLanguage () { let userLanguage = navigator.language || navigator.userLanguage let available = locales.locales.map(e => { return e.code }) @@ -184,14 +193,37 @@ export default { console.log('Connected to WebSocket') }) }, + getTrackInformationText(track) { + const trackTitle = track.title + const artistName = ( + (track.artist) ? track.artist.name : track.album.artist.name) + const text = `♫ ${trackTitle} – ${artistName} ♫` + return text + }, + updateDocumentTitle() { + let parts = [] + const currentTrackPart = ( + (this.currentTrack) ? this.getTrackInformationText(this.currentTrack) + : null) + if (currentTrackPart) { + parts.push(currentTrackPart) + } + if (this.$store.state.ui.pageTitle) { + parts.push(this.$store.state.ui.pageTitle) + } + parts.push(this.$store.state.instance.settings.instance.name.value || 'Funkwhale') + document.title = parts.join(' – ') + }, }, computed: { ...mapState({ messages: state => state.ui.messages }), + ...mapGetters({ + currentTrack: 'queue/currentTrack' + }), suggestedInstances () { let instances = this.$store.state.instance.knownInstances.slice(0) - console.log('instance', instances) if (this.$store.state.instance.frontSettings.defaultServerUrl) { let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl if (!serverUrl.endsWith('/')) { @@ -200,7 +232,6 @@ export default { instances.push(serverUrl) } instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/') - console.log('HELLO', instances) return _.uniq(instances.filter((e) => {return e})) }, version () { @@ -254,9 +285,20 @@ export default { console.log('No momentjs locale available for', shortLocale) }) }) - console.log(moment.locales()) } - } + }, + 'currentTrack': { + immediate: true, + handler(newValue) { + this.updateDocumentTitle() + }, + }, + '$store.state.ui.pageTitle': { + immediate: true, + handler(newValue) { + this.updateDocumentTitle() + }, + }, } } </script> diff --git a/front/src/EmbedFrame.vue b/front/src/EmbedFrame.vue index 7987b054a44213976944251afe7c152e7043ad82..9c677e56e3a52ac1845460e86657c04fda60e873 100644 --- a/front/src/EmbedFrame.vue +++ b/front/src/EmbedFrame.vue @@ -139,7 +139,7 @@ export default { data () { return { time, - supportedTypes: ['track', 'album'], + supportedTypes: ['track', 'album', 'artist'], baseUrl: '', error: null, type: null, @@ -158,6 +158,7 @@ export default { }, created () { let params = getURLParams() + this.baseUrl = params.b || '' this.type = params.type if (this.supportedTypes.indexOf(this.type) === -1) { this.error = 'invalid_type' @@ -229,7 +230,10 @@ export default { this.fetchTrack(id) } if (type === 'album') { - this.fetchTracks({album: id, playable: true}) + this.fetchTracks({album: id, playable: true, ordering: ",disc_number,position"}) + } + if (type === 'artist') { + this.fetchTracks({artist: id, playable: true, ordering: "-release_date,disc_number,position"}) } }, play (index) { @@ -353,7 +357,11 @@ export default { this.$nextTick(() => { self.bindEvents() if (self.tracks.length > 0) { - var topPos = document.getElementById(`queue-item-${v}`).offsetTop; + let el = document.getElementById(`queue-item-${v}`); + if (!el) { + return + } + var topPos = el.offsetTop; document.getElementById('queue').scrollTop = topPos-10; } }) @@ -395,6 +403,7 @@ a:hover { } section.controls { display: flex; + width: 100%; } .cover { max-width: 120px; @@ -405,6 +414,9 @@ section.controls { flex: 1; align-self: flex-end; } +.player .plyr { + min-width: inherit; +} article .content { flex: 1; display: flex; @@ -484,10 +496,16 @@ section .plyr--audio .plyr__controls { @media screen and (max-width: 460px) { article, article .content { + position: relative; display: block; } + .content header { + padding-right: 80px; + } .cover.main { - float: right; + position: absolute; + right: 0; + top: 0; img { height: 60px; width: 60px; @@ -496,12 +514,67 @@ section .plyr--audio .plyr__controls { } @media screen and (max-width: 320px) { + .content header { + font-size: 14px; + } + .content h3 { + font-size: 15px; + } .logo-wrapper, .position-cell { display: none; } + .plyr__volume { + min-width: 70px; + } + .queue .artist { + display: none; + } +} + +@media screen and (max-width: 200px) { + .content header { + padding-right: 1em; + font-size: 13px; + } + .content h3 { + font-size: 14px; + } + .cover.main { + display: none; + } + .plyr__progress { + display: none; + } + .controls .plyr__control, + .player .plyr__control { + padding: 3px; + } + .queue td:last-child { + display: none; + } +} + +@media screen and (max-width: 170px) { + .plyr__volume { + min-width: inherit; + } } +@media screen and (max-height: 180px) { + .queue-wrapper { + display: none; + } + article .content { + display: flex; + align-items: flex-start; + width: 100%; + height: 100vh; + } + article .content header { + flex-grow: 1; + } +} // themes .dark { diff --git a/front/src/assets/logo/logo-full-500.png b/front/src/assets/logo/logo-full-500.png index 952f3803326c30ceb84c54913203015efc7fddb9..b9d5b395feb0668f9cf1b1d7d0915d38768f78c9 100644 Binary files a/front/src/assets/logo/logo-full-500.png and b/front/src/assets/logo/logo-full-500.png differ diff --git a/front/src/assets/logo/logo-full.png b/front/src/assets/logo/logo-full.png deleted file mode 100644 index 725dcc8acfcc2165751caf98b6fee964832b4677..0000000000000000000000000000000000000000 Binary files a/front/src/assets/logo/logo-full.png and /dev/null differ diff --git a/front/src/assets/logo/logo-with-text.svg b/front/src/assets/logo/logo-with-text.svg deleted file mode 100644 index 46ee3b3c628177e221fd81fe6376e3d4f1ae8cb6..0000000000000000000000000000000000000000 --- a/front/src/assets/logo/logo-with-text.svg +++ /dev/null @@ -1,34 +0,0 @@ -<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 283.5 44.7" enable-background="new 0 0 283.5 44.7" xml:space="preserve"> -<g> - <path fill="#222222" d="M3.9,16.7c0-9,3.5-12.5,14-12.5c2.2,0,5,0.2,6.5,0.5c0.8,0.2,1.5,0.8,1.5,1.5v2.7c0,0.8-0.6,1.5-1.5,1.5 - h-0.9c-1.1,0-2-0.4-3.4-0.4c-6.5,0-7.8,1.3-7.8,6.7v0.4h8.9c0.8,0,1.5,0.6,1.5,1.5v2.9c0,0.9-0.6,1.5-1.5,1.5h-8.9v15.2 - c0,0.8-0.6,1.5-1.5,1.5H5.4c-0.8,0-1.5-0.7-1.5-1.5V16.7z"/> - <path fill="#222222" d="M36.4,28.4c0,4.1,1.9,5.8,4.7,5.8c2.4,0,4.7-1.7,6.5-3.5V14.4c0-0.8,0.7-1.5,1.5-1.5h5.5 - c0.8,0,1.5,0.7,1.5,1.5v23.8c0,0.8-0.6,1.5-1.5,1.5h-5.5c-0.8,0-1.5-0.7-1.5-1.5v-1.6c-2.3,2-4.8,3.6-8.5,3.6 - c-6.5,0-11.1-3.4-11.1-11.7v-14c0-0.8,0.6-1.5,1.5-1.5h5.5c0.8,0,1.5,0.7,1.5,1.5V28.4z"/> - <path fill="#222222" d="M81.7,24.2c0-4.1-1.9-5.8-4.7-5.8c-2.4,0-4.8,1.7-6.6,3.5v16.4c0,0.8-0.6,1.5-1.5,1.5h-5.5 - c-0.9,0-1.5-0.7-1.5-1.5V14.4c0-0.8,0.6-1.5,1.5-1.5H69c0.8,0,1.5,0.7,1.5,1.5V16c2.3-2,4.8-3.6,8.6-3.6c6.5,0,11.1,3.4,11.1,11.7 - v14c0,0.8-0.6,1.5-1.5,1.5h-5.5c-0.8,0-1.5-0.7-1.5-1.5V24.2z"/> - <path fill="#222222" d="M104.5,23.5c2.9,0,4.8-1.2,5.8-3.3l2.4-5c0.6-1.4,2.1-2.3,3.5-2.3h4.6c1.3,0,1.5,1,0.8,2.3l-3.2,6.7 - c-1,2.2-3,3.9-5.2,4.4c2,0.6,3.7,2,5.2,4.4l4.2,6.7c0.8,1.3,0.4,2.3-0.8,2.3h-4.6c-1.6,0-2.9-1-3.7-2.3l-3.1-5 - c-1.3-2.2-3.6-3.2-5.8-3.2v9.1c0,0.8-0.6,1.5-1.5,1.5h-5.5c-0.8,0-1.5-0.7-1.5-1.5v-32c0-0.8,0.6-1.5,1.5-1.5h5.5 - c0.8,0,1.5,0.7,1.5,1.5V23.5z"/> - <path fill="#222222" d="M148.2,12.9c1.4,0,2.4,0.8,2.8,2.1l4,13.2l4-13.2c0.4-1.4,1.8-2.1,3.3-2.1h4.5c1.2,0,1.4,0.9,1,2.1 - l-7.4,22.5c-0.4,1.3-1.8,2.1-3.1,2.1h-3.5c-1.2,0-2.7-0.8-3.1-2.1L146,23l-4.7,14.6c-0.4,1.3-1.9,2.1-3.1,2.1h-3.5 - c-1.3,0-2.6-0.8-3.1-2.1l-7.4-22.5c-0.4-1.2-0.1-2.1,1-2.1h4.5c1.5,0,2.8,0.8,3.3,2.1l4,13.2l4-13.2c0.4-1.3,1.4-2.1,2.8-2.1H148.2 - z"/> - <path fill="#222222" d="M191.1,24.2c0-4.1-1.9-5.8-4.6-5.8c-2.4,0-4.8,1.7-6.6,3.5v16.4c0,0.8-0.6,1.5-1.5,1.5h-5.5 - c-0.8,0-1.5-0.7-1.5-1.5v-32c0-0.8,0.7-1.5,1.5-1.5h5.5c0.8,0,1.5,0.7,1.5,1.5V16c2.3-2,4.8-3.6,8.6-3.6c6.5,0,11.1,3.4,11.1,11.7 - v14c0,0.8-0.7,1.5-1.5,1.5h-5.5c-0.8,0-1.5-0.7-1.5-1.5V24.2z"/> - <path fill="#222222" d="M213.9,19.6c-0.6,0.8-1.6,1.3-2.8,1.3h-3.6c-0.8,0-1.5-0.6-1.5-1.5c0-5.2,5.2-7,13.3-7 - c7.2,0,12.9,3,12.9,10.6v15.1c0,0.8-0.7,1.5-1.5,1.5h-4.7c-0.8,0-1.5-0.7-1.5-1.5v-0.8c-2.3,1.6-5,2.8-8.9,2.8 - c-6.5,0-11.5-2.9-11.5-8.6s5-8.5,11.5-8.5h8.1c0-3.9-1.6-5.1-5-5.1C216.6,18,214.7,18.6,213.9,19.6z M223.7,32.4v-3.8h-7.5 - c-2.4,0-3.7,1.3-3.7,3c0,1.7,1.3,3,4,3C219.4,34.6,221.9,33.5,223.7,32.4z"/> - <path fill="#222222" d="M239.6,39.7c-0.8,0-1.5-0.7-1.5-1.5v-32c0-0.8,0.7-1.5,1.5-1.5h5.5c0.8,0,1.5,0.7,1.5,1.5v32 - c0,0.8-0.6,1.5-1.5,1.5H239.6z"/> - <path fill="#222222" d="M259.6,28.9c0.3,4,2.1,5.7,6.2,5.7c2.1,0,4-0.6,4.8-1.6c0.7-0.8,1.6-1.3,2.8-1.3h3.6c0.8,0,1.5,0.7,1.5,1.5 - c0,5.2-5.3,7-13.3,7c-8.9,0-14.3-4.8-14.3-13.8c0-9,5.4-13.9,14.3-13.9c8.9,0,14.2,4.8,14.2,13.6v1.4c0,0.8-0.6,1.5-1.5,1.5H259.6z - M259.6,23.7h11.4c-0.2-3.7-2-5.7-5.7-5.7C261.7,18,259.8,20,259.6,23.7z"/> -</g> -</svg> diff --git a/front/src/assets/logo/logo.png b/front/src/assets/logo/logo.png index 7cf56217190077265543b6d20d8c44115122241e..409e172e90deccd8a285c5f77cbce696b3eda7a1 100644 Binary files a/front/src/assets/logo/logo.png and b/front/src/assets/logo/logo.png differ diff --git a/front/src/assets/logo/logo.svg b/front/src/assets/logo/logo.svg index 5909fcc98b73f5b6d087aeb692c633e096918010..fe3141b68eb3a163917a98a3f5002cd4da759067 100644 --- a/front/src/assets/logo/logo.svg +++ b/front/src/assets/logo/logo.svg @@ -1,19 +1,91 @@ -<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 141.7 141.7" enable-background="new 0 0 141.7 141.7" xml:space="preserve"> -<g> - <g> - <path fill="#4082B4" d="M70.9,86.1c11.7,0,21.2-9.5,21.2-21.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,6-4.9,11-11,11 - c-6,0-11-4.9-11-11c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C49.7,76.6,59.2,86.1,70.9,86.1z"/> - <path fill="#4082B4" d="M70.9,106.1c22.7,0,41.2-18.5,41.2-41.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1 - c0,17.1-13.9,31-31,31c-17.1,0-31-13.9-31-31c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C29.6,87.6,48.1,106.1,70.9,106.1z" - /> - <path fill="#4082B4" d="M131.1,63.8h-8c-0.6,0-1.1,0.5-1.1,1.1C122,93.1,99,116,70.9,116c-28.2,0-51.1-22.9-51.1-51.1 - c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,33.8,27.5,61.3,61.3,61.3c33.8,0,61.3-27.5,61.3-61.3 - C132.2,64.3,131.7,63.8,131.1,63.8z"/> +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 252.65424 228.43591" + xml:space="preserve" + sodipodi:docname="logo-transparent.svg" + width="252.65424" + height="228.43591" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata + id="metadata25"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs23" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1043" + id="namedview21" + showgrid="true" + inkscape:zoom="1.84375" + inkscape:cx="210.72257" + inkscape:cy="88.813847" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="Calque_1"><inkscape:grid + type="xygrid" + id="grid4535" + originx="-1.1305091" + originy="-14.869552" /></sodipodi:namedview> +<style + type="text/css" + id="style2"> + .st0{fill:#FFFFFF;} + .st1{fill:#009FE3;} + .st2{fill:#3C3C3B;} +</style> + +<g + id="g18" + transform="matrix(1.3627521,0,0,1.3627521,-48.105149,-68.371499)"> + <g + id="g16"> + <g + id="g12"> + <path + class="st1" + d="m 128,157.1 c 17.7,0 32.1,-14.4 32.1,-32.1 0,-0.9 -0.8,-1.7 -1.7,-1.7 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,9.1 -7.4,16.6 -16.6,16.6 -9.1,0 -16.6,-7.4 -16.6,-16.6 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 97.6 c -0.9,0 -1.7,0.8 -1.7,1.7 0,17.8 14.4,32.1 32.1,32.1 z" + id="path6" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + <path + class="st1" + d="m 128,187.4 c 34.3,0 62.3,-28 62.3,-62.3 0,-0.9 -0.8,-1.7 -1.7,-1.7 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,25.9 -21,46.9 -46.9,46.9 C 102,172 81,151 81,125.1 c 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 67.4 c -0.9,0 -1.7,0.8 -1.7,1.7 -0.2,34.3 27.8,62.3 62.3,62.3 z" + id="path8" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + <path + class="st1" + d="m 219,123.4 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,42.6 -34.8,77.3 -77.3,77.3 -42.6,0 -77.3,-34.6 -77.3,-77.3 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 37 c -0.9,0 -1.7,0.8 -1.7,1.7 0,51.1 41.6,92.7 92.7,92.7 51.1,0 92.7,-41.6 92.7,-92.7 0,-0.9 -0.8,-1.7 -1.7,-1.7 z" + id="path10" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + </g> + <path + class="st2" + d="m 86.3,83.3 c 6.2,3.2 12.9,3.8 18.9,7.3 3.9,2.3 6.4,4.8 8.8,8.6 3.8,5.7 3.6,12.9 3.6,12.9 l 0.5,7.9 c 0,0 3,7.9 9.7,7.9 7.1,0 9.7,-7.9 9.7,-7.9 l 0.5,-7.9 c 0,0 -0.2,-7.1 3.6,-12.9 2.4,-3.8 4.8,-6.5 8.8,-8.6 6,-3.5 12.7,-4.1 18.9,-7.3 6.2,-3.2 12.2,-7.3 16.3,-13 4.1,-5.7 6,-13.3 3.8,-20 -11.8,-0.6 -25.4,0.8 -35.8,6.4 -14.5,7.7 -23.3,5 -25.9,16.5 h -0.2 c -2.6,-11.6 -11.3,-8.8 -25.9,-16.5 -10.4,-5.6 -24,-7 -35.8,-6.4 -2.3,6.7 -0.3,14.2 3.8,20 4.4,5.8 10.5,9.9 16.7,13 z" + id="path14" + inkscape:connector-curvature="0" + style="fill:#3c3c3b" /> </g> - <path fill="#222222" d="M43.3,37.3c4.1,2.1,8.5,2.5,12.5,4.8c2.6,1.5,4.2,3.2,5.8,5.7c2.5,3.8,2.4,8.5,2.4,8.5l0.3,5.2 - c0,0,2,5.2,6.4,5.2c4.7,0,6.4-5.2,6.4-5.2l0.3-5.2c0,0-0.1-4.7,2.4-8.5c1.6-2.5,3.2-4.3,5.8-5.7c4-2.3,8.4-2.7,12.5-4.8 - c4.1-2.1,8.1-4.8,10.8-8.6c2.7-3.8,4-8.8,2.5-13.2c-7.8-0.4-16.8,0.5-23.7,4.2c-9.6,5.1-15.4,3.3-17.1,10.9h-0.1 - c-1.7-7.7-7.5-5.8-17.1-10.9c-6.9-3.7-15.9-4.6-23.7-4.2c-1.5,4.4-0.2,9.4,2.5,13.2C35.2,32.5,39.2,35.2,43.3,37.3z"/> </g> -</svg> +</svg> \ No newline at end of file diff --git a/front/src/assets/logo/logos.png b/front/src/assets/logo/logos.png deleted file mode 100644 index d73f4c36bb0cea8f8dd89e8696f9abd746fac518..0000000000000000000000000000000000000000 Binary files a/front/src/assets/logo/logos.png and /dev/null differ diff --git a/front/src/assets/logo/text.svg b/front/src/assets/logo/text.svg new file mode 100644 index 0000000000000000000000000000000000000000..b09a620def51a6c12a052b078c575cfcce4317aa --- /dev/null +++ b/front/src/assets/logo/text.svg @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="271.66678mm" + height="53.491325mm" + viewBox="0 0 271.66678 53.491325" + version="1.1" + id="svg4600" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="with-text.svg"> + <defs + id="defs4594"> + + + + + + + + + +</defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.7" + inkscape:cx="392.13629" + inkscape:cy="-23.988564" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1920" + inkscape:window-height="1043" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata4597"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(34.652951,-109.48195)"> + <g + id="g5240"> + <g + id="g18" + transform="matrix(0.31910664,0,0,0.31910664,-45.917415,93.471844)"> + <g + id="g16"> + <g + id="g12"> + <path + class="st1" + d="m 128,157.1 c 17.7,0 32.1,-14.4 32.1,-32.1 0,-0.9 -0.8,-1.7 -1.7,-1.7 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,9.1 -7.4,16.6 -16.6,16.6 -9.1,0 -16.6,-7.4 -16.6,-16.6 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 97.6 c -0.9,0 -1.7,0.8 -1.7,1.7 0,17.8 14.4,32.1 32.1,32.1 z" + id="path6" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + + <path + class="st1" + d="m 128,187.4 c 34.3,0 62.3,-28 62.3,-62.3 0,-0.9 -0.8,-1.7 -1.7,-1.7 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,25.9 -21,46.9 -46.9,46.9 C 102,172 81,151 81,125.1 c 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 67.4 c -0.9,0 -1.7,0.8 -1.7,1.7 -0.2,34.3 27.8,62.3 62.3,62.3 z" + id="path8" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + + <path + class="st1" + d="m 219,123.4 h -12.1 c -0.9,0 -1.7,0.8 -1.7,1.7 0,42.6 -34.8,77.3 -77.3,77.3 -42.6,0 -77.3,-34.6 -77.3,-77.3 0,-0.9 -0.8,-1.7 -1.7,-1.7 H 37 c -0.9,0 -1.7,0.8 -1.7,1.7 0,51.1 41.6,92.7 92.7,92.7 51.1,0 92.7,-41.6 92.7,-92.7 0,-0.9 -0.8,-1.7 -1.7,-1.7 z" + id="path10" + inkscape:connector-curvature="0" + style="fill:#009fe3" /> + + </g> + + <path + class="st2" + d="m 86.3,83.3 c 6.2,3.2 12.9,3.8 18.9,7.3 3.9,2.3 6.4,4.8 8.8,8.6 3.8,5.7 3.6,12.9 3.6,12.9 l 0.5,7.9 c 0,0 3,7.9 9.7,7.9 7.1,0 9.7,-7.9 9.7,-7.9 l 0.5,-7.9 c 0,0 -0.2,-7.1 3.6,-12.9 2.4,-3.8 4.8,-6.5 8.8,-8.6 6,-3.5 12.7,-4.1 18.9,-7.3 6.2,-3.2 12.2,-7.3 16.3,-13 4.1,-5.7 6,-13.3 3.8,-20 -11.8,-0.6 -25.4,0.8 -35.8,6.4 -14.5,7.7 -23.3,5 -25.9,16.5 h -0.2 c -2.6,-11.6 -11.3,-8.8 -25.9,-16.5 -10.4,-5.6 -24,-7 -35.8,-6.4 -2.3,6.7 -0.3,14.2 3.8,20 4.4,5.8 10.5,9.9 16.7,13 z" + id="path14" + inkscape:connector-curvature="0" + style="fill:#3c3c3b" /> + + </g> + +</g> + <g + transform="translate(-0.75595238,-0.75595238)" + id="g5221"> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 32.845914,132.89252 c 0,-6.69443 2.603389,-9.29781 10.413554,-9.29781 1.636415,0 3.719126,0.14876 4.834864,0.37191 0.59506,0.14876 1.115738,0.59506 1.115738,1.11574 v 2.00832 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -0.669443 c -0.818208,0 -1.48765,-0.29753 -2.529006,-0.29753 -4.834864,0 -5.801837,0.96698 -5.801837,4.98363 v 0.29753 h 6.620045 c 0.59506,0 1.115738,0.4463 1.115738,1.11574 v 2.15709 c 0,0.66945 -0.446295,1.11574 -1.115738,1.11574 h -6.620045 v 11.30614 c 0,0.59506 -0.446295,1.11574 -1.115737,1.11574 h -4.016657 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 z" + id="path5166" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 57.020235,141.59528 c 0,3.04968 1.413268,4.31418 3.495978,4.31418 1.785181,0 3.495979,-1.2645 4.834864,-2.60339 v -12.12435 c 0,-0.59506 0.520678,-1.11573 1.115738,-1.11573 h 4.091039 c 0.59506,0 1.115738,0.52067 1.115738,1.11573 v 17.70304 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -4.091039 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 v -1.19012 c -1.710798,1.48765 -3.570361,2.67777 -6.322514,2.67777 -4.834864,0 -8.25646,-2.529 -8.25646,-8.70275 v -10.41355 c 0,-0.59506 0.446295,-1.11574 1.115738,-1.11574 h 4.091038 c 0.595061,0 1.115738,0.52068 1.115738,1.11574 v 10.33917 z" + id="path5168" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 90.715518,138.47121 c 0,-3.04968 -1.413268,-4.31419 -3.495979,-4.31419 -1.78518,0 -3.570361,1.26451 -4.909246,2.60339 v 12.19874 c 0,0.59506 -0.446295,1.11573 -1.115738,1.11573 h -4.091039 c -0.669442,0 -1.115738,-0.52067 -1.115738,-1.11573 v -17.77743 c 0,-0.59506 0.446296,-1.11573 1.115738,-1.11573 h 4.165422 c 0.59506,0 1.115737,0.52067 1.115737,1.11573 v 1.19012 c 1.710798,-1.48765 3.570362,-2.67777 6.396897,-2.67777 4.834865,0 8.256461,2.52901 8.256461,8.70276 v 10.41355 c 0,0.59506 -0.446295,1.11574 -1.115738,1.11574 h -4.091039 c -0.59506,0 -1.115738,-0.52068 -1.115738,-1.11574 z" + id="path5170" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 107.67473,137.95053 c 2.1571,0 3.57036,-0.89259 4.31419,-2.45462 l 1.78518,-3.71913 c 0.4463,-1.04135 1.56203,-1.71079 2.60339,-1.71079 h 3.42159 c 0.96698,0 1.11574,0.74382 0.59507,1.71079 l -2.38025,4.98363 c -0.74382,1.63642 -2.23147,2.90092 -3.86789,3.27283 1.48765,0.4463 2.75216,1.48765 3.86789,3.27283 l 3.12407,4.98363 c 0.59506,0.96698 0.29753,1.7108 -0.59506,1.7108 h -3.4216 c -1.19012,0 -2.15709,-0.74382 -2.75215,-1.7108 l -2.30586,-3.71912 c -0.96697,-1.63642 -2.67777,-2.38024 -4.31418,-2.38024 v 6.76881 c 0,0.59506 -0.4463,1.11573 -1.11574,1.11573 h -4.09104 c -0.59506,0 -1.11574,-0.52067 -1.11574,-1.11573 v -23.80241 c 0,-0.59506 0.4463,-1.11574 1.11574,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 12.79379 z" + id="path5172" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 140.1799,130.06599 c 1.04135,0 1.78518,0.59506 2.08271,1.56203 l 2.9753,9.81849 2.9753,-9.81849 c 0.29753,-1.04136 1.33888,-1.56203 2.45462,-1.56203 h 3.34722 c 0.89259,0 1.04135,0.66944 0.74382,1.56203 l -5.50431,16.73607 c -0.29753,0.96697 -1.33888,1.56203 -2.30585,1.56203 h -2.60339 c -0.89259,0 -2.00833,-0.59506 -2.30586,-1.56203 l -3.49598,-10.78547 -3.49598,10.85985 c -0.29753,0.96697 -1.41327,1.56203 -2.30586,1.56203 h -2.60338 c -0.96698,0 -1.93395,-0.59506 -2.30586,-1.56203 l -5.50431,-16.73607 c -0.29753,-0.89259 -0.0744,-1.56203 0.74383,-1.56203 h 3.34721 c 1.11574,0 2.08271,0.59506 2.45462,1.56203 l 2.9753,9.81849 2.9753,-9.81849 c 0.29753,-0.96697 1.04136,-1.56203 2.08272,-1.56203 h 3.27283 z" + id="path5174" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 172.09,138.47121 c 0,-3.04968 -1.41327,-4.31419 -3.4216,-4.31419 -1.78518,0 -3.57036,1.26451 -4.90924,2.60339 v 12.19874 c 0,0.59506 -0.4463,1.11573 -1.11574,1.11573 h -4.09104 c -0.59506,0 -1.11574,-0.52067 -1.11574,-1.11573 v -23.80241 c 0,-0.59506 0.52068,-1.11574 1.11574,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 7.2151 c 1.71079,-1.48765 3.57036,-2.67777 6.39689,-2.67777 4.83487,0 8.25646,2.52901 8.25646,8.70276 v 10.41355 c 0,0.59506 -0.52067,1.11574 -1.11573,1.11574 h -4.09104 c -0.59506,0 -1.11574,-0.52068 -1.11574,-1.11574 z" + id="path5176" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 189.04921,135.04961 c -0.44629,0.59506 -1.19012,0.96698 -2.08271,0.96698 h -2.67777 c -0.59506,0 -1.11573,-0.4463 -1.11573,-1.11574 0,-3.86789 3.86789,-5.20678 9.89287,-5.20678 5.35554,0 9.59535,2.23148 9.59535,7.88455 v 11.23176 c 0,0.59506 -0.52068,1.11574 -1.11574,1.11574 h -3.49598 c -0.59506,0 -1.11574,-0.52068 -1.11574,-1.11574 v -0.59506 c -1.7108,1.19012 -3.71912,2.08271 -6.62004,2.08271 -4.83487,0 -8.55399,-2.15709 -8.55399,-6.39689 0,-4.23981 3.71912,-6.32252 8.55399,-6.32252 h 6.02498 c 0,-2.90092 -1.19012,-3.79351 -3.71912,-3.79351 -1.56204,0.0744 -2.9753,0.52068 -3.57037,1.2645 z m 7.28949,9.52097 v -2.82654 h -5.57869 c -1.78518,0 -2.75215,0.96697 -2.75215,2.23148 0,1.2645 0.96697,2.23147 2.9753,2.23147 2.15709,0 4.01666,-0.8182 5.35554,-1.63641 z" + id="path5178" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 208.16552,150.0005 c -0.59506,0 -1.11573,-0.52068 -1.11573,-1.11574 v -23.8024 c 0,-0.59506 0.52067,-1.11574 1.11573,-1.11574 h 4.09104 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 v 23.8024 c 0,0.59506 -0.44629,1.11574 -1.11574,1.11574 z" + id="path5180" /> + <path + style="fill:#3c3c3b;fill-opacity:1;stroke-width:0.74382526" + inkscape:connector-curvature="0" + d="m 223.04203,141.96719 c 0.22315,2.9753 1.56203,4.2398 4.61171,4.2398 1.56204,0 2.97531,-0.44629 3.57037,-1.19012 0.52067,-0.59506 1.19012,-0.96697 2.08271,-0.96697 h 2.67777 c 0.59506,0 1.11574,0.52068 1.11574,1.11574 0,3.86789 -3.94228,5.20677 -9.89288,5.20677 -6.62004,0 -10.6367,-3.57036 -10.6367,-10.26478 0,-6.69443 4.01666,-10.33917 10.6367,-10.33917 6.62004,0 10.56232,3.57036 10.56232,10.11602 v 1.04135 c 0,0.59506 -0.4463,1.11574 -1.11574,1.11574 h -13.612 z m 0,-3.86789 h 8.47961 c -0.14877,-2.75216 -1.48765,-4.23981 -4.23981,-4.23981 -2.67777,0 -4.09104,1.48765 -4.2398,4.23981 z" + id="path5182" /> + </g> + </g> + </g> + <style + id="style2" + type="text/css"> + .st0{fill:#FFFFFF;} + .st1{fill:#009FE3;} + .st2{fill:#3C3C3B;} +</style> +</svg> diff --git a/front/src/components/About.vue b/front/src/components/About.vue index cfffb7b6a2de69beec727d6a230ec4df00941d64..87f741aa819f38ba84c856522979f25e6f6016d1 100644 --- a/front/src/components/About.vue +++ b/front/src/components/About.vue @@ -3,10 +3,10 @@ <section class="ui vertical center aligned stripe segment"> <div class="ui text container"> <h1 class="ui huge header"> - <translate v-if="instance.name.value" :translate-params="{instance: instance.name.value}"> + <span v-translate="{instance: instance.name.value}" translate-context="Content/About/Title/Short, Noun" v-if="instance.name.value" :translate-params="{instance: instance.name.value}"> About %{ instance } - </translate> - <translate v-else>About this instance</translate> + </span> + <translate translate-context="Content/About/Title" v-else>About this instance</translate> </h1> <stats></stats> </div> @@ -15,12 +15,12 @@ <div class="ui middle aligned stackable text container"> <p - v-if="!instance.short_description.value && !instance.long_description.value"><translate>Unfortunately, owners of this instance did not yet take the time to complete this page.</translate></p> + v-if="!instance.short_description.value && !instance.long_description.value"><translate translate-context="Content/About/Paragraph">Unfortunately, the owners of this instance did not yet take the time to complete this page.</translate></p> <router-link class="ui button" v-if="$store.state.auth.availablePermissions['settings']" :to="{path: '/manage/settings', hash: 'instance'}"> - <i class="pencil icon"></i><translate>Edit instance info</translate> + <i class="pencil icon"></i><translate translate-context="Content/Settings/Button.Label/Verb">Edit instance info</translate> </router-link> <div class="ui hidden divider"></div> </div> @@ -64,7 +64,7 @@ export default { }), labels() { return { - title: this.$gettext("About this instance") + title: this.$pgettext('Content/About/Title', "About this instance") } } } diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue index 2767167e2fc30cf097047e61b4915df593884b06..2d06b50c768e368c538b15bbcf6b33a00c6b42ab 100644 --- a/front/src/components/Footer.vue +++ b/front/src/components/Footer.vue @@ -3,23 +3,23 @@ <div class="ui container"> <div class="ui stackable equal height stackable grid"> <section class="four wide column"> - <h4 v-translate class="ui header"> - <translate :translate-params="{instanceName: instanceHostname}" >About %{instanceName}</translate> + <h4 class="ui header"> + <translate translate-context="Footer/About/Title" :translate-params="{instanceName: instanceHostname}" >About %{instanceName}</translate> </h4> <div class="ui link list"> <router-link class="item" to="/about"> - <translate>About page</translate> + <translate translate-context="Footer/About/List item.Link">About page</translate> </router-link> - <div class="item" v-if="version"> - <translate :translate-params="{version: version}" >Version %{version}</translate> + <a v-if="version" class="item" href="https://docs.funkwhale.audio/changelog.html" target="_blank"> + <translate translate-context="Footer/*/List item" :translate-params="{version: version}" >Version %{version}</translate> + </a> + <div role="button" class="item" @click="$emit('show:set-instance-modal')" > + <translate translate-context="Footer/*/List item.Link">Use another instance</translate> </div> - <a @click="switchInstance" class="item" > - <translate>Use another instance</translate> - </a> </div> <div class="ui form"> <div class="ui field"> - <label><translate>Change language</translate></label> + <label><translate translate-context="Footer/Settings/Dropdown.Label/Short, Verb">Change language</translate></label> <select class="ui dropdown" :value="$language.current" @change="$store.commit('ui/currentLanguage', $event.target.value)"> <option v-for="(language, key) in $language.available" :key="key" :value="key">{{ language }}</option> </select> @@ -27,31 +27,31 @@ </div> </section> <section class="four wide column"> - <h4 v-translate class="ui header">Using Funkwhale</h4> + <h4 v-translate class="ui header" translate-context="Footer/*/Title">Using Funkwhale</h4> <div class="ui link list"> - <a href="https://docs.funkwhale.audio" class="item" target="_blank"><translate>Documentation</translate></a> - <a href="https://docs.funkwhale.audio/users/apps.html" class="item" target="_blank"><translate>Mobile and desktop apps</translate></a> - <div role="button" class="item" @click="$emit('show:shortcuts-modal')"><translate>Keyboard shortcuts</translate></div> + <a href="https://docs.funkwhale.audio" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate></a> + <a href="https://funkwhale.audio/apps" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Mobile and desktop apps</translate></a> + <div role="button" class="item" @click="$emit('show:shortcuts-modal')"><translate translate-context="*/*/*/Noun">Keyboard shortcuts</translate></div> </div> </section> <section class="four wide column"> - <h4 v-translate class="ui header">Getting help</h4> + <h4 v-translate translate-context="Footer/*/Link" class="ui header">Getting help</h4> <div class="ui link list"> - <a href="https://socialhub.network/c/funkwhale" class="item" target="_blank"><translate>Support forum</translate></a> - <a href="https://riot.im/app/#/room/#funkwhale-troubleshooting:matrix.org" class="item" target="_blank"><translate>Chat room</translate></a> - <a href="https://dev.funkwhale.audio/funkwhale/funkwhale/issues" class="item" target="_blank"><translate>Issue tracker</translate></a> + <a href="https://governance.funkwhale.audio/g/kQgxNq15/funkwhale" class="item" target="_blank"><translate translate-context="Footer/*/Listitem.Link">Support forum</translate></a> + <a href="https://riot.im/app/#/room/#funkwhale-troubleshooting:matrix.org" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Chat room</translate></a> + <a href="https://dev.funkwhale.audio/funkwhale/funkwhale/issues" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Issue tracker</translate></a> </div> </section> <section class="four wide column"> - <h4 v-translate class="ui header">About Funkwhale</h4> + <h4 v-translate class="ui header" translate-context="Footer/*/Title/Short">About Funkwhale</h4> <div class="ui link list"> - <a href="https://funkwhale.audio" class="item" target="_blank"><translate>Official website</translate></a> - <a href="https://contribute.funkwhale.audio" class="item" target="_blank"><translate>Contribute</translate></a> - <a href="https://dev.funkwhale.audio/funkwhale/funkwhale" class="item" target="_blank"><translate>Source code</translate></a> + <a href="https://funkwhale.audio" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Official website</translate></a> + <a href="https://contribute.funkwhale.audio" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Contribute</translate></a> + <a href="https://dev.funkwhale.audio/funkwhale/funkwhale" class="item" target="_blank"><translate translate-context="Footer/*/List item.Link">Source code</translate></a> </div> <div class="ui hidden divider"></div> <p> - <translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate> + <translate translate-context="Footer/*/List item.Link">The funkwhale logo was kindly designed and provided by Francis Gading.</translate> </p> </section> </div> @@ -66,18 +66,6 @@ import axios from 'axios' export default { props: ["version"], - methods: { - switchInstance() { - let confirm = window.confirm( - this.$gettext( - "This will erase your local data and disconnect you, do you want to continue?" - ) - ) - if (confirm) { - this.$store.commit("instance/instanceUrl", null) - } - } - }, computed: { ...mapState({ messages: state => state.ui.messages @@ -88,13 +76,6 @@ export default { parser.href = url return parser.hostname }, - suggestedInstances() { - let instances = [ - this.$store.getters["instance/defaultUrl"](), - "https://demo.funkwhale.audio" - ] - return instances - } } } </script> @@ -102,4 +83,8 @@ export default { footer p { color: grey; } + +footer#footer div.item:hover { + color: rgba(0, 0, 0, 0.87); +} </style> diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue index bdca00005221abce7058d08dd9141d3cd3175867..b88369d952a816c0a4b9acf7db0555b8290049e3 100644 --- a/front/src/components/Home.vue +++ b/front/src/components/Home.vue @@ -3,15 +3,15 @@ <section class="ui vertical center aligned stripe segment"> <div class="ui text container"> <h1 class="ui huge header"> - <translate>Welcome on Funkwhale</translate> + <translate translate-context="Content/Home/Title/Verb">Welcome on Funkwhale</translate> </h1> - <p><translate>We think listening to music should be simple.</translate></p> + <p><translate translate-context="Content/Home/Title">We think listening to music should be simple.</translate></p> <router-link class="ui icon button" to="/about"> <i class="info icon"></i> - <translate>Learn more about this instance</translate> + <translate translate-context="Content/Home/Button.Label/Verb">Learn more about this instance</translate> </router-link> <router-link class="ui icon teal button" to="/library"> - <translate>Get me to the library</translate> + <translate translate-context="Content/Home/Button.Label/Verb">Get me to the library</translate> <i class="right arrow icon"></i> </router-link> </div> @@ -22,9 +22,9 @@ <div class="row"> <div class="eight wide left floated column"> <h2 class="ui header"> - <translate>Why funkwhale?</translate> + <translate translate-context="Content/Home/Title">Why funkwhale?</translate> </h2> - <p><translate>That's simple: we loved Grooveshark and we want to build something even better.</translate></p> + <p><translate translate-context="Content/Home/Paragraph">That's simple: we loved Grooveshark and we want to build something even better.</translate></p> </div> <div class="four wide left floated column"> <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 hidden divider"></div> <h2 class="ui header"> - <translate>Unlimited music</translate> + <translate translate-context="Content/Home/Title">Unlimited music</translate> </h2> - <p><translate>Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.</translate></p> + <p><translate translate-context="Content/Home/Paragraph">Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.</translate></p> <div class="ui list"> <div class="item"> <i class="sound icon"></i> <div class="content"> - <translate>Click once, listen for hours using built-in radios</translate> + <translate translate-context="Content/Home/List item/Verb">Click once, listen for hours using built-in radios</translate> </div> </div> <div class="item"> <i class="heart icon"></i> <div class="content"> - <translate>Keep a track of your favorite songs</translate> + <translate translate-context="Content/Home/List item/Verb">Keep a track of your favorite songs</translate> </div> </div> <div class="item"> <i class="list icon"></i> <div class="content"> - <translate>Playlists? We got them</translate> + <translate translate-context="Content/Home/List item">Playlists? We got them</translate> </div> </div> </div> @@ -62,28 +62,23 @@ <div class="ui middle aligned stackable text container"> <div class="ui hidden divider"></div> <h2 class="ui header"> - <translate>Clean library</translate> + <translate translate-context="Content/Home/Title">A clean library</translate> </h2> - <p><translate>Funkwhale takes care of handling your music</translate>.</p> + <p><translate translate-context="Content/Home/Paragraph">Funkwhale takes care of handling your music</translate>.</p> <div class="ui list"> - <div class="item"> - <i class="download icon"></i> - <div class="content"> - <translate>Import music from various platforms, such as YouTube or SoundCloud</translate> - </div> - </div> <div class="item"> <i class="tag icon"></i> <div class="content" - v-translate="{url: musicbrainzUrl}"> + v-translate="{url: musicbrainzUrl}" + translate-context="Content/Home/List item/Verb"> Get quality metadata about your music thanks to <a href="%{ url }" target="_blank">MusicBrainz</a> </div> </div> <div class="item"> <i class="plus icon"></i> <div class="content"> - <translate>Covers, lyrics, our goal is to have them all ;)</translate> + <translate translate-context="Content/Home/List item">Covers, lyrics, our goal is to have them all ;)</translate> </div> </div> </div> @@ -91,20 +86,20 @@ <div class="ui middle aligned stackable text container"> <div class="ui hidden divider"></div> <h2 class="ui header"> - <translate>Easy to use</translate> + <translate translate-context="Content/Home/Title">Easy to use</translate> </h2> - <p><translate>Funkwhale is dead simple to use.</translate></p> + <p><translate translate-context="Content/Home/Paragraph">Funkwhale is dead simple to use.</translate></p> <div class="ui list"> <div class="item"> <i class="book icon"></i> <div class="content"> - <translate>No add-ons, no plugins : you only need a web library</translate> + <translate translate-context="Content/Home/List item">No add-ons, no plugins : you only need a web library</translate> </div> </div> <div class="item"> <i class="wizard icon"></i> <div class="content"> - <translate>Access your music from a clean interface that focus on what really matters</translate> + <translate translate-context="Content/Home/List item">Access your music from a clean interface that focuses on what really matters</translate> </div> </div> </div> @@ -112,26 +107,26 @@ <div class="ui middle aligned stackable text container"> <div class="ui hidden divider"></div> <h2 class="ui header"> - <translate>Your music, your way</translate> + <translate translate-context="Content/Home/Title">Your music, your way</translate> </h2> - <p><translate>Funkwhale is free and gives you control on your music.</translate></p> + <p><translate translate-context="Content/Home/Paragraph">Funkwhale is free and gives you control on your music.</translate></p> <div class="ui list"> <div class="item"> <i class="smile icon"></i> <div class="content"> - <translate>The plaform is free and open-source, you can install it and modify it without worries</translate> + <translate translate-context="Content/Home/List item">The plaform is free and open-source, you can install it and modify it without worries</translate> </div> </div> <div class="item"> <i class="protect icon"></i> <div class="content"> - <translate>We do not track you or bother you with ads</translate> + <translate translate-context="Content/Home/List item">We do not track you or bother you with ads</translate> </div> </div> <div class="item"> <i class="users icon"></i> <div class="content"> - <translate>You can invite friends and family to your instance so they can enjoy your music</translate> + <translate translate-context="Content/Home/List item">You can invite friends and family to your instance so they can enjoy your music</translate> </div> </div> </div> @@ -150,7 +145,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Welcome") + title: this.$pgettext('Head/Home/Title', "Welcome") } } } diff --git a/front/src/components/Logo.vue b/front/src/components/Logo.vue index ff87dc299885acf39c57a67967d32ab3b3b87193..9785437c975b8e19669eb8da3296553a2fb58224 100644 --- a/front/src/components/Logo.vue +++ b/front/src/components/Logo.vue @@ -1,21 +1,21 @@ <template> <svg version="1.1" id="layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 141.7 141.7" enable-background="new 0 0 141.7 141.7" xml:space="preserve"> + viewBox="0 0 141.7 141.7" enable-background="new 0 0 141.7 141.7" xml:space="preserve"> <g> - <g> - <path :fill="fill" d="M70.9,86.1c11.7,0,21.2-9.5,21.2-21.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,6-4.9,11-11,11 - c-6,0-11-4.9-11-11c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C49.7,76.6,59.2,86.1,70.9,86.1z"/> - <path :fill="fill" d="M70.9,106.1c22.7,0,41.2-18.5,41.2-41.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1 - c0,17.1-13.9,31-31,31c-17.1,0-31-13.9-31-31c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C29.6,87.6,48.1,106.1,70.9,106.1z" - /> - <path :fill="fill" d="M131.1,63.8h-8c-0.6,0-1.1,0.5-1.1,1.1C122,93.1,99,116,70.9,116c-28.2,0-51.1-22.9-51.1-51.1 - c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,33.8,27.5,61.3,61.3,61.3c33.8,0,61.3-27.5,61.3-61.3 - C132.2,64.3,131.7,63.8,131.1,63.8z"/> - </g> - <path :fill="fill" d="M43.3,37.3c4.1,2.1,8.5,2.5,12.5,4.8c2.6,1.5,4.2,3.2,5.8,5.7c2.5,3.8,2.4,8.5,2.4,8.5l0.3,5.2 - c0,0,2,5.2,6.4,5.2c4.7,0,6.4-5.2,6.4-5.2l0.3-5.2c0,0-0.1-4.7,2.4-8.5c1.6-2.5,3.2-4.3,5.8-5.7c4-2.3,8.4-2.7,12.5-4.8 - c4.1-2.1,8.1-4.8,10.8-8.6c2.7-3.8,4-8.8,2.5-13.2c-7.8-0.4-16.8,0.5-23.7,4.2c-9.6,5.1-15.4,3.3-17.1,10.9h-0.1 - c-1.7-7.7-7.5-5.8-17.1-10.9c-6.9-3.7-15.9-4.6-23.7-4.2c-1.5,4.4-0.2,9.4,2.5,13.2C35.2,32.5,39.2,35.2,43.3,37.3z"/> + <g> + <path :fill="fill" d="M70.9,86.1c11.7,0,21.2-9.5,21.2-21.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,6-4.9,11-11,11 + c-6,0-11-4.9-11-11c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C49.7,76.6,59.2,86.1,70.9,86.1z"/> + <path :fill="fill" d="M70.9,106.1c22.7,0,41.2-18.5,41.2-41.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1 + c0,17.1-13.9,31-31,31c-17.1,0-31-13.9-31-31c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C29.6,87.6,48.1,106.1,70.9,106.1z" + /> + <path :fill="fill" d="M131.1,63.8h-8c-0.6,0-1.1,0.5-1.1,1.1C122,93.1,99,116,70.9,116c-28.2,0-51.1-22.9-51.1-51.1 + c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,33.8,27.5,61.3,61.3,61.3c33.8,0,61.3-27.5,61.3-61.3 + C132.2,64.3,131.7,63.8,131.1,63.8z"/> + </g> + <path :fill="fill" d="M43.3,37.3c4.1,2.1,8.5,2.5,12.5,4.8c2.6,1.5,4.2,3.2,5.8,5.7c2.5,3.8,2.4,8.5,2.4,8.5l0.3,5.2 + c0,0,2,5.2,6.4,5.2c4.7,0,6.4-5.2,6.4-5.2l0.3-5.2c0,0-0.1-4.7,2.4-8.5c1.6-2.5,3.2-4.3,5.8-5.7c4-2.3,8.4-2.7,12.5-4.8 + c4.1-2.1,8.1-4.8,10.8-8.6c2.7-3.8,4-8.8,2.5-13.2c-7.8-0.4-16.8,0.5-23.7,4.2c-9.6,5.1-15.4,3.3-17.1,10.9h-0.1 + c-1.7-7.7-7.5-5.8-17.1-10.9c-6.9-3.7-15.9-4.6-23.7-4.2c-1.5,4.4-0.2,9.4,2.5,13.2C35.2,32.5,39.2,35.2,43.3,37.3z"/> </g> </svg> diff --git a/front/src/components/PageNotFound.vue b/front/src/components/PageNotFound.vue index ef4e66f456cb03e63ad4dfcf441a22bcc1536735..4dce665bbefaf0cc0957f8200ae939a7e641c5fd 100644 --- a/front/src/components/PageNotFound.vue +++ b/front/src/components/PageNotFound.vue @@ -5,14 +5,14 @@ <h1 class="ui huge header"> <i class="warning icon"></i> <div class="content"> - <translate>Page not found!</translate> + <translate translate-context="Content/*/Title">Page not found!</translate> </div> </h1> - <p><translate>We're sorry, the page you asked for does not exist:</translate></p> + <p><translate translate-context="Content/*/Paragraph">Sorry, the page you asked for does not exist:</translate></p> <a :href="path">{{ path }}</a> <div class="ui hidden divider"></div> - <router-link class="ui icon button" to="/"> - <translate>Go to home page</translate> + <router-link class="ui icon labeled right button" to="/"> + <translate translate-context="Content/*/Button.Label/Verb">Go to home page</translate> <i class="right arrow icon"></i> </router-link> </div> @@ -30,7 +30,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Page Not Found") + title: this.$pgettext('Head/*/Title', "Page Not Found") } } } diff --git a/front/src/components/Pagination.vue b/front/src/components/Pagination.vue index 963b000a81b490d468a439be130893b66b5a7084..4820822380b0b44b084d7569f476782ec77bc324 100644 --- a/front/src/components/Pagination.vue +++ b/front/src/components/Pagination.vue @@ -36,7 +36,7 @@ export default { computed: { labels() { return { - pagination: this.$gettext("Pagination") + pagination: this.$pgettext('Content/*/Hidden text/Noun', "Pagination") } }, pages: function() { diff --git a/front/src/components/SetInstanceModal.vue b/front/src/components/SetInstanceModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..b2c278564a8f453cb729f5d65def4ad29d73bd93 --- /dev/null +++ b/front/src/components/SetInstanceModal.vue @@ -0,0 +1,147 @@ +<template> + <modal @update:show="$emit('update:show', $event); isError = false" :show="show"> + <div class="header"><translate translate-context="Popup/Instance/Title">Choose your instance</translate></div> + <div class="scrolling content"> + <div v-if="isError" class="ui negative message"> + <div class="header"><translate translate-context="Popup/Instance/Error message.Title">It is not possible to connect to the given URL</translate></div> + <ul class="list"> + <li><translate translate-context="Popup/Instance/Error message.List item">The server might be down</translate></li> + <li><translate translate-context="Popup/Instance/Error message.List item">The given address is not a Funkwhale server</translate></li> + </ul> + </div> + <form class="ui form" @submit.prevent="checkAndSwitch(instanceUrl)"> + <p v-if="$store.state.instance.instanceUrl" class="description" translate-context="Popup/Login/Paragraph" v-translate="{url: $store.state.instance.instanceUrl, hostname: instanceHostname }"> + You are currently connected to <a href="%{ url }" target="_blank">%{ hostname } <i class="external icon"></i></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted. + </p> + <p v-else> + <translate translate-context="Popup/Instance/Paragraph">To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices.</translate> + </p> + <div class="field"> + <label><translate translate-context="Popup/Instance/Input.Label/Noun">Instance URL</translate></label> + <div class="ui action input"> + <input type="text" v-model="instanceUrl" placeholder="https://funkwhale.server"> + <button type="submit" :class="['ui', 'icon', {loading: isLoading}, 'button']"> + <translate translate-context="*/*/Button.Label/Verb">Submit</translate> + </button> + </div> + </div> + </form> + <div class="ui hidden divider"></div> + <form class="ui form" @submit.prevent=""> + <div class="field"> + <label><translate translate-context="Popup/Instance/List.Label">Suggested choices</translate></label> + <button v-for="url in suggestedInstances" @click="checkAndSwitch(url)" class="ui basic button">{{ url }}</button> + </div> + </form> + </div> + <div class="actions"> + <div class="ui cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></div> + </div> + </modal> +</template> + +<script> +import Modal from '@/components/semantic/Modal' +import axios from 'axios' +import _ from "@/lodash" + +export default { + props: ['show'], + components: { + Modal, + }, + data() { + return { + instanceUrl: null, + nodeinfo: null, + isError: false, + isLoading: false, + path: 'api/v1/instance/nodeinfo/2.0/', + } + }, + methods: { + fetchNodeInfo () { + let self = this + axios.get('instance/nodeinfo/2.0/').then(response => { + self.nodeinfo = response.data + }) + }, + fetchUrl (url) { + let urlFetch = url + if (!urlFetch.endsWith('/')) { + urlFetch = `${urlFetch}/${this.path}` + } else { + urlFetch = `${urlFetch}${this.path}` + } + if (!urlFetch.startsWith('https://') && !urlFetch.startsWith('http://')) { + urlFetch = `https://${urlFetch}` + } + return urlFetch + }, + requestDistantNodeInfo (url) { + var self = this + axios.get(this.fetchUrl(url)).then(function (response) { + self.isLoading = false + if(!url.startsWith('https://') && !url.startsWith('http://')) { + url = `https://${url}` + } + self.switchInstance(url) + }).catch(function (error) { + self.isLoading = false + self.isError = true + }) + }, + switchInstance (url) { + // Here we disconnect from the current instance and reconnect to the new one. No check is performed... + this.$emit('update:show', false) + this.isError = false + let msg = this.$pgettext('*/Instance/Message', 'You are now using the Funkwhale instance at %{ url }') + this.$store.commit('ui/addMessage', { + content: this.$gettextInterpolate(msg, {url: url}), + date: new Date() + }) + let self = this + this.$nextTick(() => { + self.$store.commit('instance/instanceUrl', null) + self.$store.dispatch('instance/setUrl', url) + }) + }, + checkAndSwitch (url) { + // First we have to check if the address is a valid FW server. If yes, we switch: + this.isError = false // Clear error message if any... + this.isLoading = true + this.requestDistantNodeInfo(url) + }, + }, + computed: { + suggestedInstances () { + let instances = this.$store.state.instance.knownInstances.slice(0) + if (this.$store.state.instance.frontSettings.defaultServerUrl) { + let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl + if (!serverUrl.endsWith('/')) { + serverUrl = serverUrl + '/' + } + instances.push(serverUrl) + } + let self = this + instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/') + return _.uniq(instances.filter((e) => {return e != self.$store.state.instance.instanceUrl})) + }, + instanceHostname() { + let url = this.$store.state.instance.instanceUrl + let parser = document.createElement("a") + parser.href = url + return parser.hostname + }, + }, + watch: { + '$store.state.instance.instanceUrl' () { + this.$store.dispatch('instance/fetchSettings') + this.fetchNodeInfo() + }, + }, +} +</script> + +<style scoped> +</style> diff --git a/front/src/components/ShortcutsModal.vue b/front/src/components/ShortcutsModal.vue index 2338658dd7b80a9d67c0e424f22b7b7f827270b1..5b1a7349d441a0de9c708f2ab9dbc03f576ab98e 100644 --- a/front/src/components/ShortcutsModal.vue +++ b/front/src/components/ShortcutsModal.vue @@ -1,11 +1,11 @@ <template> <modal @update:show="$emit('update:show', $event)" :show="show"> <header class="header"> - <translate>Keyboard shortcuts</translate> + <translate translate-context="*/*/*/Noun">Keyboard shortcuts</translate> </header> <section class="scrolling content"> <table - class="ui compact collapsing basic fixed single line table" + class="ui compact collapsing basic table" v-for="section in sections" :key="section.title"> <caption>{{ section.title }}</caption> @@ -18,7 +18,7 @@ </table> </section> <footer class="actions"> - <div class="ui cancel button"><translate>Close</translate></div> + <div class="ui cancel button"><translate translate-context="Popup/Keyboard shortcuts/Button.Label/Verb">Close</translate></div> </footer> </modal> </template> @@ -35,11 +35,11 @@ export default { sections () { return [ { - title: this.$gettext('General shortcuts'), + title: this.$pgettext('Popup/Keyboard shortcuts/Title', 'General shortcuts'), shortcuts: [ { key: 'h', - summary: this.$gettext('Show available keyboard shortcuts') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Show available keyboard shortcuts') } ] }, @@ -52,35 +52,35 @@ export default { // s.prevent.exact="shuffle" { - title: this.$gettext('Audio player shortcuts'), + title: this.$pgettext('Popup/Keyboard shortcuts/Title', 'Audio player shortcuts'), shortcuts: [ { key: 'space', - summary: this.$gettext('Pause/play the current track') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Pause/play the current track') }, { key: 'ctrl left', - summary: this.$gettext('Play previous track') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play previous track') }, { key: 'ctrl right', - summary: this.$gettext('Play next track') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play next track') }, { key: 'ctrl up', - summary: this.$gettext('Increase volume') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Increase volume') }, { key: 'ctrl down', - summary: this.$gettext('Decrease volume') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Decrease volume') }, { key: 'l', - summary: this.$gettext('Toggle queue looping') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle queue looping') }, { key: 's', - summary: this.$gettext('Shuffle queue') + summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Shuffle queue') }, ] } diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index 8c44d6041b33415423eff0fe857ebebcc83838bf..8b183b215875b532c40faeff53ed547d1eb766ef 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -16,13 +16,13 @@ <div class="menu-area"> <div class="ui compact fluid two item inverted menu"> - <a :class="[{active: selectedTab === 'library'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a> + <a :class="[{active: selectedTab === 'library'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate translate-context="*/Library/*/Verb">Browse</translate></a> <a :class="[{active: selectedTab === 'queue'}, 'item']" role="button" @click.prevent.stop="selectedTab = 'queue'" data-tab="queue"> - <translate>Queue</translate> + <translate translate-context="Sidebar/Queue/Tab.Title/Noun">Queue</translate> <template v-if="queue.tracks.length === 0"> - <translate>(empty)</translate> + <translate translate-context="Sidebar/Queue/Tab.Title">(empty)</translate> </template> - <translate v-else :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}"> + <translate translate-context="Sidebar/Queue/Tab.Title" v-else :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}"> (%{ index } of %{ length }) </translate> </a> @@ -32,70 +32,81 @@ <section :class="['ui', 'bottom', 'attached', {active: selectedTab === 'library'}, 'tab']" :aria-label="labels.mainMenu"> <nav class="ui inverted vertical large fluid menu" role="navigation" :aria-label="labels.mainMenu"> <div class="item"> - <header class="header"><translate>My account</translate></header> + <header class="header"><translate translate-context="Sidebar/Profile/Title">My account</translate></header> <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> - <translate :translate-params="{username: $store.state.auth.username}"> + <translate translate-context="Sidebar/Profile/List item.Link" :translate-params="{username: $store.state.auth.username}"> Logged in as %{ username } </translate> <img class="ui right floated circular tiny avatar image" v-if="$store.state.auth.profile.avatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.square_crop)" /> </router-link> - <router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/settings'}"><i class="setting icon"></i><translate>Settings</translate></router-link> + <router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/settings'}"><i class="setting icon"></i><translate translate-context="*/*/*/Noun">Settings</translate></router-link> <router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'notifications'}"> <i class="feed icon"></i> - <translate>Notifications</translate> + <translate translate-context="*/Notifications/*">Notifications</translate> <div v-if="$store.state.ui.notifications.inbox > 0" :class="['ui', 'teal', 'label']"> {{ $store.state.ui.notifications.inbox }}</div> </router-link> - <router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i><translate>Logout</translate></router-link> + <router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i><translate translate-context="Sidebar/Login/List item.Link/Verb">Logout</translate></router-link> <template v-else> - <router-link class="item" :to="{name: 'login'}"><i class="sign in icon"></i><translate>Login</translate></router-link> + <router-link class="item" :to="{name: 'login'}"><i class="sign in icon"></i><translate translate-context="*/Login/*/Verb">Login</translate></router-link> <router-link class="item" :to="{path: '/signup'}"> <i class="corner add icon"></i> - <translate>Create an account</translate> + <translate translate-context="*/Signup/Link/Verb">Create an account</translate> </router-link> </template> </div> </div> <div class="item"> - <header class="header"><translate>Music</translate></header> + <header class="header"><translate translate-context="*/*/*/Noun">Music</translate></header> <div class="menu"> - <router-link class="item" :to="{path: '/library'}"><i class="sound icon"></i><translate>Browse library</translate></router-link> - <router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i><translate>Favorites</translate></router-link> + <router-link class="item" :to="{path: '/library'}"><i class="sound icon"></i><translate translate-context="Sidebar/Library/List item.Link/Verb">Browse library</translate></router-link> + <router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i><translate translate-context="Sidebar/Favorites/List item.Link/Noun">Favorites</translate></router-link> <a @click="$store.commit('playlists/chooseTrack', null)" v-if="$store.state.auth.authenticated" class="item"> - <i class="list icon"></i><translate>Playlists</translate> + <i class="list icon"></i><translate translate-context="*/*/*">Playlists</translate> </a> <router-link v-if="$store.state.auth.authenticated" - class="item" :to="{name: 'content.index'}"><i class="upload icon"></i><translate>Add content</translate></router-link> + class="item" :to="{name: 'content.index'}"><i class="upload icon"></i><translate translate-context="*/Library/*/Verb">Add content</translate></router-link> </div> </div> <div class="item" v-if="$store.state.auth.availablePermissions['settings'] || $store.state.auth.availablePermissions['moderation']"> - <header class="header"><translate>Administration</translate></header> + <header class="header"><translate translate-context="Sidebar/Admin/Title/Noun">Administration</translate></header> <div class="menu"> <router-link - v-if="$store.state.auth.availablePermissions['settings']" + v-if="$store.state.auth.availablePermissions['library']" class="item" - :to="{path: '/manage/settings'}"> - <i class="settings icon"></i><translate>Settings</translate> + :to="{name: 'manage.library.edits', query: {q: 'is_approved:null'}}"> + <i class="book icon"></i><translate translate-context="*/*/*">Library</translate> + <div + v-if="$store.state.ui.notifications.pendingReviewEdits > 0" + :title="labels.pendingReviewEdits" + :class="['ui', 'teal', 'label']"> + {{ $store.state.ui.notifications.pendingReviewEdits }}</div> + </router-link> + <router-link + v-if="$store.state.auth.availablePermissions['moderation']" + class="item" + :to="{name: 'manage.moderation.domains.list'}"> + <i class="shield icon"></i><translate translate-context="*/Moderation/*">Moderation</translate> </router-link> <router-link v-if="$store.state.auth.availablePermissions['settings']" class="item" :to="{name: 'manage.users.users.list'}"> - <i class="users icon"></i><translate>Users</translate> + <i class="users icon"></i><translate translate-context="*/*/*/Noun">Users</translate> </router-link> <router-link - v-if="$store.state.auth.availablePermissions['moderation']" + v-if="$store.state.auth.availablePermissions['settings']" class="item" - :to="{name: 'manage.moderation.domains.list'}"> - <i class="shield icon"></i><translate>Moderation</translate> + :to="{path: '/manage/settings'}"> + <i class="settings icon"></i><translate translate-context="*/*/*/Noun">Settings</translate> </router-link> </div> </div> @@ -105,10 +116,10 @@ <i class="history icon"></i> <div class="content"> <div class="header"> - <translate>Do you want to restore your previous queue?</translate> + <translate translate-context="Sidebar/Queue/Message">Do you want to restore your previous queue?</translate> </div> <p> - <translate + <translate translate-context="*/*/*" translate-plural="%{ count } tracks" :translate-n="queue.previousQueue.tracks.length" :translate-params="{count: queue.previousQueue.tracks.length}"> @@ -116,8 +127,8 @@ </translate> </p> <div class="ui two buttons"> - <div @click="queue.restore()" class="ui basic inverted green button"><translate>Yes</translate></div> - <div @click="queue.removePrevious()" class="ui basic inverted red button"><translate>No</translate></div> + <div @click="queue.restore()" class="ui basic inverted green button"><translate translate-context="*/*/*">Yes</translate></div> + <div @click="queue.removePrevious()" class="ui basic inverted red button"><translate translate-context="*/*/*">No</translate></div> </div> </div> </div> @@ -158,10 +169,10 @@ <div v-if="$store.state.radios.running" class="ui black message"> <div class="content"> <div class="header"> - <i class="feed icon"></i> <translate>You have a radio playing</translate> + <i class="feed icon"></i> <translate translate-context="Sidebar/Player/Title">You have a radio playing</translate> </div> - <p><translate>New tracks will be appended here automatically.</translate></p> - <div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate>Stop radio</translate></div> + <p><translate translate-context="Sidebar/Player/Paragraph">New tracks will be appended here automatically.</translate></p> + <div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button"><translate translate-context="*/Player/Button.Label/Short, Verb">Stop radio</translate></div> </div> </div> </section> @@ -209,13 +220,15 @@ export default { url: state => state.route.path }), labels() { - let mainMenu = this.$gettext("Main menu") - let selectTrack = this.$gettext("Play this track") - let pendingFollows = this.$gettext("Pending follow requests") + let mainMenu = this.$pgettext('Sidebar/*/Hidden text', "Main menu") + let selectTrack = this.$pgettext('Sidebar/Player/Hidden text', "Play this track") + let pendingFollows = this.$pgettext('Sidebar/Notifications/Hidden text', "Pending follow requests") + let pendingReviewEdits = this.$pgettext('Sidebar/Moderation/Hidden text', "Pending review edits") return { pendingFollows, mainMenu, - selectTrack + selectTrack, + pendingReviewEdits } }, tracks: { @@ -261,6 +274,29 @@ export default { ? 0 : container.clientHeight / 2 container.scrollTop = container.scrollTop - scrollBack + }, + applyContentFilters () { + let artistIds = this.$store.getters['moderation/artistFilters']().map((f) => { + return f.target.id + }) + + if (artistIds.length === 0) { + return + } + let self = this + let tracks = this.tracks.slice().reverse() + tracks.forEach(async (t, i) => { + // we loop from the end because removing index from the start can lead to removing the wrong tracks + let realIndex = tracks.length - i - 1 + let matchArtist = artistIds.indexOf(t.artist.id) > -1 + if (matchArtist) { + return await self.cleanTrack(realIndex) + } + if (t.album && artistIds.indexOf(t.album.artist.id) > -1) { + return await self.cleanTrack(realIndex) + } + }) + } }, watch: { @@ -276,6 +312,9 @@ export default { if (this.selectedTab !== "queue") { this.scrollToCurrent() } + }, + "$store.state.moderation.lastUpdate": function () { + this.applyContentFilters() } } } diff --git a/front/src/components/admin/SettingsGroup.vue b/front/src/components/admin/SettingsGroup.vue index 85bb9eeaee5243a60d4841c264ea8947f9918fce..0c0774bbe1055f9baf771246c2c00a8b56994307 100644 --- a/front/src/components/admin/SettingsGroup.vue +++ b/front/src/components/admin/SettingsGroup.vue @@ -3,13 +3,13 @@ <div class="ui divider" /> <h3 class="ui header">{{ group.label }}</h3> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while saving settings</translate></div> + <div class="header"><translate translate-context="Content/Settings/Error message.Title">Error while saving settings</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div v-if="result" class="ui positive message"> - <translate>Settings updated successfully.</translate> + <translate translate-context="Content/Settings/Paragraph">Settings updated successfully.</translate> </div> <p v-if="group.help">{{ group.help }}</p> <div v-for="setting in settings" class="ui field"> @@ -65,7 +65,7 @@ <button type="submit" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']"> - <translate>Save</translate> + <translate translate-context="Content/*/Button.Label/Verb">Save</translate> </button> </form> </template> diff --git a/front/src/components/audio/EmbedWizard.vue b/front/src/components/audio/EmbedWizard.vue index 7a50ffa54223a189d40cd91fbbe9f0e41b0814ee..e4412937e54e542a684381656a0810cf4c924b9b 100644 --- a/front/src/components/audio/EmbedWizard.vue +++ b/front/src/components/audio/EmbedWizard.vue @@ -4,30 +4,36 @@ <div class="two fields"> <div class="field"> <div class="field"> - <label for="embed-width"><translate>Widget width</translate></label> - <p><translate>Leave empty for a responsive widget</translate></p> + <label for="embed-width"><translate translate-context="Popup/Embed/Input.Label">Widget width</translate></label> + <p><translate translate-context="Popup/Embed/Paragraph">Leave empty for a responsive widget</translate></p> <input id="embed-width" type="number" v-model.number="width" min="0" step="10" /> </div> <template v-if="type != 'track'"> <br> <div class="field"> - <label for="embed-height"><translate>Widget height</translate></label> + <label for="embed-height"><translate translate-context="Popup/Embed/Input.Label">Widget height</translate></label> <input id="embed-height" type="number" v-model="height" :min="minHeight" max="1000" step="10" /> </div> </template> </div> <div class="field"> - <button @click="copy" class="ui right floated button"><translate>Copy</translate></button> - <label for="embed-width"><translate>Embed code</translate></label> - <p><translate>Copy/paste this code in your website HTML</translate></p> - <div class="ui hidden divider"></div> - <textarea ref="textarea":value="embedCode" rows="3" readonly> + <button @click="copy" class="ui right teal labeled icon floated button"><i class="copy icon"></i><translate translate-context="*/*/Button.Label/Short, Verb">Copy</translate></button> + <label for="embed-width"><translate translate-context="Popup/Embed/Input.Label/Noun">Embed code</translate></label> + <p><translate translate-context="Popup/Embed/Paragraph">Copy/paste this code in your website HTML</translate></p> + <textarea ref="textarea":value="embedCode" rows="5" readonly> </textarea> + <div class="ui right"> + <p class="message" v-if=copied><translate translate-context="Content/*/Paragraph">Text copied to clipboard!</translate></p> + </div> </div> </div> </div> <div class="preview"> - <h3><translate>Preview</translate></h3> + <h3> + <a :href="iframeSrc" target="_blank"> + <translate translate-context="Popup/Embed/Title/Noun">Preview</translate> + </a> + </h3> <iframe :width="frameWidth" :height="height" scrolling="no" frameborder="no" :src="iframeSrc"></iframe> </div> </div> @@ -41,7 +47,8 @@ export default { let d = { width: null, height: 150, - minHeight: 100 + minHeight: 100, + copied: false } if (this.type === 'album') { d.height = 330 @@ -70,6 +77,11 @@ export default { copy () { this.$refs.textarea.select() document.execCommand("Copy") + let self = this + self.copied = true + this.timeout = setTimeout(() => { + self.copied = false + }, 5000) } } } @@ -77,4 +89,9 @@ export default { <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> +.message { + position: absolute; + right: 0; + bottom: -2em; +} </style> diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index d438a14a02b412156accdf2a45c385defa0e42f3..c1eeeedd47b30c308fee7bbba6861e1942d1d00f 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -1,5 +1,5 @@ <template> - <span :title="title" :class="['ui', {'tiny': discrete}, {'buttons': !dropdownOnly && !iconOnly}]"> + <span :title="title" :class="['ui', {'tiny': discrete}, {'icon': !discrete}, {'buttons': !dropdownOnly && !iconOnly}]"> <button v-if="!dropdownOnly" :title="labels.playNow" @@ -7,14 +7,25 @@ :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 translate-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 v-if="!discrete && !iconOnly" :class="['ui', {disabled: !playable && !filterableArtist}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]"> + <i :class="dropdownIconClasses.concat(['icon'])" :title="title" ></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 class="item basic" ref="add" data-ref="add" :disabled="!playable" @click.stop.prevent="add" :title="labels.addToQueue"> + <i class="plus icon"></i><translate translate-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>{{ labels.playNext }} + </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>{{ labels.playNow }}</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 translate-context="*/Queue/Button.Label/Short, Verb">Start radio</translate> + </button> + <button v-if="filterableArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist"> + <i class="eye slash outline icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Hide content from this artist</translate> + </button> </div> </div> </span> @@ -36,13 +47,13 @@ export default { discrete: {type: Boolean, default: false}, dropdownOnly: {type: Boolean, default: false}, iconOnly: {type: Boolean, default: false}, - artist: {type: Number, required: false}, - album: {type: Number, required: false}, + artist: {type: Object, required: false}, + album: {type: Object, required: false}, isPlayable: {type: Boolean, required: false, default: null} }, data () { return { - isLoading: false + isLoading: false, } }, mounted () { @@ -60,17 +71,18 @@ export default { computed: { labels () { return { - playNow: this.$gettext('Play now'), - addToQueue: this.$gettext('Add to current queue'), - playNext: this.$gettext('Play next') + 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') } } }, @@ -81,7 +93,7 @@ export default { if (this.track) { return this.track.uploads && this.track.uploads.length > 0 } else if (this.artist) { - return this.albums.filter((a) => { + return this.artist.albums.filter((a) => { return a.is_playable === true }).length > 0 } else if (this.tracks) { @@ -90,9 +102,24 @@ export default { }).length > 0 } return false + }, + filterableArtist () { + if (this.track) { + return this.track.artist + } + if (this.album) { + return this.album.artist + } + if (this.artist) { + return this.artist + } } }, methods: { + + filterArtist () { + this.$store.dispatch('moderation/hide', {type: 'artist', target: this.filterableArtist}) + }, getTracksPage (page, params, resolve, tracks) { if (page > 10) { // it's 10 * 100 tracks already, let's stop here @@ -103,6 +130,7 @@ export default { let self = this params['page_size'] = 100 params['page'] = page + params['hidden'] = '' tracks = tracks || [] axios.get('tracks/', {params: params}).then((response) => { response.data.results.forEach(t => { @@ -133,15 +161,27 @@ export default { } else if (self.playlist) { let url = 'playlists/' + self.playlist.id + '/' axios.get(url + 'tracks/').then((response) => { - resolve(response.data.results.map(plt => { + let artistIds = self.$store.getters['moderation/artistFilters']().map((f) => { + return f.target.id + }) + let tracks = response.data.results.map(plt => { return plt.track - })) + }) + if (artistIds.length > 0) { + // skip tracks from hidden artists + tracks = tracks.filter((t) => { + let matchArtist = artistIds.indexOf(t.artist.id) > -1 + return !(matchArtist || t.album && artistIds.indexOf(t.album.artist.id) > -1) + }) + } + + resolve(tracks) }) } else if (self.artist) { - let params = {'artist': self.artist, 'ordering': 'album__release_date,position'} + let params = {'artist': self.artist.id, 'ordering': 'album__release_date,position'} self.getTracksPage(1, params, resolve) } else if (self.album) { - let params = {'album': self.album, 'ordering': 'position'} + let params = {'album': self.album.id, 'ordering': 'position'} self.getTracksPage(1, params, resolve) } }) @@ -177,12 +217,12 @@ 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() }) - } + }, } } </script> diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue index 575f6873662aa851bc83cd623226ff5259c88329..ea15ebc8720db69b7b0ccb69319329afdbf4e94a 100644 --- a/front/src/components/audio/Player.vue +++ b/front/src/components/audio/Player.vue @@ -1,16 +1,6 @@ <template> <section class="ui inverted segment player-wrapper" :aria-label="labels.audioPlayer" :style="style"> <div class="player"> - <audio-track - ref="currentAudio" - v-if="currentTrack" - @errored="handleError" - :is-current="true" - :start-time="$store.state.player.currentTime" - :autoplay="$store.state.player.playing" - :key="audioKey" - :track="currentTrack"> - </audio-track> <div v-if="currentTrack" class="track-area ui unstackable items"> <div class="ui inverted item"> <div class="ui tiny image"> @@ -38,6 +28,14 @@ v-if="$store.state.auth.authenticated" :class="['inverted']" :track="currentTrack"></track-playlist-icon> + <button + v-if="$store.state.auth.authenticated" + @click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})" + :class="['ui', 'really', 'basic', 'circular', 'inverted', 'icon', 'button']" + :aria-label="labels.addArtistContentFilter" + :title="labels.addArtistContentFilter"> + <i :class="['eye slash outline', 'basic', 'icon']"></i> + </button> </div> </div> </div> @@ -45,7 +43,7 @@ <div class="progress-area" v-if="currentTrack && !errored"> <div class="ui grid"> <div class="left floated four wide column"> - <p class="timer start" @click="updateProgress(0)">{{currentTimeFormatted}}</p> + <p class="timer start" @click="setCurrentTime(0)">{{currentTimeFormatted}}</p> </div> <div v-if="!isLoadingAudio" class="right floated four wide column"> @@ -62,14 +60,14 @@ </div> <div class="ui small warning message" v-if="currentTrack && errored"> <div class="header"> - <translate>We cannot load this track</translate> + <translate translate-context="Sidebar/Player/Error message.Title">The track cannot be loaded</translate> </div> <p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors"> - <translate>The next track will play automatically in a few seconds…</translate> + <translate translate-context="Sidebar/Player/Error message.Paragraph">The next track will play automatically in a few seconds…</translate> <i class="loading spinner icon"></i> </p> <p> - <translate>You may have a connectivity issue.</translate> + <translate translate-context="Sidebar/Player/Error message.Paragraph">You may have a connectivity issue.</translate> </p> </div> <div class="two wide column controls ui grid"> @@ -200,7 +198,11 @@ v-if="!showVolume" @click.prevent.stop="clean()" class="two wide column control"> - <i :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + <i class="icons"> + + <i :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + <i :class="['ui corner large inverted', 'list', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> + </i> </span> </div> <GlobalEvents @@ -222,8 +224,10 @@ import GlobalEvents from "@/components/utils/global-events" import ColorThief from "@/vendor/color-thief" import { Howl } from "howler" import $ from 'jquery' +import _ from '@/lodash' +import url from '@/utils/url' +import axios from 'axios' -import AudioTrack from "@/components/audio/Track" import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon" import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon" @@ -232,7 +236,6 @@ export default { TrackFavoriteIcon, TrackPlaylistIcon, GlobalEvents, - AudioTrack }, data() { let defaultAmbiantColors = [ @@ -247,11 +250,25 @@ export default { defaultAmbiantColors: defaultAmbiantColors, showVolume: false, ambiantColors: defaultAmbiantColors, - audioKey: String(new Date()), - dummyAudio: null + currentSound: null, + dummyAudio: null, + isUpdatingTime: false, + sourceErrors: 0, + progressInterval: null, + maxPreloaded: 3, + preloadDelay: 15, + soundsCache: [], + soundId: null, + playTimeout: null, + nextTrackPreloaded: false } }, mounted() { + this.$store.dispatch('player/updateProgress', 0) + this.$store.commit('player/playing', false) + this.$store.commit("player/isLoadingAudio", false) + Howler.unload() // clear existing cache, if any + this.nextTrackPreloaded = false // we trigger the watcher explicitely it does not work otherwise this.sliderVolume = this.volume // this is needed to unlock audio playing under some browsers, @@ -262,9 +279,15 @@ export default { autoplay: false, src: ["noop.webm", "noop.mp3"] }) + if (this.currentTrack) { + this.getSound(this.currentTrack) + } }, - destroyed() { + beforeDestroy () { this.dummyAudio.unload() + this.observeProgress(false) + }, + destroyed() { }, methods: { ...mapActions({ @@ -272,15 +295,31 @@ export default { mute: "player/mute", unmute: "player/unmute", clean: "queue/clean", - updateProgress: "player/updateProgress" }), + async getTrackData (trackData) { + let data = null + if (!trackData.uploads.length || trackData.uploads.length === 0) { + // we don't have upload informations for this track, we need to fetch it + await axios.get(`tracks/${trackData.id}/`).then((response) => { + data = response.data + }, error => { + data = null + }) + } else { + return trackData + } + if (data === null) { + return + } + return data + }, shuffle() { let disabled = this.queue.tracks.length === 0 if (this.isShuffling || disabled) { return } let self = this - let msg = this.$gettext("Queue shuffled!") + let msg = this.$pgettext('Content/Queue/Message', "Queue shuffled!") this.isShuffling = true setTimeout(() => { self.$store.dispatch("queue/shuffle", () => { @@ -308,7 +347,7 @@ export default { let time let target = this.$refs.progress time = (e.layerX / target.offsetWidth) * this.duration - this.$refs.currentAudio.setCurrentTime(time) + this.setCurrentTime(time) }, updateBackground() { // delete existing canvas, if any @@ -323,6 +362,239 @@ export default { handleError({ sound, error }) { this.$store.commit("player/isLoadingAudio", false) this.$store.dispatch("player/trackErrored") + }, + getSound (trackData) { + let cached = this.getSoundFromCache(trackData) + if (cached) { + return cached.sound + } + let srcs = this.getSrcs(trackData) + let self = this + let sound = new Howl({ + src: srcs.map((s) => { return s.url }), + format: srcs.map((s) => { return s.type }), + autoplay: false, + loop: false, + html5: true, + preload: true, + volume: this.volume, + onend: function () { + self.ended() + }, + onunlock: function () { + if (this.$store.state.player.playing) { + self.soundId = self.sound.play(self.soundId) + } + }, + onload: function () { + let sound = this + let node = this._sounds[0]._node; + node.addEventListener('progress', () => { + if (sound != self.currentSound) { + return + } + self.updateBuffer(node) + }) + }, + onplay: function () { + self.$store.commit('player/isLoadingAudio', false) + self.$store.commit('player/resetErrorCount') + self.$store.commit('player/errored', false) + self.$store.commit('player/duration', this.duration()) + }, + onloaderror: function (sound, error) { + self.removeFromCache(this) + if (this != self.currentSound) { + return + } + console.log('Error while playing:', sound, error) + self.handleError({sound, error}) + }, + }) + this.addSoundToCache(sound, trackData) + return sound + }, + getSrcs: function (trackData) { + let sources = trackData.uploads.map(u => { + return { + type: u.extension, + url: this.$store.getters['instance/absoluteUrl'](u.listen_url), + } + }) + // We always add a transcoded MP3 src at the end + // because transcoding is expensive, but we want browsers that do + // not support other codecs to be able to play it :) + sources.push({ + type: 'mp3', + url: url.updateQueryString( + this.$store.getters['instance/absoluteUrl'](trackData.listen_url), + 'to', + 'mp3' + ) + }) + if (this.$store.state.auth.authenticated) { + // we need to send the token directly in url + // so authentication can be checked by the backend + // because for audio files we cannot use the regular Authentication + // header + sources.forEach(e => { + e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token) + }) + } + return sources + }, + + updateBuffer (node) { + // from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163 + let range = 0; + let bf = node.buffered; + let time = node.currentTime; + try { + while(!(bf.start(range) <= time && time <= bf.end(range))) { + range += 1; + } + } catch (IndexSizeError) { + return + } + let loadPercentage + let start = bf.start(range) + let end = bf.end(range) + if (range === 0) { + // easy case, no user-seek + let loadStartPercentage = start / node.duration; + let loadEndPercentage = end / node.duration; + loadPercentage = loadEndPercentage - loadStartPercentage; + } else { + let loaded = end - start + let remainingToLoad = node.duration - start + // user seeked a specific position in the audio, our progress must be + // computed based on the remaining portion of the track + loadPercentage = loaded / remainingToLoad; + } + if (loadPercentage * 100 === this.bufferProgress) { + return + } + this.$store.commit('player/bufferProgress', loadPercentage * 100) + }, + updateProgress: function () { + this.isUpdatingTime = true + if (this.currentSound && this.currentSound.state() === 'loaded') { + let t = this.currentSound.seek() + let d = this.currentSound.duration() + this.$store.dispatch('player/updateProgress', t) + this.updateBuffer(this.currentSound._sounds[0]._node) + let toPreload = this.$store.state.queue.tracks[this.currentIndex + 1] + if (!this.nextTrackPreloaded && toPreload && !this.getSoundFromCache(toPreload) && (t > this.preloadDelay || d - t < 30)) { + this.getSound(toPreload) + this.nextTrackPreloaded = true + } + } + }, + observeProgress: function (enable) { + let self = this + if (enable) { + if (self.progressInterval) { + clearInterval(self.progressInterval) + } + self.progressInterval = setInterval(() => { + self.updateProgress() + }, 1000) + } else { + clearInterval(self.progressInterval) + } + }, + setCurrentTime (t) { + if (t < 0 | t > this.duration) { + return + } + if (!this.currentSound || !this.currentSound._sounds[0]) { + return + } + if (t === this.currentSound.seek()) { + return + } + if (t === 0) { + this.updateProgressThrottled.cancel() + } + this.currentSound.seek(t) + }, + ended: function () { + let onlyTrack = this.$store.state.queue.tracks.length === 1 + if (this.looping === 1 || (onlyTrack && this.looping === 2)) { + this.currentSound.seek(0) + this.$store.dispatch('player/updateProgress', 0) + this.soundId = this.currentSound.play(this.soundId) + } else { + this.$store.dispatch('player/trackEnded', this.currentTrack) + } + }, + getSoundFromCache (trackData) { + return this.soundsCache.filter((d) => { + if (d.track.id !== trackData.id) { + return false + } + + return true + })[0] + }, + addSoundToCache (sound, trackData) { + let data = { + date: new Date(), + track: trackData, + sound: sound + } + this.soundsCache.push(data) + this.checkCache() + }, + checkCache () { + let self = this + let toKeep = [] + _.reverse(this.soundsCache).forEach((e) => { + if (toKeep.length < self.maxPreloaded) { + toKeep.push(e) + } else { + let src = e.sound._src + e.sound.unload() + } + }) + this.soundsCache = _.reverse(toKeep) + }, + removeFromCache (sound) { + let toKeep = [] + this.soundsCache.forEach((e) => { + if (e.sound === sound) { + e.sound.unload() + } else { + toKeep.push(e) + } + }) + this.soundsCache = toKeep + }, + async loadSound (newValue, oldValue) { + let trackData = newValue + let oldSound = this.currentSound + if (oldSound && trackData !== oldValue) { + oldSound.stop(this.soundId) + this.soundId = null + } + if (!trackData) { + return + } + if (!this.isShuffling && trackData != oldValue) { + trackData = await this.getTrackData(trackData) + if (trackData === null) { + this.handleError({}) + } + this.currentSound = this.getSound(trackData) + this.$store.commit('player/isLoadingAudio', true) + if (this.playing) { + this.soundId = this.currentSound.play() + this.$store.commit('player/errored', false) + this.$store.commit('player/playing', true) + this.$store.dispatch('player/updateProgress', 0) + this.observeProgress(true) + } + } } }, computed: { @@ -335,6 +607,7 @@ export default { duration: state => state.player.duration, bufferProgress: state => state.player.bufferProgress, errored: state => state.player.errored, + currentTime: state => state.player.currentTime, queue: state => state.queue }), ...mapGetters({ @@ -345,25 +618,29 @@ export default { currentTimeFormatted: "player/currentTimeFormatted", progress: "player/progress" }), + updateProgressThrottled () { + return _.throttle(this.updateProgress, 250) + }, labels() { - let audioPlayer = this.$gettext("Media player") - let previousTrack = this.$gettext("Previous track") - let play = this.$gettext("Play track") - let pause = this.$gettext("Pause track") - let next = this.$gettext("Next track") - let unmute = this.$gettext("Unmute") - let mute = this.$gettext("Mute") - let loopingDisabled = this.$gettext( + let audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', "Media player") + let previousTrack = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Previous track") + let play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Play track") + let pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Pause track") + let next = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Next track") + let unmute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Unmute") + let mute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Mute") + let loopingDisabled = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Looping disabled. Click to switch to single-track looping." ) - let loopingSingle = this.$gettext( + let loopingSingle = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Looping on a single track. Click to switch to whole queue looping." ) - let loopingWhole = this.$gettext( + let loopingWhole = this.$pgettext('Sidebar/Player/Icon.Tooltip', "Looping on whole queue. Click to disable looping." ) - let shuffle = this.$gettext("Shuffle your queue") - let clear = this.$gettext("Clear your queue") + let shuffle = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Shuffle your queue") + let clear = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Clear your queue") + let addArtistContentFilter = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…') return { audioPlayer, previousTrack, @@ -376,7 +653,8 @@ export default { loopingSingle, loopingWhole, shuffle, - clear + clear, + addArtistContentFilter, } }, style: function() { @@ -404,22 +682,62 @@ export default { }) .join(", ") return gradients - } + }, }, watch: { - currentTrack(newValue, oldValue) { - if (!this.isShuffling && newValue != oldValue) { - this.audioKey = String(new Date()) - } - if (!newValue || !newValue.album.cover) { - this.ambiantColors = this.defaultAmbiantColors - } + currentTrack: { + async handler (newValue, oldValue) { + if (newValue === oldValue) { + return + } + this.nextTrackPreloaded = false + clearTimeout(this.playTimeout) + let self = this + if (this.currentSound) { + this.currentSound.pause() + } + this.$store.commit("player/isLoadingAudio", true) + this.playTimeout = setTimeout(async () => { + await self.loadSound(newValue, oldValue) + if (!newValue || !newValue.album.cover) { + self.ambiantColors = self.defaultAmbiantColors + } + }, 500); + }, + immediate: false }, volume(newValue) { this.sliderVolume = newValue + if (this.currentSound) { + this.currentSound.volume(newValue) + } }, sliderVolume(newValue) { this.$store.commit("player/volume", newValue) + }, + playing: async function (newValue) { + if (this.currentSound) { + if (newValue === true) { + this.soundId = this.currentSound.play(this.soundId) + } else { + this.currentSound.pause(this.soundId) + } + } else { + await this.loadSound(this.currentTrack, null) + } + + this.observeProgress(newValue) + }, + currentTime (newValue) { + if (!this.isUpdatingTime) { + this.setCurrentTime(newValue) + } + this.isUpdatingTime = false + }, + emptyQueue (newValue) { + if (newValue) { + Howler.unload() + } } } } @@ -491,6 +809,7 @@ export default { bottom: 1.1rem; left: 25%; cursor: pointer; + background-color: transparent; } input[type="range"]:focus { outline: none; @@ -607,4 +926,8 @@ export default { animation-timing-function: linear; animation-iteration-count: infinite; } +i.icons .corner.icon { + font-size: 1em; + right: -0.3em; +} </style> diff --git a/front/src/components/audio/Search.vue b/front/src/components/audio/Search.vue index 307ca878f88061f038722b53a800489d8aa95ceb..40dbe403d4a2d8b95ae515498521a12b1cd8b041 100644 --- a/front/src/components/audio/Search.vue +++ b/front/src/components/audio/Search.vue @@ -1,6 +1,6 @@ <template> <div> - <h2><translate>Search for some music</translate></h2> + <h2><translate translate-context="Content/Search/Title">Search for some music</translate></h2> <div :class="['ui', {'loading': isLoading }, 'search']"> <div class="ui icon big input"> <i class="search icon"></i> @@ -8,22 +8,22 @@ </div> </div> <template v-if="query.length > 0"> - <h3 class="ui title"><translate>Artists</translate></h3> + <h3 class="ui title"><translate translate-context="*/*/*/Noun">Artists</translate></h3> <div v-if="results.artists.length > 0"> <div class="ui cards"> <artist-card :key="artist.id" v-for="artist in results.artists" :artist="artist" ></artist-card> </div> </div> - <p v-else><translate>No artist matched your query</translate></p> + <p v-else><translate translate-context="Content/Search/Paragraph">No artist matched your query</translate></p> </template> <template v-if="query.length > 0"> - <h3 class="ui title"><translate>Albums</translate></h3> + <h3 class="ui title"><translate translate-context="*/*/*">Albums</translate></h3> <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"> <album-card class="fluid" :album="album" ></album-card> </div> </div> - <p v-else><translate>No album matched your query</translate></p> + <p v-else><translate translate-context="Content/Search/Paragraph">No album matched your query</translate></p> </template> </div> </template> @@ -62,7 +62,7 @@ export default { computed: { labels () { return { - searchPlaceholder: this.$gettext('Artist, album, track…') + searchPlaceholder: this.$pgettext('*/Search/Input.Placeholder', 'Artist, album, track…') } } }, diff --git a/front/src/components/audio/SearchBar.vue b/front/src/components/audio/SearchBar.vue index 534ac0ac4f3862170a9b2a649a1746bba783cb0e..28b11b040d19dec5832cd5eb44971fd0e3a9b764 100644 --- a/front/src/components/audio/SearchBar.vue +++ b/front/src/components/audio/SearchBar.vue @@ -17,14 +17,14 @@ export default { computed: { labels () { return { - placeholder: this.$gettext('Search for artists, albums, tracks…') + placeholder: this.$pgettext('Sidebar/Search/Input.Placeholder', 'Search for artists, albums, tracks…') } } }, mounted () { - let artistLabel = this.$gettext('Artist') - let albumLabel = this.$gettext('Album') - let trackLabel = this.$gettext('Track') + let artistLabel = this.$pgettext('*/*/*/Noun', 'Artist') + let albumLabel = this.$pgettext('*/*/*', 'Album') + let trackLabel = this.$pgettext('*/*/*/Noun', 'Track') let self = this jQuery(this.$el).search({ type: 'category', diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue deleted file mode 100644 index 2e0f8c421a0e780247224b93764b55fd476ae541..0000000000000000000000000000000000000000 --- a/front/src/components/audio/Track.vue +++ /dev/null @@ -1,225 +0,0 @@ -<template> - <i /> -</template> - -<script> -import {mapState} from 'vuex' -import _ from '@/lodash' -import url from '@/utils/url' -import {Howl} from 'howler' -import axios from 'axios' - -// import logger from '@/logging' - -export default { - props: { - track: {type: Object}, - isCurrent: {type: Boolean, default: false}, - startTime: {type: Number, default: 0}, - autoplay: {type: Boolean, default: false} - }, - data () { - return { - trackData: this.track, - sourceErrors: 0, - sound: null, - isUpdatingTime: false, - progressInterval: null - } - }, - mounted () { - let self = this - if (!this.trackData.uploads.length || this.trackData.uploads.length === 0) { - // we don't have upload informations for this track, we need to fetch it - axios.get(`tracks/${this.trackData.id}/`).then((response) => { - self.trackData = response.data - self.setupSound() - }, error => { - self.$emit('errored', {}) - }) - } else { - this.setupSound() - } - }, - destroyed () { - this.observeProgress(false) - this.sound.unload() - }, - computed: { - ...mapState({ - playing: state => state.player.playing, - currentTime: state => state.player.currentTime, - duration: state => state.player.duration, - volume: state => state.player.volume, - looping: state => state.player.looping - }), - srcs: function () { - let sources = this.trackData.uploads.map(u => { - return { - type: u.extension, - url: this.$store.getters['instance/absoluteUrl'](u.listen_url), - } - }) - // We always add a transcoded MP3 src at the end - // because transcoding is expensive, but we want browsers that do - // not support other codecs to be able to play it :) - sources.push({ - type: 'mp3', - url: url.updateQueryString( - this.$store.getters['instance/absoluteUrl'](this.trackData.listen_url), - 'to', - 'mp3' - ) - }) - if (this.$store.state.auth.authenticated) { - // we need to send the token directly in url - // so authentication can be checked by the backend - // because for audio files we cannot use the regular Authentication - // header - sources.forEach(e => { - e.url = url.updateQueryString(e.url, 'jwt', this.$store.state.auth.token) - }) - } - return sources - }, - updateProgressThrottled () { - return _.throttle(this.updateProgress, 250) - } - }, - methods: { - setupSound () { - let self = this - this.sound = new Howl({ - src: this.srcs.map((s) => { return s.url }), - format: this.srcs.map((s) => { return s.type }), - autoplay: false, - loop: false, - html5: true, - preload: true, - volume: this.volume, - onend: function () { - self.ended() - }, - onunlock: function () { - if (this.$store.state.player.playing) { - self.sound.play() - } - }, - onload: function () { - self.$store.commit('player/isLoadingAudio', false) - self.$store.commit('player/resetErrorCount') - self.$store.commit('player/errored', false) - self.$store.commit('player/duration', self.sound.duration()) - let node = self.sound._sounds[0]._node; - node.addEventListener('progress', () => { - self.updateBuffer(node) - }) - }, - onloaderror: function (sound, error) { - console.log('Error while playing:', sound, error) - self.$emit('errored', {sound, error}) - }, - }) - if (this.autoplay) { - self.$store.commit('player/isLoadingAudio', true) - this.sound.play() - this.$store.commit('player/playing', true) - this.observeProgress(true) - } - }, - updateBuffer (node) { - // from https://github.com/goldfire/howler.js/issues/752#issuecomment-372083163 - let range = 0; - let bf = node.buffered; - let time = node.currentTime; - try { - while(!(bf.start(range) <= time && time <= bf.end(range))) { - range += 1; - } - } catch (IndexSizeError) { - return - } - let loadPercentage - let start = bf.start(range) - let end = bf.end(range) - if (range === 0) { - // easy case, no user-seek - let loadStartPercentage = start / node.duration; - let loadEndPercentage = end / node.duration; - loadPercentage = loadEndPercentage - loadStartPercentage; - } else { - let loaded = end - start - let remainingToLoad = node.duration - start - // user seeked a specific position in the audio, our progress must be - // computed based on the remaining portion of the track - loadPercentage = loaded / remainingToLoad; - } - this.$store.commit('player/bufferProgress', loadPercentage * 100) - }, - updateProgress: function () { - this.isUpdatingTime = true - if (this.sound && this.sound.state() === 'loaded') { - this.$store.dispatch('player/updateProgress', this.sound.seek()) - this.updateBuffer(this.sound._sounds[0]._node) - } - }, - observeProgress: function (enable) { - let self = this - if (enable) { - if (self.progressInterval) { - clearInterval(self.progressInterval) - } - self.progressInterval = setInterval(() => { - self.updateProgress() - }, 1000) - } else { - clearInterval(self.progressInterval) - } - }, - setCurrentTime (t) { - if (t < 0 | t > this.duration) { - return - } - if (t === this.sound.seek()) { - return - } - if (t === 0) { - this.updateProgressThrottled.cancel() - } - this.sound.seek(t) - }, - ended: function () { - let onlyTrack = this.$store.state.queue.tracks.length === 1 - if (this.looping === 1 || (onlyTrack && this.looping === 2)) { - this.sound.seek(0) - this.sound.play() - } else { - this.$store.dispatch('player/trackEnded', this.trackData) - } - } - }, - watch: { - playing: function (newValue) { - if (newValue === true) { - this.sound.play() - } else { - this.sound.pause() - } - this.observeProgress(newValue) - }, - volume: function (newValue) { - this.sound.volume(newValue) - }, - currentTime (newValue) { - if (!this.isUpdatingTime) { - this.setCurrentTime(newValue) - } - this.isUpdatingTime = false - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> -</style> diff --git a/front/src/components/audio/album/Card.vue b/front/src/components/audio/album/Card.vue index 258b0f9d1a67e51b2f4e7349bd45c83feb7c08f2..bdc0dd6cb57b18eed7dfcc00ff45c929a027f4fb 100644 --- a/front/src/components/audio/album/Card.vue +++ b/front/src/components/audio/album/Card.vue @@ -11,7 +11,7 @@ <div class="meta"> <span> <router-link :title="album.artist.name" tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}"> - <span v-translate="{artist: album.artist.name}" :translate-params="{artist: album.artist.name}">By %{ artist }</span> + <span v-translate="{artist: album.artist.name}" translate-context="Content/Album/Card" :translate-params="{artist: album.artist.name}">By %{ artist }</span> </router-link> </span><span class="time" v-if="album.release_date">– {{ album.release_date | year }}</span> </div> @@ -36,21 +36,21 @@ </table> <div class="center aligned segment" v-if="album.tracks.length > initialTracks"> <em v-if="!showAllTracks" @click="showAllTracks = true" class="expand"> - <translate :translate-params="{count: album.tracks.length - initialTracks}" :translate-n="album.tracks.length - initialTracks" translate-plural="Show %{ count } more tracks">Show %{ count } more track</translate> + <translate translate-context="Content/Album/Card.Link/Verb" :translate-params="{count: album.tracks.length - initialTracks}" :translate-n="album.tracks.length - initialTracks" translate-plural="Show %{ count } more tracks">Show %{ count } more track</translate> </em> <em v-else @click="showAllTracks = false" class="expand"> - <translate>Collapse</translate> + <translate translate-context="Content/*/Card.Link/Verb">Collapse</translate> </em> </div> </div> </div> <div class="extra content"> - <play-button class="mini basic orange right floated" :tracks="album.tracks"> - <translate>Play all</translate> + <play-button class="mini basic orange right floated" :tracks="tracksWithAlbum" :album="album"> + <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate> </play-button> <span> <i class="music icon"></i> - <translate :translate-params="{count: album.tracks.length}" :translate-n="album.tracks.length" translate-plural="%{ count } tracks">%{ count } track</translate> + <translate translate-context="*/*/*" :translate-params="{count: album.tracks.length}" :translate-n="album.tracks.length" translate-plural="%{ count } tracks">%{ count } track</translate> </span> </div> </div> @@ -83,6 +83,20 @@ export default { return this.album.tracks } return this.album.tracks.slice(0, this.initialTracks) + }, + tracksWithAlbum () { + // needed to include album data (especially cover) + // with tracks appended in queue (#795) + let self = this + return this.album.tracks.map(t => { + return { + ...t, + album: { + ...self.album, + tracks: [] + } + } + }) } } } diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue index f7100a9b86c3f33ea5ac3d0bef26f84d3f45f8ec..ecc0a280ed04b26779de42fad4537b77b4243426 100644 --- a/front/src/components/audio/album/Widget.vue +++ b/front/src/components/audio/album/Widget.vue @@ -5,6 +5,7 @@ </h3> <button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button> <button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button> + <button @click="fetchData('albums/')" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button> <div class="ui hidden divider"></div> <div class="ui five cards"> <div v-if="isLoading" class="ui inverted active dimmer"> @@ -12,7 +13,7 @@ </div> <div class="card" v-for="album in albums" :key="album.id"> <div :class="['ui', 'image', 'with-overlay', {'default-cover': !album.cover.original}]" v-lazy:background-image="getImageUrl(album)"> - <play-button class="play-overlay" :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :album="album.id"></play-button> + <play-button class="play-overlay" :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'orange', 'icon', 'button']" :album="album"></play-button> </div> <div class="content"> <router-link :title="album.title" :to="{name: 'library.albums.detail', params: {id: album.id}}"> @@ -28,7 +29,7 @@ </div> <div class="extra content"> <human-date class="left floated" :date="album.creation_date"></human-date> - <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="album.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :album="album.id"></play-button> + <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="album.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large', 'grey']" :album="album"></play-button> </div> </div> </div> @@ -101,6 +102,9 @@ export default { watch: { offset () { this.fetchData() + }, + "$store.state.moderation.lastUpdate": function () { + this.fetchData('albums/') } } } diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue index d255928547da237b2074b6fb64a82717dc9dd2a5..b06cdcd75b126c3d5b7f68abce5923caf0c18a2a 100644 --- a/front/src/components/audio/artist/Card.vue +++ b/front/src/components/audio/artist/Card.vue @@ -21,17 +21,17 @@ {{ album.tracks_count }} tracks </td> <td> - <play-button class="right floated basic icon" :is-playable="album.is_playable" :discrete="true" :album="album.id"></play-button> + <play-button class="right floated basic icon" :is-playable="album.is_playable" :discrete="true" :album="album"></play-button> </td> </tr> </tbody> </table> <div class="center aligned segment" v-if="artist.albums.length > initialAlbums"> <em v-if="!showAllAlbums" @click="showAllAlbums = true" class="expand"> - <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> + <translate translate-context="Content/Artist/Card.Link" :translate-params="{count: artist.albums.length - initialAlbums}" :translate-n="artist.albums.length - initialAlbums" translate-plural="Show %{ count } more albums">Show 1 more album</translate> </em> <em v-else @click="showAllAlbums = false" class="expand"> - <translate>Collapse</translate> + <translate translate-context="Content/*/Card.Link/Verb">Collapse</translate> </em> </div> </div> @@ -39,10 +39,10 @@ <div class="extra content"> <span> <i class="sound icon"></i> - <translate :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate> + <translate translate-context="Content/Artist/Card" :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate> </span> - <play-button :is-playable="isPlayable" class="mini basic orange right floated" :artist="artist.id"> - <translate>Play all</translate> + <play-button :is-playable="isPlayable" class="mini basic orange right floated" :artist="artist"> + <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate> </play-button> </div> </div> diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue index e391977f32cf70cb80adbfbccef029b752a4084e..abc4137bf397306b457cb19e8e9d39e6b21c0838 100644 --- a/front/src/components/audio/track/Row.vue +++ b/front/src/components/audio/track/Row.vue @@ -38,9 +38,9 @@ {{ time.parse(track.uploads[0].duration) }} </td> <td colspan="4" v-else> - <translate>N/A</translate> + <translate translate-context="*/*/*">N/A</translate> </td> - <td colspan="2"> + <td colspan="2" class="align right"> <track-favorite-icon class="favorite-icon" :track="track"></track-favorite-icon> <track-playlist-icon v-if="$store.state.auth.authenticated" diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue index 937025807864f950f3c4dae60a503c7ca8b29f54..31327ee3a5e3286cec4eded21ccc801d73e0b1eb 100644 --- a/front/src/components/audio/track/Table.vue +++ b/front/src/components/audio/track/Table.vue @@ -1,30 +1,36 @@ <template> - <table class="ui compact very basic fixed single line unstackable table"> - <thead> - <tr> - <th></th> - <th></th> - <th colspan="6"><translate>Title</translate></th> - <th colspan="4"><translate>Artist</translate></th> - <th colspan="4"><translate>Album</translate></th> - <th colspan="4"><translate>Duration</translate></th> - <th colspan="2"></th> - </tr> - </thead> - <tbody> - <track-row - :playable="playable" - :display-position="displayPosition" - :track="track" - :artist="artist" - :key="index + '-' + track.id" - v-for="(track, index) in tracks"></track-row> - </tbody> - </table> + <div class="table-wrapper"> + <table class="ui compact very basic unstackable table"> + <thead> + <tr> + <th></th> + <th></th> + <th colspan="6"><translate translate-context="Content/Track/*/Noun">Title</translate></th> + <th colspan="4"><translate translate-context="*/*/*/Noun">Artist</translate></th> + <th colspan="4"><translate translate-context="*/*/*">Album</translate></th> + <th colspan="4"><translate translate-context="Content/*/*">Duration</translate></th> + <th colspan="2"></th> + </tr> + </thead> + <tbody> + <track-row + :playable="playable" + :display-position="displayPosition" + :track="track" + :artist="artist" + :key="index + '-' + track.id" + v-for="(track, index) in allTracks"></track-row> + </tbody> + </table> + <button :class="['ui', {loading: isLoadingMore}, 'button']" v-if="loadMoreUrl" @click="loadMore(loadMoreUrl)"> + <translate translate-context="Content/*/Button.Label">Load more…</translate> + </button> + </div> </template> <script> import backend from '@/audio/backend' +import axios from 'axios' import TrackRow from '@/components/audio/track/Row' import Modal from '@/components/semantic/Modal' @@ -33,6 +39,7 @@ export default { props: { tracks: {type: Array, required: true}, playable: {type: Boolean, required: false, default: false}, + nextUrl: {type: String, required: false, default: null}, artist: {type: Object, required: false}, displayPosition: {type: Boolean, default: false} }, @@ -42,7 +49,29 @@ export default { }, data () { return { - backend: backend + backend: backend, + loadMoreUrl: this.nextUrl, + isLoadingMore: false, + additionalTracks: [] + } + }, + computed: { + allTracks () { + return this.tracks.concat(this.additionalTracks) + } + }, + methods: { + loadMore (url) { + let self = this + self.isLoadingMore = true + axios.get(url).then((response) => { + self.additionalTracks = self.additionalTracks.concat(response.data.results) + self.loadMoreUrl = response.data.next + self.isLoadingMore = false + }, (error) => { + self.isLoadingMore = false + + }) } } } diff --git a/front/src/components/audio/track/Widget.vue b/front/src/components/audio/track/Widget.vue index f909945f0f97119fcb3ab3a36d8943bfe9513a3b..b8ad3c639c66eb756ca7f3d50148d08aa5e7c5df 100644 --- a/front/src/components/audio/track/Widget.vue +++ b/front/src/components/audio/track/Widget.vue @@ -103,6 +103,9 @@ export default { watch: { offset () { this.fetchData() + }, + "$store.state.moderation.lastUpdate": function () { + this.fetchData(this.url) } } } diff --git a/front/src/components/auth/ApplicationEdit.vue b/front/src/components/auth/ApplicationEdit.vue new file mode 100644 index 0000000000000000000000000000000000000000..b22ade7af6c00c4882a5a590525f7faec58aec25 --- /dev/null +++ b/front/src/components/auth/ApplicationEdit.vue @@ -0,0 +1,80 @@ +<template> + <main class="main pusher" v-title="labels.title"> + <div class="ui vertical stripe segment"> + <section class="ui text container"> + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + <template v-else> + <router-link :to="{name: 'settings'}"> + <translate translate-context="Content/Applications/Link">Back to settings</translate> + </router-link> + <h2 class="ui header"> + <translate translate-context="Content/Applications/Title">Application details</translate> + </h2> + <div class="ui form"> + <p> + <translate translate-context="Content/Application/Paragraph/"> + Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else. + </translate> + </p> + <div class="field"> + <label><translate translate-context="Content/Applications/Label">Application ID</translate></label> + <copy-input :value="application.client_id" /> + </div> + <div class="field"> + <label><translate translate-context="Content/Applications/Label">Application secret</translate></label> + <copy-input :value="application.client_secret" /> + </div> + </div> + <h2 class="ui header"> + <translate translate-context="Content/Applications/Title">Edit application</translate> + </h2> + <application-form @updated="application = $event" :app="application" /> + </template> + </section> + </div> + </main> +</template> + +<script> +import axios from "axios" + +import ApplicationForm from "@/components/auth/ApplicationForm" + +export default { + props: ['id'], + components: { + ApplicationForm + }, + data() { + return { + application: null, + isLoading: false, + } + }, + created () { + this.fetchApplication() + }, + methods: { + fetchApplication () { + this.isLoading = true + let self = this + axios.get(`oauth/apps/${this.id}/`).then((response) => { + self.isLoading = false + self.application = response.data + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + }, + computed: { + labels() { + return { + title: this.$pgettext('Content/Applications/Title', "Edit application") + } + }, + } +} +</script> diff --git a/front/src/components/auth/ApplicationForm.vue b/front/src/components/auth/ApplicationForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..c2eefbfdfb705643aad0bf728e7f8764a7d3a6af --- /dev/null +++ b/front/src/components/auth/ApplicationForm.vue @@ -0,0 +1,185 @@ +<template> + + <form class="ui form" @submit.prevent="submit()"> + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Content/*/Error message.Title">We cannot save your changes</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div class="ui field"> + <label><translate translate-context="Content/Applications/Input.Label/Noun">Name</translate></label> + <input name="name" required type="text" v-model="fields.name" /> + </div> + <div class="ui field"> + <label><translate translate-context="Content/Applications/Input.Label/Noun">Redirect URI</translate></label> + <input name="redirect_uris" type="text" v-model="fields.redirect_uris" /> + <p class="help"> + <translate translate-context="Content/Applications/Help Text"> + Use "urn:ietf:wg:oauth:2.0:oob" as a redirect URI if your application is not served on the web. + </translate> + </p> + </div> + <div class="ui field"> + <label><translate translate-context="Content/Applications/Input.Label/Noun">Scopes</translate></label> + <p> + <translate translate-context="Content/Applications/Paragraph/"> + Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes. + </translate> + </p> + <div class="ui stackable two column grid"> + <div v-for="parent in allScopes" class="column"> + <div class="ui parent checkbox"> + <input + v-model="scopeArray" + :value="parent.id" + :id="parent.id" + type="checkbox"> + <label :for="parent.id"> + {{ parent.label }} + <p class="help"> + {{ parent.description }} + </p> + </label> + </div> + + <div v-for="child in parent.children"> + <div class="ui child checkbox"> + <input + v-model="scopeArray" + :value="child.id" + :id="child.id" + type="checkbox"> + <label :for="child.id"> + {{ child.id }} + <p class="help"> + {{ child.description }} + </p> + </label> + </div> + </div> + </div> + </div> + + </div> + <button :class="['ui', {'loading': isLoading}, 'green', 'button']" type="submit"> + <translate v-if="updating" key="2" translate-context="Content/Applications/Button.Label/Verb">Update application</translate> + <translate v-else key="2" translate-context="Content/Applications/Button.Label/Verb">Create application</translate> + </button> + </form> +</template> + +<script> +import _ from "@/lodash" +import axios from "axios" +import TranslationsMixin from "@/components/mixins/Translations" + +export default { + mixins: [TranslationsMixin], + props: { + app: {type: Object, required: false}, + defaults: {type: Object, required: false} + }, + data() { + let app = this.app || {} + let defaults = this.defaults || {} + return { + isLoading: false, + errors: [], + fields: { + name: app.name || defaults.name || '', + redirect_uris: app.redirect_uris || defaults.redirect_uris || 'urn:ietf:wg:oauth:2.0:oob', + scopes: app.scopes || defaults.scopes || 'read' + }, + scopes: [ + {id: "profile", icon: 'user'}, + {id: "libraries", icon: 'book'}, + {id: "favorites", icon: 'heart'}, + {id: "listenings", icon: 'music'}, + {id: "follows", icon: 'users'}, + {id: "playlists", icon: 'list'}, + {id: "radios", icon: 'rss'}, + {id: "filters", icon: 'eye slash'}, + {id: "notifications", icon: 'bell'}, + {id: "edits", icon: 'pencil alternate'}, + ] + } + }, + methods: { + submit () { + this.errors = [] + let self = this + self.isLoading = true + let payload = this.fields + let event, promise, message + if (this.updating) { + event = 'updated' + promise = axios.patch(`oauth/apps/${this.app.client_id}/`, payload) + } else { + event = 'created' + promise = axios.post(`oauth/apps/`, payload) + } + return promise.then( + response => { + self.isLoading = false + self.$emit(event, response.data) + }, + error => { + self.isLoading = false + self.errors = error.backendErrors + } + ) + }, + }, + computed: { + updating () { + return this.app + }, + scopeArray: { + get () { + return this.fields.scopes.split(' ') + }, + set (v) { + this.fields.scopes = _.uniq(v).join(' ') + } + }, + allScopes () { + let self = this + let parents = [ + { + id: 'read', + label: this.$pgettext('Content/OAuth Scopes/Label/Verb', 'Read'), + description: this.$pgettext('Content/OAuth Scopes/Help Text', 'Read-only access to user data'), + value: this.scopeArray.indexOf('read') > -1 + }, + { + id: 'write', + label: this.$pgettext('Content/OAuth Scopes/Label/Verb', 'Write'), + description: this.$pgettext('Content/OAuth Scopes/Help Text', 'Write-only access to user data'), + value: this.scopeArray.indexOf('write') > -1 + }, + ] + parents.forEach((p) => { + p.children = self.scopes.map(s => { + let id = `${p.id}:${s.id}` + return { + id, + value: this.scopeArray.indexOf(id) > -1, + } + }) + }) + return parents + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +.parent.checkbox { + margin: 1em 0; +} +.child.checkbox { + margin-left: 1em; +} +</style> diff --git a/front/src/components/auth/ApplicationNew.vue b/front/src/components/auth/ApplicationNew.vue new file mode 100644 index 0000000000000000000000000000000000000000..ba1a575fe79a95ae72d4c6f4611986bbc9efe9cb --- /dev/null +++ b/front/src/components/auth/ApplicationNew.vue @@ -0,0 +1,46 @@ +<template> + <main class="main pusher" v-title="labels.title"> + <div class="ui vertical stripe segment"> + <section class="ui text container"> + <router-link :to="{name: 'settings'}"> + <translate translate-context="Content/Applications/Link">Back to settings</translate> + </router-link> + <h2 class="ui header"> + <translate translate-context="Content/Applications/Title">Create a new application</translate> + </h2> + <application-form + :defaults="defaults" + @created="$router.push({name: 'settings.applications.edit', params: {id: $event.client_id}})" /> + </section> + </div> + </main> +</template> + +<script> +import ApplicationForm from "@/components/auth/ApplicationForm" + +export default { + props: ['name', 'redirect_uris', 'scopes'], + components: { + ApplicationForm + }, + data() { + return { + application: null, + isLoading: false, + defaults: { + name: this.name, + redirect_uris: this.redirect_uris, + scopes: this.scopes, + } + } + }, + computed: { + labels() { + return { + title: this.$pgettext('Content/Applications/Title', "Create a new application") + } + }, + } +} +</script> diff --git a/front/src/components/auth/Authorize.vue b/front/src/components/auth/Authorize.vue new file mode 100644 index 0000000000000000000000000000000000000000..dd9bf6eebfddc2cc993401fdd31d94cce2892e40 --- /dev/null +++ b/front/src/components/auth/Authorize.vue @@ -0,0 +1,201 @@ +<template> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> + <div class="ui small text container"> + <h2><i class="lock open icon"></i><translate translate-context="Content/Auth/Title/Verb">Authorize third-party app</translate></h2> + <div v-if="errors.length > 0" class="ui negative message"> + <div v-if="application" class="header"><translate translate-context="Popup/Moderation/Error message">Error while authorizing application</translate></div> + <div v-else class="header"><translate translate-context="Popup/Moderation/Error message">Error while fetching application data</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + <form v-else-if="application && !code" :class="['ui', {loading: isLoading}, 'form']" @submit.prevent="submit"> + <h3><translate translate-context="Content/Auth/Title" :translate-params="{app: application.name}">%{ app } wants to access your Funkwhale account</translate></h3> + + <h4 v-for="topic in topicScopes" class="ui header"> + <span v-if="topic.write && !topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'label']"> + <i class="pencil icon"></i> + <translate translate-context="Content/Auth/Label/Noun">Write-only</translate> + </span> + <span v-else-if="!topic.write && topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'label']"> + <translate translate-context="Content/Auth/Label/Noun">Read-only</translate> + </span> + <span v-else-if="topic.write && topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'label']"> + <i class="pencil icon"></i> + <translate translate-context="Content/Auth/Label/Noun">Full access</translate> + </span> + <i :class="[topic.icon, 'icon']"></i> + <div class="content"> + {{ topic.label }} + <div class="sub header"> + {{ topic.description }} + </div> + </div> + </h4> + <div v-if="unknownRequestedScopes.length > 0"> + <p><strong><translate translate-context="Content/Auth/Paragraph">The application is also requesting the following unknown permissions:</translate></strong></p> + <ul v-for="scope in unknownRequestedScopes"> + <li>{{ scope }}</li> + </ul> + + </div> + <button class="ui green labeled icon button" type="submit"> + <i class="lock open icon"></i> + <translate translate-context="Content/Signup/Button.Label/Verb" :translate-params="{app: application.name}">Authorize %{ app }</translate> + </button> + <p v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'" key="1" v-translate translate-context="Content/Auth/Paragraph"> + You will be shown a code to copy-paste in the application.</p> + <p v-else key="2" v-translate="{url: redirectUri}" translate-context="Content/Auth/Paragraph" :translate-params="{url: redirectUri}">You will be redirected to <strong>%{ url }</strong></p> + + </form> + <div v-else-if="code"> + <p><strong><translate translate-context="Content/Auth/Paragraph">Copy-paste the following code in the application:</translate></strong></p> + <copy-input :value="code"></copy-input> + </div> + </div> + </section> + </main> +</template> + +<script> +import TranslationsMixin from "@/components/mixins/Translations" + +import axios from 'axios' + +export default { + mixins: [TranslationsMixin], + props: [ + 'clientId', + 'redirectUri', + 'scope', + 'responseType', + 'nonce', + 'state', + ], + data() { + return { + application: null, + isLoading: false, + errors: [], + code: null, + knownScopes: [ + {id: "profile", icon: 'user'}, + {id: "libraries", icon: 'book'}, + {id: "favorites", icon: 'heart'}, + {id: "listenings", icon: 'music'}, + {id: "follows", icon: 'users'}, + {id: "playlists", icon: 'list'}, + {id: "radios", icon: 'rss'}, + {id: "filters", icon: 'eye slash'}, + {id: "notifications", icon: 'bell'}, + {id: "edits", icon: 'pencil alternate'}, + ] + } + }, + created () { + if (this.clientId) { + this.fetchApplication() + } + }, + computed: { + labels () { + return { + title: this.$pgettext('Head/Authorize/Title', "Allow application") + } + }, + requestedScopes () { + return (this.scope || '').split(' ') + }, + supportedScopes () { + let supported = ['read', 'write'] + this.knownScopes.forEach(s => { + supported.push(`read:${s.id}`) + supported.push(`write:${s.id}`) + }) + return supported + }, + unknownRequestedScopes () { + let self = this + return this.requestedScopes.filter(s => { + return self.supportedScopes.indexOf(s) < 0 + }) + }, + topicScopes () { + let self = this + let requested = this.requestedScopes + let write = false + let read = false + if (requested.indexOf('read') > -1) { + read = true + } + if (requested.indexOf('write') > -1) { + write = true + } + + return this.knownScopes.map(s => { + let id = s.id + return { + id: id, + icon: s.icon, + label: self.sharedLabels.scopes[s.id].label, + description: self.sharedLabels.scopes[s.id].description, + read: read || requested.indexOf(`read:${id}`) > -1, + write: write || requested.indexOf(`write:${id}`) > -1, + } + }).filter(c => { + return c.read || c.write + }) + } + }, + methods: { + fetchApplication () { + this.isLoading = true + let self = this + axios.get(`oauth/apps/${this.clientId}/`).then((response) => { + self.isLoading = false + self.application = response.data + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + submit () { + this.isLoading = true + let self = this + let data = new FormData(); + data.set('redirect_uri', this.redirectUri) + data.set('scope', this.scope) + data.set('allow', true) + data.set('client_id', this.clientId) + data.set('response_type', this.responseType) + data.set('state', this.state) + data.set('nonce', this.nonce) + axios.post(`oauth/authorize/`, data, {headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest'}}).then((response) => { + if (self.redirectUri === 'urn:ietf:wg:oauth:2.0:oob') { + self.isLoading = false + self.code = response.data.code + } else { + window.location.href = response.data.redirect_uri + } + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +.ui.header .content { + text-align: left; +} +.ui.header > .ui.label { + margin-top: 0.3em; +} +</style> diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue index 09a9c6c29d5bb690b5c6eac3cbabd24883295b3d..20aff3236bd797e0c03a50ce9ec36b29287e20c9 100644 --- a/front/src/components/auth/Login.vue +++ b/front/src/components/auth/Login.vue @@ -2,20 +2,20 @@ <main class="main pusher" v-title="labels.title"> <section class="ui vertical stripe segment"> <div class="ui small text container"> - <h2><translate>Log in to your Funkwhale account</translate></h2> + <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2> <form class="ui form" @submit.prevent="submit()"> <div v-if="error" class="ui negative message"> - <div class="header"><translate>We cannot log you in</translate></div> + <div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div> <ul class="list"> - <li v-if="error == 'invalid_credentials'"><translate>Please double-check your username/password couple is correct</translate></li> - <li v-else><translate>An unknown error happend, this can mean the server is down or cannot be reached</translate></li> + <li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li> + <li v-else><translate translate-context="Content/Login/Error message/List item">An unknown error happend, this can mean the server is down or cannot be reached</translate></li> </ul> </div> <div class="field"> <label> - <translate>Username or email</translate> | + <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate> | <router-link :to="{path: '/signup'}"> - <translate>Create an account</translate> + <translate translate-context="*/Signup/Link/Verb">Create an account</translate> </router-link> </label> <input @@ -31,16 +31,16 @@ </div> <div class="field"> <label> - <translate>Password</translate> | + <translate translate-context="Content/*/Input.Label">Password</translate> | <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}"> - <translate>Reset your password</translate> + <translate translate-context="*/Login/*/Verb">Reset your password</translate> </router-link> </label> <password-input :index="2" required v-model="credentials.password" /> </div> <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> - <translate>Login</translate> + <translate translate-context="*/Login/*/Verb">Login</translate> </button> </form> </div> @@ -80,8 +80,8 @@ export default { }, computed: { labels() { - let usernamePlaceholder = this.$gettext("Enter your username or email") - let title = this.$gettext("Log In") + let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email") + let title = this.$pgettext('Head/Login/Title', "Log In") return { usernamePlaceholder, title @@ -97,7 +97,6 @@ export default { username: this.credentials.username, password: this.credentials.password } - console.log('NEXT', this.next) this.$store .dispatch("auth/login", { credentials, diff --git a/front/src/components/auth/Logout.vue b/front/src/components/auth/Logout.vue index 90495f5e0ed17fbb2485da7003a93d97a7bbf5a9..99fbc9da6ec7e8b11f64ff6dd4ec02af0ba080f1 100644 --- a/front/src/components/auth/Logout.vue +++ b/front/src/components/auth/Logout.vue @@ -3,10 +3,10 @@ <section class="ui vertical stripe segment"> <div class="ui small text container"> <h2> - <translate>Are you sure you want to log out?</translate> + <translate translate-context="Content/Login/Title">Are you sure you want to log out?</translate> </h2> - <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')"><translate>Yes, log me out!</translate></button> + <p v-translate="{username: $store.state.auth.username}" translate-context="Content/Login/Paragraph">You are currently logged in as %{ username }</p> + <button class="ui button" @click="$store.dispatch('auth/logout')"><translate translate-context="Content/Login/Button.Label">Yes, log me out!</translate></button> </div> </section> </main> @@ -17,7 +17,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Log Out") + title: this.$pgettext('Head/Login/Title', "Log Out") } } } diff --git a/front/src/components/auth/Profile.vue b/front/src/components/auth/Profile.vue index 73c9ab2d9c64950cd145c04cb70813a9e2671612..278f62ec0092492c733d524f6c4773c5b7575b9e 100644 --- a/front/src/components/auth/Profile.vue +++ b/front/src/components/auth/Profile.vue @@ -10,18 +10,18 @@ <img class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](profile.avatar.square_crop)" /> <div class="content"> {{ profile.username }} - <div class="sub header" v-translate="{date: signupDate}">Member since %{ date }</div> + <div class="sub header" v-translate="{date: signupDate}" translate-context="Content/Profile/Paragraph">Member since %{ date }</div> </div> </h2> <div class="ui basic green label"> - <translate>This is you!</translate> + <translate translate-context="Content/Profile/Button.Paragraph">This is you!</translate> </div> <a v-if="profile.is_staff" class="ui yellow label" :href="$store.getters['instance/absoluteUrl']('/api/admin')" target="_blank"> <i class="star icon"></i> - <translate>Staff member</translate> + <translate translate-context="Content/Profile/User role">Staff member</translate> </a> </div> </template> @@ -43,7 +43,7 @@ export default { profile: state => state.auth.profile }), labels() { - let msg = this.$gettext("%{ username }'s profile") + let msg = this.$pgettext('Head/Profile/Title', "%{ username }'s profile") let usernameProfile = this.$gettextInterpolate(msg, { username: this.username }) diff --git a/front/src/components/auth/Settings.vue b/front/src/components/auth/Settings.vue index 8305d0e05dd5b11553d842cc97b224261cf1859b..6e2516ed87156da3c31b360d6358d027ef3009cb 100644 --- a/front/src/components/auth/Settings.vue +++ b/front/src/components/auth/Settings.vue @@ -1,18 +1,18 @@ <template> <main class="main pusher" v-title="labels.title"> <div class="ui vertical stripe segment"> - <section class="ui small text container"> + <section class="ui text container"> <h2 class="ui header"> - <translate>Account settings</translate> + <translate translate-context="Content/Settings/Title">Account settings</translate> </h2> <form class="ui form" @submit.prevent="submitSettings()"> <div v-if="settings.success" class="ui positive message"> <div class="header"> - <translate>Settings updated</translate> + <translate translate-context="Content/Settings/Message">Settings updated</translate> </div> </div> <div v-if="settings.errors.length > 0" class="ui negative message"> - <div class="header"><translate>We cannot save your settings</translate></div> + <div class="header"><translate translate-context="Content/Settings/Error message.Title">Your settings can't be updateds</translate></div> <ul class="list"> <li v-for="error in settings.errors">{{ error }}</li> </ul> @@ -25,88 +25,248 @@ </select> </div> <button :class="['ui', {'loading': isLoading}, 'button']" type="submit"> - <translate>Update settings</translate> + <translate translate-context="Content/Settings/Button.Label/Verb">Update settings</translate> </button> </form> </section> - <div class="ui hidden divider"></div> - <section class="ui small text container"> + <section class="ui text container"> + <div class="ui hidden divider"></div> <h2 class="ui header"> - <translate>Avatar</translate> + <translate translate-context="Content/Settings/Title">Avatar</translate> </h2> <div class="ui form"> <div v-if="avatarErrors.length > 0" class="ui negative message"> - <div class="header"><translate>We cannot save your avatar</translate></div> + <div class="header"><translate translate-context="Content/Settings/Error message.Title">Your avatar cannot be saved</translate></div> <ul class="list"> <li v-for="error in avatarErrors">{{ error }}</li> </ul> </div> <div class="ui stackable grid"> <div class="ui ten wide column"> - <h3 class="ui header"><translate>Upload a new avatar</translate></h3> - <p><translate>PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px.</translate></p> + <h3 class="ui header"><translate translate-context="Content/Settings/Title/Verb">Upload a new avatar</translate></h3> + <p><translate translate-context="Content/Settings/Paragraph">PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px.</translate></p> <input class="ui input" ref="avatar" type="file" /> <div class="ui hidden divider"></div> <button @click="submitAvatar" :class="['ui', {'loading': isLoadingAvatar}, 'button']"> - <translate>Update avatar</translate> + <translate translate-context="Content/Settings/Button.Label/Verb">Update avatar</translate> </button> </div> <div class="ui six wide column"> - <h3 class="ui header"><translate>Current avatar</translate></h3> + <h3 class="ui header"><translate translate-context="Content/Settings/Title/Noun">Current avatar</translate></h3> <img class="ui circular image" v-if="currentAvatar && currentAvatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](currentAvatar.medium_square_crop)" /> <div class="ui hidden divider"></div> <button @click="removeAvatar" v-if="currentAvatar && currentAvatar.square_crop" :class="['ui', {'loading': isLoadingAvatar}, ,'yellow', 'button']"> - <translate>Remove avatar</translate> + <translate translate-context="Content/Settings/Button.Label/Verb">Remove avatar</translate> </button> </div> </div> </div> </section> - <div class="ui hidden divider"></div> - <section class="ui small text container"> + + <section class="ui text container"> + <div class="ui hidden divider"></div> <h2 class="ui header"> - <translate>Change my password</translate> + <translate translate-context="Content/Settings/Title/Verb">Change my password</translate> </h2> <div class="ui message"> - <translate>Changing your password will also change your Subsonic API password if you have requested one.</translate> <translate>You will have to update your password on your clients that use this password.</translate> + <translate translate-context="Content/Settings/Paragraph'">Changing your password will also change your Subsonic API password if you have requested one.</translate> <translate translate-context="Content/Settings/Paragraph">You will have to update your password on your clients that use this password.</translate> </div> <form class="ui form" @submit.prevent="submitPassword()"> <div v-if="passwordError" class="ui negative message"> <div class="header"> - <translate>Cannot change your password</translate> + <translate translate-context="Content/Settings/Error message.Title">Your password cannot be changed</translate> </div> <ul class="list"> - <li v-if="passwordError == 'invalid_credentials'"><translate>Please double-check your password is correct</translate></li> + <li v-if="passwordError == 'invalid_credentials'"><translate translate-context="Content/Settings/Error message.List item/Call to action">Please double-check your password is correct</translate></li> </ul> </div> <div class="field"> - <label><translate>Old password</translate></label> + <label><translate translate-context="Content/Settings/Input.Label">Old password</translate></label> <password-input required v-model="old_password" /> </div> <div class="field"> - <label><translate>New password</translate></label> + <label><translate translate-context="Content/Settings/Input.Label">New password</translate></label> <password-input required v-model="new_password" /> </div> <dangerous-button color="yellow" :class="['ui', {'loading': isLoading}, 'button']" :action="submitPassword"> - <translate>Change password</translate> - <p slot="modal-header"><translate>Change your password?</translate></p> + <translate translate-context="Content/Settings/Button.Label">Change password</translate> + <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Change your password?</translate></p> <div slot="modal-content"> - <p><translate>Changing your password will have the following consequences</translate></p> + <p><translate translate-context="Popup/Settings/Paragraph">Changing your password will have the following consequences:</translate></p> <ul> - <li><translate>You will be logged out from this session and have to log in with the new one</translate></li> - <li><translate>Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password</translate></li> + <li><translate translate-context="Popup/Settings/List item">You will be logged out from this session and have to log in with the new one</translate></li> + <li><translate translate-context="Popup/Settings/List item">Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password</translate></li> </ul> </div> - <p slot="modal-confirm"><translate>Disable access</translate></p> + <div slot="modal-confirm"><translate translate-context="Popup/Settings/Button.Label">Disable access</translate></div> </dangerous-button> </form> <div class="ui hidden divider" /> <subsonic-token-form /> </section> + + <section class="ui text container" id="content-filters"> + <div class="ui hidden divider"></div> + <h2 class="ui header"> + <i class="eye slash outline icon"></i> + <div class="content"> + <translate translate-context="Content/Settings/Title/Noun">Content filters</translate> + </div> + </h2> + <p><translate translate-context="Content/Settings/Paragraph">Content filters help you hide content you don't want to see on the service.</translate></p> + + <button + @click="$store.dispatch('moderation/fetchContentFilters')" + class="ui basic icon button"> + <i class="refresh icon"></i> + <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate> + </button> + <h3 class="ui header"> + <translate translate-context="Content/Settings/Title">Hidden artists</translate> + </h3> + <table class="ui compact very basic unstackable table"> + <thead> + <tr> + <th><translate translate-context="*/*/*/Noun">Name</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="filter in $store.getters['moderation/artistFilters']()" :key='filter.uuid'> + <td> + <router-link :to="{name: 'library.artists.detail', params: {id: filter.target.id }}"> + {{ filter.target.name }} + </router-link> + </td> + <td> + <human-date :date="filter.creation_date"></human-date> + </td> + <td> + <button @click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)" class="ui basic tiny button"> + <translate translate-context="*/*/*/Verb">Delete</translate> + </button> + </td> + </tr> + </tbody> + </table> + </section> + <section class="ui text container" id="grants"> + <div class="ui hidden divider"></div> + <h2 class="ui header"> + <i class="open lock icon"></i> + <div class="content"> + <translate translate-context="Content/Settings/Title/Noun">Authorized apps</translate> + </div> + </h2> + <p><translate translate-context="Content/Settings/Paragraph">This is the list of applications that have access to your account data.</translate></p> + <button + @click="fetchApps()" + class="ui basic icon button"> + <i class="refresh icon"></i> + <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate> + </button> + <table v-if="apps.length > 0" class="ui compact very basic unstackable table"> + <thead> + <tr> + <th><translate translate-context="*/*/*/Noun">Application</translate></th> + <th><translate translate-context="Content/*/*/Noun">Permissions</translate></th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="app in apps" :key='app.client_id'> + <td> + {{ app.name }} + </td> + <td> + {{ app.scopes }} + </td> + <td> + <dangerous-button + class="ui tiny basic button" + @confirm="revokeApp(app.client_id)"> + <translate translate-context="*/*/*/Verb">Revoke</translate> + <p slot="modal-header" v-translate="{application: app.name}" translate-context="Popup/Settings/Title">Revoke access for application "%{ application }"?</p> + <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will prevent this application from accessing the service on your behalf.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Revoke access</translate></div> + </dangerous-button> + </td> + </tr> + </tbody> + </table> + <empty-state v-else> + <translate slot="title" translate-context="Content/Applications/Paragraph"> + You don't have any application connected with your account. + </translate> + <translate translate-context="Content/Applications/Paragraph"> + If you authorize third-party applications to access your data, those applications will be listed here. + </translate> + </empty-state> + </section> + <section class="ui text container" id="apps"> + <div class="ui hidden divider"></div> + <h2 class="ui header"> + <i class="code icon"></i> + <div class="content"> + <translate translate-context="Content/Settings/Title/Noun">Your applications</translate> + </div> + </h2> + <p><translate translate-context="Content/Settings/Paragraph">This is the list of applications that you have created.</translate></p> + <router-link class="ui basic green button" :to="{name: 'settings.applications.new'}"> + <translate translate-context="Content/Settings/Button.Label">Create a new application</translate> + </router-link> + <table v-if="ownedApps.length > 0" class="ui compact very basic unstackable table"> + <thead> + <tr> + <th><translate translate-context="*/*/*/Noun">Application</translate></th> + <th><translate translate-context="Content/*/*/Noun">Scopes</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="app in ownedApps" :key='app.client_id'> + <td> + <router-link :to="{name: 'settings.applications.edit', params: {id: app.client_id}}"> + {{ app.name }} + </router-link> + </td> + <td> + {{ app.scopes }} + </td> + <td> + <human-date :date="app.created" /> + </td> + <td> + <router-link class="ui basic tiny green button" :to="{name: 'settings.applications.edit', params: {id: app.client_id}}"> + <translate translate-context="Content/Settings/Button.Label">Edit</translate> + </router-link> + <dangerous-button + class="ui tiny basic button" + @confirm="deleteApp(app.client_id)"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header" v-translate="{application: app.name}" translate-context="Popup/Settings/Title">Delete application "%{ application }"?</p> + <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will permanently delete the application and all the associated tokens.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Delete application</translate></div> + </dangerous-button> + </td> + </tr> + </tbody> + </table> + <empty-state v-else> + <translate slot="title" translate-context="Content/Applications/Paragraph"> + You don't have any configured application yet. + </translate> + <translate translate-context="Content/Applications/Paragraph"> + Create one to integrate Funkwhale with third-party applications. + </translate> + </empty-state> + </section> </div> </main> </template> @@ -137,6 +297,8 @@ export default { isLoadingAvatar: false, avatarErrors: [], avatar: null, + apps: [], + ownedApps: [], settings: { success: false, errors: [], @@ -156,6 +318,10 @@ export default { }) return d }, + created () { + this.fetchApps() + this.fetchOwnedApps() + }, mounted() { $("select.dropdown").dropdown() }, @@ -181,6 +347,56 @@ export default { } ) }, + fetchApps() { + this.apps = [] + let self = this + let url = `oauth/grants/` + return axios.get(url).then( + response => { + self.apps = response.data + }, + error => { + } + ) + }, + fetchOwnedApps() { + this.ownedApps = [] + let self = this + let url = `oauth/apps/` + return axios.get(url).then( + response => { + self.ownedApps = response.data.results + }, + error => { + } + ) + }, + revokeApp (id) { + let self = this + let url = `oauth/grants/${id}/` + return axios.delete(url).then( + response => { + self.apps = self.apps.filter(a => { + return a.client_id != id + }) + }, + error => { + } + ) + }, + deleteApp (id) { + let self = this + let url = `oauth/apps/${id}/` + return axios.delete(url).then( + response => { + self.ownedApps = self.ownedApps.filter(a => { + return a.client_id != id + }) + }, + error => { + } + ) + }, submitAvatar() { this.isLoadingAvatar = true this.avatarErrors = [] @@ -260,7 +476,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Account Settings") + title: this.$pgettext('Head/Settings/Title', "Account Settings") } }, orderedSettingsFields() { diff --git a/front/src/components/auth/Signup.vue b/front/src/components/auth/Signup.vue index 815f0253a72b697ea663801dff4ba43f5909988d..974d082bda98153009cc2e9f35cd4c0e67ab16fd 100644 --- a/front/src/components/auth/Signup.vue +++ b/front/src/components/auth/Signup.vue @@ -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 translate-context="Content/Signup/Title">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 translate-context="Content/Signup/Form/Paragraph">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 translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</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 translate-context="Content/*/*">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 translate-context="Content/*/*/Noun">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 translate-context="Content/*/Input.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 translate-context="Content/*/Input.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 translate-context="Content/Signup/Button.Label">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.$pgettext("Content/Signup/Form/Placeholder", "Enter your username") + let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email") return { title, usernamePlaceholder, diff --git a/front/src/components/auth/SubsonicTokenForm.vue b/front/src/components/auth/SubsonicTokenForm.vue index ac752d92166778d7135916709f33d68ad80370b5..0184074e294365a15e453db7184644d66d75796a 100644 --- a/front/src/components/auth/SubsonicTokenForm.vue +++ b/front/src/components/auth/SubsonicTokenForm.vue @@ -1,23 +1,23 @@ <template> <form class="ui form" @submit.prevent="requestNewToken()"> - <h2><translate>Subsonic API password</translate></h2> + <h2><translate translate-context="Content/Settings/Title">Subsonic API password</translate></h2> <p class="ui message" v-if="!subsonicEnabled"> - <translate>The Subsonic API is not available on this Funkwhale instance.</translate> + <translate translate-context="Content/Settings/Paragraph">The Subsonic API is not available on this Funkwhale instance.</translate> </p> <p> - <translate>Funkwhale is compatible with other music players that support the Subsonic API.</translate> <translate>You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate> + <translate translate-context="Content/Settings/Paragraph'">Funkwhale is compatible with other music players that support the Subsonic API.</translate> <translate translate-context="Content/Settings/Paragraph">You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate> </p> <p> - <translate>However, accessing Funkwhale from those clients require a separate password you can set below.</translate> + <translate translate-context="Content/Settings/Paragraph">However, accessing Funkwhale from those clients require a separate password you can set below.</translate> </p> <p><a href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" target="_blank"> - <translate>Discover how to use Funkwhale from other apps</translate> + <translate translate-context="Content/Settings/Link">Discover how to use Funkwhale from other apps</translate> </a></p> <div v-if="success" class="ui positive message"> <div class="header">{{ successMessage }}</div> </div> <div v-if="subsonicEnabled && errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error</translate></div> + <div class="header"><translate translate-context="Content/*/Error message.Title">Error</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> @@ -31,25 +31,25 @@ color="grey" :class="['ui', {'loading': isLoading}, 'button']" :action="requestNewToken"> - <translate>Request a new password</translate> - <p slot="modal-header"><translate>Request a new Subsonic API password?</translate></p> - <p slot="modal-content"><translate>This will log you out from existing devices that use the current password.</translate></p> - <p slot="modal-confirm"><translate>Request a new password</translate></p> + <translate translate-context="*/Settings/Button.Label/Verb">Request a new password</translate> + <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Request a new Subsonic API password?</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will log you out from existing devices that use the current password.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Request a new password</translate></div> </dangerous-button> <button v-else color="grey" :class="['ui', {'loading': isLoading}, 'button']" - @click="requestNewToken"><translate>Request a password</translate></button> + @click="requestNewToken"><translate translate-context="Content/Settings/Button.Label/Verb">Request a password</translate></button> <dangerous-button v-if="token" color="yellow" :class="['ui', {'loading': isLoading}, 'button']" :action="disable"> - <translate>Disable Subsonic access</translate> - <p slot="modal-header"><translate>Disable Subsonic API access?</translate></p> - <p slot="modal-content"><translate>This will completely disable access to the Subsonic API using from account.</translate></p> - <p slot="modal-confirm"><translate>Disable access</translate></p> + <translate translate-context="Content/Settings/Button.Label/Verb">Disable Subsonic access</translate> + <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Disable Subsonic API access?</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will completely disable access to the Subsonic API using from account.</translate></p> + <div slot="modal-confirm"><translate translate-context="Popup/Settings/Button.Label">Disable access</translate></div> </dangerous-button> </template> </form> @@ -91,7 +91,7 @@ export default { }) }, requestNewToken () { - this.successMessage = this.$gettext('Password updated') + this.successMessage = this.$pgettext('Content/Settings/Message', 'Password updated') this.success = false this.errors = [] this.isLoading = true @@ -107,7 +107,7 @@ export default { }) }, disable () { - this.successMessage = this.$gettext('Access disabled') + this.successMessage = this.$pgettext('Content/Settings/Message', 'Access disabled') this.success = false this.errors = [] this.isLoading = true diff --git a/front/src/components/common/ActionTable.vue b/front/src/components/common/ActionTable.vue index 5fcaceaacacf3c37629d4734e0fd700880463299..1a43a5f9ff1ba4ea1e87ff61992458e8c947f0b1 100644 --- a/front/src/components/common/ActionTable.vue +++ b/front/src/components/common/ActionTable.vue @@ -1,142 +1,145 @@ <template> - <table class="ui compact very basic single line unstackable table"> - <thead> - <tr> - <th colspan="1000"> - <div v-if="refreshable" class="right floated"> - <span v-if="needsRefresh"> - <translate>Content have been updated, click refresh to see up-to-date content</translate> - </span> - <button - @click="$emit('refresh')" - class="ui basic icon button" - :title="labels.refresh" - :aria-label="labels.refresh"> - <i class="refresh icon"></i> - </button> - </div> + <div class="table-wrapper"> + <table class="ui compact very basic unstackable table"> + <thead> + <tr> + <th colspan="1000"> + <div v-if="refreshable" class="right floated"> + <span v-if="needsRefresh"> + <translate translate-context="Content/*/Button.Help text.Paragraph">Content have been updated, click refresh to see up-to-date content</translate> + </span> + <button + @click="$emit('refresh')" + class="ui basic icon button" + :title="labels.refresh" + :aria-label="labels.refresh"> + <i class="refresh icon"></i> + </button> + </div> - <div class="ui small left floated form" v-if="actionUrl && actions.length > 0"> - <div class="ui inline fields"> - <div class="field"> - <label><translate>Actions</translate></label> - <select class="ui dropdown" v-model="currentActionName"> - <option v-for="action in actions" :value="action.name"> - {{ action.label }} - </option> - </select> + <div class="ui small left floated form" v-if="actionUrl && actions.length > 0"> + <div class="ui inline fields"> + <div class="field"> + <label><translate translate-context="Content/*/*/Noun">Actions</translate></label> + <select class="ui dropdown" v-model="currentActionName"> + <option v-for="action in actions" :value="action.name"> + {{ action.label }} + </option> + </select> + </div> + <div class="field"> + <dangerous-button + v-if="selectAll || currentAction.isDangerous" :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']" + :confirm-color="currentAction.confirmColor || 'green'" + color="" + @confirm="launchAction"> + <translate translate-context="Content/*/Button.Label/Short, Verb">Go</translate> + <p slot="modal-header"> + <translate translate-context="Modal/*/Title" + key="1" + :translate-n="affectedObjectsCount" + :translate-params="{count: affectedObjectsCount, 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 slot="modal-content"> + <template v-if="currentAction.confirmationMessage">{{ currentAction.confirmationMessage }}</template> + <translate v-else translate-context="Modal/*/Paragraph">This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.</translate> + </p> + <div slot="modal-confirm"><translate translate-context="Modal/*/Button.Label/Short, Verb">Launch</translate></div> + </dangerous-button> + <div + v-else + @click="launchAction" + :disabled="checked.length === 0" + :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"> + <translate translate-context="Content/*/Button.Label/Short, Verb">Go</translate></div> + </div> + <div class="count field"> + <translate translate-context="Content/*/Paragraph" + tag="span" + v-if="selectAll" + key="1" + :translate-n="objectsData.count" + :translate-params="{count: objectsData.count, total: objectsData.count}" + translate-plural="All %{ count } elements selected"> + All %{ count } element selected + </translate> + <translate translate-context="Content/*/Paragraph" + tag="span" + v-else + key="2" + :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.allowAll && checkable.length > 0 && checkable.length === checked.length"> + <a @click="selectAll = true" v-if="!selectAll"> + <translate translate-context="Content/*/Link/Verb" + key="3" + :translate-n="objectsData.count" + :translate-params="{total: objectsData.count}" + translate-plural="Select all %{ total } elements"> + Select all %{ total } elements + </translate> + </a> + <a @click="selectAll = false" v-else> + <translate translate-context="Content/*/Link/Verb" key="4">Select only current page</translate> + </a> + </template> + </div> </div> - <div class="field"> - <dangerous-button - v-if="selectAll || currentAction.isDangerous" :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']" - confirm-color="green" - color="" - @confirm="launchAction"> - <translate>Go</translate> - <p slot="modal-header"> - <translate - key="1" - :translate-n="affectedObjectsCount" - :translate-params="{count: affectedObjectsCount, 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 slot="modal-content"> - <translate>This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.</translate> - </p> - <p slot="modal-confirm"><translate>Launch</translate></p> - </dangerous-button> - <div - v-else - @click="launchAction" - :disabled="checked.length === 0" - :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"> - <translate>Go</translate></div> + <div v-if="actionErrors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Content/*/Error message/Header">Error while applying action</translate></div> + <ul class="list"> + <li v-for="error in actionErrors">{{ error }}</li> + </ul> </div> - <div class="count field"> - <translate - tag="span" - v-if="selectAll" - key="1" - :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 - key="2" - :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.allowAll && checkable.length > 0 && checkable.length === checked.length"> - <a @click="selectAll = true" v-if="!selectAll"> - <translate - key="3" - :translate-n="objectsData.count" - :translate-params="{total: objectsData.count}" - translate-plural="Select all %{ total } elements"> - Select all %{ total } elements - </translate> - </a> - <a @click="selectAll = false" v-else> - <translate key="4">Select only current page</translate> - </a> - </template> + <div v-if="actionResult" class="ui positive message"> + <p> + <translate translate-context="Content/*/Paragraph" + :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> </div> </div> - <div v-if="actionErrors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while applying action</translate></div> - <ul class="list"> - <li v-for="error in actionErrors">{{ error }}</li> - </ul> - </div> - <div v-if="actionResult" class="ui positive message"> - <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> + </th> + </tr> + <tr> + <th v-if="actions.length > 0"> + <div class="ui checkbox"> + <input + type="checkbox" + @change="toggleCheckAll" + :disabled="checkable.length === 0" + :checked="checkable.length > 0 && checked.length === checkable.length"><label> </label> </div> - </div> - </th> - </tr> - <tr> - <th v-if="actions.length > 0"> - <div class="ui checkbox"> + </th> + <slot name="header-cells"></slot> + </tr> + </thead> + <tbody v-if="objectsData.count > 0"> + <tr v-for="(obj, index) in objects"> + <td v-if="actions.length > 0" class="collapsing"> <input type="checkbox" - @change="toggleCheckAll" - :disabled="checkable.length === 0" - :checked="checkable.length > 0 && checked.length === checkable.length"><label> </label> - </div> - </th> - <slot name="header-cells"></slot> - </tr> - </thead> - <tbody v-if="objectsData.count > 0"> - <tr v-for="(obj, index) in objects"> - <td v-if="actions.length > 0" class="collapsing"> - <input - type="checkbox" - :disabled="checkable.indexOf(getId(obj)) === -1" - @click="toggleCheck($event, getId(obj), index)" - :checked="checked.indexOf(getId(obj)) > -1"><label> </label> - </td> - <slot name="row-cells" :obj="obj"></slot> - </tr> - </tbody> - </table> + :disabled="checkable.indexOf(getId(obj)) === -1" + @click="toggleCheck($event, getId(obj), index)" + :checked="checked.indexOf(getId(obj)) > -1"><label> </label> + </td> + <slot name="row-cells" :obj="obj"></slot> + </tr> + </tbody> + </table> + </div> </template> <script> import axios from 'axios' @@ -269,7 +272,7 @@ export default { }, labels () { return { - refresh: this.$gettext('Refresh table content') + refresh: this.$pgettext('Content/*/Button.Tooltip/Verb', 'Refresh table content') } }, affectedObjectsCount () { diff --git a/front/src/components/common/CopyInput.vue b/front/src/components/common/CopyInput.vue index af82f2c661aec207bbae6208d192ca4376dc02b6..415b2fc9bcd88e84aa8e87b996c04808eb6a922b 100644 --- a/front/src/components/common/CopyInput.vue +++ b/front/src/components/common/CopyInput.vue @@ -1,12 +1,12 @@ <template> <div class="ui fluid action input"> <p class="message" v-if="copied"> - <translate>Text copied to clipboard!</translate> + <translate translate-context="Content/*/Paragraph">Text copied to clipboard!</translate> </p> <input ref="input" :value="value" type="text"> <button @click="copy" :class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']"> <i class="copy icon"></i> - <translate>Copy</translate> + <translate translate-context="*/*/Button.Label/Short, Verb">Copy</translate> </button> </div> </template> diff --git a/front/src/components/common/DangerousButton.vue b/front/src/components/common/DangerousButton.vue index e6139203a7c6f96e1eb1fc57ec498b7318643590..502accf0f9acc8813cfbd4587c199ac8a118b907 100644 --- a/front/src/components/common/DangerousButton.vue +++ b/front/src/components/common/DangerousButton.vue @@ -5,7 +5,7 @@ <modal class="small" :show.sync="showModal"> <div class="header"> <slot name="modal-header"> - <translate>Do you want to confirm this action?</translate> + <translate translate-context="Modal/*/Title">Do you want to confirm this action?</translate> </slot> </div> <div class="scrolling content"> @@ -15,11 +15,11 @@ </div> <div class="actions"> <div class="ui cancel button"> - <translate>Cancel</translate> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> </div> <div :class="['ui', 'confirm', confirmButtonColor, 'button']" @click="confirm"> <slot name="modal-confirm"> - <translate>Confirm</translate> + <translate translate-context="Modal/*/Button.Label/Short, Verb">Confirm</translate> </slot> </div> </div> diff --git a/front/src/components/common/Duration.vue b/front/src/components/common/Duration.vue index 85b070fcd4f18a121e6ce76bdc52bf6fd9762e51..4ee8ccfd168ead2571caaabc33094d3ce43b7d55 100644 --- a/front/src/components/common/Duration.vue +++ b/front/src/components/common/Duration.vue @@ -1,9 +1,9 @@ <template> <span> - <translate + <translate translate-context="Content/*/Paragraph" v-if="durationData.hours > 0" :translate-params="{minutes: durationData.minutes, hours: durationData.hours}">%{ hours } h %{ minutes } min</translate> - <translate + <translate translate-context="Content/*/Paragraph" v-else :translate-params="{minutes: durationData.minutes}">%{ minutes } min</translate> </span> diff --git a/front/src/components/common/EmptyState.vue b/front/src/components/common/EmptyState.vue new file mode 100644 index 0000000000000000000000000000000000000000..862700679e2a5e9db60d756d26f6081fdb7d9b7a --- /dev/null +++ b/front/src/components/common/EmptyState.vue @@ -0,0 +1,40 @@ +<template> + <div class="ui small placeholder segment"> + <div class="ui header"> + <div class="content"> + <slot name="title"> + + <i class="search icon"></i> + <translate translate-context="Content/*/Paragraph"> + No results were found. + </translate> + </slot> + </div> + </div> + <div class="inline"> + <slot></slot> + <button v-if="refresh" class="ui button" @click="$emit('refresh')"> + <translate translate-context="Content/*/Button.Label/Short, Verb"> + Refresh + </translate></button> + </button> + </div> + </div> +</template> +<script> +export default { + props: { + refresh: {type: Boolean, default: false} + } +} +</script> + +<style scoped> +.ui.small.placeholder.segment { + min-height: auto; +} +.ui.header .content { + text-align: center; + display: block; +} +</style> diff --git a/front/src/components/common/HumanDate.vue b/front/src/components/common/HumanDate.vue index eed245ea6ce1538d9b0afcd9bcd6297e5695f3a7..fde04f141399428ff413154775ce51eada0d0623 100644 --- a/front/src/components/common/HumanDate.vue +++ b/front/src/components/common/HumanDate.vue @@ -1,10 +1,16 @@ <template> - <time :datetime="date" :title="date | moment">{{ realDate | ago($store.state.ui.momentLocale) }}</time> + <time :datetime="date" :title="date | moment"> + <i v-if="icon" class="outline clock icon"></i> + {{ realDate | ago($store.state.ui.momentLocale) }} + </time> </template> <script> import {mapState} from 'vuex' export default { - props: ['date'], + props: { + date: {required: true}, + icon: {type: Boolean, required: false, default: false}, + }, computed: { ...mapState({ lastDate: state => state.ui.lastDate diff --git a/front/src/components/favorites/List.vue b/front/src/components/favorites/List.vue index 8015b96762a634e85d4815d01e13b45fce908341..d1bde0935341934af4988c64d1ec766d8da22c9b 100644 --- a/front/src/components/favorites/List.vue +++ b/front/src/components/favorites/List.vue @@ -3,7 +3,7 @@ <section class="ui vertical center aligned stripe segment"> <div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> <div class="ui text loader"> - <translate>Loading your favorites…</translate> + <translate translate-context="Content/Favorites/Message">Loading your favorites…</translate> </div> </div> <h2 v-if="results" class="ui center aligned icon header"> @@ -11,7 +11,8 @@ <translate translate-plural="%{ count } favorites" :translate-n="$store.state.favorites.count" - :translate-params="{count: results.count}"> + :translate-params="{count: results.count}" + translate-context="Content/Favorites/Title"> 1 favorite </translate> </h2> @@ -21,7 +22,7 @@ <div :class="['ui', {'loading': isLoading}, 'form']"> <div class="fields"> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -29,14 +30,14 @@ </select> </div> <div class="field"> - <label><translate>Order</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> <div class="field"> - <label><translate>Results per page</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> <select class="ui dropdown" v-model="paginateBy"> <option :value="parseInt(12)">12</option> <option :value="parseInt(25)">25</option> @@ -112,7 +113,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Your Favorites") + title: this.$pgettext('Head/Favorites/Title', 'Your Favorites') } } }, diff --git a/front/src/components/favorites/TrackFavoriteIcon.vue b/front/src/components/favorites/TrackFavoriteIcon.vue index 690dab21b67f0f2e742ed236564331dbfabfcfca..e6cde2265390f99a9b8553951a3e896cd3e186f8 100644 --- a/front/src/components/favorites/TrackFavoriteIcon.vue +++ b/front/src/components/favorites/TrackFavoriteIcon.vue @@ -1,8 +1,8 @@ <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}, 'icon', 'labeled', 'button']"> <i class="heart icon"></i> - <translate v-if="isFavorite">In favorites</translate> - <translate v-else>Add to favorites</translate> + <translate v-if="isFavorite" translate-context="Content/Track/Button.Message">In favorites</translate> + <translate v-else translate-context="Content/Track/*/Verb">Add to favorites</translate> </button> <button v-else @@ -23,9 +23,9 @@ export default { computed: { title () { if (this.isFavorite) { - return this.$gettext('Remove from favorites') + return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites') } else { - return this.$gettext('Add to favorites') + return this.$pgettext('Content/Track/*/Verb', 'Add to favorites') } }, isFavorite () { diff --git a/front/src/components/federation/FetchButton.vue b/front/src/components/federation/FetchButton.vue new file mode 100644 index 0000000000000000000000000000000000000000..5c7f4b66e2638c7777d4d9783739bd6b11b7504c --- /dev/null +++ b/front/src/components/federation/FetchButton.vue @@ -0,0 +1,157 @@ +<template> + <div @click="createFetch" role="button"> + <div> + <slot></slot> + </div> + <modal class="small" :show.sync="showModal"> + <h3 class="header"> + <translate translate-context="Popup/*/Title">Refreshing object from remote…</translate> + </h3> + <div class="scrolling content"> + <template v-if="fetch && fetch.status != 'pending'"> + <div v-if="fetch.status === 'skipped'" class="ui message"> + <div class="header"><translate translate-context="Popup/*/Message.Title">Refresh was skipped</translate></div> + <p><translate translate-context="Popup/*/Message.Content">The remote server answered, but returned data was unsupported by Funkwhale.</translate></p> + </div> + <div v-else-if="fetch.status === 'finished'" class="ui success message"> + <div class="header"><translate translate-context="Popup/*/Message.Title">Refresh successful</translate></div> + <p><translate translate-context="Popup/*/Message.Content">Data was refreshed successfully from remote server.</translate></p> + </div> + <div v-else-if="fetch.status === 'errored'" class="ui error message"> + <div class="header"><translate translate-context="Popup/*/Message.Title">Refresh error</translate></div> + <p><translate translate-context="Popup/*/Message.Content">An error occured while trying to refresh data:</translate></p> + <table class="ui very basic collapsing celled table"> + <tbody> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Error type</translate> + </td> + <td> + {{ fetch.detail.error_code }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Error detail</translate> + </td> + <td> + <translate + v-if="fetch.detail.error_code === 'http' && fetch.detail.status_code" + :translate-params="{status: fetch.detail.status_code}" + translate-context="*/*/Error">The remote server answered with HTTP %{ status }</translate> + <translate + v-else-if="['http', 'request'].indexOf(fetch.detail.error_code) > -1" + translate-context="*/*/Error">An HTTP error occured while contacting the remote server</translate> + <translate + v-else-if="fetch.detail.error_code === 'timeout'" + translate-context="*/*/Error">The remote server didn't answered fast enough</translate> + <translate + v-else-if="fetch.detail.error_code === 'connection'" + translate-context="*/*/Error">Impossible to connect to the remote server</translate> + <translate + v-else-if="['invalid_json', 'invalid_jsonld', 'missing_jsonld_type'].indexOf(fetch.detail.error_code) > -1" + translate-context="*/*/Error">The return server returned invalid JSON or JSON-LD data</translate> + <translate v-else-if="fetch.detail.error_code === 'validation'" translate-context="*/*/Error">Data returned by the remote server had invalid or missing attributes</translate> + <translate v-else-if="fetch.detail.error_code === 'unhandled'" translate-context="*/*/Error">Unknowkn error</translate> + <translate v-else translate-context="*/*/Error">Unknowkn error</translate> + </td> + </tr> + </tbody> + </table> + </div> + </template> + <div v-else-if="isCreatingFetch" class="ui active inverted dimmer"> + <div class="ui text loader"> + <translate translate-context="Popup/*/Loading.Title">Requesting a fetch…</translate> + </div> + </div> + <div v-else-if="isWaitingFetch" class="ui active inverted dimmer"> + <div class="ui text loader"> + <translate translate-context="Popup/*/Loading.Title">Waiting for result…</translate> + </div> + </div> + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Content/*/Error message.Title">Error while saving settings</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div v-else-if="fetch && fetch.status === 'pending' && pollsCount >= maxPolls" class="ui warning message"> + <div class="header"><translate translate-context="Popup/*/Message.Title">Refresh pending</translate></div> + <p><translate translate-context="Popup/*/Message.Content">Refresh request wasn't proceed in time by our server. It will be processed later.</translate></p> + </div> + </div> + <div class="actions"> + <div role="button" class="ui cancel button"> + <translate translate-context="*/*/Button.Label/Verb">Close</translate> + </div> + <div role="button" @click="showModal = false; $emit('refresh')" class="ui confirm green button" v-if="fetch && fetch.status === 'finished'"> + <translate translate-context="*/*/Button.Label/Verb">Close and reload page</translate> + </div> + </div> + </modal> + </div> +</template> + +<script> +import axios from "axios" +import Modal from '@/components/semantic/Modal' + +export default { + props: ['url'], + components: { + Modal + }, + data () { + return { + fetch: null, + isCreatingFetch: false, + errors: [], + showModal: false, + isWaitingFetch: false, + maxPolls: 15, + pollsCount: 0, + } + }, + methods: { + createFetch () { + let self = this + this.fetch = null + this.pollsCount = 0 + this.errors = [] + this.isCreatingFetch = true + this.isWaitingFetch = false + self.showModal = true + axios.post(this.url).then((response) => { + self.isCreatingFetch = false + self.fetch = response.data + self.pollFetch() + }, (error) => { + self.isCreatingFetch = false + self.errors = error.backendErrors + }) + }, + pollFetch () { + this.isWaitingFetch = true + this.pollsCount += 1 + let url = `federation/fetches/${this.fetch.id}/` + let self = this + self.showModal = true + axios.get(url).then((response) => { + self.isCreatingFetch = false + self.fetch = response.data + if (self.fetch.status === 'pending' && self.pollsCount < self.maxPolls) { + setTimeout(() => { + self.pollFetch() + }, 1000) + } else { + self.isWaitingFetch = false + } + }, (error) => { + self.errors = error.backendErrors + self.isWaitingFetch = false + }) + } + } +} +</script> diff --git a/front/src/components/federation/LibraryWidget.vue b/front/src/components/federation/LibraryWidget.vue index b9554d01ff36b235456dcf467647683ae769992b..7d24180879a70222cfefd18d4789953270af0f22 100644 --- a/front/src/components/federation/LibraryWidget.vue +++ b/front/src/components/federation/LibraryWidget.vue @@ -4,7 +4,7 @@ <slot name="title"></slot> </h3> <p v-if="!isLoading && libraries.length > 0" class="ui subtitle"><slot name="subtitle"></slot></p> - <p v-if="!isLoading && libraries.length === 0" class="ui subtitle"><translate>No matching library.</translate></p> + <p v-if="!isLoading && libraries.length === 0" class="ui subtitle"><translate translate-context="Content/Federation/Paragraph">No matching library.</translate></p> <i @click="fetchData(previousPage)" :disabled="!previousPage" :class="['ui', {disabled: !previousPage}, 'circular', 'angle left', 'icon']"> </i> <i @click="fetchData(nextPage)" :disabled="!nextPage" :class="['ui', {disabled: !nextPage}, 'circular', 'angle right', 'icon']"> diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue index 26454048e41ff328bf26e9288af6091fe9c8e050..d57e3017f3010000e6e45932e25e2092982ef246 100644 --- a/front/src/components/forms/PasswordInput.vue +++ b/front/src/components/forms/PasswordInput.vue @@ -23,7 +23,7 @@ export default { computed: { labels () { return { - title: this.$gettext('Show/hide password') + title: this.$pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password') } }, passwordInputType () { diff --git a/front/src/components/globals.js b/front/src/components/globals.js index 99e57095c0735a96c55d99e8fd00827203eaa929..711b227ae956a8943c8085087b0215029a50499d 100644 --- a/front/src/components/globals.js +++ b/front/src/components/globals.js @@ -44,5 +44,8 @@ import Tooltip from '@/components/common/Tooltip' Vue.component('tooltip', Tooltip) +import EmptyState from '@/components/common/EmptyState' + +Vue.component('empty-state', EmptyState) export default {} diff --git a/front/src/components/instance/Stats.vue b/front/src/components/instance/Stats.vue index 78632f6d23fa38702d1edefd78527bfd2d762f22..9b08d2f3718537ec025d17c3f1350986e5745089 100644 --- a/front/src/components/instance/Stats.vue +++ b/front/src/components/instance/Stats.vue @@ -3,7 +3,7 @@ <div v-if="stats" class="ui stackable two column grid"> <div class="column"> <h3 class="ui left aligned header"> - <translate>User activity</translate> + <translate translate-context="Content/About/Title/Noun">User activity</translate> </h3> <div v-if="stats" class="ui mini horizontal statistics"> <div class="statistic"> @@ -11,48 +11,48 @@ <i class="green user icon"></i> {{ stats.users }} </div> - <div class="label"><translate>users</translate></div> + <div class="label"><translate translate-context="Content/About/Paragraph/Unit">users</translate></div> </div> <div class="statistic"> <div class="value"> <i class="orange sound icon"></i> {{ stats.listenings }} </div> - <div class="label"><translate>tracks listened</translate></div> + <div class="label"><translate translate-context="Content/About/Paragraph/Unit">tracks listened</translate></div> </div> <div class="statistic"> <div class="value"> <i class="pink heart icon"></i> {{ stats.trackFavorites }} </div> - <div class="label"><translate>Tracks favorited</translate></div> + <div class="label"><translate translate-context="Content/About/Paragraph/Unit">Tracks favorited</translate></div> </div> </div> </div> <div class="column"> - <h3 class="ui left aligned header"><translate>Library</translate></h3> + <h3 class="ui left aligned header"><translate translate-context="*/*/*">Library</translate></h3> <div class="ui mini horizontal statistics"> <div class="statistic"> <div class="value"> {{ parseInt(stats.musicDuration) }} </div> - <div class="label"><translate>Hours of music</translate></div> + <div class="label"><translate translate-context="Content/About/Paragraph/Unit">Hours of music</translate></div> </div> <div class="statistic"> <div class="value"> {{ stats.artists }} </div> - <div class="label"><translate>Artists</translate></div> + <div class="label"><translate translate-context="*/*/*/Noun">Artists</translate></div> </div> <div class="statistic"> <div class="value"> {{ stats.albums }} </div> - <div class="label"><translate>Albums</translate></div> + <div class="label"><translate translate-context="*/*/*">Albums</translate></div> </div> <div class="statistic"> <div class="value"> {{ stats.tracks }} </div> - <div class="label"><translate>tracks</translate></div> + <div class="label"><translate translate-context="*/*/*/Noun">Tracks</translate></div> </div> </div> </div> diff --git a/front/src/components/library/Album.vue b/front/src/components/library/Album.vue deleted file mode 100644 index bb33b7e7fa52c1eaf6c4ed0f630788faf0a25e6e..0000000000000000000000000000000000000000 --- a/front/src/components/library/Album.vue +++ /dev/null @@ -1,197 +0,0 @@ -<template> - <main> - <div v-if="isLoading" class="ui vertical segment" v-title=""> - <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> - </div> - <template v-if="album"> - <section :class="['ui', 'head', {'with-background': album.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="album.title"> - <div class="segment-content"> - <h2 class="ui center aligned icon header"> - <i class="circular inverted sound yellow icon"></i> - <div class="content"> - {{ album.title }} - <div v-html="subtitle"></div> - </div> - <div class="ui buttons"> - <router-link class="ui button" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}"> - <translate>Artist page</translate> - </router-link> - </div> - </h2> - <div class="ui hidden divider"></div> - <play-button class="orange" :tracks="album.tracks"> - <translate>Play all</translate> - </play-button> - - <a :href="wikipediaUrl" target="_blank" class="ui button"> - <i class="wikipedia w icon"></i> - <translate>Search on Wikipedia</translate> - </a> - <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" class="ui button"> - <i class="external icon"></i> - <translate>View on MusicBrainz</translate> - </a> - <template v-if="publicLibraries.length > 0"> - <button - @click="showEmbedModal = !showEmbedModal" - class="ui button"> - <i class="code icon"></i> - <translate>Embed</translate> - </button> - <modal :show.sync="showEmbedModal"> - <div class="header"> - <translate>Embed this album on your website</translate> - </div> - <div class="content"> - <div class="description"> - <embed-wizard type="album" :id="album.id" /> - - </div> - </div> - <div class="actions"> - <div class="ui deny button"> - <translate>Cancel</translate> - </div> - </div> - </modal> - </template> - </div> - </section> - <template v-if="discs && discs.length > 1"> - <section v-for="(tracks, disc_number) in discs" class="ui vertical stripe segment"> - <translate - tag="h2" - class="left floated" - :translate-params="{number: disc_number + 1}" - >Volume %{ number }</translate> - <play-button class="right floated orange" :tracks="tracks"> - <translate>Play all</translate> - </play-button> - <track-table :artist="album.artist" :display-position="true" :tracks="tracks"></track-table> - </section> - </template> - <template v-else> - <section class="ui vertical stripe segment"> - <h2> - <translate>Tracks</translate> - </h2> - <track-table v-if="album" :artist="album.artist" :display-position="true" :tracks="album.tracks"></track-table> - </section> - </template> - <section class="ui vertical stripe segment"> - <h2> - <translate>User libraries</translate> - </h2> - <library-widget @loaded="libraries = $event" :url="'albums/' + id + '/libraries/'"> - <translate slot="subtitle">This album is present in the following libraries:</translate> - </library-widget> - </section> - </template> - </main> -</template> - -<script> -import axios from "axios" -import logger from "@/logging" -import backend from "@/audio/backend" -import PlayButton from "@/components/audio/PlayButton" -import TrackTable from "@/components/audio/track/Table" -import LibraryWidget from "@/components/federation/LibraryWidget" -import EmbedWizard from "@/components/audio/EmbedWizard" -import Modal from '@/components/semantic/Modal' - -const FETCH_URL = "albums/" - -function groupByDisc(acc, track) { - var dn = track.disc_number - 1 - if (dn < 0) dn = 0 - if (acc[dn] == undefined) { - acc.push([track]) - } else { - acc[dn].push(track) - } - return acc -} - -export default { - props: ["id"], - components: { - PlayButton, - TrackTable, - LibraryWidget, - EmbedWizard, - Modal - }, - data() { - return { - isLoading: true, - album: null, - discs: [], - libraries: [], - showEmbedModal: false - } - }, - created() { - this.fetchData() - }, - methods: { - fetchData() { - var self = this - this.isLoading = true - let url = FETCH_URL + this.id + "/" - logger.default.debug('Fetching album "' + this.id + '"') - axios.get(url).then(response => { - self.album = backend.Album.clean(response.data) - self.discs = self.album.tracks.reduce(groupByDisc, []) - self.isLoading = false - }) - } - }, - computed: { - labels() { - return { - title: this.$gettext("Album") - } - }, - publicLibraries () { - return this.libraries.filter(l => { - return l.privacy_level === 'everyone' - }) - }, - wikipediaUrl() { - return ( - "https://en.wikipedia.org/w/index.php?search=" + - encodeURI(this.album.title + " " + this.album.artist.name) - ) - }, - musicbrainzUrl() { - if (this.album.mbid) { - return "https://musicbrainz.org/release/" + this.album.mbid - } - }, - headerStyle() { - if (!this.album.cover.original) { - return "" - } - return ( - "background-image: url(" + - this.$store.getters["instance/absoluteUrl"](this.album.cover.original) + - ")" - ) - }, - subtitle () { - let msg = this.$ngettext('Album containing %{ count } track, by %{ artist }', 'Album containing %{ count } tracks, by %{ artist }', this.album.tracks.length) - return this.$gettextInterpolate(msg, {count: this.album.tracks.length, artist: this.album.artist.name}) - } - }, - watch: { - id() { - this.fetchData() - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped lang="scss"> -</style> diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue new file mode 100644 index 0000000000000000000000000000000000000000..1f89bef8898f9a1a496545688ff818a10e071e2a --- /dev/null +++ b/front/src/components/library/AlbumBase.vue @@ -0,0 +1,193 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment" v-title="labels.title"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <section :class="['ui', 'head', {'with-background': object.cover.original}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="object.title"> + <div class="segment-content"> + <h2 class="ui center aligned icon header"> + <i class="circular inverted sound yellow icon"></i> + <div class="content"> + {{ object.title }} + <div v-html="subtitle"></div> + </div> + </h2> + <div class="ui hidden divider"></div> + <div class="header-buttons"> + + <div class="ui buttons"> + <play-button class="orange" :tracks="object.tracks"> + <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate> + </play-button> + </div> + + <modal v-if="publicLibraries.length > 0" :show.sync="showEmbedModal"> + <div class="header"> + <translate translate-context="Popup/Album/Title/Verb">Embed this album on your website</translate> + </div> + <div class="content"> + <div class="description"> + <embed-wizard type="album" :id="object.id" /> + + </div> + </div> + <div class="actions"> + <div class="ui deny button"> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> + </div> + </div> + </modal> + <div class="ui buttons"> + <button class="ui button" @click="$refs.dropdown.click()"> + <translate translate-context="*/*/Button.Label/Noun">More…</translate> + </button> + <div class="ui floating dropdown icon button" ref="dropdown" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <div + role="button" + v-if="publicLibraries.length > 0" + @click="showEmbedModal = !showEmbedModal" + class="basic item"> + <i class="code icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Embed</translate> + </div> + <a :href="wikipediaUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="wikipedia w icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate> + </a> + <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="external icon"></i> + <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> + </a> + <router-link + v-if="object.is_local" + :to="{name: 'library.albums.edit', params: {id: object.id }}" + class="basic item"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + <div class="divider"></div> + <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate> + </router-link> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + </div> + </div> + </div> + </div> + </div> + </section> + <router-view v-if="object" :discs="discs" @libraries-loaded="libraries = $event" :object="object" object-type="album" :key="$route.fullPath"></router-view> + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import backend from "@/audio/backend" +import PlayButton from "@/components/audio/PlayButton" +import EmbedWizard from "@/components/audio/EmbedWizard" +import Modal from '@/components/semantic/Modal' + +const FETCH_URL = "albums/" + + +function groupByDisc(acc, track) { + var dn = track.disc_number - 1 + if (dn < 0) dn = 0 + if (acc[dn] == undefined) { + acc.push([track]) + } else { + acc[dn].push(track) + } + return acc +} + +export default { + props: ["id"], + components: { + PlayButton, + EmbedWizard, + Modal + }, + data() { + return { + isLoading: true, + object: null, + discs: [], + libraries: [], + showEmbedModal: false + } + }, + created() { + this.fetchData() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = FETCH_URL + this.id + "/" + logger.default.debug('Fetching album "' + this.id + '"') + axios.get(url).then(response => { + self.object = backend.Album.clean(response.data) + self.discs = self.object.tracks.reduce(groupByDisc, []) + self.isLoading = false + }) + } + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Album') + } + }, + publicLibraries () { + return this.libraries.filter(l => { + return l.privacy_level === 'everyone' + }) + }, + wikipediaUrl() { + return ( + "https://en.wikipedia.org/w/index.php?search=" + + encodeURI(this.object.title + " " + this.object.artist.name) + ) + }, + musicbrainzUrl() { + if (this.object.mbid) { + return "https://musicbrainz.org/release/" + this.object.mbid + } + }, + headerStyle() { + if (!this.object.cover.original) { + return "" + } + return ( + "background-image: url(" + + this.$store.getters["instance/absoluteUrl"](this.object.cover.original) + + ")" + ) + }, + subtitle () { + let route = this.$router.resolve({name: 'library.artists.detail', params: {id: this.object.artist.id }}) + let msg = this.$npgettext('Content/Album/Header.Title', 'Album containing %{ count } track, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', 'Album containing %{ count } tracks, by <a class="internal" href="%{ artistUrl }">%{ artist }</a>', this.object.tracks.length) + return this.$gettextInterpolate(msg, {count: this.object.tracks.length, artist: this.object.artist.name, artistUrl: route.location.path}) + } + }, + watch: { + id() { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/library/AlbumDetail.vue b/front/src/components/library/AlbumDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..d695b6e596b3d46de95774d8191f18e7309bb277 --- /dev/null +++ b/front/src/components/library/AlbumDetail.vue @@ -0,0 +1,62 @@ +<template> + <div v-if="object"> + <template v-if="discs && discs.length > 1"> + <section v-for="(tracks, disc_number) in discs" class="ui vertical stripe segment"> + <translate + tag="h2" + class="left floated" + :translate-params="{number: disc_number + 1}" + translate-context="Content/Album/" + >Volume %{ number }</translate> + <play-button class="right floated orange" :tracks="tracks"> + <translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate> + </play-button> + <track-table :artist="object.artist" :display-position="true" :tracks="tracks"></track-table> + </section> + </template> + <template v-else> + <section class="ui vertical stripe segment"> + <h2> + <translate translate-context="*/*/*/Noun">Tracks</translate> + </h2> + <track-table v-if="object" :artist="object.artist" :display-position="true" :tracks="object.tracks"></track-table> + </section> + </template> + <section class="ui vertical stripe segment"> + <h2> + <translate translate-context="Content/*/Title/Noun">User libraries</translate> + </h2> + <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'albums/' + object.id + '/libraries/'"> + <translate slot="subtitle" translate-context="Content/Album/Paragraph">This album is present in the following libraries:</translate> + </library-widget> + </section> + </div> +</template> + +<script> + +import time from "@/utils/time" +import axios from "axios" +import url from "@/utils/url" +import logger from "@/logging" +import LibraryWidget from "@/components/federation/LibraryWidget" +import TrackTable from "@/components/audio/track/Table" + +export default { + props: ["object", "libraries", "discs"], + components: { + LibraryWidget, + TrackTable + }, + data() { + return { + time, + id: this.object.id, + } + }, +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped lang="scss"> +</style> diff --git a/front/src/components/library/AlbumEdit.vue b/front/src/components/library/AlbumEdit.vue new file mode 100644 index 0000000000000000000000000000000000000000..b7c24737c1dd5e07b737dd1a6e088f1edc179920 --- /dev/null +++ b/front/src/components/library/AlbumEdit.vue @@ -0,0 +1,41 @@ +<template> + + <section class="ui vertical stripe segment"> + <div class="ui text container"> + <h2> + <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this album</translate> + <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this album</translate> + </h2> + <div class="ui message" v-if="!object.is_local"> + <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate> + </div> + <edit-form + v-else + :object-type="objectType" + :object="object" + :can-edit="canEdit"></edit-form> + </div> + </section> +</template> + +<script> +import axios from "axios" + +import EditForm from '@/components/library/EditForm' +export default { + props: ["objectType", "object", "libraries"], + data() { + return { + id: this.object.id, + } + }, + components: { + EditForm + }, + computed: { + canEdit () { + return true + } + } +} +</script> diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue new file mode 100644 index 0000000000000000000000000000000000000000..9817af830770142065f1e84f4253cbdda23c41e6 --- /dev/null +++ b/front/src/components/library/Albums.vue @@ -0,0 +1,189 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header"> + <translate translate-context="Content/Album/Title">Browsing albums</translate> + </h2> + <div :class="['ui', {'loading': isLoading}, 'form']"> + <div class="fields"> + <div class="field"> + <label> + <translate translate-context="Content/Search/Input.Label/Noun">Search</translate> + </label> + <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> + <select class="ui dropdown" v-model="paginateBy"> + <option :value="parseInt(12)">12</option> + <option :value="parseInt(25)">25</option> + <option :value="parseInt(50)">50</option> + </select> + </div> + </div> + </div> + <div class="ui hidden divider"></div> + <div + v-if="result" + transition-duration="0" + item-selector=".column" + percent-position="true" + stagger="0" + class="ui stackable three column doubling grid"> + <div + v-if="result.results.length > 0" + class="ui cards"> + <album-card + :mode="'simple'" + v-masonry-tile + v-for="album in result.results" + :key="album.id" + :album="album"></album-card> + </div> + </div> + <div class="ui center aligned basic segment"> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + </div> + </section> + </main> +</template> + +<script> +import axios from "axios" +import _ from "@/lodash" +import $ from "jquery" + +import logger from "@/logging" + +import OrderingMixin from "@/components/mixins/Ordering" +import PaginationMixin from "@/components/mixins/Pagination" +import TranslationsMixin from "@/components/mixins/Translations" +import AlbumCard from "@/components/audio/album/Card" +import Pagination from "@/components/Pagination" + +const FETCH_URL = "albums/" + +export default { + mixins: [OrderingMixin, PaginationMixin, TranslationsMixin], + props: { + defaultQuery: { type: String, required: false, default: "" } + }, + components: { + AlbumCard, + Pagination + }, + data() { + let defaultOrdering = this.getOrderingFromString( + this.defaultOrdering || "-creation_date" + ) + return { + isLoading: true, + result: null, + page: parseInt(this.defaultPage), + query: this.defaultQuery, + paginateBy: parseInt(this.defaultPaginateBy || 25), + orderingDirection: defaultOrdering.direction || "+", + ordering: defaultOrdering.field, + orderingOptions: [["creation_date", "creation_date"], ["title", "album_title"]] + } + }, + created() { + this.fetchData() + }, + mounted() { + $(".ui.dropdown").dropdown() + }, + computed: { + labels() { + let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Enter album title...") + let title = this.$pgettext('*/*/*', "Albums") + return { + searchPlaceholder, + title + } + } + }, + methods: { + updateQueryString: _.debounce(function() { + this.$router.replace({ + query: { + query: this.query, + page: this.page, + paginateBy: this.paginateBy, + ordering: this.getOrderingAsString() + } + }) + }, 500), + fetchData: _.debounce(function() { + var self = this + this.isLoading = true + let url = FETCH_URL + let params = { + page: this.page, + page_size: this.paginateBy, + q: this.query, + ordering: this.getOrderingAsString(), + playable: "true" + } + logger.default.debug("Fetching albums") + axios.get(url, { params: params }).then(response => { + self.result = response.data + self.isLoading = false + }) + }, 500), + selectPage: function(page) { + this.page = page + } + }, + watch: { + page() { + this.updateQueryString() + this.fetchData() + }, + paginateBy() { + this.updateQueryString() + this.fetchData() + }, + ordering() { + this.updateQueryString() + this.fetchData() + }, + orderingDirection() { + this.updateQueryString() + this.fetchData() + }, + query() { + this.updateQueryString() + this.fetchData() + }, + "$store.state.moderation.lastUpdate": function () { + this.fetchData() + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/library/Artist.vue b/front/src/components/library/Artist.vue deleted file mode 100644 index dd2be35ae1b00abba1077e3896d8368213c3f6ae..0000000000000000000000000000000000000000 --- a/front/src/components/library/Artist.vue +++ /dev/null @@ -1,195 +0,0 @@ -<template> - <main v-title="labels.title"> - <div v-if="isLoading" class="ui vertical segment"> - <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> - </div> - <template v-if="artist"> - <section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="artist.name"> - <div class="segment-content"> - <h2 class="ui center aligned icon header"> - <i class="circular inverted users violet icon"></i> - <div class="content"> - {{ artist.name }} - <div class="sub header" v-if="albums"> - <translate - tag="div" - translate-plural="%{ count } tracks in %{ albumsCount } albums" - :translate-n="totalTracks" - :translate-params="{count: totalTracks, albumsCount: totalAlbums}"> - %{ count } track in %{ albumsCount } albums - </translate> - </div> - </div> - </h2> - <div class="ui hidden divider"></div> - <radio-button type="artist" :object-id="artist.id"></radio-button> - <play-button :is-playable="isPlayable" class="orange" :artist="artist.id"> - <translate>Play all albums</translate> - </play-button> - - <a :href="wikipediaUrl" target="_blank" class="ui button"> - <i class="wikipedia w icon"></i> - <translate>Search on Wikipedia</translate> - </a> - <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" class="ui button"> - <i class="external icon"></i> - <translate>View on MusicBrainz</translate> - </a> - </div> - </section> - <section v-if="isLoadingAlbums" class="ui vertical stripe segment"> - <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> - </section> - <section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment"> - <h2> - <translate>Albums by this artist</translate> - </h2> - <div class="ui cards" > - <album-card :mode="'rich'" :album="album" :key="album.id" v-for="album in albums"></album-card> - </div> - </section> - <section v-if="tracks.length > 0" class="ui vertical stripe segment"> - <h2> - <translate>Tracks by this artist</translate> - </h2> - <track-table :display-position="true" :tracks="tracks"></track-table> - </section> - <section class="ui vertical stripe segment"> - <h2> - <translate>User libraries</translate> - </h2> - <library-widget :url="'artists/' + id + '/libraries/'"> - <translate slot="subtitle">This artist is present in the following libraries:</translate> - </library-widget> - </section> - </template> - </main> -</template> - -<script> -import _ from "@/lodash" -import axios from "axios" -import logger from "@/logging" -import backend from "@/audio/backend" -import AlbumCard from "@/components/audio/album/Card" -import RadioButton from "@/components/radios/Button" -import PlayButton from "@/components/audio/PlayButton" -import TrackTable from "@/components/audio/track/Table" -import LibraryWidget from "@/components/federation/LibraryWidget" - -export default { - props: ["id"], - components: { - AlbumCard, - RadioButton, - PlayButton, - TrackTable, - LibraryWidget - }, - data() { - return { - isLoading: true, - isLoadingAlbums: true, - artist: null, - albums: null, - totalTracks: 0, - totalAlbums: 0, - tracks: [] - } - }, - created() { - this.fetchData() - }, - methods: { - fetchData() { - var self = this - this.isLoading = true - logger.default.debug('Fetching artist "' + this.id + '"') - axios.get("tracks/", { params: { artist: this.id } }).then(response => { - self.tracks = response.data.results - self.totalTracks = response.data.count - }) - axios.get("artists/" + this.id + "/").then(response => { - self.artist = response.data - self.isLoading = false - self.isLoadingAlbums = true - axios - .get("albums/", { - params: { artist: self.id, ordering: "-release_date" } - }) - .then(response => { - self.totalAlbums = response.data.count - let parsed = JSON.parse(JSON.stringify(response.data.results)) - self.albums = parsed.map(album => { - return backend.Album.clean(album) - }) - - self.isLoadingAlbums = false - }) - }) - } - }, - computed: { - labels() { - return { - title: this.$gettext("Artist") - } - }, - isPlayable() { - return ( - this.artist.albums.filter(a => { - return a.is_playable - }).length > 0 - ) - }, - wikipediaUrl() { - return ( - "https://en.wikipedia.org/w/index.php?search=" + - encodeURI(this.artist.name) - ) - }, - musicbrainzUrl() { - if (this.artist.mbid) { - return "https://musicbrainz.org/artist/" + this.artist.mbid - } - }, - allTracks() { - let tracks = [] - this.albums.forEach(album => { - album.tracks.forEach(track => { - tracks.push(track) - }) - }) - return tracks - }, - cover() { - return this.artist.albums - .filter(album => { - return album.cover - }) - .map(album => { - return album.cover - })[0] - }, - headerStyle() { - if (!this.cover || !this.cover.original) { - return "" - } - return ( - "background-image: url(" + - this.$store.getters["instance/absoluteUrl"](this.cover.original) + - ")" - ) - } - }, - watch: { - id() { - this.fetchData() - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> -</style> diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue new file mode 100644 index 0000000000000000000000000000000000000000..5da7370bacf5fba3abe0c44aa10eb82d421c4a46 --- /dev/null +++ b/front/src/components/library/ArtistBase.vue @@ -0,0 +1,246 @@ +<template> + <main v-title="labels.title"> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object && !isLoading"> + <section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="object.name"> + <div class="segment-content"> + <h2 class="ui center aligned icon header"> + <i class="circular inverted users violet icon"></i> + <div class="content"> + {{ object.name }} + <div class="sub header" v-if="albums"> + <translate translate-context="Content/Artist/Paragraph" + tag="div" + translate-plural="%{ count } tracks in %{ albumsCount } albums" + :translate-n="totalTracks" + :translate-params="{count: totalTracks, albumsCount: totalAlbums}"> + %{ count } track in %{ albumsCount } albums + </translate> + </div> + </div> + </h2> + <div class="ui hidden divider"></div> + <div class="header-buttons"> + <div class="ui buttons"> + <radio-button type="artist" :object-id="object.id"></radio-button> + + </div> + <div class="ui buttons"> + <play-button :is-playable="isPlayable" class="orange" :artist="object"> + <translate translate-context="Content/Artist/Button.Label/Verb">Play all albums</translate> + </play-button> + </div> + + <modal :show.sync="showEmbedModal" v-if="publicLibraries.length > 0"> + <div class="header"> + <translate translate-context="Popup/Artist/Title/Verb">Embed this artist work on your website</translate> + </div> + <div class="content"> + <div class="description"> + <embed-wizard type="artist" :id="object.id" /> + + </div> + </div> + <div class="actions"> + <div class="ui deny button"> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> + </div> + </div> + </modal> + <div class="ui buttons"> + <button class="ui button" @click="$refs.dropdown.click()"> + <translate translate-context="*/*/Button.Label/Noun">More…</translate> + </button> + <div class="ui floating dropdown icon button" ref="dropdown" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <div + role="button" + v-if="publicLibraries.length > 0" + @click="showEmbedModal = !showEmbedModal" + class="basic item"> + <i class="code icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Embed</translate> + </div> + <a :href="wikipediaUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="wikipedia w icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate> + </a> + <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="external icon"></i> + <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> + </a> + <router-link + v-if="object.is_local" + :to="{name: 'library.artists.edit', params: {id: object.id }}" + class="basic item"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + <div class="divider"></div> + <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate> + </router-link> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + </div> + </div> + </div> + </div> + </div> + </section> + <router-view + :tracks="tracks" + :next-tracks-url="nextTracksUrl" + :next-albums-url="nextAlbumsUrl" + :albums="albums" + :is-loading-albums="isLoadingAlbums" + @libraries-loaded="libraries = $event" + :object="object" object-type="artist" + :key="$route.fullPath"></router-view> + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import backend from "@/audio/backend" +import PlayButton from "@/components/audio/PlayButton" +import EmbedWizard from "@/components/audio/EmbedWizard" +import Modal from '@/components/semantic/Modal' +import RadioButton from "@/components/radios/Button" + +const FETCH_URL = "albums/" + + +export default { + props: ["id"], + components: { + PlayButton, + EmbedWizard, + Modal, + RadioButton + }, + data() { + return { + isLoading: true, + isLoadingAlbums: true, + object: null, + albums: null, + libraries: [], + showEmbedModal: false, + tracks: [], + nextAlbumsUrl: null, + nextTracksUrl: null, + totalAlbums: null, + totalTracks: null, + } + }, + async created() { + await this.fetchData() + }, + methods: { + async fetchData() { + var self = this + this.isLoading = true + logger.default.debug('Fetching artist "' + this.id + '"') + let trackPromise = axios.get("tracks/", { params: { artist: this.id, hidden: '' } }).then(response => { + self.tracks = response.data.results + self.nextTracksUrl = response.data.next + self.totalTracks = response.data.count + }) + let albumPromise = axios.get("albums/", { + params: { artist: self.id, ordering: "-release_date", hidden: '' } + }).then(response => { + self.nextAlbumsUrl = response.data.next + self.totalAlbums = response.data.count + let parsed = JSON.parse(JSON.stringify(response.data.results)) + self.albums = parsed.map(album => { + return backend.Album.clean(album) + }) + + }) + + let artistPromise = axios.get("artists/" + this.id + "/").then(response => { + self.object = response.data + }) + await trackPromise + await albumPromise + await artistPromise + self.isLoadingAlbums = false + self.isLoading = false + } + }, + computed: { + isPlayable() { + return ( + this.object.albums.filter(a => { + return a.is_playable + }).length > 0 + ) + }, + labels() { + return { + title: this.$pgettext('*/*/*', 'Album') + } + }, + wikipediaUrl() { + return ( + "https://en.wikipedia.org/w/index.php?search=" + + encodeURI(this.object.name) + ) + }, + musicbrainzUrl() { + if (this.object.mbid) { + return "https://musicbrainz.org/artist/" + this.object.mbid + } + }, + cover() { + return this.object.albums + .filter(album => { + return album.cover + }) + .map(album => { + return album.cover + })[0] + }, + + publicLibraries () { + return this.libraries.filter(l => { + return l.privacy_level === 'everyone' + }) + }, + headerStyle() { + if (!this.cover || !this.cover.original) { + return "" + } + return ( + "background-image: url(" + + this.$store.getters["instance/absoluteUrl"](this.cover.original) + + ")" + ) + }, + contentFilter () { + let self = this + return this.$store.getters['moderation/artistFilters']().filter((e) => { + return e.target.id === this.object.id + })[0] + } + }, + watch: { + id() { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/library/ArtistDetail.vue b/front/src/components/library/ArtistDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..1dfbdd0d244720ec21fe731ae01e9629c30f6ffb --- /dev/null +++ b/front/src/components/library/ArtistDetail.vue @@ -0,0 +1,102 @@ +<template> + <div v-if="object"> + <div class="ui small text container" v-if="contentFilter"> + <div class="ui hidden divider"></div> + <div class="ui message"> + <p> + <translate translate-context="Content/Artist/Paragraph">You are currently hiding content related to this artist.</translate> + </p> + <router-link class="right floated" :to="{name: 'settings'}"> + <translate translate-context="Content/Moderation/Link">Review my filters</translate> + </router-link> + <button @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)" class="ui basic tiny button"> + <translate translate-context="Content/Moderation/Button.Label">Remove filter</translate> + </button> + </div> + </div> + <section v-if="isLoadingAlbums" class="ui vertical stripe segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </section> + <section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment"> + <h2> + <translate translate-context="Content/Artist/Title">Albums by this artist</translate> + </h2> + <div class="ui cards"> + <album-card :mode="'rich'" :album="album" :key="album.id" v-for="album in allAlbums"></album-card> + </div> + <div class="ui hidden divider"></div> + <button :class="['ui', {loading: isLoadingMoreAlbums}, 'button']" v-if="nextAlbumsUrl && loadMoreAlbumsUrl" @click="loadMoreAlbums(loadMoreAlbumsUrl)"> + <translate translate-context="Content/*/Button.Label">Load more…</translate> + </button> + </section> + <section v-if="tracks.length > 0" class="ui vertical stripe segment"> + <h2> + <translate translate-context="Content/Artist/Title">Tracks by this artist</translate> + </h2> + <track-table :display-position="true" :tracks="tracks" :next-url="nextTracksUrl"></track-table> + </section> + <section class="ui vertical stripe segment"> + <h2> + <translate translate-context="Content/*/Title/Noun">User libraries</translate> + </h2> + <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'artists/' + object.id + '/libraries/'"> + <translate translate-context="Content/Artist/Paragraph" slot="subtitle">This artist is present in the following libraries:</translate> + </library-widget> + </section> + </div> +</template> + +<script> +import _ from "@/lodash" +import axios from "axios" +import logger from "@/logging" +import backend from "@/audio/backend" +import AlbumCard from "@/components/audio/album/Card" +import TrackTable from "@/components/audio/track/Table" +import LibraryWidget from "@/components/federation/LibraryWidget" + +export default { + props: ["object", "tracks", "albums", "isLoadingAlbums", "nextTracksUrl", "nextAlbumsUrl"], + components: { + AlbumCard, + TrackTable, + LibraryWidget, + }, + data () { + return { + loadMoreAlbumsUrl: this.nextAlbumsUrl, + additionalAlbums: [], + isLoadingMoreAlbums: false + } + }, + computed: { + contentFilter () { + let self = this + return this.$store.getters['moderation/artistFilters']().filter((e) => { + return e.target.id === this.object.id + })[0] + }, + allAlbums () { + return this.albums.concat(this.additionalAlbums) + } + }, + methods: { + loadMoreAlbums (url) { + let self = this + self.isLoadingMoreAlbums = true + axios.get(url).then((response) => { + self.additionalAlbums = self.additionalAlbums.concat(response.data.results) + self.loadMoreAlbumsUrl = response.data.next + self.isLoadingMoreAlbums = false + }, (error) => { + self.isLoadingMoreAlbums = false + + }) + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/library/ArtistEdit.vue b/front/src/components/library/ArtistEdit.vue new file mode 100644 index 0000000000000000000000000000000000000000..80a9ae0c3ce130720f62b389b6dac3a2c3c1baf9 --- /dev/null +++ b/front/src/components/library/ArtistEdit.vue @@ -0,0 +1,41 @@ +<template> + + <section class="ui vertical stripe segment"> + <div class="ui text container"> + <h2> + <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this artist</translate> + <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this artist</translate> + </h2> + <div class="ui message" v-if="!object.is_local"> + <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate> + </div> + <edit-form + v-else + :object-type="objectType" + :object="object" + :can-edit="canEdit"></edit-form> + </div> + </section> +</template> + +<script> +import axios from "axios" + +import EditForm from '@/components/library/EditForm' +export default { + props: ["objectType", "object", "libraries"], + data() { + return { + id: this.object.id, + } + }, + components: { + EditForm + }, + computed: { + canEdit () { + return true + } + } +} +</script> diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index 41b3b2c6fbe080e23e7ffc661b4c5d8b28f6e4bb..5f4102ab1c269cc6610bf8cec3fa95b33b9d5b83 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -2,18 +2,18 @@ <main v-title="labels.title"> <section class="ui vertical stripe segment"> <h2 class="ui header"> - <translate>Browsing artists</translate> + <translate translate-context="Content/Artist/Title">Browsing artists</translate> </h2> <div :class="['ui', {'loading': isLoading}, 'form']"> <div class="fields"> <div class="field"> <label> - <translate>Search</translate> + <translate translate-context="Content/Search/Input.Label/Noun">Search</translate> </label> <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -21,14 +21,14 @@ </select> </div> <div class="field"> - <label><translate>Ordering direction</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> <div class="field"> - <label><translate>Results per page</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> <select class="ui dropdown" v-model="paginateBy"> <option :value="parseInt(12)">12</option> <option :value="parseInt(25)">25</option> @@ -113,8 +113,8 @@ export default { }, computed: { labels() { - let searchPlaceholder = this.$gettext("Enter artist name…") - let title = this.$gettext("Artists") + let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Enter artist name…") + let title = this.$pgettext('*/*/*/Noun', "Artists") return { searchPlaceholder, title @@ -173,6 +173,9 @@ export default { query() { this.updateQueryString() this.fetchData() + }, + "$store.state.moderation.lastUpdate": function () { + this.fetchData() } } } diff --git a/front/src/components/library/EditCard.vue b/front/src/components/library/EditCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..e72823aa74d7aa406e14bb7e87eea1d7ff3c0179 --- /dev/null +++ b/front/src/components/library/EditCard.vue @@ -0,0 +1,209 @@ +<template> + <div class="ui fluid card"> + <div class="content"> + <div class="header"> + <router-link :to="detailUrl"> + <translate translate-context="Content/Library/Card/Short" :translate-params="{id: obj.uuid.substring(0, 8)}">Modification %{ id }</translate> + </router-link> + </div> + <div class="meta"> + <router-link + v-if="obj.target && obj.target.type === 'track'" + :to="{name: 'library.tracks.detail', params: {id: obj.target.id }}"> + <i class="music icon"></i> + <translate translate-context="Content/Library/Card/Short" :translate-params="{id: obj.target.id, name: obj.target.repr}">Track #%{ id } - %{ name }</translate> + </router-link> + <br> + <human-date :date="obj.creation_date" :icon="true"></human-date> + + <span class="right floated"> + <span v-if="obj.is_approved && obj.is_applied"> + <i class="green check icon"></i> + <translate translate-context="Content/Library/Card/Short">Approved and applied</translate> + </span> + <span v-else-if="obj.is_approved"> + <i class="green check icon"></i> + <translate translate-context="Content/*/*/Short">Approved</translate> + </span> + <span v-else-if="obj.is_approved === null"> + <i class="yellow hourglass icon"></i> + <translate translate-context="Content/Admin/*/Noun">Pending review</translate> + </span> + <span v-else-if="obj.is_approved === false"> + <i class="red x icon"></i> + <translate translate-context="Content/Library/*/Short">Rejected</translate> + </span> + </span> + </div> + </div> + <div v-if="obj.summary" class="content"> + {{ obj.summary }} + </div> + <div class="content"> + <table v-if="obj.type === 'update'" class="ui celled very basic fixed stacking table"> + <thead> + <tr> + <th><translate translate-context="Content/Library/Card.Table.Header/Short">Field</translate></th> + <th><translate translate-context="Content/Library/Card.Table.Header/Short">Old value</translate></th> + <th><translate translate-context="Content/Library/Card.Table.Header/Short">New value</translate></th> + </tr> + </thead> + <tbody> + <tr v-for="field in getUpdatedFields(obj.payload, previousState)" :key="field.id"> + <td>{{ field.id }}</td> + + <td v-if="field.diff"> + <span v-if="!part.added" v-for="part in field.diff" :class="['diff', {removed: part.removed}]"> + {{ part.value }} + </span> + </td> + <td v-else> + <translate translate-context="*/*/*">N/A</translate> + </td> + + <td v-if="field.diff"> + <span v-if="!part.removed" v-for="part in field.diff" :class="['diff', {added: part.added}]"> + {{ part.value }} + </span> + </td> + <td v-else>{{ field.new }}</td> + </tr> + </tbody> + </table> + </div> + <div v-if="obj.created_by" class="extra content"> + <actor-link :actor="obj.created_by" /> + </div> + <div v-if="canDelete || canApprove" class="ui bottom attached buttons"> + <button + v-if="canApprove && obj.is_approved !== true" + @click="approve(true)" + :class="['ui', {loading: isLoading}, 'green', 'basic', 'button']"> + <translate translate-context="Content/*/Button.Label/Verb">Approve</translate> + </button> + <button + v-if="canApprove && obj.is_approved === null" + @click="approve(false)" + :class="['ui', {loading: isLoading}, 'yellow', 'basic', 'button']"> + <translate translate-context="Content/Library/Button.Label">Reject</translate> + </button> + <dangerous-button + v-if="canDelete" + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this suggestion?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Popup/Library/Paragraph">The suggestion will be completely removed, this action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import { diffWordsWithSpace } from 'diff' + +import edits from '@/edits' + +function castValue (value) { + if (value === null || value === undefined) { + return '' + } + return String(value) +} + +export default { + props: { + obj: {required: true}, + currentState: {required: false} + }, + data () { + return { + isLoading: false + } + }, + computed: { + canApprove: edits.getCanApprove, + canDelete: edits.getCanDelete, + previousState () { + if (this.obj.is_applied) { + // mutation was applied, we use the previous state that is stored + // on the mutation itself + return this.obj.previous_state + } + // mutation is not applied yet, so we use the current state that was + // passed to the component, if any + return this.currentState + }, + detailUrl () { + if (!this.obj.target) { + return '' + } + let namespace + let id = this.obj.target.id + if (this.obj.target.type === 'track') { + namespace = 'library.tracks.edit.detail' + } + if (this.obj.target.type === 'album') { + namespace = 'library.albums.edit.detail' + } + if (this.obj.target.type === 'artist') { + namespace = 'library.artists.edit.detail' + } + return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href + } + }, + methods: { + remove () { + let self = this + this.isLoading = true + axios.delete(`mutations/${this.obj.uuid}/`).then((response) => { + self.$emit('deleted') + self.isLoading = false + }, error => { + self.isLoading = false + }) + }, + approve (approved) { + let url + if (approved) { + url = `mutations/${this.obj.uuid}/approve/` + } else { + url = `mutations/${this.obj.uuid}/reject/` + } + let self = this + this.isLoading = true + axios.post(url).then((response) => { + self.$emit('approved', approved) + self.isLoading = false + self.$store.commit('ui/incrementNotifications', {count: -1, type: 'pendingReviewEdits'}) + }, error => { + self.isLoading = false + }) + }, + getUpdatedFields (payload, previousState) { + let fields = Object.keys(payload) + return fields.map((f) => { + let d = { + id: f, + } + if (previousState && previousState[f]) { + d.old = previousState[f] + } + d.new = payload[f] + if (d.old) { + // we compute the diffs between the old and new values + + let oldValue = castValue(d.old.value) + let newValue = castValue(d.new) + d.diff = diffWordsWithSpace(oldValue, newValue) + } + return d + }) + } + } +} +</script> diff --git a/front/src/components/library/EditDetail.vue b/front/src/components/library/EditDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..4a0c89434de8797d37022d0d9916df1fd719647a --- /dev/null +++ b/front/src/components/library/EditDetail.vue @@ -0,0 +1,52 @@ +<template> + + <section :class="['ui', 'vertical', 'stripe', {loading: isLoading}, 'segment']"> + <div class="ui text container"> + <edit-card v-if="obj" :obj="obj" :current-state="currentState" /> + </div> + </section> +</template> + +<script> +import axios from "axios" +import edits from '@/edits' +import EditCard from '@/components/library/EditCard' +export default { + props: ["object", "objectType", "editId"], + components: { + EditCard + }, + data () { + return { + isLoading: true, + obj: null, + } + }, + created () { + this.fetchData() + }, + computed: { + configs: edits.getConfigs, + config: edits.getConfig, + currentState: edits.getCurrentState, + currentState () { + let self = this + let s = {} + this.config.fields.forEach(f => { + s[f.id] = {value: f.getValue(self.object)} + }) + return s + } + }, + methods: { + fetchData () { + var self = this + this.isLoading = true + axios.get(`mutations/${this.editId}/`).then(response => { + self.obj = response.data + self.isLoading = false + }) + } + } +} +</script> diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..617917c6812f03924934c48ec0e7d92079f95a72 --- /dev/null +++ b/front/src/components/library/EditForm.vue @@ -0,0 +1,229 @@ +<template> + <div v-if="submittedMutation"> + <div class="ui positive message"> + <div class="header"><translate translate-context="Content/Library/Paragraph">Your edit was successfully submitted.</translate></div> + </div> + <edit-card :obj="submittedMutation" :current-state="currentState" /> + <button class="ui button" @click.prevent="submittedMutation = null"> + <translate translate-context="Content/Library/Button.Label"> + Submit another edit + </translate> + </button> + </div> + <div v-else> + + <edit-list :filters="editListFilters" :url="mutationsUrl" :obj="object" :currentState="currentState"> + <div slot="title"> + <template v-if="showPendingReview"> + <translate translate-context="Content/Library/Paragraph"> + Recent edits awaiting review + </translate> + <button class="ui tiny basic right floated button" @click.prevent="showPendingReview = false"> + <translate translate-context="Content/Library/Button.Label"> + Show all edits + </translate> + </button> + </template> + <template v-else> + <translate translate-context="Content/Library/Paragraph"> + Recent edits + </translate> + <button class="ui tiny basic right floated button" @click.prevent="showPendingReview = true"> + <translate translate-context="Content/Library/Button.Label"> + Retrict to unreviewed edits + </translate> + </button> + </template> + </div> + <empty-state slot="empty-state"> + <translate translate-context="Content/Library/Paragraph"> + Suggest a change using the form below. + </translate> + </empty-state> + </edit-list> + <form class="ui form" @submit.prevent="submit()"> + <div class="ui hidden divider"></div> + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Content/Library/Error message.Title">Error while submitting edit</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div v-if="!canEdit" class="ui message"> + <translate translate-context="Content/Library/Paragraph"> + You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval. + </translate> + </div> + <div v-if="values" v-for="fieldConfig in config.fields" :key="fieldConfig.id" class="ui field"> + <template v-if="fieldConfig.type === 'text'"> + <label :for="fieldConfig.id">{{ fieldConfig.label }}</label> + <input :type="fieldConfig.inputType || 'text'" v-model="values[fieldConfig.id]" :required="fieldConfig.required" :name="fieldConfig.id" :id="fieldConfig.id"> + </template> + <template v-else-if="fieldConfig.type === 'license'"> + <label :for="fieldConfig.id">{{ fieldConfig.label }}</label> + + <select + ref="license" + v-model="values[fieldConfig.id]" + :required="fieldConfig.required" + :id="fieldConfig.id" + class="ui fluid search dropdown"> + <option :value="null"><translate translate-context="*/*/*">N/A</translate></option> + <option v-for="license in licenses" :key="license.code" :value="license.code">{{ license.name}}</option> + </select> + <button class="ui tiny basic left floated button" form="noop" @click.prevent="values[fieldConfig.id] = null"> + <i class="x icon"></i> + <translate translate-context="Content/Library/Button.Label">Clear</translate> + </button> + + </template> + <div v-if="values[fieldConfig.id] != initialValues[fieldConfig.id]"> + <button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = initialValues[fieldConfig.id]"> + <i class="undo icon"></i> + <translate translate-context="Content/Library/Button.Label" :translate-params="{value: initialValues[fieldConfig.id] || ''}">Reset to initial value: %{ value }</translate> + </button> + </div> + </div> + <div class="field"> + <label for="summary"><translate translate-context="*/*/*">Summary (optional)</translate></label> + <textarea name="change-summary" v-model="summary" id="change-summary" rows="3" :placeholder="labels.summaryPlaceholder"></textarea> + </div> + <router-link + class="ui left floated button" + v-if="objectType === 'track'" + :to="{name: 'library.tracks.detail', params: {id: object.id }}" + > + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> + </router-link> + <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit" :disabled="isLoading || !mutationPayload"> + <translate v-if="canEdit" key="1" translate-context="Content/Library/Button.Label/Verb">Submit and apply edit</translate> + <translate v-else key="2" translate-context="Content/Library/Button.Label/Verb">Submit suggestion</translate> + </button> + </form> + </div> + </div> +</template> + +<script> +import $ from 'jquery' +import _ from '@/lodash' +import axios from "axios" +import EditList from '@/components/library/EditList' +import EditCard from '@/components/library/EditCard' +import edits from '@/edits' + +export default { + props: ["objectType", "object", "licenses"], + components: { + EditList, + EditCard + }, + data() { + return { + isLoading: false, + errors: [], + values: {}, + initialValues: {}, + summary: '', + submittedMutation: null, + showPendingReview: true, + } + }, + created () { + this.setValues() + }, + mounted() { + $(".ui.dropdown").dropdown({fullTextSearch: true}) + }, + computed: { + configs: edits.getConfigs, + config: edits.getConfig, + currentState: edits.getCurrentState, + canEdit: edits.getCanEdit, + labels () { + return { + summaryPlaceholder: this.$pgettext('*/*/Placeholder', 'A short summary describing your changes.'), + } + }, + mutationsUrl () { + if (this.objectType === 'track') { + return `tracks/${this.object.id}/mutations/` + } + if (this.objectType === 'album') { + return `albums/${this.object.id}/mutations/` + } + if (this.objectType === 'artist') { + return `artists/${this.object.id}/mutations/` + } + }, + mutationPayload () { + let self = this + let changedFields = this.config.fields.filter(f => { + return self.values[f.id] != self.initialValues[f.id] + }) + if (changedFields.length === 0) { + return null + } + let payload = { + type: 'update', + payload: {}, + summary: this.summary, + } + changedFields.forEach((f) => { + payload.payload[f.id] = self.values[f.id] + }) + return payload + }, + editListFilters () { + if (this.showPendingReview) { + return {is_approved: 'null'} + } else { + return {} + } + }, + }, + + methods: { + setValues () { + let self = this + this.config.fields.forEach(f => { + self.$set(self.values, f.id, f.getValue(self.object)) + self.$set(self.initialValues, f.id, self.values[f.id]) + }) + }, + submit() { + let self = this + self.isLoading = true + self.errors = [] + let payload = _.clone(this.mutationPayload || {}) + if (this.canEdit) { + payload.is_approved = true + } + return axios.post(this.mutationsUrl, payload).then( + response => { + self.isLoading = false + self.submittedMutation = response.data + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) + } + }, + watch: { + 'values.license' (newValue) { + if (newValue === null) { + $(this.$refs.license).dropdown('clear') + } else { + $(this.$refs.license).dropdown('set selected', newValue) + } + } + } +} +</script> +<style> +.reset.button { + margin-top: 0.5em; +} +</style> diff --git a/front/src/components/library/EditList.vue b/front/src/components/library/EditList.vue new file mode 100644 index 0000000000000000000000000000000000000000..2ff1fc72a0661e0e9a21c3e97e17fe73aabd1b39 --- /dev/null +++ b/front/src/components/library/EditList.vue @@ -0,0 +1,74 @@ +<template> + <div class="wrapper"> + <h3 class="ui header"> + <slot name="title"></slot> + </h3> + <slot v-if="!isLoading && objects.length === 0" name="empty-state"></slot> + <button v-if="nextPage || previousPage" :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button> + <button v-if="nextPage || previousPage" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button> + <div class="ui hidden divider"></div> + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + <edit-card @updated="fetchData(url)" @deleted="fetchData(url)" v-for="obj in objects" :key="obj.uuid" :obj="obj" :current-state="currentState" /> + </div> +</template> + +<script> +import _ from '@/lodash' +import axios from 'axios' + +import EditCard from '@/components/library/EditCard' + +export default { + props: { + url: {type: String, required: true}, + filters: {type: Object, required: false, default: () => {return {}}}, + currentState: {required: false}, + }, + components: { + EditCard + }, + data () { + return { + objects: [], + limit: 5, + isLoading: false, + errors: null, + previousPage: null, + nextPage: null + } + }, + created () { + this.fetchData(this.url) + }, + methods: { + fetchData (url) { + if (!url) { + return + } + this.isLoading = true + let self = this + let params = _.clone(this.filters) + params.page_size = this.limit + axios.get(url, {params: params}).then((response) => { + self.previousPage = response.data.previous + self.nextPage = response.data.next + self.isLoading = false + self.objects = response.data.results + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + }, + watch: { + filters: { + handler () { + this.fetchData(this.url) + }, + deep: true + } + } +} +</script> diff --git a/front/src/components/library/FileUpload.vue b/front/src/components/library/FileUpload.vue index 7b7673b5abb5c6b7d429cdca5ed5edb92172dfdc..a8a5d2f374e0eb4ce0c51f4c134fac28ed78c0b2 100644 --- a/front/src/components/library/FileUpload.vue +++ b/front/src/components/library/FileUpload.vue @@ -1,9 +1,9 @@ <template> <div> <div class="ui top attached tabular menu"> - <a :class="['item', {active: currentTab === 'summary'}]" @click="currentTab = 'summary'"><translate>Summary</translate></a> + <a :class="['item', {active: currentTab === 'summary'}]" @click="currentTab = 'summary'"><translate translate-context="Content/Library/Tab.Title/Short">Summary</translate></a> <a :class="['item', {active: currentTab === 'uploads'}]" @click="currentTab = 'uploads'"> - <translate>Uploading</translate> + <translate translate-context="Content/Library/Tab.Title/Short">Uploading</translate> <div v-if="files.length === 0" class="ui label"> 0 </div> @@ -15,7 +15,7 @@ </div> </a> <a :class="['item', {active: currentTab === 'processing'}]" @click="currentTab = 'processing'"> - <translate>Processing</translate> + <translate translate-context="Content/Library/Tab.Title/Short">Processing</translate> <div v-if="processableFiles === 0" class="ui label"> 0 </div> @@ -27,21 +27,20 @@ </div> </a> </div> - <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'summary'}]"> - <h2 class="ui header"><translate>Upload new tracks</translate></h2> + <h2 class="ui header"><translate translate-context="Content/Library/Title/Verb">Upload new tracks</translate></h2> <div class="ui message"> - <p><translate>You are about to upload music to your library. Before proceeding, please ensure that:</translate></p> + <p><translate translate-context="Content/Library/Paragraph">You are about to upload music to your library. Before proceeding, please ensure that:</translate></p> <ul> <li v-if="library.privacy_level != 'me'"> - You are not uploading copyrighted content in a public library, otherwise you may be infringing the law + <translate translate-context="Content/Library/List item">You are not uploading copyrighted content in a public library, otherwise you may be infringing the law</translate> </li> <li> - <translate>The music files you are uploading are tagged properly:</translate> - <a href="http://picard.musicbrainz.org/" target='_blank'><translate>We recommend using Picard for that purpose.</translate></a> + <translate translate-context="Content/Library/List item">The music files you are uploading are tagged properly.</translate> + <a href="http://picard.musicbrainz.org/" target='_blank'><translate translate-context="Content/Library/Link">We recommend using Picard for that purpose.</translate></a> </li> <li> - <translate>The uploaded music files are in OGG, Flac or MP3 format</translate> + <translate translate-context="Content/Library/List item">The uploaded music files are in OGG, Flac or MP3 format</translate> </li> </ul> </div> @@ -49,17 +48,25 @@ <div class="ui form"> <div class="fields"> <div class="ui four wide field"> - <label><translate>Import reference</translate></label> - <p><translate>This reference will be used to group imported files together.</translate></p> + <label><translate translate-context="Content/Library/Input.Label/Noun">Import reference</translate></label> + <p><translate translate-context="Content/Library/Paragraph">This reference will be used to group imported files together.</translate></p> <input name="import-ref" type="text" v-model="importReference" /> </div> </div> </div> - <div class="ui green button" @click="currentTab = 'uploads'"><translate>Proceed</translate></div> + <div class="ui green button" @click="currentTab = 'uploads'"><translate translate-context="Content/Library/Button.Label">Proceed</translate></div> </div> <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'uploads'}]"> - <div class="ui container"> + <div :class="['ui', {loading: isLoadingQuota}, 'container']"> + <div :class="['ui', {red: remainingSpace === 0}, {yellow: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']"> + <div class="label"> + <translate translate-context="Content/Library/Paragraph">Remaining storage space</translate> + </div> + <div class="value"> + {{ remainingSpace * 1000 * 1000 | humanSize}} + </div> + </div> <file-upload-widget :class="['ui', 'icon', 'basic', 'button']" :post-action="uploadUrl" @@ -73,45 +80,48 @@ @input-file="inputFile" ref="upload"> <i class="upload icon"></i> - <translate>Click to select files to upload or drag and drop files or directories</translate> + <translate translate-context="Content/Library/Paragraph/Call to action">Click to select files to upload or drag and drop files or directories</translate> <br /> <br /> - <i><translate :translate-params="{extensions: supportedExtensions.join(', ')}"> Supported extensions: %{ extensions }</translate></i> + <i><translate translate-context="Content/Library/Paragraph" :translate-params="{extensions: supportedExtensions.join(', ')}">Supported extensions: %{ extensions }</translate></i> </file-upload-widget> </div> - <table v-if="files.length > 0" class="ui single line table"> - <thead> - <tr> - <th><translate>Filename</translate></th> - <th><translate>Size</translate></th> - <th><translate>Status</translate></th> - </tr> - </thead> - <tbody> - <tr v-for="(file, index) in sortedFiles" :key="file.id"> - <td :title="file.name">{{ file.name | truncate(60) }}</td> - <td>{{ file.size | humanSize }}</td> - <td> - <span v-if="file.error" class="ui tooltip" :data-tooltip="labels.tooltips[file.error]"> - <span class="ui red icon label"> - <i class="question circle outline icon" /> {{ file.error }} + <div v-if="files.length > 0" class="table-wrapper"> + <div class="ui hidden divider"></div> + <table class="ui unstackable table"> + <thead> + <tr> + <th><translate translate-context="Content/Library/Table.Label">Filename</translate></th> + <th><translate translate-context="Content/Library/*/in MB">Size</translate></th> + <th><translate translate-context="Content/Library/Table.Label (Value is Uploading/Uploaded/Error)">Status</translate></th> + </tr> + </thead> + <tbody> + <tr v-for="(file, index) in sortedFiles" :key="file.id"> + <td :title="file.name">{{ file.name | truncate(60) }}</td> + <td>{{ file.size | humanSize }}</td> + <td> + <span v-if="file.error" class="ui tooltip" :data-tooltip="labels.tooltips[file.error]"> + <span class="ui red icon label"> + <i class="question circle outline icon" /> {{ file.error }} + </span> </span> - </span> - <span v-else-if="file.success" class="ui green label"> - <translate key="1">Uploaded</translate> - </span> - <span v-else-if="file.active" class="ui yellow label"> - <translate key="2">Uploading…</translate> - ({{ parseInt(file.progress) }}%) - </span> - <template v-else> - <span class="ui label"><translate key="3">Pending</translate></span> - <button class="ui tiny basic red icon button" @click.prevent="$refs.upload.remove(file)"><i class="delete icon"></i></button> - </template> - </td> - </tr> - </tbody> - </table> + <span v-else-if="file.success" class="ui green label"> + <translate translate-context="Content/Library/Table" key="1">Uploaded</translate> + </span> + <span v-else-if="file.active" class="ui yellow label"> + <translate translate-context="Content/Library/Table" key="2">Uploading…</translate> + ({{ parseInt(file.progress) }}%) + </span> + <template v-else> + <span class="ui label"><translate translate-context="Content/Library/*/Short" key="3">Pending</translate></span> + <button class="ui tiny basic red icon button" @click.prevent="$refs.upload.remove(file)"><i class="delete icon"></i></button> + </template> + </td> + </tr> + </tbody> + </table> + </div> </div> <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'processing'}]"> @@ -149,6 +159,8 @@ export default { uploadUrl: this.$store.getters['instance/absoluteUrl']("/api/v1/uploads/"), importReference, supportedExtensions: ["flac", "ogg", "mp3", "opus"], + isLoadingQuota: false, + quotaStatus: null, uploads: { pending: 0, finished: 0, @@ -161,6 +173,7 @@ export default { }, created() { this.fetchStatus(); + this.fetchQuota(); this.$store.commit("ui/addWebsocketEventHandler", { eventName: "import.status_updated", id: "fileUpload", @@ -184,8 +197,23 @@ export default { }); return returnValue; }, + fetchQuota () { + let self = this + self.isLoadingQuota = true + axios.get('users/users/me/').then((response) => { + self.quotaStatus = response.data.quota_status + self.isLoadingQuota = false + }) + }, inputFile(newFile, oldFile) { - this.$refs.upload.active = true; + if (!newFile) { + return + } + if (this.remainingSpace < newFile.size / (1000 * 1000)) { + newFile.error = 'denied' + } else { + this.$refs.upload.active = true; + } }, fetchStatus() { let self = this; @@ -227,17 +255,17 @@ export default { }, computed: { labels() { - let denied = this.$gettext( + let denied = this.$pgettext('Content/Library/Help text', "Upload denied, ensure the file is not too big and that you have not reached your quota" ); - let server = this.$gettext( + let server = this.$pgettext('Content/Library/Help text', "Cannot upload this file, ensure it is not too big" ); - let network = this.$gettext( + let network = this.$pgettext('Content/Library/Help text', "A network error occured while uploading this file" ); - let timeout = this.$gettext("Upload timeout, please try again"); - let extension = this.$gettext( + let timeout = this.$pgettext('Content/Library/Help text', "Upload timeout, please try again"); + let extension = this.$pgettext('Content/Library/Help text', "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }" ); return { @@ -304,6 +332,19 @@ export default { }, hasActiveUploads () { return this.sortedFiles.filter((f) => { return f.active }).length > 0 + }, + remainingSpace () { + if (!this.quotaStatus) { + return 0 + } + return Math.max(0, this.quotaStatus.remaining - (this.uploadedSize / (1000 * 1000))) + }, + uploadedSize () { + let uploaded = 0 + this.files.forEach((f) => { + uploaded += f.size * (f.progress / 100) + }) + return uploaded } }, watch: { @@ -315,7 +356,12 @@ export default { }, importReference: _.debounce(function() { this.$router.replace({ query: { import: this.importReference } }); - }, 500) + }, 500), + remainingSpace (newValue) { + if (newValue <= 0) { + this.$refs.upload.active = false; + } + } } }; </script> diff --git a/front/src/components/library/Home.vue b/front/src/components/library/Home.vue index 19b667335fe14228118650a44bc080b1ab8fae57..eea8f22c5eb8bdf00b6c539455ace0603c2a3d42 100644 --- a/front/src/components/library/Home.vue +++ b/front/src/components/library/Home.vue @@ -4,17 +4,17 @@ <div class="ui stackable three column grid"> <div class="column"> <track-widget :url="'history/listenings/'" :filters="{scope: 'user', ordering: '-creation_date'}"> - <template slot="title"><translate>Recently listened</translate></template> + <template slot="title"><translate translate-context="Content/Home/Title">Recently listened</translate></template> </track-widget> </div> <div class="column"> <track-widget :url="'favorites/tracks/'" :filters="{scope: 'user', ordering: '-creation_date'}"> - <template slot="title"><translate>Recently favorited</translate></template> + <template slot="title"><translate translate-context="Content/Home/Title">Recently favorited</translate></template> </track-widget> </div> <div class="column"> <playlist-widget :url="'playlists/'" :filters="{scope: 'user', playable: true, ordering: '-creation_date'}"> - <template slot="title"><translate>Playlists</translate></template> + <template slot="title"><translate translate-context="*/*/*">Playlists</translate></template> </playlist-widget> </div> </div> @@ -22,7 +22,7 @@ <div class="ui stackable one column grid"> <div class="column"> <album-widget :filters="{playable: true, ordering: '-creation_date'}"> - <template slot="title"><translate>Recently added</translate></template> + <template slot="title"><translate translate-context="Content/Home/Title">Recently added</translate></template> </album-widget> </div> </div> @@ -62,7 +62,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Home") + title: this.$pgettext('Head/Home/Title', "Home") } } }, diff --git a/front/src/components/library/ImportStatusModal.vue b/front/src/components/library/ImportStatusModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..5632e95943cb78571ed55af3a458ab788e7bceca --- /dev/null +++ b/front/src/components/library/ImportStatusModal.vue @@ -0,0 +1,164 @@ +<template> + + <modal :show.sync="showModal"> + <div class="header"> + <translate translate-context="Popup/Import/Title">Import detail</translate> + </div> + <div class="content" v-if="upload"> + <div class="description"> + <div class="ui message" v-if="upload.import_status === 'pending'"> + <translate translate-context="Popup/Import/Message">Upload is still pending and will soon be processed by the server.</translate> + </div> + <div class="ui success message" v-if="upload.import_status === 'finished'"> + <translate translate-context="Popup/Import/Message">Upload was successfully processed by the server.</translate> + </div> + <div class="ui warning message" v-if="upload.import_status === 'skipped'"> + <translate translate-context="Popup/Import/Message">Upload was skipped because a similar one is already available in one of your libraries.</translate> + </div> + <div class="ui error message" v-if="upload.import_status === 'errored'"> + <translate translate-context="Popup/Import/Message">An error occured during upload processing. You will find more information below.</translate> + </div> + <template v-if="upload.import_status === 'errored'"> + <table class="ui very basic collapsing celled table"> + <tbody> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Error type</translate> + </td> + <td> + {{ getErrorData(upload).label }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Error detail</translate> + </td> + <td> + {{ getErrorData(upload).detail }} + <ul v-if="getErrorData(upload).errorRows.length > 0"> + <li v-for="row in getErrorData(upload).errorRows"> + {{ row.key}}: {{ row.value}} + </li> + </ul> + </td> + </tr> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Getting help</translate> + </td> + <td> + <ul> + <li> + <a :href="getErrorData(upload).documentationUrl" target="_blank"> + <translate translate-context="Popup/Import/Table.Label/Value">Read our documentation for this error</translate> + </a> + </li> + <li> + <a :href="getErrorData(upload).supportUrl" target="_blank"> + <translate translate-context="Popup/Import/Table.Label/Value">Open a support thread (include the debug information below in your message)</translate> + </a> + </li> + </ul> + </td> + </tr> + <tr> + <td> + <translate translate-context="Popup/Import/Table.Label/Noun">Debug information</translate> + </td> + <td> + <div class="ui form"> + <textarea class="ui textarea" rows="10" :value="getErrorData(upload).debugInfo"></textarea> + </div> + </td> + </tr> + </tbody> + </table> + </template> + </div> + </div> + <div class="actions"> + <div class="ui deny button"> + <translate translate-context="*/*/Button.Label/Verb">Close</translate> + </div> + </div> + </modal> +</template> +<script> +import Modal from '@/components/semantic/Modal' + +function getErrors(payload) { + let errors = [] + for (var k in payload) { + if (payload.hasOwnProperty(k)) { + let value = payload[k] + if (Array.isArray(value)) { + errors.push({ + key: k, + value: value.join(', ') + }) + } else { + // possibly artists, so nested errors + if (typeof value === 'object') { + getErrors(value).forEach((e) => { + errors.push({ + key: `${k} / ${e.key}`, + value: e.value + }) + }) + } + } + } + } + return errors +} + +export default { + props: ['upload', "show"], + components: { + Modal + }, + data () { + return { + showModal: this.show + } + }, + methods: { + getErrorData (upload) { + let payload = upload.import_details || {} + let d = { + supportUrl: 'https://governance.funkwhale.audio/g/246YOJ1m/funkwhale-support', + errorRows: [] + } + if (!payload.error_code) { + d.errorCode = 'unknown_error' + } else { + d.errorCode = payload.error_code + } + d.documentationUrl = `https://docs.funkwhale.audio/users/upload.html#${d.errorCode}` + if (d.errorCode === 'invalid_metadata') { + d.label = this.$pgettext('Popup/Import/Error.Label', 'Invalid metadata') + d.detail = this.$pgettext('Popup/Import/Error.Label', 'The metadata included in the file is invalid or some mandatory fields are missing.') + let detail = payload.detail || {} + d.errorRows = getErrors(detail) + } else { + d.label = this.$pgettext('Popup/Import/Error.Label', 'Unkwown error') + d.detail = this.$pgettext('Popup/Import/Error.Label', 'An unkwown error occured') + } + let debugInfo = { + source: upload.source, + ...payload, + } + d.debugInfo = JSON.stringify(debugInfo, null, 4) + return d + } + }, + watch: { + showModal (v) { + this.$emit('update:show', v) + }, + show (v) { + this.showModal = v + } + } +} +</script> diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue index c0371fe58800145856eaf18c30192565184a183f..ea4d98e6543e7382ead061577fc660d01e8a341d 100644 --- a/front/src/components/library/Library.vue +++ b/front/src/components/library/Library.vue @@ -2,16 +2,19 @@ <div class="main library pusher"> <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" to="/library" exact> - <translate>Browse</translate> + <translate translate-context="*/Library/*/Verb">Browse</translate> + </router-link> + <router-link class="ui item" to="/library/albums" exact> + <translate translate-context="*/*/*">Albums</translate> </router-link> <router-link class="ui item" to="/library/artists" exact> - <translate>Artists</translate> + <translate translate-context="*/*/*/Noun">Artists</translate> </router-link> <router-link class="ui item" to="/library/radios" exact> - <translate>Radios</translate> + <translate translate-context="*/*/*">Radios</translate> </router-link> <router-link class="ui item" to="/library/playlists" exact> - <translate>Playlists</translate> + <translate translate-context="*/*/*">Playlists</translate> </router-link> </nav> <router-view :key="$route.fullPath"></router-view> @@ -29,7 +32,7 @@ export default { }, labels() { return { - secondaryMenu: this.$gettext("Secondary menu") + secondaryMenu: this.$pgettext('Menu/*/Hidden text', "Secondary menu") } } } diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue index 5b433dcb2d3da801b819c7171ff5f3859b70268e..9b2f7bf82010bc1eed389ca072468a5c51c62ff0 100644 --- a/front/src/components/library/Radios.vue +++ b/front/src/components/library/Radios.vue @@ -2,36 +2,36 @@ <main v-title="labels.title"> <section class="ui vertical stripe segment"> <h2 class="ui header"> - <translate>Browsing radios</translate> + <translate translate-context="Content/Radio/Title">Browsing radios</translate> </h2> <div class="ui hidden divider"></div> <div class="ui row"> <h3 class="ui header"> - <translate>Instance radios</translate> + <translate translate-context="Content/Radio/Title">Instance radios</translate> </h3> <div class="ui cards"> - <radio-card :type="'favorites'"></radio-card> + <radio-card v-if="$store.state.auth.authenticated" :type="'favorites'"></radio-card> <radio-card :type="'random'"></radio-card> - <radio-card :type="'less-listened'"></radio-card> + <radio-card v-if="$store.state.auth.authenticated" :type="'less-listened'"></radio-card> </div> </div> <div class="ui hidden divider"></div> <h3 class="ui header"> - <translate>User radios</translate> + <translate translate-context="Content/Radio/Title">User radios</translate> </h3> <router-link class="ui green basic button" to="/library/radios/build" exact> - <translate>Create your own radio</translate> + <translate translate-context="Content/Radio/Button.Label/Verb">Create your own radio</translate> </router-link> <div class="ui hidden divider"></div> <div :class="['ui', {'loading': isLoading}, 'form']"> <div class="fields"> <div class="field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input name="search" type="text" v-model="query" :placeholder="labels.searchPlaceholder"/> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -39,18 +39,18 @@ </select> </div> <div class="field"> - <label><translate>Order</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label> <select class="ui dropdown" v-model="orderingDirection"> <option value="+"> - <translate>Ascending</translate> + <translate translate-context="Content/Search/Dropdown">Ascending</translate> </option> <option value="-"> - <translate>Descending</translate> + <translate translate-context="Content/Search/Dropdown">Descending</translate> </option> </select> </div> <div class="field"> - <label><translate>Results per page</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> <select class="ui dropdown" v-model="paginateBy"> <option :value="parseInt(12)">12</option> <option :value="parseInt(25)">25</option> @@ -138,8 +138,8 @@ export default { }, computed: { labels() { - let searchPlaceholder = this.$gettext("Enter a radio name…") - let title = this.$gettext("Radios") + let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Enter a radio name…") + let title = this.$pgettext('*/*/*', "Radios") return { searchPlaceholder, title diff --git a/front/src/components/library/Track.vue b/front/src/components/library/Track.vue deleted file mode 100644 index 4e0faaeece2e0073538319a74c69ed68ac7df34e..0000000000000000000000000000000000000000 --- a/front/src/components/library/Track.vue +++ /dev/null @@ -1,339 +0,0 @@ -<template> - <main> - <div v-if="isLoadingTrack" class="ui vertical segment" v-title="labels.title"> - <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> - </div> - <template v-if="track"> - <section - :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" - :style="headerStyle" - v-title="track.title" - > - <div class="segment-content"> - <h2 class="ui center aligned icon header"> - <i class="circular inverted music orange icon"></i> - <div class="content"> - {{ track.title }} - <div class="sub header"> - <span - v-translate="{album: track.album.title, artist: track.artist.name}" - :translate-params="{album: track.album.title, artist: track.artist.name}" - >From album %{ album } by %{ artist }</span> - </div> - <br> - <div class="ui basic buttons"> - <router-link - class="ui button" - :to="{name: 'library.albums.detail', params: {id: track.album.id }}" - > - <translate>Album page</translate> - </router-link> - <router-link - class="ui button" - :to="{name: 'library.artists.detail', params: {id: track.artist.id }}" - > - <translate>Artist page</translate> - </router-link> - </div> - </div> - </h2> - - <play-button class="orange" :track="track"> - <translate>Play</translate> - </play-button> - <track-favorite-icon :track="track" :button="true"></track-favorite-icon> - <track-playlist-icon :button="true" v-if="$store.state.auth.authenticated" :track="track"></track-playlist-icon> - - <a :href="wikipediaUrl" target="_blank" class="ui button"> - <i class="wikipedia w icon"></i> - <translate>Search on Wikipedia</translate> - </a> - <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" class="ui button"> - <i class="external icon"></i> - <translate>View on MusicBrainz</translate> - </a> - <a v-if="upload" :href="downloadUrl" target="_blank" class="ui button"> - <i class="download icon"></i> - <translate>Download</translate> - </a> - <template v-if="publicLibraries.length > 0"> - <button - @click="showEmbedModal = !showEmbedModal" - class="ui button"> - <i class="code icon"></i> - <translate>Embed</translate> - </button> - <modal :show.sync="showEmbedModal"> - <div class="header"> - <translate>Embed this track on your website</translate> - </div> - <div class="content"> - <div class="description"> - <embed-wizard type="track" :id="track.id" /> - - </div> - </div> - <div class="actions"> - <div class="ui deny button"> - <translate>Cancel</translate> - </div> - </div> - </modal> - </template> - </div> - </section> - <section class="ui vertical stripe center aligned segment"> - <h2 class="ui header"> - <translate>Track information</translate> - </h2> - <table class="ui very basic collapsing celled center aligned table"> - <tbody> - <tr> - <td> - <translate>Copyright</translate> - </td> - <td v-if="track.copyright" :title="track.copyright">{{ track.copyright|truncate(50) }}</td> - <td v-else> - <translate>We don't have any copyright information for this track</translate> - </td> - </tr> - <tr> - <td> - <translate>License</translate> - </td> - <td v-if="license"> - <a :href="license.url" target="_blank" rel="noopener noreferrer">{{ license.name }}</a> - </td> - <td v-else> - <translate>We don't have any licensing information for this track</translate> - </td> - </tr> - <tr> - <td> - <translate>Duration</translate> - </td> - <td v-if="upload && upload.duration">{{ time.parse(upload.duration) }}</td> - <td v-else> - <translate>N/A</translate> - </td> - </tr> - <tr> - <td> - <translate>Size</translate> - </td> - <td v-if="upload && upload.size">{{ upload.size | humanSize }}</td> - <td v-else> - <translate>N/A</translate> - </td> - </tr> - <tr> - <td> - <translate>Bitrate</translate> - </td> - <td v-if="upload && upload.bitrate">{{ upload.bitrate | humanSize }}/s</td> - <td v-else> - <translate>N/A</translate> - </td> - </tr> - <tr> - <td> - <translate>Type</translate> - </td> - <td v-if="upload && upload.extension">{{ upload.extension }}</td> - <td v-else> - <translate>N/A</translate> - </td> - </tr> - </tbody> - </table> - </section> - <section class="ui vertical stripe center aligned segment"> - <h2> - <translate>Lyrics</translate> - </h2> - <div v-if="isLoadingLyrics" class="ui vertical segment"> - <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> - </div> - <div v-if="lyrics" v-html="lyrics.content_rendered"></div> - <template v-if="!isLoadingLyrics & !lyrics"> - <p> - <translate>No lyrics available for this track.</translate> - </p> - <a class="ui button" target="_blank" :href="lyricsSearchUrl"> - <i class="search icon"></i> - <translate>Search on lyrics.wikia.com</translate> - </a> - </template> - </section> - <section class="ui vertical stripe segment"> - <h2> - <translate>User libraries</translate> - </h2> - <library-widget @loaded="libraries = $event" :url="'tracks/' + id + '/libraries/'"> - <translate slot="subtitle">This track is present in the following libraries:</translate> - </library-widget> - </section> - </template> - </main> -</template> - -<script> -import time from "@/utils/time" -import axios from "axios" -import url from "@/utils/url" -import logger from "@/logging" -import PlayButton from "@/components/audio/PlayButton" -import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon" -import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon" -import LibraryWidget from "@/components/federation/LibraryWidget" -import Modal from '@/components/semantic/Modal' -import EmbedWizard from "@/components/audio/EmbedWizard" - -const FETCH_URL = "tracks/" - -export default { - props: ["id"], - components: { - PlayButton, - TrackPlaylistIcon, - TrackFavoriteIcon, - LibraryWidget, - Modal, - EmbedWizard - }, - data() { - return { - time, - isLoadingTrack: true, - isLoadingLyrics: true, - track: null, - lyrics: null, - licenseData: null, - libraries: [], - showEmbedModal: false - } - }, - created() { - this.fetchData() - this.fetchLyrics() - }, - methods: { - fetchData() { - var self = this - this.isLoadingTrack = true - let url = FETCH_URL + this.id + "/" - logger.default.debug('Fetching track "' + this.id + '"') - axios.get(url).then(response => { - self.track = response.data - self.isLoadingTrack = false - }) - }, - fetchLicenseData(licenseId) { - var self = this - let url = `licenses/${licenseId}/` - axios.get(url).then(response => { - self.licenseData = response.data - }) - }, - fetchLyrics() { - var self = this - this.isLoadingLyrics = true - let url = FETCH_URL + this.id + "/lyrics/" - logger.default.debug('Fetching lyrics for track "' + this.id + '"') - axios.get(url).then( - response => { - self.lyrics = response.data - self.isLoadingLyrics = false - }, - response => { - console.error("No lyrics available") - self.isLoadingLyrics = false - } - ) - } - }, - computed: { - publicLibraries () { - return this.libraries.filter(l => { - return l.privacy_level === 'everyone' - }) - }, - labels() { - return { - title: this.$gettext("Track") - } - }, - upload() { - if (this.track.uploads) { - return this.track.uploads[0] - } - }, - wikipediaUrl() { - return ( - "https://en.wikipedia.org/w/index.php?search=" + - encodeURI(this.track.title + " " + this.track.artist.name) - ) - }, - musicbrainzUrl() { - if (this.track.mbid) { - return "https://musicbrainz.org/recording/" + this.track.mbid - } - }, - downloadUrl() { - let u = this.$store.getters["instance/absoluteUrl"]( - this.upload.listen_url - ) - if (this.$store.state.auth.authenticated) { - u = url.updateQueryString( - u, - "jwt", - encodeURI(this.$store.state.auth.token) - ) - } - return u - }, - lyricsSearchUrl() { - let base = "http://lyrics.wikia.com/wiki/Special:Search?query=" - let query = this.track.artist.name + ":" + this.track.title - return base + encodeURI(query) - }, - cover() { - return null - }, - headerStyle() { - if (!this.cover) { - return "" - } - return ( - "background-image: url(" + - this.$store.getters["instance/absoluteUrl"](this.cover) + - ")" - ) - }, - license() { - if (!this.track || !this.track.license) { - return null - } - return this.licenseData - } - }, - watch: { - id() { - this.fetchData() - }, - track (v) { - if (v && v.license) { - this.fetchLicenseData(v.license) - } - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped lang="scss"> -.table.center.aligned { - margin-left: auto; - margin-right: auto; -} -</style> diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue new file mode 100644 index 0000000000000000000000000000000000000000..639c8f51b83c63166d1ec4d70d668a82352e48a1 --- /dev/null +++ b/front/src/components/library/TrackBase.vue @@ -0,0 +1,228 @@ +<template> + <main> + <div v-if="isLoadingTrack" class="ui vertical segment" v-title="labels.title"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="track"> + <section + :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" + :style="headerStyle" + v-title="track.title" + > + <div class="segment-content"> + <h2 class="ui center aligned icon header"> + <i class="circular inverted music orange icon"></i> + <div class="content"> + {{ track.title }} + <div class="sub header"> + <div translate-context="Content/Track/Paragraph" + v-translate="{album: track.album.title, artist: track.artist.name, albumUrl: albumUrl, artistUrl: artistUrl}" + >From album <a class="internal" href="%{ albumUrl }">%{ album }</a> by <a class="internal" href="%{ artistUrl }">%{ artist }</a></div> + </div> + </div> + </h2> + <div class="header-buttons"> + <div class="ui buttons"> + <play-button class="orange" :track="track"> + <translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate> + </play-button> + </div> + <div class="ui buttons"> + <track-favorite-icon :track="track" :button="true"></track-favorite-icon> + </div> + <div class="ui buttons"> + <track-playlist-icon :button="true" v-if="$store.state.auth.authenticated" :track="track"></track-playlist-icon> + </div> + + <div class="ui buttons"> + <a v-if="upload" :href="downloadUrl" target="_blank" class="ui icon labeled button"> + <i class="download icon"></i> + <translate translate-context="Content/Track/Link/Verb">Download</translate> + </a> + </div> + + <modal v-if="publicLibraries.length > 0" :show.sync="showEmbedModal"> + <div class="header"> + <translate translate-context="Popup/Track/Title">Embed this track on your website</translate> + </div> + <div class="content"> + <div class="description"> + <embed-wizard type="track" :id="track.id" /> + + </div> + </div> + <div class="actions"> + <div class="ui deny button"> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> + </div> + </div> + </modal> + <div class="ui buttons"> + <button class="ui button" @click="$refs.dropdown.click()"> + <translate translate-context="*/*/Button.Label/Noun">More…</translate> + </button> + <div class="ui floating dropdown icon button" ref="dropdown" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <div + role="button" + v-if="publicLibraries.length > 0" + @click="showEmbedModal = !showEmbedModal" + class="basic item"> + <i class="code icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Embed</translate> + </div> + <a :href="wikipediaUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="wikipedia w icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate> + </a> + <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener" class="basic item"> + <i class="external icon"></i> + <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> + </a> + <router-link + v-if="track.is_local" + :to="{name: 'library.tracks.edit', params: {id: track.id }}" + class="basic item"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + <div class="divider"></div> + <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate> + </router-link> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + </div> + </div> + </div> + </div> + </div> + </section> + <router-view v-if="track" @libraries-loaded="libraries = $event" :track="track" :object="track" object-type="track" :key="$route.fullPath"></router-view> + </template> + </main> +</template> + +<script> +import time from "@/utils/time" +import axios from "axios" +import url from "@/utils/url" +import logger from "@/logging" +import PlayButton from "@/components/audio/PlayButton" +import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon" +import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon" +import Modal from '@/components/semantic/Modal' +import EmbedWizard from "@/components/audio/EmbedWizard" + +const FETCH_URL = "tracks/" + +export default { + props: ["id"], + components: { + PlayButton, + TrackPlaylistIcon, + TrackFavoriteIcon, + Modal, + EmbedWizard + }, + data() { + return { + time, + isLoadingTrack: true, + track: null, + showEmbedModal: false, + libraries: [] + } + }, + created() { + this.fetchData() + }, + methods: { + fetchData() { + var self = this + this.isLoadingTrack = true + let url = FETCH_URL + this.id + "/" + logger.default.debug('Fetching track "' + this.id + '"') + axios.get(url).then(response => { + self.track = response.data + self.isLoadingTrack = false + }) + }, + }, + computed: { + publicLibraries () { + return this.libraries.filter(l => { + return l.privacy_level === 'everyone' + }) + }, + upload() { + if (this.track.uploads) { + return this.track.uploads[0] + } + }, + labels() { + return { + title: this.$pgettext('*/*/*/Noun', "Track") + } + }, + wikipediaUrl() { + return ( + "https://en.wikipedia.org/w/index.php?search=" + + encodeURI(this.track.title + " " + this.track.artist.name) + ) + }, + musicbrainzUrl() { + if (this.track.mbid) { + return "https://musicbrainz.org/recording/" + this.track.mbid + } + }, + downloadUrl() { + let u = this.$store.getters["instance/absoluteUrl"]( + this.upload.listen_url + ) + if (this.$store.state.auth.authenticated) { + u = url.updateQueryString( + u, + "jwt", + encodeURI(this.$store.state.auth.token) + ) + } + return u + }, + cover() { + return null + }, + albumUrl () { + let route = this.$router.resolve({name: 'library.albums.detail', params: {id: this.track.album.id }}) + return route.location.path + }, + artistUrl () { + let route = this.$router.resolve({name: 'library.artists.detail', params: {id: this.track.artist.id }}) + return route.location.path + }, + headerStyle() { + if (!this.cover) { + return "" + } + return ( + "background-image: url(" + + this.$store.getters["instance/absoluteUrl"](this.cover) + + ")" + ) + }, + }, + watch: { + id() { + this.fetchData() + }, + } +} +</script> diff --git a/front/src/components/library/TrackDetail.vue b/front/src/components/library/TrackDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..2eb9a0009b0bbf813b19a1affdcd2d4f129c6a7c --- /dev/null +++ b/front/src/components/library/TrackDetail.vue @@ -0,0 +1,159 @@ +<template> + + <div v-if="track"> + <section class="ui vertical stripe center aligned segment"> + <h2 class="ui header"> + <translate translate-context="Content/Track/Title/Noun">Track information</translate> + </h2> + <table class="ui very basic collapsing celled center aligned table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Track/Table.Label/Noun">Copyright</translate> + </td> + <td v-if="track.copyright" :title="track.copyright">{{ track.copyright|truncate(50) }}</td> + <td v-else> + <translate translate-context="Content/Track/Table.Paragraph">No copyright information available for this track</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*/Noun">License</translate> + </td> + <td v-if="license"> + <a :href="license.url" target="_blank" rel="noopener noreferrer">{{ license.name }}</a> + </td> + <td v-else> + <translate translate-context="Content/Track/Table.Paragraph">No licensing information for this track</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*">Duration</translate> + </td> + <td v-if="upload && upload.duration">{{ time.parse(upload.duration) }}</td> + <td v-else> + <translate translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Library/*/in MB">Size</translate> + </td> + <td v-if="upload && upload.size">{{ upload.size | humanSize }}</td> + <td v-else> + <translate translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Track/*/Noun">Bitrate</translate> + </td> + <td v-if="upload && upload.bitrate">{{ upload.bitrate | humanSize }}/s</td> + <td v-else> + <translate translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Track/Table.Label/Noun">Type</translate> + </td> + <td v-if="upload && upload.extension">{{ upload.extension }}</td> + <td v-else> + <translate translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*/Noun">Federation ID</translate> + </td> + <td :title="track.fid"> + <a :href="track.fid" target="_blank" rel="noopener noreferrer"> + {{ track.fid|truncate(65)}} + </a> + </td> + </tr> + </tbody> + </table> + </section> + <section class="ui vertical stripe segment"> + <h2> + <translate translate-context="Content/*/Title/Noun">User libraries</translate> + </h2> + <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'tracks/' + id + '/libraries/'"> + <translate translate-context="Content/Track/Paragraph" slot="subtitle">This track is present in the following libraries:</translate> + </library-widget> + </section> + </div> +</template> + +<script> +import time from "@/utils/time" +import axios from "axios" +import url from "@/utils/url" +import logger from "@/logging" +import LibraryWidget from "@/components/federation/LibraryWidget" + +const FETCH_URL = "tracks/" + +export default { + props: ["track", "libraries"], + components: { + LibraryWidget, + }, + data() { + return { + time, + id: this.track.id, + licenseData: null + } + }, + created() { + if (this.track && this.track.license) { + this.fetchLicenseData(this.track.license) + } + }, + methods: { + fetchLicenseData(licenseId) { + var self = this + let url = `licenses/${licenseId}/` + axios.get(url).then(response => { + self.licenseData = response.data + }) + }, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*/Noun', "Track") + } + }, + upload() { + if (this.track.uploads) { + return this.track.uploads[0] + } + }, + license() { + if (!this.track || !this.track.license) { + return null + } + return this.licenseData + } + }, + watch: { + track (v) { + if (v && v.license) { + this.fetchLicenseData(v.license) + } + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped lang="scss"> +.table.center.aligned { + margin-left: auto; + margin-right: auto; +} +</style> diff --git a/front/src/components/library/TrackEdit.vue b/front/src/components/library/TrackEdit.vue new file mode 100644 index 0000000000000000000000000000000000000000..18e71e8fa62ea25884ff72819699c2dafbba591d --- /dev/null +++ b/front/src/components/library/TrackEdit.vue @@ -0,0 +1,60 @@ +<template> + + <section class="ui vertical stripe segment"> + <div class="ui text container"> + <h2> + <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this track</translate> + <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this track</translate> + </h2> + <div class="ui message" v-if="!object.is_local"> + <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate> + </div> + <edit-form + v-else-if="!isLoadingLicenses" + :object-type="objectType" + :object="object" + :can-edit="canEdit" + :licenses="licenses"></edit-form> + <div v-else class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + </div> + </section> +</template> + +<script> +import axios from "axios" + +import EditForm from '@/components/library/EditForm' +export default { + props: ["objectType", "object", "libraries"], + data() { + return { + id: this.object.id, + isLoadingLicenses: false, + licenses: [] + } + }, + components: { + EditForm + }, + created () { + this.fetchLicenses() + }, + methods: { + fetchLicenses () { + let self = this + self.isLoadingLicenses = true + axios.get('licenses/').then((response) => { + self.isLoadingLicenses = false + self.licenses = response.data.results + }) + } + }, + computed: { + canEdit () { + return true + } + } +} +</script> diff --git a/front/src/components/library/radios/Builder.vue b/front/src/components/library/radios/Builder.vue index 77a63b86d8af952cf6eb01db8a43622ac9cd8d6e..c6101a3200b0def3a9b3249dbba6bdbb70e740c1 100644 --- a/front/src/components/library/radios/Builder.vue +++ b/front/src/components/library/radios/Builder.vue @@ -3,52 +3,53 @@ <div> <section> <h2 class="ui header"> - <translate>Builder</translate> + <translate translate-context="Content/Radio/Title">Builder</translate> </h2> - <p><translate>You can use this interface to build your own custom radio, which will play tracks according to your criteria.</translate></p> + <p><translate translate-context="Content/Radio/Paragraph">You can use this interface to build your own custom radio, which will play tracks according to your criteria.</translate></p> <div class="ui form"> <div v-if="success" class="ui positive message"> <div class="header"> <template v-if="radioName"> - <translate>Radio updated</translate> + <translate translate-context="Content/Radio/Message">Radio updated</translate> </template> <template v-else> - <translate>Radio created</translate> + <translate translate-context="Content/Radio/Message">Radio created</translate> </template> </div> </div> <div class=""> <div class="field"> - <label for="name"><translate>Radio name</translate></label> + <label for="name"><translate translate-context="Content/Radio/Input.Label/Noun">Radio name</translate></label> <input id="name" name="name" type="text" v-model="radioName" :placeholder="labels.placeholder.name" /> </div> <div class="field"> - <label for="description"><translate>Description</translate></label> + <label for="description"><translate translate-context="Content/*/Input.Label/Noun">Description</translate></label> <textarea rows="2" id="description" type="text" v-model="radioDesc" :placeholder="labels.placeholder.description" /> </div> - <div class="inline field"> + <div class="ui toggle checkbox"> <input id="public" type="checkbox" v-model="isPublic" /> - <label for="public"><translate>Display publicly</translate></label> + <label for="public"><translate translate-context="Content/Radio/Checkbox.Label/Verb">Display publicly</translate></label> </div> + <div class="ui hidden divider"></div> <button :disabled="!canSave" @click="save" :class="['ui', 'green', {loading: isLoading}, 'button']"> - <translate>Save</translate> + <translate translate-context="Content/*/Button.Label/Verb">Save</translate> </button> <radio-button v-if="id" type="custom" :custom-radio-id="id"></radio-button> </div> </div> <div class="ui form"> <p> - <translate>Add filters to customize your radio</translate> + <translate translate-context="Content/Radio/Paragraph">Add filters to customize your radio</translate> </p> <div class="inline field"> <select class="ui dropdown" v-model="currentFilterType"> <option value=""> - <translate>Select a filter</translate> + <translate translate-context="Content/Radio/Dropdown.Placeholder/Verb">Select a filter</translate> </option> <option v-for="f in availableFilters" :value="f.type">{{ f.label }}</option> </select> <button :disabled="!currentFilterType" @click="add" class="ui button"> - <translate>Add filter</translate> + <translate translate-context="Content/Radio/Button.Label/Verb">Add filter</translate> </button> </div> <p v-if="currentFilter"> @@ -58,11 +59,11 @@ <table class="ui table"> <thead> <tr> - <th class="two wide"><translate>Filter name</translate></th> - <th class="one wide"><translate>Exclude</translate></th> - <th class="six wide"><translate>Config</translate></th> - <th class="five wide"><translate>Candidates</translate></th> - <th class="two wide"><translate>Actions</translate></th> + <th class="two wide"><translate translate-context="Content/Radio/Table.Label/Noun">Filter name</translate></th> + <th class="one wide"><translate translate-context="Content/Radio/Table.Label/Verb">Exclude</translate></th> + <th class="six wide"><translate translate-context="Content/Radio/Table.Label/Verb (Value is a List of Parameters)">Config</translate></th> + <th class="five wide"><translate translate-context="Content/Radio/Table.Label/Noun (Value is a number of Tracks)">Candidates</translate></th> + <th class="two wide"><translate translate-context="Content/*/*/Noun">Actions</translate></th> </tr> </thead> <tbody> @@ -82,7 +83,8 @@ class="ui header" v-translate="{count: checkResult.candidates.count}" :translate-n="checkResult.candidates.count" - translate-plural="%{ count } tracks matching combined filters"> + translate-plural="%{ count } tracks matching combined filters" + translate-context="Content/Radio/Table.Paragraph/Short"> %{ count } track matching combined filters </h3> <track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample" :playable="true"></track-table> @@ -230,10 +232,10 @@ export default { }, computed: { labels() { - let title = this.$gettext("Radio Builder") + let title = this.$pgettext('Head/Radio/Title', "Radio Builder") let placeholder = { - name: this.$gettext("My awesome radio"), - description: this.$gettext("My awesome description") + name: this.$pgettext('Content/Radio/Input.Placeholder', "My awesome radio"), + description: this.$pgettext('Content/Radio/Input.Placeholder', "My awesome description") } return { title, diff --git a/front/src/components/library/radios/Filter.vue b/front/src/components/library/radios/Filter.vue index fcf5efb8de7d8eb751c702ae0c8c410219e63dd4..b0eabbd6e6054f5be4a6afbee00e16fb2d423a26 100644 --- a/front/src/components/library/radios/Filter.vue +++ b/front/src/components/library/radios/Filter.vue @@ -42,7 +42,7 @@ </span> <modal v-if="checkResult" :show.sync="showCandidadesModal"> <div class="header"> - <translate>Track matching filter</translate> + <translate translate-context="Popup/Radio/Title/Noun">Tracks matching filter</translate> </div> <div class="content"> <div class="description"> @@ -51,13 +51,13 @@ </div> <div class="actions"> <div class="ui black deny button"> - <translate>Cancel</translate> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> </div> </div> </modal> </td> <td> - <button @click="$emit('delete', index)" class="ui basic red button"><translate>Remove</translate></button> + <button @click="$emit('delete', index)" class="ui basic red button"><translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate></button> </td> </tr> </template> diff --git a/front/src/components/manage/library/AlbumsTable.vue b/front/src/components/manage/library/AlbumsTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..3af2da4db9bccd1d5d691f2b0ee9152fe9d2b7e1 --- /dev/null +++ b/front/src/components/manage/library/AlbumsTable.vue @@ -0,0 +1,218 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui six wide field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + action-url="manage/library/albums/action/" + :filters="actionFilters"> + <template slot="header-cells"> + <th><translate translate-context="*/*/*">Title</translate></th> + <th><translate translate-context="*/*/*">Artist</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="*/*/*">Tracks</translate></th> + <th><translate translate-context="Content/*/*/Noun">Release date</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <router-link :to="{name: 'manage.library.albums.detail', params: {id: scope.obj.id }}">{{ scope.obj.title }}</router-link> + </td> + <td> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: scope.obj.artist.id }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('artist', scope.obj.artist.name)" :title="scope.obj.artist.name">{{ scope.obj.artist.name }}</span> + </td> + <td> + <template v-if="!scope.obj.is_local"> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</span> + </template> + <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + </td> + <td> + {{ scope.obj.tracks.length }} + </td> + <td> + <human-date v-if="scope.obj.release_date" :date="scope.obj.release_date"></human-date> + <translate v-else translate-context="*/*/*">N/A</translate> + + </td> + <td> + <human-date :date="scope.obj.creation_date"></human-date> + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + <translate translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> + Showing results %{ start }-%{ end } on %{ total } + </translate> + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from '@/lodash' +import time from '@/utils/time' +import {normalizeQuery, parseTokens} from '@/search' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' +import TranslationsMixin from '@/components/mixins/Translations' +import SmartSearchMixin from '@/components/mixins/SmartSearch' + + +export default { + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], + props: { + filters: {type: Object, required: false}, + }, + components: { + Pagination, + ActionTable + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 50, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'creation_date'], + ['release_date', 'release_date'], + ["name", "name"], + ] + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search.query, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/library/albums/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + }, + }, + computed: { + labels () { + return { + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, MusicBrainz ID…') + } + }, + actionFilters () { + var currentFilters = { + q: this.search.query + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') + let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.') + return [ + { + name: 'delete', + label: deleteLabel, + confirmationMessage: confirmationMessage, + isDangerous: true, + allowAll: false, + confirmColor: 'red', + }, + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/manage/library/FilesTable.vue b/front/src/components/manage/library/ArtistsTable.vue similarity index 52% rename from front/src/components/manage/library/FilesTable.vue rename to front/src/components/manage/library/ArtistsTable.vue index 23f33d0f350922d450a56944f976f077a0063648..84c873832d5f3429565b4fd55caec6e03c1501bf 100644 --- a/front/src/components/manage/library/FilesTable.vue +++ b/front/src/components/manage/library/ArtistsTable.vue @@ -2,12 +2,14 @@ <div> <div class="ui inline form"> <div class="fields"> - <div class="ui field"> - <label><translate>Search</translate></label> - <input name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" /> + <div class="ui six wide field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -15,10 +17,10 @@ </select> </div> <div class="field"> - <label><translate>Order</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> </div> @@ -32,54 +34,39 @@ @action-launched="fetchData" :objects-data="result" :actions="actions" - :action-url="'manage/library/uploads/action/'" + action-url="manage/library/artists/action/" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Title</translate></th> - <th><translate>Artist</translate></th> - <th><translate>Album</translate></th> - <th><translate>Import date</translate></th> - <th><translate>Type</translate></th> - <th><translate>Bitrate</translate></th> - <th><translate>Duration</translate></th> - <th><translate>Size</translate></th> + <th><translate translate-context="*/*/*/Noun">Name</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="*/*/*">Albums</translate></th> + <th><translate translate-context="*/*/*">Tracks</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <td> - <span :title="scope.obj.track.title">{{ scope.obj.track.title|truncate(30) }}</span> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: scope.obj.id }}">{{ scope.obj.name }}</router-link> </td> <td> - <span :title="scope.obj.track.artist.name">{{ scope.obj.track.artist.name|truncate(30) }}</span> + <template v-if="!scope.obj.is_local"> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</span> + </template> + <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> </td> <td> - <span :title="scope.obj.track.album.title">{{ scope.obj.track.album.title|truncate(20) }}</span> + {{ scope.obj.albums.length }} </td> <td> - <human-date :date="scope.obj.creation_date"></human-date> - </td> - <td v-if="scope.obj.mimetype"> - {{ scope.obj.mimetype }} - </td> - <td v-else> - <translate>N/A</translate> - </td> - <td v-if="scope.obj.bitrate"> - {{ scope.obj.bitrate | humanSize }}/s - </td> - <td v-else> - <translate>N/A</translate> - </td> - <td v-if="scope.obj.duration"> - {{ time.parse(scope.obj.duration) }} - </td> - <td v-else> - <translate>N/A</translate> + {{ scope.obj.tracks.length }} </td> - <td v-if="scope.obj.size"> - {{ scope.obj.size | humanSize }} - </td> - <td v-else> - <translate>N/A</translate> + <td> + <human-date :date="scope.obj.creation_date"></human-date> </td> </template> </action-table> @@ -95,7 +82,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -108,15 +95,18 @@ import axios from 'axios' import _ from '@/lodash' import time from '@/utils/time' +import {normalizeQuery, parseTokens} from '@/search' import Pagination from '@/components/Pagination' import ActionTable from '@/components/common/ActionTable' import OrderingMixin from '@/components/mixins/Ordering' import TranslationsMixin from '@/components/mixins/Translations' +import SmartSearchMixin from '@/components/mixins/SmartSearch' + export default { - mixins: [OrderingMixin, TranslationsMixin], + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], props: { - filters: {type: Object, required: false} + filters: {type: Object, required: false}, }, components: { Pagination, @@ -129,19 +119,17 @@ export default { isLoading: false, result: null, page: 1, - paginateBy: 25, - search: '', + paginateBy: 50, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, orderingDirection: defaultOrdering.direction || '+', ordering: defaultOrdering.field, orderingOptions: [ ['creation_date', 'creation_date'], - ['accessed_date', 'accessed_date'], - ['modification_date', 'modification_date'], - ['size', 'size'], - ['bitrate', 'bitrate'], - ['duration', 'duration'] + ["name", "name"], ] - } }, created () { @@ -152,13 +140,13 @@ export default { let params = _.merge({ 'page': this.page, 'page_size': this.paginateBy, - 'q': this.search, + 'q': this.search.query, 'ordering': this.getOrderingAsString() }, this.filters) let self = this self.isLoading = true self.checked = [] - axios.get('/manage/library/uploads/', {params: params}).then((response) => { + axios.get('/manage/library/artists/', {params: params}).then((response) => { self.result = response.data self.isLoading = false }, error => { @@ -168,17 +156,17 @@ export default { }, selectPage: function (page) { this.page = page - } + }, }, computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by title, artist, domain…') + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, name, MusicBrainz ID…') } }, actionFilters () { var currentFilters = { - q: this.search + q: this.search.query } if (this.filters) { return _.merge(currentFilters, this.filters) @@ -187,13 +175,17 @@ export default { } }, actions () { - let msg = this.$gettext('Delete') + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') + let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.') return [ { name: 'delete', - label: msg, - isDangerous: true - } + label: deleteLabel, + confirmationMessage: confirmationMessage, + isDangerous: true, + allowAll: false, + confirmColor: 'red', + }, ] } }, diff --git a/front/src/components/manage/library/EditsCardList.vue b/front/src/components/manage/library/EditsCardList.vue new file mode 100644 index 0000000000000000000000000000000000000000..28b07f37480b334e5b563e603977bc2a7d5fa245 --- /dev/null +++ b/front/src/components/manage/library/EditsCardList.vue @@ -0,0 +1,231 @@ +<template> + <div class="ui text container"> + <slot></slot> + <div class="ui inline form"> + <div class="fields"> + <div class="ui field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label (Value is All/Pending review/Approved/Rejected)">Status</translate></label> + <select class="ui dropdown" @change="addSearchToken('is_approved', $event.target.value)" :value="getTokenValue('is_approved', '')"> + <option value=""> + <translate translate-context="Content/*/Dropdown">All</translate> + </option> + <option value="null"> + <translate translate-context="Content/Admin/*/Noun">Pending review</translate> + </option> + <option value="yes"> + <translate translate-context="Content/*/*/Short">Approved</translate> + </option> + <option value="no"> + <translate translate-context="Content/Library/*/Short">Rejected</translate> + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <div v-else-if="result && result.count > 0"> + <edit-card + :obj="obj" + :current-state="getCurrentState(obj.target)" + v-for="obj in result.results" + @deleted="handle('delete', obj.uuid, null)" + @approved="handle('approved', obj.uuid, $event)" + :key="obj.uuid" /> + </div> + <empty-state v-else :refresh="true" @refresh="fetchData()"></empty-state> + </div> + <div class="ui hidden divider"></div> + <div> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + <translate translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> + Showing results %{ start }-%{ end } on %{ total } + </translate> + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from '@/lodash' +import time from '@/utils/time' +import Pagination from '@/components/Pagination' +import OrderingMixin from '@/components/mixins/Ordering' +import TranslationsMixin from '@/components/mixins/Translations' +import EditCard from '@/components/library/EditCard' +import {normalizeQuery, parseTokens} from '@/search' +import SmartSearchMixin from '@/components/mixins/SmartSearch' + +import edits from '@/edits' + + +export default { + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], + props: { + filters: {type: Object, required: false} + }, + components: { + Pagination, + EditCard + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 25, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'creation_date'], + ['applied_date', 'applied_date'], + ], + targets: { + track: {} + } + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search.query, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + this.result = null + axios.get('mutations/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + self.fetchTargets() + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + fetchTargets () { + // we request target data via the API so we can display previous state + // additionnal data next to the edit card + let self = this + let typesAndIds = { + track: { + url: 'tracks/', + ids: [], + } + } + this.result.results.forEach((m) => { + if (!m.target || !typesAndIds[m.target.type]) { + return + } + typesAndIds[m.target.type]['ids'].push(m.target.id) + }) + Object.keys(typesAndIds).forEach((k) => { + let config = typesAndIds[k] + if (config.ids.length === 0) { + return + } + axios.get(config.url, {params: {id: _.uniq(config.ids), hidden: 'null'}}).then((response) => { + response.data.results.forEach((e) => { + self.$set(self.targets[k], e.id, { + payload: e, + currentState: edits.getCurrentStateForObj(e, edits.getConfigs.bind(self)()[k]) + }) + }) + }, error => { + self.errors = error.backendErrors + }) + }) + }, + selectPage: function (page) { + this.page = page + }, + handle (type, id, value) { + if (type === 'delete') { + this.exclude.push(id) + } + + this.result.results.forEach((e) => { + if (e.uuid === id) { + e.is_approved = value + } + }) + }, + getCurrentState (target) { + if (!target) { + return {} + } + if (this.targets[target.type] && this.targets[target.type][String(target.id)]) { + return this.targets[target.type][String(target.id)].currentState + } + return {} + } + }, + computed: { + labels () { + return { + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…') + } + }, + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/manage/library/LibrariesTable.vue b/front/src/components/manage/library/LibrariesTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..88c58f3110ff068b9905a81d9bd09deffc1e7bda --- /dev/null +++ b/front/src/components/manage/library/LibrariesTable.vue @@ -0,0 +1,235 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui six wide field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> + </div> + <div class="field"> + <label><translate translate-context="*/*/*">Visibility</translate></label> + <select class="ui dropdown" @change="addSearchToken('privacy_level', $event.target.value)" :value="getTokenValue('privacy_level', '')"> + <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> + <option value="me">{{ sharedLabels.fields.privacy_level.shortChoices.me }}</option> + <option value="instance">{{ sharedLabels.fields.privacy_level.shortChoices.instance }}</option> + <option value="everyone">{{ sharedLabels.fields.privacy_level.shortChoices.everyone }}</option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + action-url="manage/library/libraries/action/" + :filters="actionFilters"> + <template slot="header-cells"> + <th><translate translate-context="*/*/*">Name</translate></th> + <th><translate translate-context="*/*/*">Account</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="*/*/*">Visibility</translate></th> + <th><translate translate-context="Content/*/*/Noun">Uploads</translate></th> + <th><translate translate-context="Content/*/*/Noun">Followers</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <router-link :to="{name: 'manage.library.libraries.detail', params: {id: scope.obj.uuid }}">{{ scope.obj.name }}</router-link> + </td> + <td> + <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.actor.full_username }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('account', scope.obj.actor.full_username)" :title="scope.obj.actor.full_username">{{ scope.obj.actor.preferred_username }}</span> + </td> + <td> + <template v-if="!scope.obj.is_local"> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</span> + </template> + <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + </td> + <td> + <span + role="button" + class="discrete link" + @click="addSearchToken('privacy_level', scope.obj.privacy_level)" + :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.privacy_level]"> + {{ sharedLabels.fields.privacy_level.shortChoices[scope.obj.privacy_level] }} + </span> + </td> + <td> + {{ scope.obj.uploads_count }} + </td> + <td> + {{ scope.obj.followers_count }} + </td> + <td> + <human-date :date="scope.obj.creation_date"></human-date> + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + <translate translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> + Showing results %{ start }-%{ end } on %{ total } + </translate> + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from '@/lodash' +import time from '@/utils/time' +import {normalizeQuery, parseTokens} from '@/search' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' +import TranslationsMixin from '@/components/mixins/Translations' +import SmartSearchMixin from '@/components/mixins/SmartSearch' + + +export default { + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], + props: { + filters: {type: Object, required: false}, + }, + components: { + Pagination, + ActionTable + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 50, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'creation_date'], + ['followers_count', 'followers'], + ['uploads_count', 'uploads'], + ] + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search.query, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/library/libraries/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + }, + }, + computed: { + labels () { + return { + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, actor, name, description…') + } + }, + actionFilters () { + var currentFilters = { + q: this.search.query + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') + let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected library will be removed, as well as associated uploads and follows. This action is irreversible.') + return [ + { + name: 'delete', + label: deleteLabel, + confirmationMessage: confirmationMessage, + isDangerous: true, + allowAll: false, + confirmColor: 'red', + }, + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/manage/library/TracksTable.vue b/front/src/components/manage/library/TracksTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..a702d8e25cb180827e1134aad530f99936bd7675 --- /dev/null +++ b/front/src/components/manage/library/TracksTable.vue @@ -0,0 +1,218 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui six wide field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + action-url="manage/library/tracks/action/" + :filters="actionFilters"> + <template slot="header-cells"> + <th><translate translate-context="*/*/*">Title</translate></th> + <th><translate translate-context="*/*/*">Album</translate></th> + <th><translate translate-context="*/*/*">Artist</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="Content/*/*/Noun">License</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <router-link :to="{name: 'manage.library.tracks.detail', params: {id: scope.obj.id }}">{{ scope.obj.title }}</router-link> + </td> + <td> + <router-link :to="{name: 'manage.library.albums.detail', params: {id: scope.obj.album.id }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('album_id', scope.obj.album.id)" :title="scope.obj.album.title">{{ scope.obj.album.title }}</span> + </td> + <td> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: scope.obj.artist.id }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('artist_id', scope.obj.artist.id)" :title="scope.obj.artist.name">{{ scope.obj.artist.name }}</span> + </td> + <td> + <template v-if="!scope.obj.is_local"> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</span> + </template> + <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + </td> + <td> + <span role="button" v-if="scope.obj.license" class="discrete link" @click="addSearchToken('license', scope.obj.license)" :title="scope.obj.license">{{ scope.obj.license }}</span> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + <td> + <human-date :date="scope.obj.creation_date"></human-date> + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + <translate translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> + Showing results %{ start }-%{ end } on %{ total } + </translate> + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from '@/lodash' +import time from '@/utils/time' +import {normalizeQuery, parseTokens} from '@/search' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' +import TranslationsMixin from '@/components/mixins/Translations' +import SmartSearchMixin from '@/components/mixins/SmartSearch' + + +export default { + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], + props: { + filters: {type: Object, required: false}, + }, + components: { + Pagination, + ActionTable + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 50, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'creation_date'], + ] + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search.query, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/library/tracks/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + }, + }, + computed: { + labels () { + return { + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, album, MusicBrainz ID…') + } + }, + actionFilters () { + var currentFilters = { + q: this.search.query + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') + let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.') + return [ + { + name: 'delete', + label: deleteLabel, + confirmationMessage: confirmationMessage, + isDangerous: true, + allowAll: false, + confirmColor: 'red', + }, + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/manage/library/UploadsTable.vue b/front/src/components/manage/library/UploadsTable.vue new file mode 100644 index 0000000000000000000000000000000000000000..efc4e23944387bf84ae8330452a561d341e90dfe --- /dev/null +++ b/front/src/components/manage/library/UploadsTable.vue @@ -0,0 +1,285 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui six wide field"> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> + <form @submit.prevent="search.query = $refs.search.value"> + <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> + </form> + </div> + <div class="field"> + <label><translate translate-context="*/*/*">Visibility</translate></label> + <select class="ui dropdown" @change="addSearchToken('privacy_level', $event.target.value)" :value="getTokenValue('privacy_level', '')"> + <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> + <option value="me">{{ sharedLabels.fields.privacy_level.shortChoices.me }}</option> + <option value="instance">{{ sharedLabels.fields.privacy_level.shortChoices.instance }}</option> + <option value="everyone">{{ sharedLabels.fields.privacy_level.shortChoices.everyone }}</option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Library/*/Noun">Import status</translate></label> + <select class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')"> + <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> + <option value="pending"><translate translate-context="Content/Library/*/Short">Pending</translate></option> + <option value="skipped"><translate translate-context="Content/Library/*">Skipped</translate></option> + <option value="errored"><translate translate-context="Content/Library/Dropdown">Failed</translate></option> + <option value="finished"><translate translate-context="Content/Library/*">Finished</translate></option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ sharedLabels.filters[option[1]] }} + </option> + </select> + </div> + <div class="field"> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> + </select> + </div> + </div> + </div> + <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" /> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + action-url="manage/library/uploads/action/" + :filters="actionFilters"> + <template slot="header-cells"> + <th><translate translate-context="*/*/*">Name</translate></th> + <th><translate translate-context="*/*/*">Library</translate></th> + <th><translate translate-context="*/*/*">Account</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="*/*/*">Visibility</translate></th> + <th><translate translate-context="Content/*/*/Noun">Import status</translate></th> + <th><translate translate-context="Content/*/*/Noun">Size</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + <th><translate translate-context="Content/*/*/Noun">Accessed date</translate></th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <router-link :to="{name: 'manage.library.uploads.detail', params: {id: scope.obj.uuid }}" :title="displayName(scope.obj)"> + {{ displayName(scope.obj)|truncate(30, "…", true) }} + </router-link> + </td> + <td> + <router-link :to="{name: 'manage.library.libraries.detail', params: {id: scope.obj.library.uuid }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" + @click="addSearchToken('library_id', scope.obj.library.id)" + :title="scope.obj.library.name"> + {{ scope.obj.library.name | truncate(20) }} + </span> + </td> + <td> + <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.library.actor.full_username }}"> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('account', scope.obj.library.actor.full_username)" :title="scope.obj.library.actor.full_username">{{ scope.obj.library.actor.preferred_username }}</span> + </td> + <td> + <template v-if="!scope.obj.is_local"> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}"> + <i class="wrench icon"></i> + </router-link> + <span role="button" class="discrete link" @click="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</span> + </template> + <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + </td> + <td> + <span + role="button" + class="discrete link" + @click="addSearchToken('privacy_level', scope.obj.library.privacy_level)" + :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.library.privacy_level]"> + {{ sharedLabels.fields.privacy_level.shortChoices[scope.obj.library.privacy_level] }} + </span> + </td> + <td> + <span class="discrete link" @click="addSearchToken('status', scope.obj.import_status)" :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"> + {{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }} + </span> + <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = scope.obj; showUploadDetailModal = true"> + <i class="question circle outline icon"></i> + </button> + </td> + <td> + <span v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</span> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + <td> + <human-date :date="scope.obj.creation_date"></human-date> + </td> + <td> + <human-date v-if="scope.obj.accessed_date" :date="scope.obj.accessed_date"></human-date> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.count > paginateBy" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + <translate translate-context="Content/*/Paragraph" + :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> + Showing results %{ start }-%{ end } on %{ total } + </translate> + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from '@/lodash' +import time from '@/utils/time' +import {normalizeQuery, parseTokens} from '@/search' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' +import TranslationsMixin from '@/components/mixins/Translations' +import SmartSearchMixin from '@/components/mixins/SmartSearch' +import ImportStatusModal from '@/components/library/ImportStatusModal' + + +export default { + mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], + props: { + filters: {type: Object, required: false}, + }, + components: { + Pagination, + ActionTable, + ImportStatusModal + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date') + return { + detailedUpload: null, + showUploadDetailModal: false, + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 50, + search: { + query: this.defaultQuery, + tokens: parseTokens(normalizeQuery(this.defaultQuery)) + }, + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['creation_date', 'creation_date'], + ['modification_date', 'modification_date'], + ['accessed_date', 'accessed_date'], + ['size', 'size'], + ['bitrate', 'bitrate'], + ['duration', 'duration'], + ] + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search.query, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/library/uploads/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + }, + displayName (upload) { + if (upload.filename) { + return upload.filename + } + if (upload.source) { + return upload.source + } + return upload.uuid + } + }, + computed: { + labels () { + return { + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, actor, name, reference, source…') + } + }, + actionFilters () { + var currentFilters = { + q: this.search.query + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') + let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected upload will be removed. This action is irreversible.') + return [ + { + name: 'delete', + label: deleteLabel, + confirmationMessage: confirmationMessage, + isDangerous: true, + allowAll: false, + confirmColor: 'red', + }, + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/components/manage/moderation/AccountsTable.vue b/front/src/components/manage/moderation/AccountsTable.vue index 8259e8ec8b0b0fc170424236daaa6674b07519d1..ce91fe43e32df84913066f594442b8adbfdac07e 100644 --- a/front/src/components/manage/moderation/AccountsTable.vue +++ b/front/src/components/manage/moderation/AccountsTable.vue @@ -3,13 +3,13 @@ <div class="ui inline form"> <div class="fields"> <div class="ui six wide field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <form @submit.prevent="search.query = $refs.search.value"> <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> </form> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -17,10 +17,10 @@ </select> </div> <div class="field"> - <label><translate>Ordering direction</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> </div> @@ -37,12 +37,12 @@ action-url="manage/accounts/action/" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Name</translate></th> - <th><translate>Domain</translate></th> - <th><translate>Uploads</translate></th> - <th><translate>First seen</translate></th> - <th><translate>Last seen</translate></th> - <th><translate>Under moderation rule</translate></th> + <th><translate translate-context="*/*/*/Noun">Name</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Noun">Last seen</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Short">Under moderation rule</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <td> @@ -57,7 +57,7 @@ </template> <span role="button" v-else class="ui tiny teal icon link label" @click="addSearchToken('domain', scope.obj.domain)"> <i class="home icon"></i> - <translate>Local account</translate> + <translate translate-context="Content/Moderation/*/Short, Noun">Local account</translate> </span> </td> <td> @@ -70,7 +70,7 @@ <human-date v-if="scope.obj.last_fetch_date" :date="scope.obj.last_fetch_date"></human-date> </td> <td> - <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate>Yes</translate></span> + <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate translate-context="*/*/*">Yes</translate></span> </td> </template> </action-table> @@ -86,7 +86,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -168,7 +168,7 @@ export default { computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by domain, username, bio…') + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by domain, username, bio…') } }, actionFilters () { @@ -185,7 +185,7 @@ export default { return [ { name: 'purge', - label: this.$gettext('Purge'), + label: this.$pgettext('*/*/*/Verb', 'Purge'), isDangerous: true } ] diff --git a/front/src/components/manage/moderation/DomainsTable.vue b/front/src/components/manage/moderation/DomainsTable.vue index ed9eb5be8066755fc316ba12ce69c3d3dbca8fda..544d91156f3101236b8395bec133b9248d2d112a 100644 --- a/front/src/components/manage/moderation/DomainsTable.vue +++ b/front/src/components/manage/moderation/DomainsTable.vue @@ -3,11 +3,11 @@ <div class="ui inline form"> <div class="fields"> <div class="ui field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" /> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -15,10 +15,10 @@ </select> </div> <div class="field"> - <label><translate>Ordering direction</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> </div> @@ -36,11 +36,11 @@ idField="name" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Name</translate></th> - <th><translate>Users</translate></th> - <th><translate>Received messages</translate></th> - <th><translate>First seen</translate></th> - <th><translate>Under moderation rule</translate></th> + <th><translate translate-context="*/*/*/Noun">Name</translate></th> + <th><translate translate-context="*/*/*/Noun">Users</translate></th> + <th><translate translate-context="Content/Moderation/*/Noun">Received messages</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate></th> + <th><translate translate-context="Content/Moderation/Table.Label/Short">Under moderation rule</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <td> @@ -56,7 +56,7 @@ <human-date :date="scope.obj.creation_date"></human-date> </td> <td> - <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate>Yes</translate></span> + <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate translate-context="*/*/*">Yes</translate></span> </td> </template> </action-table> @@ -72,7 +72,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -148,7 +148,7 @@ export default { computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by name…') + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by name…') } }, actionFilters () { @@ -165,7 +165,7 @@ export default { return [ { name: 'purge', - label: this.$gettext('Purge'), + label: this.$pgettext('*/*/*/Verb', 'Purge'), isDangerous: true } ] diff --git a/front/src/components/manage/moderation/InstancePolicyCard.vue b/front/src/components/manage/moderation/InstancePolicyCard.vue index b72c6bac8dfea889b60cc7a19d3c44650c467011..040195d0975f259a4a613d329284d1c95e84e9b4 100644 --- a/front/src/components/manage/moderation/InstancePolicyCard.vue +++ b/front/src/components/manage/moderation/InstancePolicyCard.vue @@ -6,44 +6,44 @@ <i class="user icon"></i>{{ object.actor }} <template v-if="object.is_active"> <i class="play icon"></i> - <translate>Enabled</translate> + <translate translate-context="*/*/*">Enabled</translate> </template> <template v-if="!object.is_active"> <i class="pause icon"></i> - <translate>Paused</translate> + <translate translate-context="Content/Moderation/Card.List item">Paused</translate> </template> </p> <div> - <p><strong><translate>Rule</translate></strong></p> + <p><strong><translate translate-context="Content/Moderation/Card.Title/Noun">Rule</translate></strong></p> <p v-if="object.block_all"> <i class="ban icon"></i> - <translate>Block everything</translate> + <translate translate-context="Content/Moderation/*/Verb">Block everything</translate> </p> <div v-else class="ui list"> <div class="ui item" v-if="object.silence_activity"> <i class="feed icon"></i> - <div class="content"><translate>Mute activity</translate></div> + <div class="content"><translate translate-context="Content/Moderation/*/Verb">Mute activity</translate></div> </div> <div class="ui item" v-if="object.silence_notifications"> <i class="bell icon"></i> - <div class="content"><translate>Mute notifications</translate></div> + <div class="content"><translate translate-context="Content/Moderation/*/Verb">Mute notifications</translate></div> </div> <div class="ui item" v-if="object.reject_media"> <i class="file icon"></i> - <div class="content"><translate>Reject media</translate></div> + <div class="content"><translate translate-context="Content/Moderation/*/Verb">Reject media</translate></div> </div> </div> </div> <div v-if="markdown && object.summary"> <div class="ui hidden divider"></div> - <p><strong><translate>Reason</translate></strong></p> + <p><strong><translate translate-context="Content/Moderation/*/Noun">Reason</translate></strong></p> <div v-html="markdown.makeHtml(object.summary)"></div> </div> <div class="ui hidden divider"></div> <button @click="$emit('update')" class="ui right floated labeled icon button"> <i class="edit icon"></i> - <translate>Update</translate> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> </button> </div> </template> diff --git a/front/src/components/manage/moderation/InstancePolicyForm.vue b/front/src/components/manage/moderation/InstancePolicyForm.vue index a31f162b9d4d0291fd2da2a0a9b9aa0cc29a45a2..b38a2a9a586f22038ea5de6b42ced404067e1526 100644 --- a/front/src/components/manage/moderation/InstancePolicyForm.vue +++ b/front/src/components/manage/moderation/InstancePolicyForm.vue @@ -1,11 +1,11 @@ <template> <form class="ui form" @submit.prevent="createOrUpdate"> <h3 class="ui header"> - <translate v-if="object" key="1">Update moderation rule</translate> - <translate v-else key="2">Add a new moderation rule</translate> + <translate translate-context="Content/Moderation/Card.Title/Verb" v-if="object" key="1">Edit moderation rule</translate> + <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-else key="2">Add a new moderation rule</translate> </h3> <div v-if="errors && errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while creating rule</translate></div> + <div class="header"><translate translate-context="Content/Moderation/Error message.Title">Error while creating rule</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> @@ -15,15 +15,15 @@ <div class="ui toggle checkbox"> <input id="policy-is-active" v-model="current.isActive" type="checkbox"> <label for="policy-is-active"> - <translate v-if="current.isActive" key="1">Enabled</translate> - <translate v-else key="2">Disabled</translate> + <translate translate-context="*/*/*" v-if="current.isActive" key="1">Enabled</translate> + <translate translate-context="*/*/*" v-else key="2">Disabled</translate> <tooltip :content="labels.isActiveHelp" /> </label> </div> </div> <div class="field"> <label for="policy-summary"> - <translate>Reason</translate> + <translate translate-context="Content/Moderation/*/Noun">Reason</translate> <tooltip :content="labels.summaryHelp" /> </label> <textarea name="policy-summary" id="policy-summary" rows="5" v-model="current.summary"></textarea> @@ -32,13 +32,13 @@ <div class="ui toggle checkbox"> <input id="policy-is-active" v-model="current.blockAll" type="checkbox"> <label for="policy-is-active"> - <translate>Block everything</translate> + <translate translate-context="Content/Moderation/*/Verb">Block everything</translate> <tooltip :content="labels.blockAllHelp" /> </label> </div> </div> <div class="ui horizontal divider"> - <translate>Or customize your rule</translate> + <translate translate-context="Content/Moderation/Card.Title">Or customize your rule</translate> </div> <div v-for="config in fieldConfig" :class="['field']"> <div class="ui toggle checkbox"> @@ -52,30 +52,30 @@ </div> <div class="ui hidden divider"></div> <button @click.prevent="$emit('cancel')" class="ui basic left floated button"> - <translate>Cancel</translate> + <translate translate-context="*/*/Button.Label/Verb">Cancel</translate> </button> <button :class="['ui', 'right', 'floated', 'green', {'disabled loading': isLoading}, 'button']" :disabled="isLoading"> - <translate v-if="object" key="1">Update</translate> - <translate v-else key="2">Create</translate> + <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-if="object" key="1">Update</translate> + <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-else key="2">Create</translate> </button> <dangerous-button v-if="object" class="right floated basic button" color='red' @confirm="remove"> - <translate>Delete</translate> + <translate translate-context="*/*/*/Verb">Delete</translate> <p slot="modal-header"> - <translate>Delete this moderation rule?</translate> + <translate translate-context="Popup/Moderation/Title">Delete this moderation rule?</translate> </p> <p slot="modal-content"> - <translate>This action is irreversible.</translate> - </p> - <p slot="modal-confirm"> - <translate>Delete moderation rule</translate> + <translate translate-context="Popup/Moderation/Paragraph">This action is irreversible.</translate> </p> + <div slot="modal-confirm"> + <translate translate-context="Popup/Moderation/Button.Label/Verb">Delete moderation rule</translate> + </div> </dangerous-button> </form> </template> <script> import axios from 'axios' -import _ from 'lodash' +import _ from '@/lodash' export default { props: { @@ -107,20 +107,20 @@ export default { computed: { labels () { return { - summaryHelp: this.$gettext("Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place."), - isActiveHelp: this.$gettext("Use this setting to temporarily enable/disable the policy without completely removing it."), - blockAllHelp: this.$gettext("Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"), + summaryHelp: this.$pgettext('Content/Moderation/Help text', "Explain why you're applying this policy. Depending on your instance configuration, this will help you remember why you acted on this account or domain, and may be displayed publicly to help users understand what moderation rules are in place."), + isActiveHelp: this.$pgettext('Content/Moderation/Help text', "Use this setting to temporarily enable/disable the policy without completely removing it."), + blockAllHelp: this.$pgettext('Content/Moderation/Help text', "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"), silenceActivity: { - help: this.$gettext("Hide account or domain content, except from followers."), - label: this.$gettext("Mute activity"), + help: this.$pgettext('Content/Moderation/Help text', "Hide account or domain content, except from followers."), + label: this.$pgettext('Content/Moderation/*/Verb', "Mute activity"), }, silenceNotifications: { - help: this.$gettext("Prevent account or domain from triggering notifications, except from followers."), - label: this.$gettext("Silence notifications"), + help: this.$pgettext('Content/Moderation/Help text', "Prevent account or domain from triggering notifications, except from followers."), + label: this.$pgettext('Content/Moderation/*/Verb', "Mute notifications"), }, rejectMedia: { - help: this.$gettext("Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well."), - label: this.$gettext("Reject media"), + help: this.$pgettext('Content/Moderation/Help text', "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well."), + label: this.$pgettext('Content/Moderation/*/Verb', "Reject media"), } } } diff --git a/front/src/components/manage/users/InvitationForm.vue b/front/src/components/manage/users/InvitationForm.vue index 02cfba5a949734d7dd0c5f88dd54a58a10fe5f55..950e11e08e72c6f5dedbabd72a071b1cdc8f4b0f 100644 --- a/front/src/components/manage/users/InvitationForm.vue +++ b/front/src/components/manage/users/InvitationForm.vue @@ -2,19 +2,19 @@ <div> <form class="ui form" @submit.prevent="submit"> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while creating invitation</translate></div> + <div class="header"><translate translate-context="Content/Admin/Error message.Title">Error while creating invitation</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="inline fields"> <div class="ui field"> - <label><translate>Invitation code</translate></label> + <label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label> <input name="code" type="text" v-model="code" :placeholder="labels.placeholder" /> </div> <div class="ui field"> <button :class="['ui', {loading: isLoading}, 'button']" :disabled="isLoading" type="submit"> - <translate>Get a new invitation</translate> + <translate translate-context="Content/Admin/Button.Label/Verb">Get a new invitation</translate> </button> </div> </div> @@ -24,8 +24,8 @@ <table class="ui ui basic table"> <thead> <tr> - <th><translate>Code</translate></th> - <th><translate>Share link</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun">Code</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun">Share link</translate></th> </tr> </thead> <tbody> @@ -35,7 +35,7 @@ </tr> </tbody> </table> - <button class="ui basic button" @click="invitations = []"><translate>Clear</translate></button> + <button class="ui basic button" @click="invitations = []"><translate translate-context="Content/Admin/Button.Label/Verb">Clear</translate></button> </div> </div> </template> @@ -55,7 +55,7 @@ export default { computed: { labels () { return { - placeholder: this.$gettext('Leave empty for a random code') + placeholder: this.$pgettext('Content/Admin/Input.Placeholder', 'Leave empty for a random code') } } }, diff --git a/front/src/components/manage/users/InvitationsTable.vue b/front/src/components/manage/users/InvitationsTable.vue index ee9eb503501cee9541c6c9bf2d32cdaf1625ec10..6f557c8af2c879a64613f5dff68dbd0fbffd5159 100644 --- a/front/src/components/manage/users/InvitationsTable.vue +++ b/front/src/components/manage/users/InvitationsTable.vue @@ -3,11 +3,11 @@ <div class="ui inline form"> <div class="fields"> <div class="ui field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" /> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -15,11 +15,11 @@ </select> </div> <div class="field"> - <label><translate>Status</translate></label> + <label><translate translate-context="Content/Admin/*/Noun (Value is Used/Not used)">Status</translate></label> <select class="ui dropdown" v-model="isOpen"> - <option :value="null"><translate>All</translate></option> - <option :value="true"><translate>Open</translate></option> - <option :value="false"><translate>Expired/used</translate></option> + <option :value="null"><translate translate-context="Content/*/Dropdown">All</translate></option> + <option :value="true"><translate translate-context="Content/Admin/Dropdown/Adjective">Open</translate></option> + <option :value="false"><translate translate-context="Content/Admin/Dropdown/Adjective">Expired/used</translate></option> </select> </div> </div> @@ -36,20 +36,20 @@ :action-url="'manage/users/invitations/action/'" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Owner</translate></th> - <th><translate>Status</translate></th> - <th><translate>Creation date</translate></th> - <th><translate>Expiration date</translate></th> - <th><translate>Code</translate></th> + <th><translate translate-context="Content/Admin/Table.Label">Owner</translate></th> + <th><translate translate-context="Content/Admin/*/Noun (Value is Used/Not used)">Status</translate></th> + <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun">Expiration date</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun">Code</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <td> <router-link :to="{name: 'manage.users.users.detail', params: {id: scope.obj.id }}">{{ scope.obj.owner.username }}</router-link> </td> <td> - <span v-if="scope.obj.users.length > 0" class="ui green basic label"><translate>Used</translate></span> - <span v-else-if="moment().isAfter(scope.obj.expiration_date)" class="ui red basic label"><translate>Expired</translate></span> - <span v-else class="ui basic label"><translate>Not used</translate></span> + <span v-if="scope.obj.users.length > 0" class="ui green basic label"><translate translate-context="Content/Admin/Table">Used</translate></span> + <span v-else-if="moment().isAfter(scope.obj.expiration_date)" class="ui red basic label"><translate translate-context="Content/Admin/Table">Expired</translate></span> + <span v-else class="ui basic label"><translate translate-context="Content/Admin/Table">Not used</translate></span> </td> <td> <human-date :date="scope.obj.creation_date"></human-date> @@ -74,7 +74,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -150,7 +150,7 @@ export default { computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by username, e-mail address, code…') + searchPlaceholder: this.$pgettext('Content/Admin/Input.Placeholder/Verb', 'Search by username, e-mail address, code…') } }, actionFilters () { @@ -164,7 +164,7 @@ export default { } }, actions () { - let deleteLabel = this.$gettext('Delete') + let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete') return [ { name: 'delete', diff --git a/front/src/components/manage/users/UsersTable.vue b/front/src/components/manage/users/UsersTable.vue index d76634a331d186677db00c943e23483890739196..ee1d3110b2c800e7f1445718ff26f484af82b23e 100644 --- a/front/src/components/manage/users/UsersTable.vue +++ b/front/src/components/manage/users/UsersTable.vue @@ -3,11 +3,11 @@ <div class="ui inline form"> <div class="fields"> <div class="ui field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" /> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -15,10 +15,10 @@ </select> </div> <div class="field"> - <label><translate>Order</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> </div> @@ -35,13 +35,13 @@ :action-url="'manage/library/uploads/action/'" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Username</translate></th> - <th><translate>Email</translate></th> - <th><translate>Account status</translate></th> - <th><translate>Sign-up</translate></th> - <th><translate>Last activity</translate></th> - <th><translate>Permissions</translate></th> - <th><translate>Status</translate></th> + <th><translate translate-context="Content/*/*">Username</translate></th> + <th><translate translate-context="Content/*/*/Noun">Email</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Short, Noun">Account status</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Short, Noun (Value is a date)">Sign-up</translate></th> + <th><translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">Last activity</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun">Permissions</translate></th> + <th><translate translate-context="Content/Admin/Table.Label/Noun (Value is Regular user/Admin)">Status</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <td> @@ -51,15 +51,15 @@ <span>{{ scope.obj.email }}</span> </td> <td> - <span v-if="scope.obj.is_active" class="ui basic green label"><translate>Active</translate></span> - <span v-else class="ui basic grey label"><translate>Inactive</translate></span> + <span v-if="scope.obj.is_active" class="ui basic green label"><translate translate-context="Content/Admin/Table">Active</translate></span> + <span v-else class="ui basic grey label"><translate translate-context="Content/Admin/Table">Inactive</translate></span> </td> <td> <human-date :date="scope.obj.date_joined"></human-date> </td> <td> <human-date v-if="scope.obj.last_activity" :date="scope.obj.last_activity"></human-date> - <template v-else><translate>N/A</translate></template> + <template v-else><translate translate-context="*/*/*">N/A</translate></template> </td> <td> <template v-for="p in permissions"> @@ -67,9 +67,9 @@ </template> </td> <td> - <span v-if="scope.obj.is_superuser" class="ui pink label"><translate>Admin</translate></span> - <span v-else-if="scope.obj.is_staff" class="ui purple label"><translate>Staff member</translate></span> - <span v-else class="ui basic label"><translate>regular user</translate></span> + <span v-if="scope.obj.is_superuser" class="ui pink label"><translate translate-context="Content/Admin/Table.User role">Admin</translate></span> + <span v-else-if="scope.obj.is_staff" class="ui purple label"><translate translate-context="Content/Profile/User role">Staff member</translate></span> + <span v-else class="ui basic label"><translate translate-context="Content/Admin/Table, User role">Regular user</translate></span> </td> </template> </action-table> @@ -85,7 +85,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -160,7 +160,7 @@ export default { computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by username, e-mail address, name…') + searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by username, e-mail address, name…') } }, privacyLevels () { @@ -170,15 +170,15 @@ export default { return [ { 'code': 'library', - 'label': this.$gettext('Library') + 'label': this.$pgettext('*/*/*', 'Library') }, { 'code': 'moderation', - 'label': this.$gettext('Moderation') + 'label': this.$pgettext('*/Moderation/*', 'Moderation') }, { 'code': 'settings', - 'label': this.$gettext('Settings') + 'label': this.$pgettext('*/*/*/Noun', 'Settings') } ] }, @@ -196,7 +196,7 @@ export default { return [ // { // name: 'delete', - // label: this.$gettext('Delete'), + // label: this.$pgettext('Content/Admin/Button.Label/Verb', 'Delete'), // isDangerous: true // } ] diff --git a/front/src/components/metadata/ArtistCard.vue b/front/src/components/metadata/ArtistCard.vue index 98c35337ece4e0c093fc4d77fe0519b835f3b04a..531c2645c147c2504ea2856feb57923572da4bfe 100644 --- a/front/src/components/metadata/ArtistCard.vue +++ b/front/src/components/metadata/ArtistCard.vue @@ -46,7 +46,7 @@ export default Vue.extend({ computed: { labels() { return { - musicbrainz: this.$gettext("View on MusicBrainz") + musicbrainz: this.$pgettext('Content/*/*/Clickable, Verb', "View on MusicBrainz") } }, type() { diff --git a/front/src/components/metadata/ReleaseCard.vue b/front/src/components/metadata/ReleaseCard.vue index 0da2c7e2c64e4f9ec98dd38d829ef08271e778b7..08a0fe4a596f57e962b3be6525d0c2c19ecd4e4e 100644 --- a/front/src/components/metadata/ReleaseCard.vue +++ b/front/src/components/metadata/ReleaseCard.vue @@ -50,7 +50,7 @@ export default Vue.extend({ computed: { labels () { return { - musicbrainz: this.$gettext('View on MusicBrainz') + musicbrainz: this.$pgettext('Content/*/*/Clickable, Verb', 'View on MusicBrainz') } }, type () { diff --git a/front/src/components/metadata/Search.vue b/front/src/components/metadata/Search.vue index c06e2706297fb4da0960434ced776c8f79db3298..f7feb511f095741b77d02e2e45f4fe012173be53 100644 --- a/front/src/components/metadata/Search.vue +++ b/front/src/components/metadata/Search.vue @@ -111,7 +111,7 @@ export default { computed: { labels () { return { - placeholder: this.$gettext('Enter your search query…') + placeholder: this.$pgettext('Content/Library/Input.Placeholder/Verb', 'Enter your search query…') } }, currentTypeObject: function () { @@ -127,15 +127,15 @@ export default { return [ { value: 'artist', - label: this.$gettext('Artist') + label: this.$pgettext('*/*/*/Noun', 'Artist') }, { value: 'release', - label: this.$gettext('Album') + label: this.$pgettext('*/*/*', 'Album') }, { value: 'recording', - label: this.$gettext('Track') + label: this.$pgettext('*/*/*/Noun', 'Track') } ] } diff --git a/front/src/components/mixins/SmartSearch.vue b/front/src/components/mixins/SmartSearch.vue index 8b03becbbd8e7fb7b3bd38eaa673d9b01615a728..ef450603961a8cbdf61385d861698993a7ee5734 100644 --- a/front/src/components/mixins/SmartSearch.vue +++ b/front/src/components/mixins/SmartSearch.vue @@ -18,6 +18,7 @@ export default { return fallback }, addSearchToken (key, value) { + value = String(value) if (!value) { // we remove existing matching tokens, if any this.search.tokens = this.search.tokens.filter(t => { @@ -45,17 +46,19 @@ export default { }, 'search.tokens': { handler (newValue) { - this.search.query = compileTokens(newValue) - this.page = 1 - this.fetchData() + let newQuery = compileTokens(newValue) if (this.updateUrl) { let params = {} - if (this.search.query) { - params.q = this.search.query + if (newQuery) { + params.q = newQuery } this.$router.replace({ query: params }) + } else { + this.search.query = newQuery + this.page = 1 + this.fetchData() } }, deep: true diff --git a/front/src/components/mixins/Translations.vue b/front/src/components/mixins/Translations.vue index 9d237c9161bb14fb26339f18de51399ac924ac11..56ea3ed152642d6b5fa99e415d42625262571224 100644 --- a/front/src/components/mixins/Translations.vue +++ b/front/src/components/mixins/Translations.vue @@ -5,38 +5,106 @@ export default { return { fields: { privacy_level: { - label: this.$gettext('Activity visibility'), - help: this.$gettext('Determine the visibility level of your activity'), + label: this.$pgettext('Content/Settings/Dropdown.Label/Noun', 'Activity visibility'), + help: this.$pgettext('Content/Settings/Dropdown.Help text', 'Determine the visibility level of your activity'), choices: { - me: this.$gettext('Nobody except me'), - instance: this.$gettext('Everyone on this instance'), + me: this.$pgettext('Content/Settings/Dropdown', 'Nobody except me'), + instance: this.$pgettext('Content/Settings/Dropdown', 'Everyone on this instance'), + everyone: this.$pgettext('Content/Settings/Dropdown', 'Everyone, across all instances'), + }, + shortChoices: { + me: this.$pgettext('Content/Settings/Dropdown/Short', 'Private'), + instance: this.$pgettext('Content/Settings/Dropdown/Short', 'Instance'), + everyone: this.$pgettext('Content/Settings/Dropdown/Short', 'Everyone'), } - } + }, + import_status: { + detailTitle: this.$pgettext('Content/Library/Link.Title', 'Click to display more information about the import process for this upload'), + choices: { + skipped: { + label: this.$pgettext('Content/Library/*', 'Skipped'), + help: this.$pgettext('Content/Library/Help text', 'This track is already present in one of your libraries'), + }, + pending: { + label: this.$pgettext('Content/Library/*/Short', 'Pending'), + help: this.$pgettext('Content/Library/Help text', 'This track has been uploaded, but hasn\'t been processed by the server yet'), + }, + errored: { + label: this.$pgettext('Content/Library/Table/Short', 'Errored'), + help: this.$pgettext('Content/Library/Help text', 'This track could not be processed, please it is tagged correctly'), + }, + finished: { + label: this.$pgettext('Content/Library/*', 'Finished'), + help: this.$pgettext('Content/Library/Help text', 'Imported'), + }, + } + }, }, filters: { - creation_date: this.$gettext('Creation date'), - first_seen: this.$gettext('First seen date'), - last_seen: this.$gettext('Last seen date'), - accessed_date: this.$gettext('Accessed date'), - modification_date: this.$gettext('Modification date'), - imported_date: this.$gettext('Imported date'), - expiration_date: this.$gettext('Expiration date'), - track_title: this.$gettext('Track name'), - album_title: this.$gettext('Album name'), - artist_name: this.$gettext('Artist name'), - name: this.$gettext('Name'), - title: this.$gettext('Title'), - size: this.$gettext('Size'), - bitrate: this.$gettext('Bitrate'), - duration: this.$gettext('Duration'), - date_joined: this.$gettext('Sign-up date'), - last_activity: this.$gettext('Last activity'), - username: this.$gettext('Username'), - domain: this.$gettext('Domain'), - users: this.$gettext('Users'), - received_messages: this.$gettext('Received messages'), - uploads: this.$gettext('Uploads'), - followers: this.$gettext('Followers'), + creation_date: this.$pgettext('Content/*/*/Noun', 'Creation date'), + release_date: this.$pgettext('Content/*/*/Noun', 'Release date'), + accessed_date: this.$pgettext('Content/*/*/Noun', 'Accessed date'), + first_seen: this.$pgettext('Content/Moderation/Dropdown/Noun', 'First seen date'), + last_seen: this.$pgettext('Content/Moderation/Dropdown/Noun', 'Last seen date'), + modification_date: this.$pgettext('Content/Playlist/Dropdown/Noun', 'Modification date'), + expiration_date: this.$pgettext('Content/Admin/Table.Label/Noun', 'Expiration date'), + track_title: this.$pgettext('Content/*/Dropdown/Noun', 'Track name'), + album_title: this.$pgettext('Content/*/Dropdown/Noun', 'Album name'), + artist_name: this.$pgettext('Content/*/Dropdown/Noun', 'Artist name'), + name: this.$pgettext('*/*/*/Noun', 'Name'), + size: this.$pgettext('Content/Library/*/in MB', 'Size'), + bitrate: this.$pgettext('Content/Track/*/Noun', 'Bitrate'), + duration: this.$pgettext('Content/*/*', 'Duration'), + date_joined: this.$pgettext('Content/Admin/Table.Label/Noun', 'Sign-up date'), + last_activity: this.$pgettext('Content/Profile/Table.Label/Short, Noun (Value is a date)', 'Last activity'), + username: this.$pgettext('Content/*/*', 'Username'), + domain: this.$pgettext('Content/Moderation/*/Noun', 'Domain'), + users: this.$pgettext('*/*/*/Noun', 'Users'), + received_messages: this.$pgettext('Content/Moderation/*/Noun', 'Received messages'), + uploads: this.$pgettext('Content/Moderation/Table.Label/Noun', 'Uploads'), + followers: this.$pgettext('Content/Federation/*/Noun', 'Followers'), + }, + scopes: { + profile: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Profile'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to email, username, and profile information'), + }, + libraries: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Libraries and uploads'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to audio files, libraries, artists, albums and tracks'), + }, + favorites: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Favorites'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to favorites'), + }, + listenings: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Listenings'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to listening history'), + }, + follows: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Follows'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to follows'), + }, + playlists: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Playlists'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to playlists'), + }, + radios: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Radios'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to radios'), + }, + filters: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Content filters'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to content filters'), + }, + notifications: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Notifications'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to notifications'), + }, + edits: { + label: this.$pgettext('Content/OAuth Scopes/Label', 'Edits'), + description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to edits'), + } } } } diff --git a/front/src/components/moderation/FilterModal.vue b/front/src/components/moderation/FilterModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..8885e61fc08f6f9a5dde2787c1ec77ad56afa2fe --- /dev/null +++ b/front/src/components/moderation/FilterModal.vue @@ -0,0 +1,108 @@ +<template> + <modal @update:show="update" :show="$store.state.moderation.showFilterModal"> + <div class="header"> + <translate + v-if="type === 'artist'" + key="1" + translate-context="Popup/Moderation/Title/Verb" + :translate-params="{name: target.name}">Do you want to hide content from artist "%{ name }"?</translate> + </div> + <div class="scrolling content"> + <div class="description"> + + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Popup/Moderation/Error message">Error while creating filter</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <template v-if="type === 'artist'"> + <p> + <translate translate-context="Popup/Moderation/Paragraph"> + You will not see tracks, albums and user activity linked to this artist anymore: + </translate> + </p> + <ul> + <li><translate translate-context="Popup/Moderation/List item">In other users favorites and listening history</translate></li> + <li><translate translate-context="Popup/Moderation/List item">In "Recently added" widget</translate></li> + <li><translate translate-context="Popup/Moderation/List item">In artists and album listings</translate></li> + <li><translate translate-context="Popup/Moderation/List item">In radio suggestions</translate></li> + </ul> + <p> + <translate translate-context="Popup/Moderation/Paragraph"> + You can manage and update your filters anytime from your account settings. + </translate> + </p> + </template> + </div> + </div> + <div class="actions"> + <div class="ui cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></div> + <div :class="['ui', 'green', {loading: isLoading}, 'button']" @click="hide"><translate translate-context="Popup/*/Button.Label">Hide content</translate></div> + </div> + </modal> +</template> + +<script> +import _ from '@/lodash' +import axios from 'axios' +import {mapState} from 'vuex' + +import logger from '@/logging' +import Modal from '@/components/semantic/Modal' + +export default { + components: { + Modal, + }, + data () { + return { + formKey: String(new Date()), + errors: [], + isLoading: false + } + }, + computed: { + ...mapState({ + type: state => state.moderation.filterModalTarget.type, + target: state => state.moderation.filterModalTarget.target, + }) + }, + methods: { + update (v) { + this.$store.commit('moderation/showFilterModal', v) + this.errors = [] + }, + hide () { + let self = this + self.isLoading = true + let payload = { + target: { + type: this.type, + id: this.target.id, + } + } + return axios.post('moderation/content-filters/', payload).then(response => { + logger.default.info('Successfully added track to playlist') + self.update(false) + self.$store.commit('moderation/lastUpdate', new Date()) + self.isLoading = false + let msg = this.$pgettext('*/Moderation/Message', 'Content filter successfully added') + self.$store.commit('moderation/contentFilter', response.data) + self.$store.commit('ui/addMessage', { + content: msg, + date: new Date() + }) + }, error => { + logger.default.error(`Error while hiding ${self.type} ${self.target.id}`) + self.errors = error.backendErrors + self.isLoading = false + }) + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/notifications/NotificationRow.vue b/front/src/components/notifications/NotificationRow.vue index 530e1527063023f9e305a13d08e7d070e904f442..b8e145647d03340fb62b8edc981f25179f341a9a 100644 --- a/front/src/components/notifications/NotificationRow.vue +++ b/front/src/components/notifications/NotificationRow.vue @@ -36,15 +36,15 @@ export default { return 'plop' }, labels () { - let libraryFollowMessage = this.$gettext('%{ username } followed your library "%{ library }"') - let libraryAcceptFollowMessage = this.$gettext('%{ username } accepted your follow on library "%{ library }"') - let libraryPendingFollowMessage = this.$gettext('%{ username } wants to follow your library "%{ library }"') + let libraryFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } followed your library "%{ library }"') + let libraryAcceptFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } accepted your follow on library "%{ library }"') + let libraryPendingFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } wants to follow your library "%{ library }"') return { libraryFollowMessage, libraryAcceptFollowMessage, - libraryPendingFollowMessage, - markRead: this.$gettext('Mark as read'), - markUnread: this.$gettext('Mark as unread'), + libraryPendingFollowMessage, + markRead: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as read'), + markUnread: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as unread'), } }, @@ -57,18 +57,18 @@ export default { if (a.type === 'Follow') { if (a.object && a.object.type === 'music.Library') { let action = null - let message = null + let message = null if (!a.related_object.approved) { - message = this.labels.libraryPendingFollowMessage + message = this.labels.libraryPendingFollowMessage action = { buttonClass: 'green', icon: 'check', - label: this.$gettext('Approve'), + label: this.$pgettext('Content/*/Button.Label/Verb', 'Approve'), handler: () => { self.approveLibraryFollow(a.related_object) } } - } else { - message = this.labels.libraryFollowMessage - } + } else { + message = this.labels.libraryFollowMessage + } return { action, detailUrl: {name: 'content.libraries.detail', params: {id: a.object.uuid}}, diff --git a/front/src/components/playlists/Card.vue b/front/src/components/playlists/Card.vue index 344eb5fbf9ecbc17e7d744d85cdb4efdf2b3d17e..60a322ab4c86486842d79f176b6b536b6714041e 100644 --- a/front/src/components/playlists/Card.vue +++ b/front/src/components/playlists/Card.vue @@ -16,7 +16,7 @@ <duration :seconds="playlist.duration" /> | <i class="sound icon"></i> - <translate + <translate translate-context="Content/*/Card/List item" translate-plural="%{ count } tracks" :translate-n="playlist.tracks_count" :translate-params="{count: playlist.tracks_count}"> diff --git a/front/src/components/playlists/Editor.vue b/front/src/components/playlists/Editor.vue index e42c60dae17086f6683c7b2552975e429dd9dc7e..3ce554301901df62fb142b348edb51ea2d17e0f5 100644 --- a/front/src/components/playlists/Editor.vue +++ b/front/src/components/playlists/Editor.vue @@ -2,34 +2,44 @@ <div class="ui text container"> <playlist-form @updated="$emit('playlist-updated', $event)" :title="false" :playlist="playlist"></playlist-form> <h3 class="ui top attached header"> - <translate>Playlist editor</translate> + <translate translate-context="Content/Playlist/Title">Playlist editor</translate> </h3> <div class="ui attached segment"> <template v-if="status === 'loading'"> <div class="ui active tiny inline loader"></div> - <translate>Syncing changes to server…</translate> + <translate translate-context="Content/Playlist/Paragraph">Syncing changes to server…</translate> </template> <template v-else-if="status === 'errored'"> <i class="red close icon"></i> - <translate>An error occured while saving your changes</translate> + <translate translate-context="Content/Playlist/Error message.Title">An error occured while saving your changes</translate> <div v-if="errors.length > 0" class="ui negative message"> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> </template> + <div v-else-if="status === 'confirmDuplicateAdd'" class="ui warning message"> + <p translate-context="Content/Playlist/Paragraph" + v-translate="{playlist: playlist.name}">Some tracks in your queue are already in this playlist:</p> + <ul id="duplicateTrackList" class="ui relaxed divided list"> + <li v-for="track in duplicateTrackAddInfo.tracks" class="ui item">{{ track }}</li> + </ul> + <button + class="ui small green button" + @click="insertMany(queueTracks, true)"><translate translate-context="*/Playlist/Button.Label/Verb">Add anyways</translate></button> + </div> <template v-else-if="status === 'saved'"> - <i class="green check icon"></i> <translate>Changes synced with server</translate> + <i class="green check icon"></i> <translate translate-context="Content/Playlist/Paragraph">Changes synced with server</translate> </template> </div> <div class="ui bottom attached segment"> <div - @click="insertMany(queueTracks)" + @click="insertMany(queueTracks, false)" :disabled="queueTracks.length === 0" :class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']" :title="labels.copyTitle"> <i class="plus icon"></i> - <translate + <translate translate-context="Content/Playlist/Button.Label/Verb" translate-plural="Insert from queue (%{ count } tracks)" :translate-n="queueTracks.length" :translate-params="{count: queueTracks.length}"> @@ -38,34 +48,37 @@ </div> <dangerous-button :disabled="plts.length === 0" class="labeled right floated icon" color='yellow' :action="clearPlaylist"> - <i class="eraser icon"></i> <translate>Clear playlist</translate> - <p slot="modal-header" v-translate="{playlist: playlist.name}" :translate-params="{playlist: playlist.name}"> + <i class="eraser icon"></i> <translate translate-context="*/Playlist/Button.Label/Verb">Clear playlist</translate> + <p slot="modal-header" v-translate="{playlist: playlist.name}" translate-context="Popup/Playlist/Title" :translate-params="{playlist: playlist.name}"> Do you want to clear the playlist "%{ playlist }"? </p> - <p slot="modal-content"><translate>This will remove all tracks from this playlist and cannot be undone.</translate></p> - <p slot="modal-confirm"><translate>Clear playlist</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Playlist/Paragraph">This will remove all tracks from this playlist and cannot be undone.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/Playlist/Button.Label/Verb">Clear playlist</translate></div> </dangerous-button> <div class="ui hidden divider"></div> <template v-if="plts.length > 0"> - <p><translate>Drag and drop rows to reorder tracks in the playlist</translate></p> - <table class="ui compact very basic fixed single line unstackable table"> - <draggable v-model="plts" element="tbody" @update="reorder"> - <tr v-for="(plt, index) in plts" :key="plt.id"> - <td class="left aligned">{{ plt.index + 1}}</td> - <td class="center aligned"> - <img class="ui mini image" v-if="plt.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.small_square_crop)"> - <img class="ui mini image" v-else src="../../assets/audio/default-cover.png"> - </td> - <td colspan="4"> - <strong>{{ plt.track.title }}</strong><br /> - {{ plt.track.artist.name }} - </td> - <td class="right aligned"> - <i @click.stop="removePlt(index)" class="circular red trash icon"></i> - </td> - </tr> - </draggable> - </table> + <p><translate translate-context="Content/Playlist/Paragraph/Call to action">Drag and drop rows to reorder tracks in the playlist</translate></p> + <div class="table-wrapper"> + <table class="ui compact very basic unstackable table"> + <draggable v-model="plts" element="tbody" @update="reorder"> + <tr v-for="(plt, index) in plts" :key="plt.id"> + <td class="left aligned">{{ plt.index + 1}}</td> + <td class="center aligned"> + <img class="ui mini image" v-if="plt.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.small_square_crop)"> + <img class="ui mini image" v-else src="../../assets/audio/default-cover.png"> + </td> + <td colspan="4"> + <strong>{{ plt.track.title }}</strong><br /> + {{ plt.track.artist.name }} + </td> + <td class="right aligned"> + <i @click.stop="removePlt(index)" class="circular red trash icon"></i> + </td> + </tr> + </draggable> + </table> + + </div> </template> </div> </div> @@ -88,17 +101,25 @@ export default { return { plts: this.playlistTracks, isLoading: false, - errors: [] + errors: [], + duplicateTrackAddInfo: {}, + showDuplicateTrackAddConfirmation: false } }, methods: { success () { this.isLoading = false this.errors = [] + this.showDuplicateTrackAddConfirmation = false }, errored (errors) { this.isLoading = false - this.errors = errors + if (errors.length == 1 && errors[0].code == 'tracks_already_exist_in_playlist') { + this.duplicateTrackAddInfo = errors[0] + this.showDuplicateTrackAddConfirmation = true + } else { + this.errors = errors + } }, reorder ({oldIndex, newIndex}) { let self = this @@ -136,21 +157,31 @@ export default { self.errored(error.backendErrors) }) }, - insertMany (tracks) { + insertMany (tracks, allowDuplicates) { let self = this let ids = tracks.map(t => { return t.id }) + let payload = { + tracks: ids, + allow_duplicates: allowDuplicates + } self.isLoading = true let url = 'playlists/' + this.playlist.id + '/add/' - axios.post(url, {tracks: ids}).then((response) => { + axios.post(url, payload).then((response) => { response.data.results.forEach(r => { self.plts.push(r) }) self.success() self.$store.dispatch('playlists/fetchOwn') }, error => { - self.errored(error.backendErrors) + // if backendErrors isn't populated (e.g. duplicate track exceptions raised by + // the playlist model), read directly from the response + if (error.rawPayload.playlist) { + self.errored(error.rawPayload.playlist.non_field_errors) + } else { + self.errored(error.backendErrors) + } }) } }, @@ -160,7 +191,7 @@ export default { }), labels () { return { - copyTitle: this.$gettext('Copy queued tracks to playlist') + copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy queued tracks to playlist') } }, status () { @@ -170,6 +201,9 @@ export default { if (this.errors.length > 0) { return 'errored' } + if (this.showDuplicateTrackAddConfirmation) { + return 'confirmDuplicateAdd' + } return 'saved' } }, @@ -189,4 +223,8 @@ export default { <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> +#duplicateTrackList { + max-height: 10em; + overflow-y: auto; +} </style> diff --git a/front/src/components/playlists/Form.vue b/front/src/components/playlists/Form.vue index 1fe9740654192adcdbbc22872dbae38217d976d7..4fc905fc6aeb66427771c75164ff5fba519b387f 100644 --- a/front/src/components/playlists/Form.vue +++ b/front/src/components/playlists/Form.vue @@ -1,29 +1,29 @@ <template> <form class="ui form" @submit.prevent="submit()"> - <h4 v-if="title" class="ui header"><translate>Create a new playlist</translate></h4> + <h4 v-if="title" class="ui header"><translate translate-context="Popup/Playlist/Title/Verb">Create a new playlist</translate></h4> <div v-if="success" class="ui positive message"> <div class="header"> <template v-if="playlist"> - <translate>Playlist updated</translate> + <translate translate-context="Content/Playlist/Message">Playlist updated</translate> </template> <template v-else> - <translate>Playlist created</translate> + <translate translate-context="Content/Playlist/Message">Playlist created</translate> </template> </div> </div> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>We cannot create the playlist</translate></div> + <div class="header"><translate translate-context="Content/Playlist/Error message.Title">The playlist could not be created</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="three fields"> <div class="field"> - <label><translate>Playlist name</translate></label> + <label><translate translate-context="Content/Playlist/Input.Label">Playlist name</translate></label> <input name="name" v-model="name" required type="text" :placeholder="labels.placeholder" /> </div> <div class="field"> - <label><translate>Playlist visibility</translate></label> + <label><translate translate-context="Content/Playlist/Dropdown.Label">Playlist visibility</translate></label> <select class="ui dropdown" v-model="privacyLevel"> <option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option> </select> @@ -31,8 +31,8 @@ <div class="field"> <label> </label> <button :class="['ui', 'fluid', {'loading': isLoading}, 'button']" type="submit"> - <template v-if="playlist"><translate>Update playlist</translate></template> - <template v-else><translate>Create playlist</translate></template> + <template v-if="playlist"><translate translate-context="Content/Playlist/Button.Label/Verb">Update playlist</translate></template> + <template v-else><translate translate-context="Content/Playlist/Button.Label/Verb">Create playlist</translate></template> </button> </div> </div> @@ -42,10 +42,12 @@ <script> import $ from 'jquery' import axios from 'axios' +import TranslationsMixin from "@/components/mixins/Translations" import logger from '@/logging' export default { + mixins: [TranslationsMixin], props: { title: {type: Boolean, default: true}, playlist: {type: Object, default: null} @@ -71,22 +73,22 @@ export default { computed: { labels () { return { - placeholder: this.$gettext('My awesome playlist') + placeholder: this.$pgettext('Content/Playlist/Input.Placeholder', 'My awesome playlist') } }, privacyLevelChoices: function () { return [ { value: 'me', - label: this.$gettext('Nobody except me') + label: this.sharedLabels.fields.privacy_level.choices['me'] }, { value: 'instance', - label: this.$gettext('Everyone on this instance') + label: this.sharedLabels.fields.privacy_level.choices['instance'] }, { value: 'everyone', - label: this.$gettext('Everyone') + label: this.sharedLabels.fields.privacy_level.choices['everyone'] } ] } diff --git a/front/src/components/playlists/PlaylistModal.vue b/front/src/components/playlists/PlaylistModal.vue index 7d44f0e191d5a55afaf645e5fa5181dfd329f5f5..44969afbfd8860c565173292c8eb338178869173 100644 --- a/front/src/components/playlists/PlaylistModal.vue +++ b/front/src/components/playlists/PlaylistModal.vue @@ -1,13 +1,14 @@ <template> <modal @update:show="update" :show="$store.state.playlists.showModal"> <div class="header"> - <translate>Manage playlists</translate> + <translate translate-context="Popup/Playlist/Title/Verb">Manage playlists</translate> </div> <div class="scrolling content"> <div class="description"> <template v-if="track"> - <h4 class="ui header"><translate>Current track</translate></h4> + <h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Current track</translate></h4> <span + translate-context="Popup/Playlist/Paragraph" v-translate="{artist: track.artist.name, title: track.title}" :translate-params="{artist: track.artist.name, title: track.title}"> "%{ title }", by %{ artist } @@ -17,21 +18,34 @@ <playlist-form :key="formKey"></playlist-form> <div class="ui divider"></div> + <div v-if="showDuplicateTrackAddConfirmation" class="ui warning message"> + <p translate-context="Popup/Playlist/Paragraph" + v-translate="{track: track.title, playlist: duplicateTrackAddInfo.playlist_name}" + :translate-params="{track: track.title, playlist: duplicateTrackAddInfo.playlist_name}"><strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>.</p> + <button + @click="update(false)" + class="ui small cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate> + </button> + <button + class="ui small green button" + @click="addToPlaylist(lastSelectedPlaylist, true)"> + <translate translate-context="*/Playlist/Button.Label/Verb">Add anyways</translate></button> + </div> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>We cannot add the track to a playlist</translate></div> + <div class="header"><translate translate-context="Popup/Playlist/Error message.Title">The track can't be added to a playlist</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> </div> - <h4 class="ui header"><translate>Available playlists</translate></h4> + <h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Available playlists</translate></h4> <table class="ui unstackable very basic table"> <thead> <tr> <th></th> - <th><translate>Name</translate></th> - <th class="sorted descending"><translate>Last modification</translate></th> - <th><translate>Tracks</translate></th> + <th><translate translate-context="*/*/*/Noun">Name</translate></th> + <th class="sorted descending"><translate translate-context="Popup/Playlist/Table.Label/Short">Last modification</translate></th> + <th><translate translate-context="*/*/*/Noun">Tracks</translate></th> <th></th> </tr> </thead> @@ -51,8 +65,8 @@ v-if="track" class="ui green icon basic small right floated button" :title="labels.addToPlaylist" - @click="addToPlaylist(playlist.id)"> - <i class="plus icon"></i> <translate>Add track</translate> + @click="addToPlaylist(playlist.id, false)"> + <i class="plus icon"></i> <translate translate-context="Popup/Playlist/Table.Button.Label/Verb">Add track</translate> </div> </td> </tr> @@ -61,7 +75,7 @@ </div> </div> <div class="actions"> - <div class="ui cancel button"><translate>Cancel</translate></div> + <div class="ui cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></div> </div> </modal> </template> @@ -83,26 +97,38 @@ export default { data () { return { formKey: String(new Date()), - errors: [] + errors: [], + duplicateTrackAddInfo: {}, + showDuplicateTrackAddConfirmation: false, + lastSelectedPlaylist: -1, } }, methods: { update (v) { this.$store.commit('playlists/showModal', v) }, - addToPlaylist (playlistId) { + addToPlaylist (playlistId, allowDuplicate) { let self = this let payload = { track: this.track.id, - playlist: playlistId + playlist: playlistId, + allow_duplicates: allowDuplicate } + + self.lastSelectedPlaylist = playlistId + return axios.post('playlist-tracks/', payload).then(response => { logger.default.info('Successfully added track to playlist') self.update(false) self.$store.dispatch('playlists/fetchOwn') }, error => { - logger.default.error('Error while adding track to playlist') - self.errors = error.backendErrors + if (error.backendErrors.length == 1 && error.backendErrors[0].code == 'tracks_already_exist_in_playlist') { + self.duplicateTrackAddInfo = error.backendErrors[0] + self.showDuplicateTrackAddConfirmation = true + } else { + self.errors = error.backendErrors + self.showDuplicateTrackAddConfirmation = false + } }) } }, @@ -113,7 +139,7 @@ export default { }), labels () { return { - addToPlaylist: this.$gettext('Add to this playlist') + addToPlaylist: this.$pgettext('Popup/Playlist/Table.Button.Tooltip/Verb', 'Add to this playlist') } }, sortedPlaylists () { @@ -125,9 +151,11 @@ export default { watch: { '$store.state.route.path' () { this.$store.commit('playlists/showModal', false) + this.showDuplicateTrackAddConfirmation = false }, '$store.state.playlists.showModal' () { this.formKey = String(new Date()) + this.showDuplicateTrackAddConfirmation = false } } } diff --git a/front/src/components/playlists/TrackPlaylistIcon.vue b/front/src/components/playlists/TrackPlaylistIcon.vue index 8487e6777b23c181749e8b8764fdc79bfeb7dc61..2f57eb163ab3c698bc4695ed69a1d2644a930e8b 100644 --- a/front/src/components/playlists/TrackPlaylistIcon.vue +++ b/front/src/components/playlists/TrackPlaylistIcon.vue @@ -2,9 +2,9 @@ <button @click="$store.commit('playlists/chooseTrack', track)" v-if="button" - :class="['ui', 'button']"> + :class="['ui', 'icon', 'labeled', 'button']"> <i class="list icon"></i> - <translate>Add to playlist…</translate> + <translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate> </button> <button v-else @@ -31,7 +31,7 @@ export default { computed: { labels () { return { - addToPlaylist: this.$gettext('Add to playlist…') + addToPlaylist: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…') } } } diff --git a/front/src/components/playlists/Widget.vue b/front/src/components/playlists/Widget.vue index 7329c502ec27f0f1f55cf41abea6636c9cf53ea7..b018f9731e97b307df3ded23a5d1660c6eba9ea9 100644 --- a/front/src/components/playlists/Widget.vue +++ b/front/src/components/playlists/Widget.vue @@ -6,7 +6,6 @@ <button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle up', 'icon']"></i></button> <button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle down', 'icon']"></i></button> <button @click="fetchData(url)" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button> - <div v-if="isLoading" class="ui inverted active dimmer"> <div class="ui loader"></div> </div> @@ -71,6 +70,9 @@ export default { watch: { offset () { this.fetchData() + }, + "$store.state.moderation.lastUpdate": function () { + this.fetchData(this.url) } } } diff --git a/front/src/components/radios/Button.vue b/front/src/components/radios/Button.vue index a33478c9d1e9e8932ffbb6781e3c5d7a8f4bdaa9..e66f97ad5fafe1622e6a586c8e2d88155e87a766 100644 --- a/front/src/components/radios/Button.vue +++ b/front/src/components/radios/Button.vue @@ -1,9 +1,8 @@ <template> - <button @click="toggleRadio" :class="['ui', 'blue', {'inverted': running}, 'button']"> + <button @click="toggleRadio" :class="['ui', 'blue', {'inverted': running}, 'icon', 'labeled', 'button']"> <i class="ui feed icon"></i> - <template v-if="running"><translate>Stop</translate></template> - <template v-else><translate>Start</translate></template> - radio + <template v-if="running"><translate translate-context="*/Player/Button.Label/Short, Verb">Stop radio</translate></template> + <template v-else><translate translate-context="*/Queue/Button.Label/Short, Verb">Start radio</translate></template> </button> </template> diff --git a/front/src/components/radios/Card.vue b/front/src/components/radios/Card.vue index ae0ae20960e36e4f076b1da6c50fc4b3b7a9fa58..e72b9f1c1ca65cf6fd10020fe1b0b720de5a49f9 100644 --- a/front/src/components/radios/Card.vue +++ b/front/src/components/radios/Card.vue @@ -15,12 +15,13 @@ </div> <div class="extra content"> <user-link v-if="radio.user" :user="radio.user" class="left floated" /> + <div class="ui hidden divider"></div> <radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId"></radio-button> <router-link class="ui basic yellow button right floated" v-if="$store.state.auth.authenticated && type === 'custom' && radio.user.id === $store.state.auth.profile.id" :to="{name: 'library.radios.edit', params: {id: customRadioId }}"> - <translate>Edit…</translate> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> </router-link> </div> </div> diff --git a/front/src/components/semantic/Modal.vue b/front/src/components/semantic/Modal.vue index fec8fdd0595ffd8866d93e54bd2cffaedd2f52d5..076b3e466dac8b487924d06f0ae24d4d04a0b854 100644 --- a/front/src/components/semantic/Modal.vue +++ b/front/src/components/semantic/Modal.vue @@ -21,8 +21,9 @@ export default { }, beforeDestroy () { if (this.control) { - this.control.remove() + $(this.$el).modal('hide') } + $(this.$el).remove() }, methods: { initModal () { @@ -61,5 +62,4 @@ export default { <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped lang="scss"> - </style> diff --git a/front/src/edits.js b/front/src/edits.js new file mode 100644 index 0000000000000000000000000000000000000000..a53ab2fcc286d31815b81b99cb4a161a53416c4a --- /dev/null +++ b/front/src/edits.js @@ -0,0 +1,117 @@ +export default { + getConfigs () { + return { + artist: { + fields: [ + { + id: 'name', + type: 'text', + required: true, + label: this.$pgettext('*/*/*/Noun', 'Name'), + getValue: (obj) => { return obj.name } + }, + ] + }, + album: { + fields: [ + { + id: 'title', + type: 'text', + required: true, + label: this.$pgettext('*/*/*/Noun', 'Title'), + getValue: (obj) => { return obj.title } + }, + { + id: 'release_date', + type: 'text', + required: false, + label: this.$pgettext('Content/*/*/Noun', 'Release date'), + getValue: (obj) => { return obj.release_date } + }, + ] + }, + track: { + fields: [ + { + id: 'title', + type: 'text', + required: true, + label: this.$pgettext('*/*/*/Noun', 'Title'), + getValue: (obj) => { return obj.title } + }, + { + id: 'position', + type: 'text', + inputType: 'number', + required: false, + label: this.$pgettext('*/*/*/Short, Noun', 'Position'), + getValue: (obj) => { return obj.position } + }, + { + id: 'copyright', + type: 'text', + required: false, + label: this.$pgettext('Content/Track/*/Noun', 'Copyright'), + getValue: (obj) => { return obj.copyright } + }, + { + id: 'license', + type: 'license', + required: false, + label: this.$pgettext('Content/*/*/Noun', 'License'), + getValue: (obj) => { return obj.license }, + }, + ] + } + } + }, + + getConfig () { + return this.configs[this.objectType] + }, + + getCurrentState () { + let self = this + let s = {} + this.config.fields.forEach(f => { + s[f.id] = {value: f.getValue(self.object)} + }) + return s + }, + getCurrentStateForObj (obj, config) { + let s = {} + config.fields.forEach(f => { + s[f.id] = {value: f.getValue(obj)} + }) + return s + }, + + getCanDelete () { + if (this.obj.is_applied || this.obj.is_approved) { + return false + } + if (!this.$store.state.auth.authenticated) { + return false + } + return ( + this.obj.created_by.full_username === this.$store.state.auth.fullUsername + || this.$store.state.auth.availablePermissions['library'] + ) + }, + getCanApprove () { + if (this.obj.is_applied) { + return false + } + if (!this.$store.state.auth.authenticated) { + return false + } + return this.$store.state.auth.availablePermissions['library'] + }, + getCanEdit () { + if (!this.$store.state.auth.authenticated) { + return false + } + return this.$store.state.auth.availablePermissions['library'] + }, + +} diff --git a/front/src/filters.js b/front/src/filters.js index 1edea76f6afb6b8f929b7ab0566b15d4f1813e75..9667426191e6fae0aa76ba9eba47dcad023730fe 100644 --- a/front/src/filters.js +++ b/front/src/filters.js @@ -2,13 +2,24 @@ import Vue from 'vue' import moment from 'moment' -export function truncate (str, max, ellipsis) { +export function truncate (str, max, ellipsis, middle) { max = max || 100 ellipsis = ellipsis || '…' if (str.length <= max) { return str } - return str.slice(0, max) + ellipsis + if (middle) { + var sepLen = 1, + charsToShow = max - sepLen, + frontChars = Math.ceil(charsToShow/2), + backChars = Math.floor(charsToShow/2); + + return str.substr(0, frontChars) + + ellipsis + + str.substr(str.length - backChars); + } else { + return str.slice(0, max) + ellipsis + } } Vue.filter('truncate', truncate) diff --git a/front/src/lodash.js b/front/src/lodash.js index 91e1a0eac30bdf5104a18913578bafcb35298fa6..8cd3ed92f41e86b0eff9b88bd5af923742b1864e 100644 --- a/front/src/lodash.js +++ b/front/src/lodash.js @@ -10,4 +10,6 @@ export default { sortBy: require('lodash/sortBy'), throttle: require('lodash/throttle'), uniq: require('lodash/uniq'), + remove: require('lodash/remove'), + reverse: require('lodash/reverse'), } diff --git a/front/src/main.js b/front/src/main.js index 9f058d8ecbef6491660647a67e815d0b5beb8f62..dd755dee356f427a58d3ceb89b6e0c5e5419d35f 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -4,6 +4,7 @@ import logger from '@/logging' logger.default.info('Loading environment:', process.env.NODE_ENV) logger.default.debug('Environment variables:', process.env) +import jQuery from "jquery" import Vue from 'vue' import App from './App' @@ -58,16 +59,13 @@ Vue.use(VueMasonryPlugin) Vue.use(VueLazyload) Vue.config.productionTip = false Vue.directive('title', function (el, binding) { - let parts = [] - let instanceName = store.state.instance.settings.instance.name.value - if (instanceName.length === 0) { - instanceName = 'Funkwhale' - } - parts.unshift(instanceName) - parts.unshift(binding.value) - document.title = parts.join(' - ') - } -) + store.commit('ui/pageTitle', binding.value) +}) +Vue.directive('dropdown', function (el, binding) { + jQuery(el).dropdown({ + selectOnKeydown: false, + }) +}) axios.interceptors.request.use(function (config) { // Do something before request is sent if (store.state.auth.token) { @@ -99,8 +97,11 @@ axios.interceptors.response.use(function (response) { if (error.response.data.detail) { error.backendErrors.push(error.response.data.detail) } else { + error.rawPayload = error.response.data for (var field in error.response.data) { - if (error.response.data.hasOwnProperty(field)) { + // some views (e.g. v1/playlists/{id}/add) have deeper nested data (e.g. data[field] + // is another object), so don't try to unpack non-array fields + if (error.response.data.hasOwnProperty(field) && error.response.data[field].forEach) { error.response.data[field].forEach(e => { error.backendErrors.push(e) }) diff --git a/front/src/router/index.js b/front/src/router/index.js index 1b60a681331fe0e0c9e58691eec1b305658fb35a..182cf61b0714f6f2feecc529526b314d475ccab3 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -1,47 +1,5 @@ import Vue from 'vue' import Router from 'vue-router' -import PageNotFound from '@/components/PageNotFound' -import About from '@/components/About' -import Home from '@/components/Home' -import Login from '@/components/auth/Login' -import Signup from '@/components/auth/Signup' -import Profile from '@/components/auth/Profile' -import Settings from '@/components/auth/Settings' -import Logout from '@/components/auth/Logout' -import PasswordReset from '@/views/auth/PasswordReset' -import PasswordResetConfirm from '@/views/auth/PasswordResetConfirm' -import EmailConfirm from '@/views/auth/EmailConfirm' -import Library from '@/components/library/Library' -import LibraryHome from '@/components/library/Home' -import LibraryArtist from '@/components/library/Artist' -import LibraryArtists from '@/components/library/Artists' -import LibraryAlbum from '@/components/library/Album' -import LibraryTrack from '@/components/library/Track' -import LibraryRadios from '@/components/library/Radios' -import RadioBuilder from '@/components/library/radios/Builder' -import RadioDetail from '@/views/radios/Detail' -import PlaylistDetail from '@/views/playlists/Detail' -import PlaylistList from '@/views/playlists/List' -import Favorites from '@/components/favorites/List' -import AdminSettings from '@/views/admin/Settings' -import AdminLibraryBase from '@/views/admin/library/Base' -import AdminLibraryFilesList from '@/views/admin/library/FilesList' -import AdminUsersBase from '@/views/admin/users/Base' -import AdminUsersList from '@/views/admin/users/UsersList' -import AdminInvitationsList from '@/views/admin/users/InvitationsList' -import AdminModerationBase from '@/views/admin/moderation/Base' -import AdminDomainsList from '@/views/admin/moderation/DomainsList' -import AdminDomainsDetail from '@/views/admin/moderation/DomainsDetail' -import AdminAccountsList from '@/views/admin/moderation/AccountsList' -import AdminAccountsDetail from '@/views/admin/moderation/AccountsDetail' -import ContentBase from '@/views/content/Base' -import ContentHome from '@/views/content/Home' -import LibrariesHome from '@/views/content/libraries/Home' -import LibrariesUpload from '@/views/content/libraries/Upload' -import LibrariesDetail from '@/views/content/libraries/Detail' -import LibrariesFiles from '@/views/content/libraries/Files' -import RemoteLibrariesHome from '@/views/content/remote/Home' -import Notifications from '@/views/Notifications' Vue.use(Router) @@ -52,7 +10,8 @@ export default new Router({ { path: '/', name: 'index', - component: Home + component: () => + import(/* webpackChunkName: "core" */ "@/components/Home"), }, { path: '/front', @@ -62,23 +21,27 @@ export default new Router({ { path: '/about', name: 'about', - component: About + component: () => + import(/* webpackChunkName: "core" */ "@/components/About"), }, { path: '/login', name: 'login', - component: Login, + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Login"), props: (route) => ({ next: route.query.next || '/library' }) }, { path: '/notifications', name: 'notifications', - component: Notifications + component: () => + import(/* webpackChunkName: "core" */ "@/views/Notifications"), }, { path: '/auth/password/reset', name: 'auth.password-reset', - component: PasswordReset, + component: () => + import(/* webpackChunkName: "core" */ "@/views/auth/PasswordReset"), props: (route) => ({ defaultEmail: route.query.email }) @@ -86,7 +49,8 @@ export default new Router({ { path: '/auth/email/confirm', name: 'auth.email-confirm', - component: EmailConfirm, + component: () => + import(/* webpackChunkName: "core" */ "@/views/auth/EmailConfirm"), props: (route) => ({ defaultKey: route.query.key }) @@ -94,16 +58,32 @@ export default new Router({ { path: '/auth/password/reset/confirm', name: 'auth.password-reset-confirm', - component: PasswordResetConfirm, + component: () => + import(/* webpackChunkName: "core" */ "@/views/auth/PasswordResetConfirm"), props: (route) => ({ defaultUid: route.query.uid, defaultToken: route.query.token }) }, + { + path: '/authorize', + name: 'authorize', + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Authorize"), + props: (route) => ({ + clientId: route.query.client_id, + redirectUri: route.query.redirect_uri, + scope: route.query.scope, + responseType: route.query.response_type, + nonce: route.query.nonce, + state: route.query.state, + }) + }, { path: '/signup', name: 'signup', - component: Signup, + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Signup"), props: (route) => ({ defaultInvitation: route.query.invitation }) @@ -111,22 +91,45 @@ export default new Router({ { path: '/logout', name: 'logout', - component: Logout + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Logout"), + }, { path: '/settings', name: 'settings', - component: Settings + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Settings"), + }, + { + path: '/settings/applications/new', + name: 'settings.applications.new', + props: (route) => ({ + scopes: route.query.scopes, + name: route.query.name, + redirect_uris: route.query.redirect_uris, + }), + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/ApplicationNew"), + }, + { + path: '/settings/applications/:id/edit', + name: 'settings.applications.edit', + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/ApplicationEdit"), + props: true }, { path: '/@:username', name: 'profile', - component: Profile, + component: () => + import(/* webpackChunkName: "core" */ "@/components/auth/Profile"), props: true }, { path: '/favorites', - component: Favorites, + component: () => + import(/* webpackChunkName: "core" */ "@/components/favorites/List"), props: (route) => ({ defaultOrdering: route.query.ordering, defaultPage: route.query.page, @@ -135,23 +138,27 @@ export default new Router({ }, { path: '/content', - component: ContentBase, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/Base"), children: [ { path: '', name: 'content.index', - component: ContentHome + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/Home"), } ] }, { path: '/content/libraries/tracks', - component: ContentBase, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/Base"), children: [ { path: '', name: 'content.libraries.files', - component: LibrariesFiles, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/libraries/Files"), props: (route) => ({ query: route.query.q }) @@ -160,17 +167,20 @@ export default new Router({ }, { path: '/content/libraries', - component: ContentBase, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/Base"), children: [ { path: '', name: 'content.libraries.index', - component: LibrariesHome + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/libraries/Home"), }, { path: ':id/upload', name: 'content.libraries.detail.upload', - component: LibrariesUpload, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/libraries/Upload"), props: (route) => ({ id: route.params.id, defaultImportReference: route.query.import @@ -179,73 +189,181 @@ export default new Router({ { path: ':id', name: 'content.libraries.detail', - component: LibrariesDetail, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/libraries/Detail"), props: true } ] }, { path: '/content/remote', - component: ContentBase, + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/Base"), children: [ { path: '', name: 'content.remote.index', - component: RemoteLibrariesHome + component: () => + import(/* webpackChunkName: "core" */ "@/views/content/remote/Home"), } ] }, { path: '/manage/settings', name: 'manage.settings', - component: AdminSettings + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/Settings"), }, { path: '/manage/library', - component: AdminLibraryBase, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/Base"), children: [ { - path: 'files', - name: 'manage.library.files', - component: AdminLibraryFilesList - } + path: 'edits', + name: 'manage.library.edits', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/EditsList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'artists', + name: 'manage.library.artists', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/ArtistsList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'artists/:id', + name: 'manage.library.artists.detail', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/ArtistDetail"), + props: true + }, + { + path: 'albums', + name: 'manage.library.albums', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/AlbumsList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'albums/:id', + name: 'manage.library.albums.detail', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/AlbumDetail"), + props: true + }, + { + path: 'tracks', + name: 'manage.library.tracks', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/TracksList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'tracks/:id', + name: 'manage.library.tracks.detail', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/TrackDetail"), + props: true + }, + { + path: 'libraries', + name: 'manage.library.libraries', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/LibrariesList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'libraries/:id', + name: 'manage.library.libraries.detail', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/LibraryDetail"), + props: true + }, + { + path: 'uploads', + name: 'manage.library.uploads', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/UploadsList"), + props: (route) => { + return { + defaultQuery: route.query.q, + } + } + }, + { + path: 'uploads/:id', + name: 'manage.library.uploads.detail', + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/library/UploadDetail"), + props: true + }, ] }, { path: '/manage/users', - component: AdminUsersBase, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/users/Base"), children: [ { path: 'users', name: 'manage.users.users.list', - component: AdminUsersList + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/users/UsersList"), }, { path: 'invitations', name: 'manage.users.invitations.list', - component: AdminInvitationsList + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/users/InvitationsList"), } ] }, { path: '/manage/moderation', - component: AdminModerationBase, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/Base"), children: [ { path: 'domains', name: 'manage.moderation.domains.list', - component: AdminDomainsList + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/DomainsList"), }, { path: 'domains/:id', name: 'manage.moderation.domains.detail', - component: AdminDomainsDetail, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/DomainsDetail"), props: true }, { path: 'accounts', name: 'manage.moderation.accounts.list', - component: AdminAccountsList, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/AccountsList"), props: (route) => { return { defaultQuery: route.query.q, @@ -256,20 +374,40 @@ export default new Router({ { path: 'accounts/:id', name: 'manage.moderation.accounts.detail', - component: AdminAccountsDetail, + component: () => + import(/* webpackChunkName: "admin" */ "@/views/admin/moderation/AccountsDetail"), props: true } ] }, { path: '/library', - component: Library, + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/Library"), children: [ - { path: '', component: LibraryHome, name: 'library.index' }, + { + path: '', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/Home"), + name: 'library.index' + }, { path: 'artists/', name: 'library.artists.browse', - component: LibraryArtists, + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/Artists"), + props: (route) => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultPaginateBy: route.query.paginateBy, + defaultPage: route.query.page + }) + }, + { + path: 'albums/', + name: 'library.albums.browse', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/Albums"), props: (route) => ({ defaultOrdering: route.query.ordering, defaultQuery: route.query.query, @@ -280,7 +418,8 @@ export default new Router({ { path: 'radios/', name: 'library.radios.browse', - component: LibraryRadios, + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/Radios"), props: (route) => ({ defaultOrdering: route.query.ordering, defaultQuery: route.query.query, @@ -288,13 +427,32 @@ export default new Router({ defaultPage: route.query.page }) }, - { path: 'radios/build', name: 'library.radios.build', component: RadioBuilder, props: true }, - { path: 'radios/build/:id', name: 'library.radios.edit', component: RadioBuilder, props: true }, - { path: 'radios/:id', name: 'library.radios.detail', component: RadioDetail, props: true }, + { + path: 'radios/build', + name: 'library.radios.build', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/radios/Builder"), + props: true + }, + { + path: 'radios/build/:id', + name: 'library.radios.edit', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/radios/Builder"), + props: true + }, + { + path: 'radios/:id', + name: 'library.radios.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/views/radios/Detail"), + props: true + }, { path: 'playlists/', name: 'library.playlists.browse', - component: PlaylistList, + component: () => + import(/* webpackChunkName: "core" */ "@/views/playlists/List"), props: (route) => ({ defaultOrdering: route.query.ordering, defaultQuery: route.query.query, @@ -305,16 +463,99 @@ export default new Router({ { path: 'playlists/:id', name: 'library.playlists.detail', - component: PlaylistDetail, + component: () => + import(/* webpackChunkName: "core" */ "@/views/playlists/Detail"), props: (route) => ({ id: route.params.id, defaultEdit: route.query.mode === 'edit' }) }, - { path: 'artists/:id', name: 'library.artists.detail', component: LibraryArtist, props: true }, - { path: 'albums/:id', name: 'library.albums.detail', component: LibraryAlbum, props: true }, - { path: 'tracks/:id', name: 'library.tracks.detail', component: LibraryTrack, props: true }, + { + path: 'artists/:id', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/ArtistBase"), + props: true, + children: [ + { + path: '', + name: 'library.artists.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/ArtistDetail"), + }, + { + path: 'edit', + name: 'library.artists.edit', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/ArtistEdit"), + }, + { + path: 'edit/:editId', + name: 'library.artists.edit.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/EditDetail"), + props: true, + } + ] + }, + { + path: 'albums/:id', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/AlbumBase"), + props: true, + children: [ + { + path: '', + name: 'library.albums.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/AlbumDetail"), + }, + { + path: 'edit', + name: 'library.albums.edit', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/AlbumEdit"), + }, + { + path: 'edit/:editId', + name: 'library.albums.edit.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/EditDetail"), + props: true, + } + ] + }, + { + path: 'tracks/:id', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/TrackBase"), + props: true, + children: [ + { + path: '', + name: 'library.tracks.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/TrackDetail"), + }, + { + path: 'edit', + name: 'library.tracks.edit', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/TrackEdit"), + }, + { + path: 'edit/:editId', + name: 'library.tracks.edit.detail', + component: () => + import(/* webpackChunkName: "core" */ "@/components/library/EditDetail"), + props: true, + } + ] + }, ] }, - { path: '*', component: PageNotFound } + { + path: '*', + component: () => + import(/* webpackChunkName: "core" */ "@/components/PageNotFound"), + } ] }) diff --git a/front/src/store/auth.js b/front/src/store/auth.js index 90cd27e9e094b62534ec1ed94c325e4a4ae8ae7e..52cc98a6de7abde1ebfb1c03bd5ea434e1fa2af8 100644 --- a/front/src/store/auth.js +++ b/front/src/store/auth.js @@ -8,6 +8,7 @@ export default { state: { authenticated: false, username: '', + fullUsername: '', availablePermissions: { settings: false, library: false, @@ -27,6 +28,7 @@ export default { state.authenticated = false state.profile = null state.username = '' + state.fullUsername = '' state.token = '' state.tokenData = {} state.availablePermissions = { @@ -43,6 +45,7 @@ export default { state.authenticated = value if (value === false) { state.username = null + state.fullUsername = null state.token = null state.tokenData = null state.profile = null @@ -52,6 +55,9 @@ export default { username: (state, value) => { state.username = value }, + fullUsername: (state, value) => { + state.fullUsername = value + }, avatar: (state, value) => { if (state.profile) { state.profile.avatar = value @@ -112,10 +118,6 @@ export default { } }, fetchProfile ({commit, dispatch, state}) { - if (document) { - // this is to ensure we do not have any leaking cookie set by django - document.cookie = 'sessionid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' - } return new Promise((resolve, reject) => { axios.get('users/users/me/').then((response) => { @@ -124,7 +126,9 @@ export default { resolve(response.data) }) dispatch('ui/fetchUnreadNotifications', null, { root: true }) + dispatch('ui/fetchPendingReviewEdits', null, { root: true }) dispatch('favorites/fetch', null, { root: true }) + dispatch('moderation/fetchContentFilters', null, { root: true }) dispatch('playlists/fetchOwn', null, { root: true }) }, (response) => { logger.default.info('Error while fetching user profile') @@ -137,6 +141,7 @@ export default { commit("authenticated", true) commit("profile", data) commit("username", data.username) + commit("fullUsername", data.full_username) Object.keys(data.permissions).forEach(function(key) { // this makes it easier to check for permissions in templates commit("permission", { diff --git a/front/src/store/index.js b/front/src/store/index.js index 2454dd20a1497333d5a7b3416859952fdd323d32..791dbb1e92fd1d3add3509ea67575dc734c64dd8 100644 --- a/front/src/store/index.js +++ b/front/src/store/index.js @@ -5,6 +5,7 @@ import createPersistedState from 'vuex-persistedstate' import favorites from './favorites' import auth from './auth' import instance from './instance' +import moderation from './moderation' import queue from './queue' import radios from './radios' import player from './player' @@ -19,6 +20,7 @@ export default new Vuex.Store({ auth, favorites, instance, + moderation, queue, radios, playlists, @@ -52,8 +54,7 @@ export default new Vuex.Store({ paths: [ 'player.looping', 'player.volume', - 'player.duration', - 'player.errored'], + 'player.duration'], filter: (mutation) => { return mutation.type.startsWith('player/') && mutation.type !== 'player/currentTime' } diff --git a/front/src/store/instance.js b/front/src/store/instance.js index d0bff7dcb217007ff662838f5f96c2fb26b009aa..6af36e8090e3ba276c7fabaf870ebd251f086a15 100644 --- a/front/src/store/instance.js +++ b/front/src/store/instance.js @@ -104,6 +104,7 @@ export default { let modules = [ 'auth', 'favorites', + 'moderation', 'player', 'playlists', 'queue', diff --git a/front/src/store/moderation.js b/front/src/store/moderation.js new file mode 100644 index 0000000000000000000000000000000000000000..153f3cd5959d307d0edbf7760a4ff34d6a8eced3 --- /dev/null +++ b/front/src/store/moderation.js @@ -0,0 +1,93 @@ +import axios from 'axios' +import logger from '@/logging' +import _ from '@/lodash' + +export default { + namespaced: true, + state: { + filters: [], + showFilterModal: false, + lastUpdate: new Date(), + filterModalTarget: { + type: null, + target: null, + } + }, + mutations: { + filterModalTarget (state, value) { + state.filterModalTarget = value + }, + empty (state) { + state.filters = [] + }, + lastUpdate (state, value) { + state.lastUpdate = value + }, + contentFilter (state, value) { + state.filters.push(value) + }, + showFilterModal (state, value) { + state.showFilterModal = value + if (!value) { + state.filterModalTarget = { + type: null, + target: null, + } + } + }, + reset (state) { + state.filters = [] + state.filterModalTarget = null + state.showFilterModal = false + }, + deleteContentFilter (state, uuid) { + state.filters = state.filters.filter((e) => { + return e.uuid != uuid + }) + } + }, + getters: { + artistFilters: (state) => () => { + let f = state.filters.filter((f) => { + return f.target.type === 'artist' + }) + let p = _.sortBy(f, [(e) => { return e.creation_date }]) + p.reverse() + return p + }, + }, + actions: { + hide ({commit}, payload) { + commit('filterModalTarget', payload) + commit('showFilterModal', true) + }, + fetchContentFilters ({dispatch, state, commit, rootState}, url) { + let params = {} + let promise + if (url) { + promise = axios.get(url) + } else { + commit('empty') + params = { + page_size: 100, + ordering: '-creation_date' + } + promise = axios.get('moderation/content-filters/', {params: params}) + } + return promise.then((response) => { + logger.default.info('Fetched a batch of ' + response.data.results.length + ' filters') + if (response.data.next) { + dispatch('fetchContentFilters', response.data.next) + } + response.data.results.forEach(result => { + commit('contentFilter', result) + }) + }) + }, + deleteContentFilter ({commit}, uuid) { + return axios.delete(`moderation/content-filters/${ uuid }/`).then((response) => { + commit('deleteContentFilter', uuid) + }) + } + } +} diff --git a/front/src/store/ui.js b/front/src/store/ui.js index fa4624c700c73b0581d9f2d881e70f81096a2b57..8a8bc1da01854a6b630c5a3877bca91217feb524 100644 --- a/front/src/store/ui.js +++ b/front/src/store/ui.js @@ -12,11 +12,15 @@ export default { messages: [], notifications: { inbox: 0, + pendingReviewEdits: 0, }, websocketEventsHandlers: { 'inbox.item_added': {}, 'import.status_updated': {}, - } + 'mutation.created': {}, + 'mutation.updated': {}, + }, + pageTitle: null }, mutations: { addWebsocketEventHandler: (state, {eventName, id, handler}) => { @@ -44,8 +48,15 @@ export default { notifications (state, {type, count}) { state.notifications[type] = count }, - incrementNotifications (state, {type, count}) { - state.notifications[type] = Math.max(0, state.notifications[type] + count) + incrementNotifications (state, {type, count, value}) { + if (value != undefined) { + state.notifications[type] = Math.max(0, value) + } else { + state.notifications[type] = Math.max(0, state.notifications[type] + count) + } + }, + pageTitle: (state, value) => { + state.pageTitle = value } }, actions: { @@ -54,6 +65,11 @@ export default { commit('notifications', {type: 'inbox', count: response.data.count}) }) }, + fetchPendingReviewEdits ({commit, rootState}, payload) { + axios.get('mutations/', {params: {is_approved: 'null', page_size: 1}}).then((response) => { + commit('notifications', {type: 'pendingReviewEdits', count: response.data.count}) + }) + }, websocketEvent ({state}, event) { let handlers = state.websocketEventsHandlers[event.type] console.log('Dispatching websocket event', event, handlers) diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss index 76d0372f336578f4658adf6328990accaa038932..8f8ee8b16f514dac63480f0375774166e17db18f 100644 --- a/front/src/style/_main.scss +++ b/front/src/style/_main.scss @@ -88,8 +88,14 @@ body { font-family: "Avenir", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + display: flex; + min-height: 100vh; + flex-direction: column; } +#app > main, #app > .main { + flex: 1; +} .instance-chooser { margin-top: 2em; } @@ -225,8 +231,15 @@ body { justify-content: center; } -.segment-content .button { - margin: 0.5em; +.header-buttons > .buttons { + display: inline-block; + padding: 0.2em; + margin: 0; + font-size: 1em; + .buttons { + margin: 0; + } + } a { @@ -296,3 +309,49 @@ canvas.color-thief { .ui.list .list.icon { padding-left: 0; } + + +.ui.dropdown .item[disabled] { + display: none; +} + +span.diff.added { + background-color:rgba(0, 255, 0, 0.25); +} + + +span.diff.removed { + background-color: rgba(255, 0, 0, 0.25); +} + +.table-wrapper { + display: block; + overflow-x: auto; +} + +td.align.right { + text-align: right; +} + +.ui.pagination.menu { + margin-top: 1em; + + span { + margin-left: 1em; + } +} + +.card .description { + word-wrap: break-word; +} + +.ui.checkbox label { + cursor: pointer; +} + +input + .help { + margin-top: 0.5em; +} + +.table td .ui.dropdown { + min-width: 150px; +} diff --git a/front/src/views/Notifications.vue b/front/src/views/Notifications.vue index 2ba075b34c32af977c2ef972b6058214088f2d64..648654c8fc1c02ede3fc86a94263bacb528f902e 100644 --- a/front/src/views/Notifications.vue +++ b/front/src/views/Notifications.vue @@ -2,22 +2,22 @@ <main class="main pusher" v-title="labels.title"> <section class="ui vertical aligned stripe segment"> <div class="ui container"> - <h1 class="ui header"><translate>Your notifications</translate></h1> + <h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your notifications</translate></h1> <div class="ui toggle checkbox"> <input v-model="filters.is_read" type="checkbox"> - <label><translate>Show read notifications</translate></label> + <label><translate translate-context="Content/Notifications/Form.Label/Verb">Show read notifications</translate></label> </div> <div v-if="filters.is_read === false && notifications.count > 0" @click="markAllAsRead" class="ui basic labeled icon right floated button"> <i class="ui check icon" /> - <translate>Mark all as read</translate> + <translate translate-context="Content/Notifications/Button.Label/Verb">Mark all as read</translate> </div> <div class="ui hidden divider" /> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading notifications…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Notifications/Paragraph">Loading notifications…</translate></div> </div> <table v-else-if="notifications.count > 0" class="ui table"> @@ -26,7 +26,7 @@ </tbody> </table> <p v-else> - <translate>No notification to show.</translate> + <translate translate-context="Content/Notifications/Paragraph">No notification to show.</translate> </p> </div> </section> @@ -73,7 +73,7 @@ export default { }), labels() { return { - title: this.$gettext("Notifications") + title: this.$pgettext('*/Notifications/*', "Notifications") } } }, diff --git a/front/src/views/admin/Settings.vue b/front/src/views/admin/Settings.vue index 779bb7459bd0d9e6966370f22cdd74cc789f77f7..7102fb311b1b64fa9c63f160d026659ebe06a087 100644 --- a/front/src/views/admin/Settings.vue +++ b/front/src/views/admin/Settings.vue @@ -13,7 +13,7 @@ </div> <div class="four wide column"> <div class="ui sticky vertical secondary menu"> - <div class="header item"><translate>Sections</translate></div> + <div class="header item"><translate translate-context="Content/Admin/Menu.Title">Sections</translate></div> <a :class="['menu', {active: group.id === current}, 'item']" @click.prevent="scrollTo(group.id)" :href="'#' + group.id" @@ -72,19 +72,19 @@ export default { computed: { labels() { return { - settings: this.$gettext("Instance settings") + settings: this.$pgettext('Head/Admin/Title', 'Instance settings') } }, groups() { // somehow, extraction fails if in the return block directly - let instanceLabel = this.$gettext("Instance information") - let usersLabel = this.$gettext("Users") - let musicLabel = this.$gettext("Music") - 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") + let instanceLabel = this.$pgettext('Content/Admin/Menu','Instance information') + let usersLabel = this.$pgettext('*/*/*/Noun', 'Users') + let musicLabel = this.$pgettext('*/*/*/Noun', 'Music') + let playlistsLabel = this.$pgettext('*/*/*', 'Playlists') + let federationLabel = this.$pgettext('Content/Admin/Menu', 'Federation') + let subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic') + let statisticsLabel = this.$pgettext('Content/Admin/Menu', 'Statistics') + let errorLabel = this.$pgettext('Content/Admin/Menu', 'Error reporting') return [ { label: instanceLabel, diff --git a/front/src/views/admin/library/AlbumDetail.vue b/front/src/views/admin/library/AlbumDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..b89afb945d7003cef05c7a92a68c8404b4e2e1c0 --- /dev/null +++ b/front/src/views/admin/library/AlbumDetail.vue @@ -0,0 +1,334 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.title"> + <div class="ui stackable one column grid"> + <div class="ui column"> + <div class="segment-content"> + <h2 class="ui header"> + <img v-if="object.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.square_crop)"> + <img v-else src="../../../assets/audio/default-cover.png"> + <div class="content"> + {{ object.title | truncate(100) }} + <div class="sub header"> + <template v-if="object.is_local"> + <span class="ui tiny teal label"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + + </template> + </div> + </div> + </h2> + <div class="header-buttons"> + + <div class="ui icon buttons"> + <router-link class="ui labeled icon button" :to="{name: 'library.albums.detail', params: {id: object.id }}"> + <i class="info icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate> + </router-link> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/release/${object.mbid}`" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate> + </a> + <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`albums/${object.id}/fetches/`"> + <i class="refresh icon"></i> + <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate> + </fetch-button> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + <div class="ui buttons"> + <router-link + v-if="object.is_local" + :to="{name: 'library.albums.edit', params: {id: object.id }}" + class="ui labeled icon button"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + </div> + <div class="ui buttons"> + <dangerous-button + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this album?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Content/Moderation/Paragraph">The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> + </div> + </div> + </div> + </section> + <div class="ui vertical stripe segment"> + <div class="ui stackable three column grid"> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="info icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Album data</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Title</translate> + </td> + <td> + {{ object.title }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.artist.id }}"> + <translate translate-context="*/*/*/Noun">Artist</translate> + </router-link> + </td> + <td> + {{ object.artist.name }} + </td> + </tr> + <tr v-if="!object.is_local"> + <td> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> + </router-link> + </td> + <td> + {{ object.domain }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="feed icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Activity</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Listenings</translate> + </td> + <td> + {{ stats.listenings }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Favorited tracks</translate> + </td> + <td> + {{ stats.track_favorites }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Playlists</translate> + </td> + <td> + {{ stats.playlists }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'album ' + object.id)}}"> + <translate translate-context="*/Admin/*/Noun">Edits</translate> + </router-link> + </td> + <td> + {{ stats.mutations }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="music icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Audio content</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> + </td> + <td> + {{ stats.media_downloaded_size | humanSize }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> + </td> + <td> + {{ stats.media_total_size | humanSize }} + </td> + </tr> + + <tr> + <td> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('album_id', object.id) }}"> + <translate translate-context="*/*/*/Noun">Libraries</translate> + </router-link> + </td> + <td> + {{ stats.libraries }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('album_id', object.id) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> + </td> + <td> + {{ stats.uploads }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('album_id', object.id) }}"> + <translate translate-context="*/*/*">Tracks</translate> + </router-link> + </td> + <td> + {{ object.tracks.length }} + </td> + </tr> + </tbody> + </table> + + </section> + </div> + </div> + </div> + + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import FetchButton from "@/components/federation/FetchButton" + + +export default { + props: ["id"], + components: { + FetchButton + }, + data() { + return { + isLoading: true, + isLoadingStats: false, + object: null, + stats: null, + } + }, + created() { + this.fetchData() + this.fetchStats() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = `manage/library/albums/${this.id}/` + axios.get(url).then(response => { + self.object = response.data + self.isLoading = false + }) + }, + fetchStats() { + var self = this + this.isLoadingStats = true + let url = `manage/library/albums/${this.id}/stats/` + axios.get(url).then(response => { + self.stats = response.data + self.isLoadingStats = false + }) + }, + remove () { + var self = this + this.isLoading = true + let url = `manage/library/albums/${this.id}/` + axios.delete(url).then(response => { + self.$router.push({name: 'manage.library.albums'}) + }) + }, + getQuery (field, value) { + return `${field}:"${value}"` + } + }, + computed: { + labels() { + return { + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'), + } + }, + } +} +</script> diff --git a/front/src/views/admin/library/AlbumsList.vue b/front/src/views/admin/library/AlbumsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..650b4d69389aadd05ae8fd2e7a385ed02c2a5945 --- /dev/null +++ b/front/src/views/admin/library/AlbumsList.vue @@ -0,0 +1,29 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header">{{ labels.title }}</h2> + <div class="ui hidden divider"></div> + <albums-table :update-url="true" :default-query="defaultQuery"></albums-table> + </section> + </main> +</template> + +<script> +import AlbumsTable from "@/components/manage/library/AlbumsTable" + +export default { + components: { + AlbumsTable + }, + props: { + defaultQuery: {type: String, required: false}, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Albums') + } + } + } +} +</script> diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..0c4175bae4a84a68bb1f526deafd1af2fce42cea --- /dev/null +++ b/front/src/views/admin/library/ArtistDetail.vue @@ -0,0 +1,333 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name"> + <div class="ui stackable one column grid"> + <div class="ui column"> + <div class="segment-content"> + <h2 class="ui header"> + <i class="circular inverted user icon"></i> + <div class="content"> + {{ object.name | truncate(100) }} + <div class="sub header"> + <template v-if="object.is_local"> + <span class="ui tiny teal label"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + + </template> + </div> + </div> + </h2> + <div class="header-buttons"> + + <div class="ui icon buttons"> + <router-link class="ui labeled icon button" :to="{name: 'library.artists.detail', params: {id: object.id }}"> + <i class="info icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate> + </router-link> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/artist/${object.mbid}`" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate> + </a> + <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`artists/${object.id}/fetches/`"> + <i class="refresh icon"></i> + <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate> + </fetch-button> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + <div class="ui buttons"> + <router-link + v-if="object.is_local" + :to="{name: 'library.artists.edit', params: {id: object.id }}" + class="ui labeled icon button"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + </div> + <div class="ui buttons"> + <dangerous-button + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this artist?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Content/Moderation/Paragraph">The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> + </div> + </div> + </div> + </section> + <div class="ui vertical stripe segment"> + <div class="ui stackable three column grid"> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="info icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Artist data</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Name</translate> + </td> + <td> + {{ object.name }} + </td> + </tr> + <tr v-if="!object.is_local"> + <td> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> + </router-link> + </td> + <td> + {{ object.domain }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="feed icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Activity</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Listenings</translate> + </td> + <td> + {{ stats.listenings }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Favorited tracks</translate> + </td> + <td> + {{ stats.track_favorites }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Playlists</translate> + </td> + <td> + {{ stats.playlists }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'artist ' + object.id)}}"> + <translate translate-context="*/Admin/*/Noun">Edits</translate> + </router-link> + </td> + <td> + {{ stats.mutations }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="music icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Audio content</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> + </td> + <td> + {{ stats.media_downloaded_size | humanSize }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> + </td> + <td> + {{ stats.media_total_size | humanSize }} + </td> + </tr> + + <tr> + <td> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('artist_id', object.id) }}"> + <translate translate-context="*/*/*/Noun">Libraries</translate> + </router-link> + </td> + <td> + {{ stats.libraries }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('artist_id', object.id) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> + </td> + <td> + {{ stats.uploads }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('artist_id', object.id) }}"> + <translate translate-context="*/*/*">Albums</translate> + </router-link> + </td> + <td> + {{ object.albums.length }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('artist_id', object.id) }}"> + <translate translate-context="*/*/*">Tracks</translate> + </router-link> + </td> + <td> + {{ object.tracks.length }} + </td> + </tr> + </tbody> + </table> + + </section> + </div> + </div> + </div> + + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" + +import FetchButton from "@/components/federation/FetchButton" + +export default { + props: ["id"], + components: { + FetchButton + }, + data() { + return { + isLoading: true, + isLoadingStats: false, + object: null, + stats: null, + } + }, + created() { + this.fetchData() + this.fetchStats() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = `manage/library/artists/${this.id}/` + axios.get(url).then(response => { + self.object = response.data + self.isLoading = false + }) + }, + fetchStats() { + var self = this + this.isLoadingStats = true + let url = `manage/library/artists/${this.id}/stats/` + axios.get(url).then(response => { + self.stats = response.data + self.isLoadingStats = false + }) + }, + remove () { + var self = this + this.isLoading = true + let url = `manage/library/artists/${this.id}/` + axios.delete(url).then(response => { + self.$router.push({name: 'manage.library.artists'}) + }) + }, + getQuery (field, value) { + return `${field}:"${value}"` + } + }, + computed: { + labels() { + return { + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'), + } + }, + } +} +</script> diff --git a/front/src/views/admin/library/ArtistsList.vue b/front/src/views/admin/library/ArtistsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..2a5932796ec6003703942ee1fe6e7e31f4d532c9 --- /dev/null +++ b/front/src/views/admin/library/ArtistsList.vue @@ -0,0 +1,29 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header">{{ labels.title }}</h2> + <div class="ui hidden divider"></div> + <artists-table :update-url="true" :default-query="defaultQuery"></artists-table> + </section> + </main> +</template> + +<script> +import ArtistsTable from "@/components/manage/library/ArtistsTable" + +export default { + components: { + ArtistsTable + }, + props: { + defaultQuery: {type: String, required: false}, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Artists') + } + } + } +} +</script> diff --git a/front/src/views/admin/library/Base.vue b/front/src/views/admin/library/Base.vue index 45d257606b403769d865560749cf31a0700e0d09..009e1ca95c9f8bc615c73c37f808887b14949711 100644 --- a/front/src/views/admin/library/Base.vue +++ b/front/src/views/admin/library/Base.vue @@ -3,7 +3,22 @@ <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" - :to="{name: 'manage.library.files'}"><translate>Files</translate></router-link> + :to="{name: 'manage.library.edits'}"><translate translate-context="*/Admin/*/Noun">Edits</translate></router-link> + <router-link + class="ui item" + :to="{name: 'manage.library.artists'}"><translate translate-context="*/*/*">Artists</translate></router-link> + <router-link + class="ui item" + :to="{name: 'manage.library.albums'}"><translate translate-context="*/*/*">Albums</translate></router-link> + <router-link + class="ui item" + :to="{name: 'manage.library.tracks'}"><translate translate-context="*/*/*">Tracks</translate></router-link> + <router-link + class="ui item" + :to="{name: 'manage.library.libraries'}"><translate translate-context="*/*/*">Libraries</translate></router-link> + <router-link + class="ui item" + :to="{name: 'manage.library.uploads'}"><translate translate-context="*/*/*">Uploads</translate></router-link> </nav> <router-view :key="$route.fullPath"></router-view> </div> @@ -13,8 +28,8 @@ export default { computed: { labels() { - let title = this.$gettext("Manage library") - let secondaryMenu = this.$gettext("Secondary menu") + let title = this.$pgettext('Head/Admin/Title', 'Manage library') + let secondaryMenu = this.$pgettext('Menu/*/Hidden text', 'Secondary menu') return { title, secondaryMenu diff --git a/front/src/views/admin/library/EditsList.vue b/front/src/views/admin/library/EditsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..ec1ddb15e7e09374bb7c0d1622dad41814e3850e --- /dev/null +++ b/front/src/views/admin/library/EditsList.vue @@ -0,0 +1,33 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <edits-card-list :update-url="true" :default-query="defaultQuery"> + <h2 class="ui header"><translate translate-context="Content/Admin/Title/Noun">Library edits</translate></h2> + </edits-card-list> + </section> + </main> +</template> + +<script> +import EditsCardList from "@/components/manage/library/EditsCardList" + +export default { + props: { + defaultQuery: {type: String, required: false}, + }, + components: { + EditsCardList + }, + computed: { + labels() { + return { + title: this.$pgettext('*/Admin/*/Noun', 'Edits') + } + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/views/admin/library/FilesList.vue b/front/src/views/admin/library/FilesList.vue deleted file mode 100644 index 3557879bb29acb14a704a6c5113cf243ca0a4edc..0000000000000000000000000000000000000000 --- a/front/src/views/admin/library/FilesList.vue +++ /dev/null @@ -1,30 +0,0 @@ -<template> - <main v-title="labels.title"> - <section class="ui vertical stripe segment"> - <h2 class="ui header"><translate>Library files</translate></h2> - <div class="ui hidden divider"></div> - <library-files-table :show-library="true"></library-files-table> - </section> - </main> -</template> - -<script> -import LibraryFilesTable from "@/components/manage/library/FilesTable" - -export default { - components: { - LibraryFilesTable - }, - computed: { - labels() { - return { - title: this.$gettext("Files") - } - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> -</style> diff --git a/front/src/views/admin/library/LibrariesList.vue b/front/src/views/admin/library/LibrariesList.vue new file mode 100644 index 0000000000000000000000000000000000000000..495a660c19609fde101fff87b2774f6ca7890c3c --- /dev/null +++ b/front/src/views/admin/library/LibrariesList.vue @@ -0,0 +1,29 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header">{{ labels.title }}</h2> + <div class="ui hidden divider"></div> + <libraries-table :update-url="true" :default-query="defaultQuery"></libraries-table> + </section> + </main> +</template> + +<script> +import LibrariesTable from "@/components/manage/library/LibrariesTable" + +export default { + components: { + LibrariesTable + }, + props: { + defaultQuery: {type: String, required: false}, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Libraries') + } + } + } +} +</script> diff --git a/front/src/views/admin/library/LibraryDetail.vue b/front/src/views/admin/library/LibraryDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..beec7e2b408cf6b032c5f0114c979d77f958a64c --- /dev/null +++ b/front/src/views/admin/library/LibraryDetail.vue @@ -0,0 +1,321 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name"> + <div class="ui stackable one column grid"> + <div class="ui column"> + <div class="segment-content"> + <h2 class="ui header"> + <i class="circular inverted book icon"></i> + <div class="content"> + {{ object.name | truncate(100) }} + <div class="sub header"> + <template v-if="object.is_local"> + <span class="ui tiny teal label"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + + </template> + </div> + </div> + </h2> + <div class="header-buttons"> + + <div class="ui icon buttons"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="ui labeled icon button" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + <div class="ui buttons"> + <dangerous-button + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this library?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Content/Moderation/Paragraph">The library will be removed, as well as associated uploads, and follows. This action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> + </div> + </div> + </div> + </section> + <div class="ui vertical stripe segment"> + <div class="ui stackable three column grid"> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="info icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Library data</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Name</translate> + </td> + <td> + {{ object.name }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('privacy_level', object.privacy_level) }}"> + <translate translate-context="*/*/*">Visibility</translate> + </router-link> + </td> + <td> + {{ sharedLabels.fields.privacy_level.shortChoices[object.privacy_level] }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: object.actor.full_username }}"> + <translate translate-context="*/*/*/Noun">Account</translate> + </router-link> + </td> + <td> + {{ object.actor.preferred_username }} + </td> + </tr> + <tr v-if="!object.is_local"> + <td> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> + </router-link> + </td> + <td> + {{ object.domain }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Description</translate> + </td> + <td> + {{ object.description }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="feed icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Activity</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Federation/*/Noun">Followers</translate> + </td> + <td> + {{ stats.followers }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="music icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Audio content</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> + </td> + <td> + {{ stats.media_downloaded_size | humanSize }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> + </td> + <td> + {{ stats.media_total_size | humanSize }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('library_id', object.id) }}"> + <translate translate-context="*/*/*">Artists</translate> + </router-link> + </td> + <td> + {{ stats.artists }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('library_id', object.id) }}"> + <translate translate-context="*/*/*">Albums</translate> + </router-link> + </td> + <td> + {{ stats.albums }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('library_id', object.id) }}"> + <translate translate-context="*/*/*">Tracks</translate> + </router-link> + </td> + <td> + {{ stats.tracks }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('library_id', object.id) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> + </td> + <td> + {{ stats.uploads }} + </td> + </tr> + </tbody> + </table> + + </section> + </div> + </div> + </div> + + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import TranslationsMixin from "@/components/mixins/Translations" + + +export default { + props: ["id"], + mixins: [ + TranslationsMixin + ], + data() { + return { + isLoading: true, + isLoadingStats: false, + object: null, + stats: null, + } + }, + created() { + this.fetchData() + this.fetchStats() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = `manage/library/libraries/${this.id}/` + axios.get(url).then(response => { + self.object = response.data + self.isLoading = false + }) + }, + fetchStats() { + var self = this + this.isLoadingStats = true + let url = `manage/library/libraries/${this.id}/stats/` + axios.get(url).then(response => { + self.stats = response.data + self.isLoadingStats = false + }) + }, + remove () { + var self = this + this.isLoading = true + let url = `manage/library/libraries/${this.id}/` + axios.delete(url).then(response => { + self.$router.push({name: 'manage.library.libraries'}) + }) + }, + getQuery (field, value) { + return `${field}:"${value}"` + } + }, + computed: { + labels() { + return { + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'), + } + }, + } +} +</script> diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..29cd29810ee619bf96be7ac499b6a5f6d3d5cbcb --- /dev/null +++ b/front/src/views/admin/library/TrackDetail.vue @@ -0,0 +1,376 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.title"> + <div class="ui stackable one column grid"> + <div class="ui column"> + <div class="segment-content"> + <h2 class="ui header"> + <i class="circular inverted user icon"></i> + <div class="content"> + {{ object.title | truncate(100) }} + <div class="sub header"> + <template v-if="object.is_local"> + <span class="ui tiny teal label"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + + </template> + </div> + </div> + </h2> + <div class="header-buttons"> + + <div class="ui icon buttons"> + <router-link class="ui icon labeled button" :to="{name: 'library.tracks.detail', params: {id: object.id }}"> + <i class="info icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate> + </router-link> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/recording/${object.mbid}`" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate> + </a> + <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`tracks/${object.id}/fetches/`"> + <i class="refresh icon"></i> + <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate> + </fetch-button> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + <div class="ui buttons"> + <router-link + v-if="object.is_local" + :to="{name: 'library.tracks.edit', params: {id: object.id }}" + class="ui labeled icon button"> + <i class="edit icon"></i> + <translate translate-context="Content/*/Button.Label/Verb">Edit</translate> + </router-link> + </div> + <div class="ui buttons"> + <dangerous-button + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this album?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Content/Moderation/Paragraph">The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> + </div> + </div> + </div> + </section> + <div class="ui vertical stripe segment"> + <div class="ui stackable three column grid"> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="info icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Track data</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Title</translate> + </td> + <td> + {{ object.title }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.albums.detail', params: {id: object.album.id }}"> + <translate translate-context="*/*/*/Noun">Album</translate> + </router-link> + </td> + <td> + {{ object.album.title }} + </td> + </tr> + + <tr> + <td> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.artist.id }}"> + <translate translate-context="*/*/*/Noun">Artist</translate> + </router-link> + </td> + <td> + {{ object.artist.name }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.album.artist.id }}"> + <translate translate-context="*/*/*/Noun">Album artist</translate> + </router-link> + </td> + <td> + {{ object.album.artist.name }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Position</translate> + </td> + <td> + {{ object.position }} + </td> + </tr> + <tr v-if="object.disc_number"> + <td> + <translate translate-context="*/*/*/Noun">Disc number</translate> + </td> + <td> + {{ object.disc_number }} + </td> + </tr> + <tr v-if="object.copyright"> + <td> + <translate translate-context="Content/Track/Table.Label/Noun">Copyright</translate> + </td> + <td>{{ object.copyright }}</td> + </tr> + <tr v-if="object.license"> + <td> + <translate translate-context="Content/*/*/Noun">License</translate> + </td> + <td> + <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('license', object.license)}}"> + {{ object.license }} + </router-link> + </td> + </tr> + <tr v-if="!object.is_local"> + <td> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> + </router-link> + </td> + <td> + {{ object.domain }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="feed icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Activity</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Listenings</translate> + </td> + <td> + {{ stats.listenings }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Favorited tracks</translate> + </td> + <td> + {{ stats.track_favorites }} + </td> + </tr> + <tr> + <td> + <translate translate-context="*/*/*">Playlists</translate> + </td> + <td> + {{ stats.playlists }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'track ' + object.id)}}"> + <translate translate-context="*/Admin/*/Noun">Edits</translate> + </router-link> + </td> + <td> + {{ stats.mutations }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="music icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Audio content</translate> + <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> + + </div> + </h3> + <div v-if="isLoadingStats" class="ui placeholder"> + <div class="full line"></div> + <div class="short line"></div> + <div class="medium line"></div> + <div class="long line"></div> + </div> + <table v-else class="ui very basic table"> + <tbody> + + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> + </td> + <td> + {{ stats.media_downloaded_size | humanSize }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> + </td> + <td> + {{ stats.media_total_size | humanSize }} + </td> + </tr> + + <tr> + <td> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('track_id', object.id) }}"> + <translate translate-context="*/*/*/Noun">Libraries</translate> + </router-link> + </td> + <td> + {{ stats.libraries }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('track_id', object.id) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> + </td> + <td> + {{ stats.uploads }} + </td> + </tr> + </tbody> + </table> + + </section> + </div> + </div> + </div> + + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import FetchButton from "@/components/federation/FetchButton" + + +export default { + props: ["id"], + components: { + FetchButton + }, + data() { + return { + isLoading: true, + isLoadingStats: false, + object: null, + stats: null, + } + }, + created() { + this.fetchData() + this.fetchStats() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = `manage/library/tracks/${this.id}/` + axios.get(url).then(response => { + self.object = response.data + self.isLoading = false + }) + }, + fetchStats() { + var self = this + this.isLoadingStats = true + let url = `manage/library/tracks/${this.id}/stats/` + axios.get(url).then(response => { + self.stats = response.data + self.isLoadingStats = false + }) + }, + remove () { + var self = this + this.isLoading = true + let url = `manage/library/tracks/${this.id}/` + axios.delete(url).then(response => { + self.$router.push({name: 'manage.library.tracks'}) + }) + }, + getQuery (field, value) { + return `${field}:"${value}"` + } + }, + computed: { + labels() { + return { + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'), + } + }, + } +} +</script> diff --git a/front/src/views/admin/library/TracksList.vue b/front/src/views/admin/library/TracksList.vue new file mode 100644 index 0000000000000000000000000000000000000000..3aefc86060af1a12aa006136f49c224f27b2b7af --- /dev/null +++ b/front/src/views/admin/library/TracksList.vue @@ -0,0 +1,29 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header">{{ labels.title }}</h2> + <div class="ui hidden divider"></div> + <tracks-table :update-url="true" :default-query="defaultQuery"></tracks-table> + </section> + </main> +</template> + +<script> +import TracksTable from "@/components/manage/library/TracksTable" + +export default { + components: { + TracksTable + }, + props: { + defaultQuery: {type: String, required: false}, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Tracks') + } + } + } +} +</script> diff --git a/front/src/views/admin/library/UploadDetail.vue b/front/src/views/admin/library/UploadDetail.vue new file mode 100644 index 0000000000000000000000000000000000000000..4dbd83793c5b52374d174417265eae7b5cabcf4b --- /dev/null +++ b/front/src/views/admin/library/UploadDetail.vue @@ -0,0 +1,340 @@ +<template> + <main> + <div v-if="isLoading" class="ui vertical segment"> + <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> + </div> + <template v-if="object"> + <import-status-modal :upload="object" :show.sync="showUploadDetailModal" /> + <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="displayName(object)"> + <div class="ui stackable one column grid"> + <div class="ui column"> + <div class="segment-content"> + <h2 class="ui header"> + <i class="circular inverted file icon"></i> + <div class="content"> + {{ displayName(object) | truncate(100) }} + <div class="sub header"> + <template v-if="object.is_local"> + <span class="ui tiny teal label"> + <i class="home icon"></i> + <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> + </span> + + </template> + </div> + </div> + </h2> + <div class="header-buttons"> + + <div class="ui icon buttons"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="ui labeled icon button" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a + v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="basic item" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + <div class="ui buttons"> + <a class="ui labeled icon button" v-if="object.audio_file" :href="$store.getters['instance/absoluteUrl'](object.audio_file)" target="_blank" rel="noopener noreferrer"> + <i class="download icon"></i> + <translate translate-context="Content/Track/Link/Verb">Download</translate> + </a> + </div> + <div class="ui buttons"> + <dangerous-button + :class="['ui', {loading: isLoading}, 'basic button']" + :action="remove"> + <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this upload?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Content/Moderation/Paragraph">The upload will be removed. This action is irreversible.</translate></p> + </div> + <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p> + </dangerous-button> + </div> + </div> + </div> + </div> + </div> + </section> + <div class="ui vertical stripe segment"> + <div class="ui stackable three column grid"> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="info icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Upload data</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="*/*/*/Noun">Name</translate> + </td> + <td> + {{ displayName(object) }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('privacy_level', object.library.privacy_level) }}"> + <translate translate-context="*/*/*">Visibility</translate> + </router-link> + </td> + <td> + {{ sharedLabels.fields.privacy_level.shortChoices[object.library.privacy_level] }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: object.library.actor.full_username }}"> + <translate translate-context="*/*/*/Noun">Account</translate> + </router-link> + </td> + <td> + {{ object.library.actor.preferred_username }} + </td> + </tr> + <tr v-if="!object.is_local"> + <td> + <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> + </router-link> + </td> + <td> + {{ object.domain }} + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('status', object.import_status) }}"> + <translate translate-context="Content/*/*/Noun">Import status</translate> + </router-link> + </td> + <td> + {{ sharedLabels.fields.import_status.choices[object.import_status].label }} + <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = object; showUploadDetailModal = true"> + <i class="question circle outline icon"></i> + </button> + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.libraries.detail', params: {id: object.library.uuid }}"> + <translate translate-context="*/*/*">Library</translate> + </router-link> + </td> + <td> + {{ object.library.name }} + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="feed icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Activity</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*/Noun">Accessed date</translate> + </td> + <td> + <human-date v-if="object.accessed_date" :date="object.accessed_date"></human-date> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </tr> + </tbody> + </table> + </section> + </div> + <div class="column"> + <section> + <h3 class="ui header"> + <i class="music icon"></i> + <div class="content"> + <translate translate-context="Content/Moderation/Title">Audio content</translate> + </div> + </h3> + <table class="ui very basic table"> + <tbody> + <tr v-if="object.track"> + <td> + <router-link :to="{name: 'manage.library.tracks.detail', params: {id: object.track.id }}"> + <translate translate-context="*/*/*">Track</translate> + </router-link> + </td> + <td> + {{ object.track.title }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> + </td> + <td> + <template v-if="object.audio_file"> + {{ object.size | humanSize }} + </template> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*/Noun">Size</translate> + </td> + <td> + {{ object.size | humanSize }} + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/Track/*/Noun">Bitrate</translate> + </td> + <td> + <template v-if="object.bitrate"> + {{ object.bitrate | humanSize }}/s + </template> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <translate translate-context="Content/*/*">Duration</translate> + </td> + <td> + <template v-if="object.duration"> + {{ time.parse(object.duration) }} + </template> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </tr> + <tr> + <td> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('type', object.mimetype) }}"> + <translate translate-context="Content/Track/Table.Label/Noun">Type</translate> + </router-link> + </td> + <td> + <template v-if="object.mimetype"> + {{ object.mimetype }} + </template> + <translate v-else translate-context="*/*/*">N/A</translate> + </td> + </tr> + </tbody> + </table> + </section> + </div> + </div> + </div> + + </template> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" +import TranslationsMixin from "@/components/mixins/Translations" +import ImportStatusModal from '@/components/library/ImportStatusModal' +import time from '@/utils/time' + + +export default { + props: ["id"], + mixins: [ + TranslationsMixin, + ], + components: { + ImportStatusModal + }, + data() { + return { + time, + detailedUpload: null, + showUploadDetailModal: false, + isLoading: true, + object: null, + stats: null, + } + }, + created() { + this.fetchData() + }, + methods: { + fetchData() { + var self = this + this.isLoading = true + let url = `manage/library/uploads/${this.id}/` + axios.get(url).then(response => { + self.object = response.data + self.isLoading = false + }) + }, + remove () { + var self = this + this.isLoading = true + let url = `manage/library/uploads/${this.id}/` + axios.delete(url).then(response => { + self.$router.push({name: 'manage.library.uploads'}) + }) + }, + getQuery (field, value) { + return `${field}:"${value}"` + }, + displayName (upload) { + if (upload.filename) { + return upload.filename + } + if (upload.source) { + return upload.source + } + return upload.uuid + } + }, + computed: { + labels() { + return { + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'), + } + }, + } +} +</script> diff --git a/front/src/views/admin/library/UploadsList.vue b/front/src/views/admin/library/UploadsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..0d4d7b5e3c2975914ebbb05f3a5d3e26e044cd6a --- /dev/null +++ b/front/src/views/admin/library/UploadsList.vue @@ -0,0 +1,29 @@ +<template> + <main v-title="labels.title"> + <section class="ui vertical stripe segment"> + <h2 class="ui header">{{ labels.title }}</h2> + <div class="ui hidden divider"></div> + <uploads-table :update-url="true" :default-query="defaultQuery"></uploads-table> + </section> + </main> +</template> + +<script> +import UploadsTable from "@/components/manage/library/UploadsTable" + +export default { + components: { + UploadsTable + }, + props: { + defaultQuery: {type: String, required: false}, + }, + computed: { + labels() { + return { + title: this.$pgettext('*/*/*', 'Uploads') + } + } + } +} +</script> diff --git a/front/src/views/admin/moderation/AccountsDetail.vue b/front/src/views/admin/moderation/AccountsDetail.vue index 5a339db5656e7e453fcabbafd9c9af20ca6eb3b8..09b5bb8244faf339fd08a20e31068bf7e4a8cdb7 100644 --- a/front/src/views/admin/moderation/AccountsDetail.vue +++ b/front/src/views/admin/moderation/AccountsDetail.vue @@ -14,19 +14,48 @@ {{ object.full_username }} <div class="sub header"> <template v-if="object.user"> - <span class="ui tiny teal icon label"> + <span class="ui tiny teal label"> <i class="home icon"></i> - <translate>Local account</translate> + <translate translate-context="Content/Moderation/*/Short, Noun">Local account</translate> </span> </template> <a :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> - <translate>Open profile</translate> + <translate translate-context="Content/Moderation/Link/Verb">Open profile</translate> <i class="external icon"></i> </a> </div> </div> </h2> + <div class="header-buttons"> + <div class="ui icon buttons"> + <a + v-if="object.user && $store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="ui labeled icon button" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/users/user/${object.user.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <a + v-else-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser" + class="ui labeled icon button" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object.id}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + <div class="ui floating dropdown icon button" v-dropdown> + <i class="dropdown icon"></i> + <div class="menu"> + <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer"> + <i class="external icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate> + </a> + </div> + </div> + </div> + </div> </div> </div> <div class="ui column"> @@ -44,16 +73,16 @@ <header class="ui header"> <h3> <i class="shield icon"></i> - <translate>You don't have any rule in place for this account.</translate> + <translate translate-context="Content/Moderation/Card.Title">You don't have any rule in place for this account.</translate> </h3> </header> - <p><translate>Moderation policies help you control how your instance interact with a given domain or account.</translate></p> - <button @click="showPolicyForm = true" class="ui primary button">Add a moderation policy</button> + <p><translate translate-context="Content/Moderation/Card.Paragraph">Moderation policies help you control how your instance interact with a given domain or account.</translate></p> + <button @click="showPolicyForm = true" class="ui primary button"><translate translate-context="Content/Moderation/Button/Verb">Add a moderation policy</translate></button> </template> <instance-policy-card v-else-if="policy && !showPolicyForm" :object="policy" @update="showPolicyForm = true"> <header class="ui header"> <h3> - <translate>This domain is subject to specific moderation rules</translate> + <translate translate-context="Content/Moderation/Card.Title">This domain is subject to specific moderation rules</translate> </h3> </header> </instance-policy-card> @@ -76,32 +105,32 @@ <h3 class="ui header"> <i class="info icon"></i> <div class="content"> - <translate>Account data</translate> + <translate translate-context="Content/Moderation/Title">Account data</translate> </div> </h3> <table class="ui very basic table"> <tbody> <tr> <td> - <translate>Username</translate> + <translate translate-context="Content/*/*">Username</translate> </td> <td> {{ object.preferred_username }} </td> </tr> <tr v-if="!object.user"> - <td> - <translate>Domain</translate> - </td> <td> <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}"> - {{ object.domain }} + <translate translate-context="Content/Moderation/*/Noun">Domain</translate> </router-link> </td> + <td> + {{ object.domain }} + </td> </tr> <tr> <td> - <translate>Display name</translate> + <translate translate-context="'Content/*/*/Noun'">Display name</translate> </td> <td> {{ object.name }} @@ -109,7 +138,7 @@ </tr> <tr v-if="object.user"> <td> - <translate>Email address</translate> + <translate translate-context="Content/*/*">Email address</translate> </td> <td> {{ object.user.email }} @@ -117,7 +146,7 @@ </tr> <tr v-if="object.user"> <td> - <translate>Login status</translate> + <translate translate-context="Content/*/*/Noun">Login status</translate> </td> <td> <div class="ui toggle checkbox" v-if="object.user.username != $store.state.auth.profile.username"> @@ -125,17 +154,17 @@ @change="updateUser('is_active')" v-model="object.user.is_active" type="checkbox"> <label> - <translate v-if="object.user.is_active" key="1">Enabled</translate> - <translate v-else key="2">Disabled</translate> + <translate v-if="object.user.is_active" key="1" translate-context="*/*/*">Enabled</translate> + <translate v-else key="2" translate-context="*/*/*">Disabled</translate> </label> </div> - <translate v-else-if="object.user.is_active" key="1">Enabled</translate> - <translate v-else key="2">Disabled</translate> + <translate v-else-if="object.user.is_active" key="1" translate-context="*/*/*">Enabled</translate> + <translate v-else key="2" translate-context="*/*/*">Disabled</translate> </td> </tr> <tr v-if="object.user"> <td> - <translate>Permissions</translate> + <translate translate-context="Content/Admin/Table.Label/Noun">Permissions</translate> </td> <td> <select @@ -149,7 +178,7 @@ </tr> <tr> <td> - <translate>Type</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Type</translate> </td> <td> {{ object.type }} @@ -157,24 +186,16 @@ </tr> <tr v-if="!object.user"> <td> - <translate>First seen</translate> - </td> - <td> - <human-date :date="object.creation_date"></human-date> - </td> - </tr> - <tr v-if="!object.user"> - <td> - <translate>Last checked</translate> + <translate translate-context="Content/*/Table.Label">Last checked</translate> </td> <td> <human-date v-if="object.last_fetch_date" :date="object.last_fetch_date"></human-date> - <translate v-else>N/A</translate> + <translate v-else translate-context="*/*/*">N/A</translate> </td> </tr> <tr v-if="object.user"> <td> - <translate>Sign-up date</translate> + <translate translate-context="Content/Admin/Table.Label/Noun">Sign-up date</translate> </td> <td> <human-date :date="object.user.date_joined"></human-date> @@ -182,7 +203,7 @@ </tr> <tr v-if="object.user"> <td> - <translate>Last activity</translate> + <translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">Last activity</translate> </td> <td> <human-date :date="object.user.last_activity"></human-date> @@ -197,7 +218,7 @@ <h3 class="ui header"> <i class="feed icon"></i> <div class="content"> - <translate>Activity</translate> + <translate translate-context="Content/Moderation/Title">Activity</translate> <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> </div> @@ -210,9 +231,17 @@ </div> <table v-else class="ui very basic table"> <tbody> + <tr v-if="!object.user"> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> <tr> <td> - <translate>Emitted messages</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted messages</translate> </td> <td> {{ stats.outbox_activities}} @@ -220,7 +249,7 @@ </tr> <tr> <td> - <translate>Received library follows</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Received library follows</translate> </td> <td> {{ stats.received_library_follows}} @@ -228,7 +257,7 @@ </tr> <tr> <td> - <translate>Emitted library follows</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted library follows</translate> </td> <td> {{ stats.emitted_library_follows}} @@ -243,7 +272,7 @@ <h3 class="ui header"> <i class="music icon"></i> <div class="content"> - <translate>Audio content</translate> + <translate translate-context="Content/Moderation/Title">Audio content</translate> <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> </div> @@ -259,7 +288,7 @@ <tr v-if="!object.user"> <td> - <translate>Cached size</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> </td> <td> {{ stats.media_downloaded_size | humanSize }} @@ -267,26 +296,26 @@ </tr> <tr v-if="object.user"> <td> - <translate>Upload quota</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun" >Upload quota</translate> <span :data-tooltip="labels.uploadQuota"><i class="question circle icon"></i></span> </td> <td> <div class="ui right labeled input"> <input - class="ui input" @change="updateUser('upload_quota', true)" v-model.number="object.user.upload_quota" step="100" + name="quota" type="number" /> <div class="ui basic label"> - <translate>MB</translate> + <translate translate-context="Content/*/*/Unit">MB</translate> </div> </div> </td> </tr> <tr> <td> - <translate>Total size</translate> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> </td> <td> {{ stats.media_total_size | humanSize }} @@ -295,7 +324,9 @@ <tr> <td> - <translate>Libraries</translate> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('account', object.full_username) }}"> + <translate translate-context="*/*/*/Noun">Libraries</translate> + </router-link> </td> <td> {{ stats.libraries }} @@ -303,7 +334,9 @@ </tr> <tr> <td> - <translate>Uploads</translate> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('account', object.full_username) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> </td> <td> {{ stats.uploads }} @@ -311,7 +344,7 @@ </tr> <tr> <td> - <translate>Artists</translate> + <translate translate-context="*/*/*/Noun">Artists</translate> </td> <td> {{ stats.artists }} @@ -319,7 +352,7 @@ </tr> <tr> <td> - <translate>Albums</translate> + <translate translate-context="*/*/*">Albums</translate> </td> <td> {{ stats.albums}} @@ -327,7 +360,7 @@ </tr> <tr> <td> - <translate>Tracks</translate> + <translate translate-context="*/*/*/Noun">Tracks</translate> </td> <td> {{ stats.tracks }} @@ -446,30 +479,31 @@ export default { ) } ) + }, + getQuery (field, value) { + return `${field}:"${value}"` } }, computed: { labels() { return { - statsWarning: this.$gettext("Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account"), - uploadQuota: this.$gettext( - "Determine how much content the user can upload. Leave empty to use the default value of the instance." - ), + statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account'), + uploadQuota: this.$pgettext('Content/Moderation/Help text', 'Determine how much content the user can upload. Leave empty to use the default value of the instance.'), } }, allPermissions() { return [ { code: "library", - label: this.$gettext("Library") + label: this.$pgettext('*/*/*', "Library") }, { code: "moderation", - label: this.$gettext("Moderation") + label: this.$pgettext('*/Moderation/*', "Moderation") }, { code: "settings", - label: this.$gettext("Settings") + label: this.$pgettext('*/*/*/Noun', "Settings") } ] } @@ -489,4 +523,7 @@ export default { .placeholder.segment { width: 100%; } +.ui.input input[name="quota"] { + max-width: 7em; +} </style> diff --git a/front/src/views/admin/moderation/AccountsList.vue b/front/src/views/admin/moderation/AccountsList.vue index 877c96c5ee548615fdfb4720c07a4e6b5c8af211..1505619f5754cb5c1af761475d0f6688a9e2f2f5 100644 --- a/front/src/views/admin/moderation/AccountsList.vue +++ b/front/src/views/admin/moderation/AccountsList.vue @@ -1,7 +1,7 @@ <template> <main v-title="labels.accounts"> <section class="ui vertical stripe segment"> - <h2 class="ui header"><translate>Accounts</translate></h2> + <h2 class="ui header"><translate translate-context="*/Moderation/Title">Accounts</translate></h2> <div class="ui hidden divider"></div> <accounts-table :update-url="true" :default-query="defaultQuery"></accounts-table> </section> @@ -21,7 +21,7 @@ export default { computed: { labels() { return { - accounts: this.$gettext("Accounts") + accounts: this.$pgettext('*/Moderation/Title', "Accounts") } } } diff --git a/front/src/views/admin/moderation/Base.vue b/front/src/views/admin/moderation/Base.vue index e0bf6c1adcd41f3df44526fde5bcbf8ecbb9ce4a..564debf79ae8f7dcc42abe94e073511de237f1ac 100644 --- a/front/src/views/admin/moderation/Base.vue +++ b/front/src/views/admin/moderation/Base.vue @@ -3,10 +3,10 @@ <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" - :to="{name: 'manage.moderation.domains.list'}"><translate>Domains</translate></router-link> + :to="{name: 'manage.moderation.domains.list'}"><translate translate-context="*/Moderation/*/Noun">Domains</translate></router-link> <router-link class="ui item" - :to="{name: 'manage.moderation.accounts.list'}"><translate>Accounts</translate></router-link> + :to="{name: 'manage.moderation.accounts.list'}"><translate translate-context="*/Moderation/Title">Accounts</translate></router-link> </nav> <router-view :key="$route.fullPath"></router-view> @@ -18,8 +18,8 @@ export default { computed: { labels() { return { - moderation: this.$gettext("Moderation"), - secondaryMenu: this.$gettext("Secondary menu") + moderation: this.$pgettext('*/Moderation/*', "Moderation"), + secondaryMenu: this.$pgettext('Menu/*/Hidden text', "Secondary menu") } } } diff --git a/front/src/views/admin/moderation/DomainsDetail.vue b/front/src/views/admin/moderation/DomainsDetail.vue index f5f9643c8a0eddcf65098d6076ddffc9fe99aa13..575b15f93ca2bbf696b9672cbb38e7535c8b09ab 100644 --- a/front/src/views/admin/moderation/DomainsDetail.vue +++ b/front/src/views/admin/moderation/DomainsDetail.vue @@ -14,7 +14,7 @@ {{ object.name }} <div class="sub header"> <a :href="externalUrl" target="_blank" rel="noopener noreferrer" class="logo-wrapper"> - <translate>Open website</translate> + <translate translate-context="Content/Moderation/Link/Verb">Open website</translate> <i class="external icon"></i> </a> </div> @@ -37,16 +37,16 @@ <header class="ui header"> <h3> <i class="shield icon"></i> - <translate>You don't have any rule in place for this domain.</translate> + <translate translate-context="Content/Moderation/Card.Title">You don't have any rule in place for this domain.</translate> </h3> </header> - <p><translate>Moderation policies help you control how your instance interact with a given domain or account.</translate></p> + <p><translate translate-context="Content/Moderation/Card.Paragraph">Moderation policies help you control how your instance interact with a given domain or account.</translate></p> <button @click="showPolicyForm = true" class="ui primary button">Add a moderation policy</button> </template> <instance-policy-card v-else-if="policy && !showPolicyForm" :object="policy" @update="showPolicyForm = true"> <header class="ui header"> <h3> - <translate>This domain is subject to specific moderation rules</translate> + <translate translate-context="Content/Moderation/Card.Title">This domain is subject to specific moderation rules</translate> </h3> </header> </instance-policy-card> @@ -69,62 +69,54 @@ <h3 class="ui header"> <i class="info icon"></i> <div class="content"> - <translate>Instance data</translate> + <translate translate-context="Content/Moderation/Title">Instance data</translate> </div> </h3> <table class="ui very basic table"> <tbody> <tr> <td> - <translate>First seen</translate> - </td> - <td> - <human-date :date="object.creation_date"></human-date> - </td> - </tr> - <tr> - <td> - <translate>Last checked</translate> + <translate translate-context="Content/*/Table.Label">Last checked</translate> </td> <td> <human-date v-if="object.nodeinfo_fetch_date" :date="object.nodeinfo_fetch_date"></human-date> - <translate v-else>N/A</translate> + <translate v-else translate-context="*/*/*">N/A</translate> </td> </tr> <template v-if="object.nodeinfo && object.nodeinfo.status === 'ok'"> <tr> <td> - <translate>Software</translate> + <translate translate-context="Content/Moderation/Table.Label">Software</translate> </td> <td> - {{ lodash.get(object, 'nodeinfo.payload.software.name', $gettext('N/A')) }} ({{ lodash.get(object, 'nodeinfo.payload.software.version', $gettext('N/A')) }}) + {{ lodash.get(object, 'nodeinfo.payload.software.name', $pgettext('*/*/*', 'N/A')) }} ({{ lodash.get(object, 'nodeinfo.payload.software.version', $pgettext('*/*/*', 'N/A')) }}) </td> </tr> <tr> <td> - <translate>Name</translate> + <translate translate-context="*/*/*/Noun">Name</translate> </td> <td> - {{ lodash.get(object, 'nodeinfo.payload.metadata.nodeName', $gettext('N/A')) }} + {{ lodash.get(object, 'nodeinfo.payload.metadata.nodeName', $pgettext('*/*/*', 'N/A')) }} </td> </tr> <tr> <td> - <translate>Total users</translate> + <translate translate-context="Content/*/*">Total users</translate> </td> <td> - {{ lodash.get(object, 'nodeinfo.payload.usage.users.total', $gettext('N/A')) }} + {{ lodash.get(object, 'nodeinfo.payload.usage.users.total', $pgettext('*/*/*', 'N/A')) }} </td> </tr> </template> <template v-if="object.nodeinfo && object.nodeinfo.status === 'error'"> <tr> <td> - <translate>Status</translate> + <translate translate-context="Content/Moderation/Table.Label (Value is Error message)">Status</translate> </td> <td> - <translate>Error while fetching node info</translate> + <translate translate-context="Content/Moderation/Table">Error while fetching node info</translate> <span :data-tooltip="object.nodeinfo.error"><i class="question circle icon"></i></span> </td> @@ -133,7 +125,7 @@ </tbody> </table> <ajax-button @action-done="refreshNodeInfo" method="get" :url="'manage/federation/domains/' + object.name + '/nodeinfo/'"> - <translate>Refresh node info</translate> + <translate translate-context="Content/Moderation/Button.Label/Verb">Refresh node info</translate> </ajax-button> </section> </div> @@ -142,7 +134,7 @@ <h3 class="ui header"> <i class="feed icon"></i> <div class="content"> - <translate>Activity</translate> + <translate translate-context="Content/Moderation/Title">Activity</translate> <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> </div> @@ -155,11 +147,19 @@ </div> <table v-else class="ui very basic table"> <tbody> + <tr> + <td> + <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate> + </td> + <td> + <human-date :date="object.creation_date"></human-date> + </td> + </tr> <tr> <td> <router-link :to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object.name }}"> - <translate>Known accounts</translate> + <translate translate-context="Content/Moderation/Table.Label.Link">Known accounts</translate> </router-link> </td> @@ -169,7 +169,7 @@ </tr> <tr> <td> - <translate>Emitted messages</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted messages</translate> </td> <td> {{ stats.outbox_activities}} @@ -177,7 +177,7 @@ </tr> <tr> <td> - <translate>Received library follows</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Received library follows</translate> </td> <td> {{ stats.received_library_follows}} @@ -185,7 +185,7 @@ </tr> <tr> <td> - <translate>Emitted library follows</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted library follows</translate> </td> <td> {{ stats.emitted_library_follows}} @@ -200,7 +200,7 @@ <h3 class="ui header"> <i class="music icon"></i> <div class="content"> - <translate>Audio content</translate> + <translate translate-context="Content/Moderation/Title">Audio content</translate> <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span> </div> @@ -215,7 +215,7 @@ <tbody> <tr> <td> - <translate>Cached size</translate> + <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate> </td> <td> {{ stats.media_downloaded_size | humanSize }} @@ -223,7 +223,7 @@ </tr> <tr> <td> - <translate>Total size</translate> + <translate translate-context="Content/Moderation/Table.Label">Total size</translate> </td> <td> {{ stats.media_total_size | humanSize }} @@ -231,7 +231,9 @@ </tr> <tr> <td> - <translate>Libraries</translate> + <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}"> + <translate translate-context="*/*/*/Noun">Libraries</translate> + </router-link> </td> <td> {{ stats.libraries }} @@ -239,7 +241,9 @@ </tr> <tr> <td> - <translate>Uploads</translate> + <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}"> + <translate translate-context="Content/Moderation/Table.Label/Noun">Uploads</translate> + </router-link> </td> <td> {{ stats.uploads }} @@ -247,7 +251,9 @@ </tr> <tr> <td> - <translate>Artists</translate> + <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}"> + <translate translate-context="*/*/*/Noun">Artists</translate> + </router-link> </td> <td> {{ stats.artists }} @@ -255,7 +261,9 @@ </tr> <tr> <td> - <translate>Albums</translate> + <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}"> + <translate translate-context="*/*/*">Albums</translate> + </router-link> </td> <td> {{ stats.albums}} @@ -263,7 +271,9 @@ </tr> <tr> <td> - <translate>Tracks</translate> + <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}"> + <translate translate-context="*/*/*/Noun">Tracks</translate> + </router-link> </td> <td> {{ stats.tracks }} @@ -350,12 +360,15 @@ export default { updatePolicy (policy) { this.policy = policy this.showPolicyForm = false + }, + getQuery (field, value) { + return `${field}:"${value}"` } }, computed: { labels() { return { - statsWarning: this.$gettext("Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain") + statsWarning: this.$pgettext('Content/Moderation/Help text', "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain") } }, externalUrl () { diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue index 8872c2cdcc1372f5348dd1ae95d2d96dc177ee52..3ce8e6afc44a2e653bc5ecb88a6837c3d80181d9 100644 --- a/front/src/views/admin/moderation/DomainsList.vue +++ b/front/src/views/admin/moderation/DomainsList.vue @@ -1,22 +1,22 @@ <template> <main v-title="labels.domains"> <section class="ui vertical stripe segment"> - <h2 class="ui left floated header"><translate>Domains</translate></h2> + <h2 class="ui left floated header"><translate translate-context="*/Moderation/*/Noun">Domains</translate></h2> <form class="ui right floated form" @submit.prevent="createDomain"> <div v-if="errors && errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while creating domain</translate></div> + <div class="header"><translate translate-context="Content/Moderation/Message.Title">Error while creating domain</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="inline fields"> <div class="field"> - <label for="domain"><translate>Add a domain</translate></label> + <label for="domain"><translate translate-context="Content/Moderation/Form.Label/Verb">Add a domain</translate></label> <input type="text" name="domain" id="domain" v-model="domainName"> </div> <div class="field"> <button :class="['ui', {'loading': isCreating}, 'green', 'button']" type="submit" :disabled="isCreating"> - <label for="domain"><translate>Add</translate></label> + <label for="domain"><translate translate-context="Content/Moderation/Button/Verb">Add</translate></label> </button> </div> </div> @@ -45,7 +45,7 @@ export default { computed: { labels() { return { - domains: this.$gettext("Domains") + domains: this.$pgettext('*/Moderation/*/Noun', "Domains") } } }, diff --git a/front/src/views/admin/users/Base.vue b/front/src/views/admin/users/Base.vue index 41110fb3c39c4ab5459182377f185fb9b6506f30..84d70bebb6cbc5c9afbaebbd3b6e0c25d90dd38d 100644 --- a/front/src/views/admin/users/Base.vue +++ b/front/src/views/admin/users/Base.vue @@ -3,10 +3,10 @@ <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" - :to="{name: 'manage.users.users.list'}"><translate>Users</translate></router-link> + :to="{name: 'manage.users.users.list'}"><translate translate-context="*/*/*/Noun">Users</translate></router-link> <router-link class="ui item" - :to="{name: 'manage.users.invitations.list'}"><translate>Invitations</translate></router-link> + :to="{name: 'manage.users.invitations.list'}"><translate translate-context="*/Admin/*/Noun">Invitations</translate></router-link> </nav> <router-view :key="$route.fullPath"></router-view> </div> @@ -17,8 +17,8 @@ export default { computed: { labels() { return { - manageUsers: this.$gettext("Manage users"), - secondaryMenu: this.$gettext("Secondary menu") + manageUsers: this.$pgettext('Head/Admin/Title', 'Manage users'), + secondaryMenu: this.$pgettext('Menu/*/Hidden text','Secondary menu') } } } diff --git a/front/src/views/admin/users/InvitationsList.vue b/front/src/views/admin/users/InvitationsList.vue index 04da76f8588af789a63fc7f8deacf2e0ff069bbc..5071dc6618f30149e90e6ad81614390bc8054a0f 100644 --- a/front/src/views/admin/users/InvitationsList.vue +++ b/front/src/views/admin/users/InvitationsList.vue @@ -1,7 +1,7 @@ <template> <main v-title="labels.invitations"> <section class="ui vertical stripe segment"> - <h2 class="ui header"><translate>Invitations</translate></h2> + <h2 class="ui header">{{ labels.invitations }}</h2> <invitation-form></invitation-form> <div class="ui hidden divider"></div> <invitations-table></invitations-table> @@ -21,7 +21,7 @@ export default { computed: { labels() { return { - invitations: this.$gettext("Invitations") + invitations: this.$pgettext('*/Admin/*/Noun', 'Invitations') } } } diff --git a/front/src/views/admin/users/UsersList.vue b/front/src/views/admin/users/UsersList.vue index c0ee4166cd1fd976738a1315b4bce9aa35e512c1..781a2e2d11bd500a2324c80d8afb2fd8405160d3 100644 --- a/front/src/views/admin/users/UsersList.vue +++ b/front/src/views/admin/users/UsersList.vue @@ -1,7 +1,7 @@ <template> <main v-title="labels.users"> <section class="ui vertical stripe segment"> - <h2 class="ui header"><translate>Users</translate></h2> + <h2 class="ui header">{{ labels.users }}</h2> <div class="ui hidden divider"></div> <users-table></users-table> </section> @@ -18,7 +18,7 @@ export default { computed: { labels() { return { - users: this.$gettext("Users") + users: this.$pgettext('*/*/*/Noun', 'Users') } } } diff --git a/front/src/views/auth/EmailConfirm.vue b/front/src/views/auth/EmailConfirm.vue index fd509272d813594fa153c6d0f25856dd94191a28..466a13c9fa899b15816c077882a3b524880342fe 100644 --- a/front/src/views/auth/EmailConfirm.vue +++ b/front/src/views/auth/EmailConfirm.vue @@ -2,29 +2,29 @@ <main class="main pusher" v-title="labels.confirm"> <section class="ui vertical stripe segment"> <div class="ui small text container"> - <h2><translate>Confirm your e-mail address</translate></h2> + <h2>{{ labels.confirm }}</h2> <form v-if="!success" class="ui form" @submit.prevent="submit()"> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Could not confirm your e-mail address</translate></div> + <div class="header"><translate translate-context="Content/Signup/Paragraph">Could not confirm your e-mail address</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="field"> - <label><translate>Confirmation code</translate></label> + <label><translate translate-context="Content/Signup/Form.Label">Confirmation code</translate></label> <input name="confirmation-code" type="text" required v-model="key" /> </div> <router-link :to="{path: '/login'}"> - <translate>Return to login</translate> + <translate translate-context="Content/Signup/Link/Verb">Return to login</translate> </router-link> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> - <translate>Confirm your e-mail address</translate></button> + {{ labels.confirm }}</button> </form> <div v-else class="ui positive message"> - <div class="header"><translate>E-mail address confirmed</translate></div> - <p><translate>You can now use the service without limitations.</translate></p> + <div class="header"><translate translate-context="Content/Signup/Message">E-mail address confirmed</translate></div> + <p><translate translate-context="Content/Signup/Paragraph">You can now use the service without limitations.</translate></p> <router-link :to="{name: 'login'}"> - <translate>Proceed to login</translate> + <translate translate-context="Content/Signup/Link/Verb">Proceed to login</translate> </router-link> </div> </div> @@ -48,7 +48,7 @@ export default { computed: { labels() { return { - confirm: this.$gettext("Confirm your e-mail address") + confirm: this.$pgettext('Head/Signup/Title', "Confirm your e-mail address") } } }, diff --git a/front/src/views/auth/PasswordReset.vue b/front/src/views/auth/PasswordReset.vue index 7940c170f0fcd427e3edcdbcc69c9d6b013696ed..b1aab6f855699955419f0cd29df95fb6d18fbce9 100644 --- a/front/src/views/auth/PasswordReset.vue +++ b/front/src/views/auth/PasswordReset.vue @@ -2,17 +2,17 @@ <main class="main pusher" v-title="labels.reset"> <section class="ui vertical stripe segment"> <div class="ui small text container"> - <h2><translate>Reset your password</translate></h2> + <h2><translate translate-context="*/Login/*/Verb">Reset your password</translate></h2> <form class="ui form" @submit.prevent="submit()"> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while asking for a password reset</translate></div> + <div class="header"><translate translate-context="Content/Signup/Card.Title">Error while asking for a password reset</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> - <p><translate>Use this form to request a password reset. We will send an email to the given address with instructions to reset your password.</translate></p> + <p><translate translate-context="Content/Signup/Paragraph">Use this form to request a password reset. We will send an email to the given address with instructions to reset your password.</translate></p> <div class="field"> - <label><translate>Account's email</translate></label> + <label><translate translate-context="Content/Signup/Input.Label">Account's email</translate></label> <input required ref="email" @@ -23,10 +23,10 @@ v-model="email"> </div> <router-link :to="{path: '/login'}"> - <translate>Back to login</translate> + <translate translate-context="Content/Signup/Link">Back to login</translate> </router-link> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> - <translate>Ask for a password reset</translate></button> + <translate translate-context="Content/Signup/Button.Label/Verb">Ask for a password reset</translate></button> </form> </div> </section> @@ -50,9 +50,8 @@ export default { }, computed: { labels() { - let reset = this.$gettext("Reset your password") - let placeholder = this.$gettext( - "Input the email address binded to your account" + let reset = this.$pgettext('*/Login/*/Verb', "Reset your password") + let placeholder = this.$pgettext('Content/Signup/Input.Placeholder', "Enter the email address binded to your account" ) return { reset, diff --git a/front/src/views/auth/PasswordResetConfirm.vue b/front/src/views/auth/PasswordResetConfirm.vue index df0589beb8d13d909bcefaefe4268fef91bacbaf..c2a7c677818059cccd84cc2986fdb732a18cdea3 100644 --- a/front/src/views/auth/PasswordResetConfirm.vue +++ b/front/src/views/auth/PasswordResetConfirm.vue @@ -2,34 +2,34 @@ <main class="main pusher" v-title="labels.changePassword"> <section class="ui vertical stripe segment"> <div class="ui small text container"> - <h2><translate>Change your password</translate></h2> + <h2>{{ labels.changePassword }}</h2> <form v-if="!success" class="ui form" @submit.prevent="submit()"> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error while changing your password</translate></div> + <div class="header"><translate translate-context="Content/Signup/Card.Title">Error while changing your password</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <template v-if="token && uid"> <div class="field"> - <label><translate>New password</translate></label> + <label><translate translate-context="Content/Settings/Input.Label">New password</translate></label> <password-input v-model="newPassword" /> </div> <router-link :to="{path: '/login'}"> - <translate>Back to login</translate> + <translate translate-context="Content/Signup/Link">Back to login</translate> </router-link> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> - <translate>Update your password</translate></button> + <translate translate-context="Content/Signup/Button.Label">Update your password</translate></button> </template> <template v-else> - <p><translate>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.</translate></p> + <p><translate translate-context="Content/Signup/Paragraph">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.</translate></p> </template> </form> <div v-else class="ui positive message"> - <div class="header"><translate>Password updated successfully</translate></div> - <p><translate>Your password has been updated successfully.</translate></p> + <div class="header"><translate translate-context="Content/Signup/Card.Title">Password updated successfully</translate></div> + <p><translate translate-context="Content/Signup/Card.Paragraph">Your password has been updated successfully.</translate></p> <router-link :to="{name: 'login'}"> - <translate>Proceed to login</translate> + <translate translate-context="Content/Signup/Link/Verb">Proceed to login</translate> </router-link> </div> </div> @@ -59,7 +59,7 @@ export default { computed: { labels() { return { - changePassword: this.$gettext("Change your password") + changePassword: this.$pgettext('*/Signup/Title', "Change your password") } } }, diff --git a/front/src/views/content/Base.vue b/front/src/views/content/Base.vue index 039e21adcd311edd99badb5443ac5f8a16f0f478..4e755ee1a8790c1311d7c0a542f44582758f56e2 100644 --- a/front/src/views/content/Base.vue +++ b/front/src/views/content/Base.vue @@ -3,10 +3,10 @@ <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu"> <router-link class="ui item" - :to="{name: 'content.libraries.index'}"><translate>Libraries</translate></router-link> + :to="{name: 'content.libraries.index'}"><translate translate-context="*/*/*/Noun">Libraries</translate></router-link> <router-link class="ui item" - :to="{name: 'content.libraries.files'}"><translate>Tracks</translate></router-link> + :to="{name: 'content.libraries.files'}"><translate translate-context="*/*/*/Noun">Tracks</translate></router-link> </nav> <router-view :key="$route.fullPath"></router-view> </main> @@ -15,8 +15,8 @@ export default { computed: { labels() { - let title = this.$gettext("Add content") - let secondaryMenu = this.$gettext("Secondary menu") + let title = this.$pgettext('*/Library/*/Verb', "Add content") + let secondaryMenu = this.$pgettext('Menu/*/Hidden text', "Secondary menu") return { title, secondaryMenu diff --git a/front/src/views/content/Home.vue b/front/src/views/content/Home.vue index a23a0e383a1627cffdc38d74856132ef1b7ddbd1..de8a86e256e8b3d9d4bd092c58abb9afc6131895 100644 --- a/front/src/views/content/Home.vue +++ b/front/src/views/content/Home.vue @@ -2,22 +2,22 @@ <section class="ui vertical aligned stripe segment" v-title="labels.title"> <div class="ui text container"> <h1>{{ labels.title }}</h1> - <p><translate>There are various ways to grab new content and make it available here.</translate></p> + <p><translate translate-context="Content/Library/Paragraph">There are various ways to grab new content and make it available here.</translate></p> <div class="ui segment"> - <h2><translate>Upload audio content</translate></h2> - <p><translate>Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here.</translate></p> + <h2><translate translate-context="Content/Library/Title/Verb">Upload audio content</translate></h2> + <p><translate translate-context="Content/Library/Paragraph">Upload music files (MP3, OGG, FLAC, etc.) from your personal library directly from your browser to enjoy them here.</translate></p> <p> - <strong><translate :translate-params="{quota: defaultQuota}">This instance offers up to %{quota} of storage space for every user.</translate></strong> + <strong><translate translate-context="Content/Library/Paragraph" :translate-params="{quota: defaultQuota}">This instance offers up to %{quota} of storage space for every user.</translate></strong> </p> <router-link :to="{name: 'content.libraries.index'}" class="ui green button"> - <translate>Get started</translate> + <translate translate-context="Content/Library/Button.Label/Verb">Get started</translate> </router-link> </div> <div class="ui segment"> - <h2><translate>Follow remote libraries</translate></h2> - <p><translate>You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner.</translate></p> + <h2><translate translate-context="Content/Library/Title/Verb">Follow remote libraries</translate></h2> + <p><translate translate-context="Content/Library/Paragraph">You can follow libraries from other users to get access to new music. Public libraries can be followed immediatly, while following a private library requires approval from its owner.</translate></p> <router-link :to="{name: 'content.remote.index'}" class="ui green button"> - <translate>Get started</translate> + <translate translate-context="Content/Library/Button.Label/Verb">Get started</translate> </router-link> </div> @@ -32,7 +32,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Add and manage content") + title: this.$pgettext('Content/Library/Title/Verb', "Add and manage content") } }, defaultQuota() { diff --git a/front/src/views/content/libraries/Card.vue b/front/src/views/content/libraries/Card.vue index 13523b198fcb76080825b8b87790ecd12430b721..4e1b497df25fa85b98bf9ad514782b601765c770 100644 --- a/front/src/views/content/libraries/Card.vue +++ b/front/src/views/content/libraries/Card.vue @@ -6,19 +6,19 @@ <span v-if="library.privacy_level === 'me'" class="right floated" - :data-tooltip="labels.tooltips.me"> + :data-tooltip="privacy_tooltips('me')"> <i class="small lock icon"></i> </span> <span v-else-if="library.privacy_level === 'instance'" class="right floated" - :data-tooltip="labels.tooltips.instance"> + :data-tooltip="privacy_tooltips('instance')"> <i class="small circle outline icon"></i> </span> <span v-else-if="library.privacy_level === 'everyone'" class="right floated" - :data-tooltip="labels.tooltips.everyone"> + :data-tooltip="privacy_tooltips('everyone')"> <i class="small globe icon"></i> </span> </div> @@ -29,46 +29,44 @@ </span> </div> <div class="description"> + {{ library.description }} <div class="ui hidden divider"></div> </div> <div class="content"> - <span v-if="library.size" class="right floated" :data-tooltip="labels.tooltips.size"> + <span v-if="library.size" class="right floated" :data-tooltip="size_label"> <i class="database icon"></i> {{ library.size | humanSize }} </span> <i class="music icon"></i> - <translate :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate> + <translate translate-context="*/*/*" :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate> </div> </div> <div class="ui bottom basic attached buttons"> <router-link :to="{name: 'content.libraries.detail.upload', params: {id: library.uuid}}" class="ui button"> - <translate>Upload</translate> + <translate translate-context="Content/Library/Card.Button.Label/Verb">Upload</translate> </router-link> <router-link :to="{name: 'content.libraries.detail', params: {id: library.uuid}}" exact class="ui button"> - <translate>Detail</translate> + <translate translate-context="Content/Library/Card.Button.Label/Noun">Details</translate> </router-link> </div> </div> </template> + <script> +import TranslationsMixin from '@/components/mixins/Translations' + export default { + mixins: [TranslationsMixin], props: ['library'], + methods: { + privacy_tooltips (level) { + return 'Visibility: ' + this.sharedLabels.fields.privacy_level.choices[level].toLowerCase() + }, + }, computed: { - labels () { - let me = this.$gettext('Visibility: nobody except me') - let instance = this.$gettext('Visibility: everyone on this instance') - let everyone = this.$gettext('Visibility: everyone, including other instances') - let size = this.$gettext('Total size of the files in this library') - - return { - tooltips: { - me, - instance, - everyone, - size - } - } - } + size_label () { + return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library') + }, } } </script> diff --git a/front/src/views/content/libraries/Detail.vue b/front/src/views/content/libraries/Detail.vue index f0bbc2a6e791abdbc54764a851b68e499ed8e019..d879ab71c7731ae9e1aae82f0f3fc2197675267c 100644 --- a/front/src/views/content/libraries/Detail.vue +++ b/front/src/views/content/libraries/Detail.vue @@ -1,33 +1,33 @@ <template> <section class="ui vertical aligned stripe segment"> <div v-if="isLoadingLibrary" :class="['ui', {'active': isLoadingLibrary}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading library data…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading library data…</translate></div> </div> <detail-area v-else :library="library"> <div class="ui top attached tabular menu"> - <a :class="['item', {active: currentTab === 'follows'}]" @click="currentTab = 'follows'"><translate>Followers</translate></a> - <a :class="['item', {active: currentTab === 'tracks'}]" @click="currentTab = 'tracks'"><translate>Tracks</translate></a> - <a :class="['item', {active: currentTab === 'edit'}]" @click="currentTab = 'edit'"><translate>Edit</translate></a> + <a :class="['item', {active: currentTab === 'follows'}]" @click="currentTab = 'follows'"><translate translate-context="Content/Federation/*/Noun">Followers</translate></a> + <a :class="['item', {active: currentTab === 'tracks'}]" @click="currentTab = 'tracks'"><translate translate-context="*/*/*/Noun">Tracks</translate></a> + <a :class="['item', {active: currentTab === 'edit'}]" @click="currentTab = 'edit'"><translate translate-context="Content/*/Button.Label/Verb">Edit</translate></a> </div> <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'follows'}]"> <div class="ui form"> <div class="field"> - <label><translate>Sharing link</translate></label> - <p><translate>Share this link with other users so they can request access to your library.</translate></p> + <label><translate translate-context="Content/Library/Title">Sharing link</translate></label> + <p><translate translate-context="Content/Library/Paragraph">Share this link with other users so they can request access to your library.</translate></p> <copy-input :value="library.fid" /> </div> </div> <div class="ui hidden divider"></div> <div v-if="isLoadingFollows" :class="['ui', {'active': isLoadingFollows}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading followers…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading followers…</translate></div> </div> <table v-else-if="follows && follows.count > 0" class="ui table"> <thead> <tr> - <th><translate>User</translate></th> - <th><translate>Date</translate></th> - <th><translate>Status</translate></th> - <th><translate>Action</translate></th> + <th><translate translate-context="Content/Library/Table.Label">User</translate></th> + <th><translate translate-context="Content/Library/Table.Label">Date</translate></th> + <th><translate translate-context="Content/Library.Federation/Table.Label (Value is Approved/Rejected)">Status</translate></th> + <th><translate translate-context="Content/Library/Table.Label">Action</translate></th> </tr> </thead> <tr v-for="follow in follows.results" :key="follow.fid"> @@ -35,27 +35,27 @@ <td><human-date :date="follow.creation_date" /></td> <td> <span :class="['ui', 'yellow', 'basic', 'label']" v-if="follow.approved === null"> - <translate>Pending approval</translate> + <translate translate-context="Content/Library/Table/Short">Pending approval</translate> </span> <span :class="['ui', 'green', 'basic', 'label']" v-else-if="follow.approved === true"> - <translate>Accepted</translate> + <translate translate-context="Content/Library/Table/Short">Accepted</translate> </span> <span :class="['ui', 'red', 'basic', 'label']" v-else-if="follow.approved === false"> - <translate>Rejected</translate> + <translate translate-context="Content/Library/*/Short">Rejected</translate> </span> </td> <td> <div @click="updateApproved(follow, true)" :class="['ui', 'mini', 'icon', 'labeled', 'green', 'button']" v-if="follow.approved === null || follow.approved === false"> - <i class="ui check icon"></i> <translate>Accept</translate> + <i class="ui check icon"></i> <translate translate-context="Content/Library/Button.Label">Accept</translate> </div> <div @click="updateApproved(follow, false)" :class="['ui', 'mini', 'icon', 'labeled', 'red', 'button']" v-if="follow.approved === null || follow.approved === true"> - <i class="ui x icon"></i> <translate>Reject</translate> + <i class="ui x icon"></i> <translate translate-context="Content/Library/Button.Label">Reject</translate> </div> </td> </tr> </table> - <p v-else><translate>Nobody is following this library</translate></p> + <p v-else><translate translate-context="Content/Library/Paragraph">Nobody is following this library</translate></p> </div> <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'tracks'}]"> <library-files-table :filters="{library: library.uuid}"></library-files-table> diff --git a/front/src/views/content/libraries/DetailArea.vue b/front/src/views/content/libraries/DetailArea.vue index eb481ea881a4d0089371182866d59b89d5948cdd..0a73c90b927589c55bd1353a399febd67bb2309e 100644 --- a/front/src/views/content/libraries/DetailArea.vue +++ b/front/src/views/content/libraries/DetailArea.vue @@ -1,8 +1,8 @@ <template> <div> - <div class="ui stackable grid"> - <div class="five wide column"> - <h3 class="ui header"><translate>Current library</translate></h3> + <div class="ui two column row"> + <div class="column"> + <h3 class="ui header"><translate translate-context="Content/Library/Title">Current library</translate></h3> <library-card :library="library" /> </div> </div> @@ -21,7 +21,7 @@ export default { }, computed: { links () { - let upload = this.$gettext('Upload') + let upload = this.$pgettext('Content/Library/Card.Button.Label/Verb', 'Upload') return [ { name: 'libraries.detail.upload', diff --git a/front/src/views/content/libraries/FilesTable.vue b/front/src/views/content/libraries/FilesTable.vue index dcdd4b87d2312d0acecfc35222b0c6d1e5824fb0..9ea4634adcb6360dbc5e8c454405b6deeac11527 100644 --- a/front/src/views/content/libraries/FilesTable.vue +++ b/front/src/views/content/libraries/FilesTable.vue @@ -3,23 +3,23 @@ <div class="ui inline form"> <div class="fields"> <div class="ui six wide field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <form @submit.prevent="search.query = $refs.search.value"> <input name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" /> </form> </div> <div class="field"> - <label><translate>Import status</translate></label> + <label><translate translate-context="Content/Library/*/Noun">Import status</translate></label> <select class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')"> - <option value=""><translate>All</translate></option> - <option value="pending"><translate>Pending</translate></option> - <option value="skipped"><translate>Skipped</translate></option> - <option value="errored"><translate>Failed</translate></option> - <option value="finished"><translate>Finished</translate></option> + <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option> + <option value="pending"><translate translate-context="Content/Library/*/Short">Pending</translate></option> + <option value="skipped"><translate translate-context="Content/Library/*">Skipped</translate></option> + <option value="errored"><translate translate-context="Content/Library/Dropdown">Failed</translate></option> + <option value="finished"><translate translate-context="Content/Library/*">Finished</translate></option> </select> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -27,14 +27,15 @@ </select> </div> <div class="field"> - <label><translate>Ordering direction</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> </div> </div> + <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" /> <div class="dimmable"> <div v-if="isLoading" class="ui active inverted dimmer"> <div class="ui loader"></div> @@ -52,13 +53,13 @@ @refresh="fetchData" :filters="actionFilters"> <template slot="header-cells"> - <th><translate>Title</translate></th> - <th><translate>Artist</translate></th> - <th><translate>Album</translate></th> - <th><translate>Upload date</translate></th> - <th><translate>Import status</translate></th> - <th><translate>Duration</translate></th> - <th><translate>Size</translate></th> + <th><translate translate-context="Content/Track/*/Noun">Title</translate></th> + <th><translate translate-context="*/*/*/Noun">Artist</translate></th> + <th><translate translate-context="*/*/*">Album</translate></th> + <th><translate translate-context="*/*/*/Noun">Upload date</translate></th> + <th><translate translate-context="Content/Library/*/Noun">Import status</translate></th> + <th><translate translate-context="Content/*/*">Duration</translate></th> + <th><translate translate-context="Content/Library/*/in MB">Size</translate></th> </template> <template slot="row-cells" slot-scope="scope"> <template v-if="scope.obj.track"> @@ -80,23 +81,25 @@ <td> <human-date :date="scope.obj.creation_date"></human-date> </td> - <td :title="labels.importStatuses[scope.obj.import_status].help"> - <span class="discrete link" @click="addSearchToken('status', scope.obj.import_status)"> - {{ labels.importStatuses[scope.obj.import_status].label }} - <i class="question circle outline icon"></i> + <td> + <span class="discrete link" @click="addSearchToken('status', scope.obj.import_status)" :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"> + {{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }} </span> + <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = scope.obj; showUploadDetailModal = true"> + <i class="question circle outline icon"></i> + </button> </td> <td v-if="scope.obj.duration"> {{ time.parse(scope.obj.duration) }} </td> <td v-else> - <translate>N/A</translate> + <translate translate-context="*/*/*">N/A</translate> </td> <td v-if="scope.obj.size"> {{ scope.obj.size | humanSize }} </td> <td v-else> - <translate>N/A</translate> + <translate translate-context="*/*/*">N/A</translate> </td> </template> </action-table> @@ -112,7 +115,7 @@ ></pagination> <span v-if="result && result.results.length > 0"> - <translate + <translate translate-context="Content/*/Paragraph" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"> Showing results %{ start }-%{ end } on %{ total } </translate> @@ -132,6 +135,7 @@ import ActionTable from '@/components/common/ActionTable' import OrderingMixin from '@/components/mixins/Ordering' import TranslationsMixin from '@/components/mixins/Translations' import SmartSearchMixin from '@/components/mixins/SmartSearch' +import ImportStatusModal from '@/components/library/ImportStatusModal' export default { mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin], @@ -142,11 +146,14 @@ export default { }, components: { Pagination, - ActionTable + ActionTable, + ImportStatusModal }, data () { return { time, + detailedUpload: null, + showUploadDetailModal: false, isLoading: false, result: null, page: 1, @@ -159,7 +166,7 @@ export default { ordering: 'creation_date', orderingOptions: [ ['creation_date', 'creation_date'], - ['title', 'title'], + ['title', 'track_title'], ['size', 'size'], ['duration', 'duration'], ['bitrate', 'bitrate'], @@ -193,30 +200,12 @@ export default { }, selectPage: function (page) { this.page = page - } + }, }, computed: { labels () { return { - searchPlaceholder: this.$gettext('Search by title, artist, album…'), - importStatuses: { - skipped: { - label: this.$gettext('Skipped'), - help: this.$gettext('Track already present in one of your libraries'), - }, - pending: { - label: this.$gettext('Pending'), - help: this.$gettext('Track uploaded, but not processed by the server yet'), - }, - errored: { - label: this.$gettext('Errored'), - help: this.$gettext('Could not process this track, ensure it is tagged correctly'), - }, - finished: { - label: this.$gettext('Finished'), - help: this.$gettext('Imported'), - }, - } + searchPlaceholder: this.$pgettext('Content/Library/Input.Placeholder', 'Search by title, artist, album…'), } }, actionFilters () { @@ -230,8 +219,8 @@ export default { } }, actions () { - let deleteMsg = this.$gettext('Delete') - let relaunchMsg = this.$gettext('Relaunch import') + let deleteMsg = this.$pgettext('*/*/*/Verb', 'Delete') + let relaunchMsg = this.$pgettext('Content/Library/Dropdown/Verb', 'Restart import') return [ { name: 'delete', diff --git a/front/src/views/content/libraries/Form.vue b/front/src/views/content/libraries/Form.vue index d197e4d5b9c11ef0c699649d0ae8d517fe5a6d98..93530df41b875cf80c61883eaad6389b92dbc8c0 100644 --- a/front/src/views/content/libraries/Form.vue +++ b/front/src/views/content/libraries/Form.vue @@ -1,52 +1,54 @@ <template> <form class="ui form" @submit.prevent="submit"> - <p v-if="!library"><translate>Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.</translate></p> + <p v-if="!library"><translate translate-context="Content/Library/Paragraph">Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.</translate></p> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Error</translate></div> + <div class="header"><translate translate-context="Content/*/Error message.Title">Error</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="required field"> - <label><translate>Name</translate></label> + <label><translate translate-context="*/*/*/Noun">Name</translate></label> <input name="name" v-model="currentName" :placeholder="labels.namePlaceholder" required maxlength="100"> </div> <div class="field"> - <label><translate>Description</translate></label> + <label><translate translate-context="Content/*/Input.Label/Noun">Description</translate></label> <textarea v-model="currentDescription" :placeholder="labels.descriptionPlaceholder" maxlength="2000"></textarea> </div> <div class="field"> - <label><translate>Visibility</translate></label> - <p><translate>You are able to share your library with other people, regardless of its visibility.</translate></p> + <label><translate translate-context="Content/Library/Dropdown.Label">Visibility</translate></label> + <p><translate translate-context="Content/Library/Paragraph">You are able to share your library with other people, regardless of its visibility.</translate></p> <select class="ui dropdown" v-model="currentVisibilityLevel"> - <option :value="c" v-for="c in ['me', 'instance', 'everyone']">{{ labels.visibility[c] }}</option> + <option :value="c" v-for="c in ['me', 'instance', 'everyone']">{{ sharedLabels.fields.privacy_level.choices[c] }}</option> </select> </div> <button class="ui submit button" type="submit"> - <translate v-if="library">Update library</translate> - <translate v-else>Create library</translate> + <translate translate-context="Content/Library/Button.Label/Verb" v-if="library">Update library</translate> + <translate translate-context="Content/Library/Button.Label/Verb" v-else>Create library</translate> </button> <dangerous-button v-if="library" class="right floated basic button" color='red' @confirm="remove()"> - <translate>Delete</translate> + <translate translate-context="*/*/*/Verb">Delete</translate> <p slot="modal-header"> - <translate>Delete this library?</translate> + <translate translate-context="Popup/Library/Title">Delete this library?</translate> </p> <p slot="modal-content"> - <translate> + <translate translate-context="Popup/Library/Paragraph"> The library and all its tracks will be deleted. This can not be undone. </translate> </p> - <p slot="modal-confirm"> - <translate>Delete library</translate> - </p> + <div slot="modal-confirm"> + <translate translate-context="Popup/Library/Button.Label/Verb">Delete library</translate> + </div> </dangerous-button> </form> </template> <script> import axios from 'axios' +import MixinsTranslation from '@/components/mixins/Translations.vue' export default { + mixins: [MixinsTranslation], props: ['library'], data () { let d = { @@ -67,19 +69,11 @@ export default { }, computed: { labels () { - let namePlaceholder = this.$gettext('My awesome library') - let descriptionPlaceholder = this.$gettext('This library contains my personal music, I hope you like it.') - let me = this.$gettext('Nobody except me') - let instance = this.$gettext('Everyone on this instance') - let everyone = this.$gettext('Everyone, across all instances') + let namePlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'My awesome library') + let descriptionPlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'This library contains my personal music, I hope you like it.') return { namePlaceholder, descriptionPlaceholder, - visibility: { - me, - instance, - everyone - } } } }, @@ -103,10 +97,10 @@ export default { let msg if (self.library) { self.$emit('updated', response.data) - msg = this.$gettext('Library updated') + msg = this.$pgettext('Content/Library/Message', 'Library updated') } else { self.$emit('created', response.data) - msg = this.$gettext('Library created') + msg = this.$pgettext('Content/Library/Message', 'Library created') } self.$store.commit('ui/addMessage', { content: msg, @@ -126,7 +120,7 @@ export default { let self = this axios.delete(`libraries/${this.library.uuid}/`).then((response) => { self.isLoading = false - let msg = this.$gettext('Library deleted') + let msg = this.$pgettext('Content/Library/Message', 'Library deleted') self.$emit('deleted', {}) self.$store.commit('ui/addMessage', { content: msg, diff --git a/front/src/views/content/libraries/Home.vue b/front/src/views/content/libraries/Home.vue index f6eafc711ab23c331e3f42c88e09a0a072ce4a5a..69b4ce3a2863ed59aad101f3ccc2881c968bbdda 100644 --- a/front/src/views/content/libraries/Home.vue +++ b/front/src/views/content/libraries/Home.vue @@ -1,18 +1,18 @@ <template> <section class="ui vertical aligned stripe segment"> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading Libraries…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading Libraries…</translate></div> </div> <div v-else class="ui text container"> - <h1 class="ui header"><translate>My libraries</translate></h1> + <h1 class="ui header"><translate translate-context="Content/Library/Title">My libraries</translate></h1> <p v-if="libraries.length == 0"> - <translate>Looks like you don't have a library, it's time to create one.</translate> + <translate translate-context="Content/Library/Paragraph">Looks like you don't have a library, it's time to create one.</translate> </p> <a @click="hiddenForm = !hiddenForm"> <i class="plus icon" v-if="hiddenForm" /> <i class="minus icon" v-else /> - <translate>Create a new library</translate> + <translate translate-context="Content/Library/Link/Verb">Create a new library</translate> </a> <library-form :library="null" v-if="!hiddenForm" @created="libraryCreated" /> <div class="ui hidden divider"></div> diff --git a/front/src/views/content/libraries/Quota.vue b/front/src/views/content/libraries/Quota.vue index ba747f0e664b37857829896829e58bcb74ec7ccf..687a825cf78e1d89b5770c98a7cfced695cead7e 100644 --- a/front/src/views/content/libraries/Quota.vue +++ b/front/src/views/content/libraries/Quota.vue @@ -1,15 +1,15 @@ <template> <div class="ui segment"> - <h3 class="ui header"><translate>Current usage</translate></h3> + <h3 class="ui header"><translate translate-context="Content/Library/Title">Current usage</translate></h3> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading usage data…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading usage data…</translate></div> </div> <div :class="['ui', {'success': progress < 60}, {'yellow': progress >= 60 && progress < 96}, {'error': progress >= 95}, 'progress']"> <div class="bar" :style="{width: `${progress}%`}"> <div class="progress">{{ progress }}%</div> </div> <div class="label" v-if="quotaStatus"> - <translate :translate-params="{max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}">%{ current } used on %{ max } allowed</translate> + <translate translate-context="Content/Library/Paragraph" :translate-params="{max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}">%{ current } used on %{ max } allowed</translate> </div> </div> <div class="ui hidden divider"></div> @@ -20,24 +20,24 @@ {{ humanSize(quotaStatus.pending * 1000 * 1000) }} </div> <div class="label"> - <translate>Pending files</translate> + <translate translate-context="Content/Library/Label">Pending files</translate> </div> </div> <div> <router-link class="ui basic blue tiny button" :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'pending'}])}}"> - <translate>View files</translate> + <translate translate-context="Content/Library/Link/Verb">View files</translate> </router-link> <dangerous-button color="grey" class="basic tiny" :action="purgePendingFiles"> - <translate>Purge</translate> - <p slot="modal-header"><translate>Purge pending files?</translate></p> - <p slot="modal-content"><translate>Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.</translate></p> - <p slot="modal-confirm"><translate>Purge</translate></p> + <translate translate-context="*/*/*/Verb">Purge</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge pending files?</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div> </dangerous-button> </div> </div> @@ -47,23 +47,23 @@ {{ humanSize(quotaStatus.skipped * 1000 * 1000) }} </div> <div class="label"> - <translate>Skipped files</translate> + <translate translate-context="Content/Library/Label">Skipped files</translate> </div> </div> <div> <router-link class="ui basic blue tiny button" :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'skipped'}])}}"> - <translate>View files</translate> + <translate translate-context="Content/Library/Link/Verb">View files</translate> </router-link> <dangerous-button color="grey" class="basic tiny" :action="purgeSkippedFiles"> - <translate>Purge</translate> - <p slot="modal-header"><translate>Purge skipped files?</translate></p> - <p slot="modal-content"><translate>Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.</translate></p> - <p slot="modal-confirm"><translate>Purge</translate></p> + <translate translate-context="*/*/*/Verb">Purge</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge skipped files?</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div> </dangerous-button> </div> </div> @@ -73,23 +73,23 @@ {{ humanSize(quotaStatus.errored * 1000 * 1000) }} </div> <div class="label"> - <translate>Errored files</translate> + <translate translate-context="Content/Library/Label">Errored files</translate> </div> </div> <div> <router-link class="ui basic blue tiny button" :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'errored'}])}}"> - <translate>View files</translate> + <translate translate-context="Content/Library/Link/Verb">View files</translate> </router-link> <dangerous-button color="grey" class="basic tiny" :action="purgeErroredFiles"> - <translate>Purge</translate> - <p slot="modal-header"><translate>Purge errored files?</translate></p> - <p slot="modal-content"><translate>Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota.</translate></p> - <p slot="modal-confirm"><translate>Purge</translate></p> + <translate translate-context="*/*/*/Verb">Purge</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge errored files?</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota.</translate></p> + <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div> </dangerous-button> </div> </div> diff --git a/front/src/views/content/libraries/Upload.vue b/front/src/views/content/libraries/Upload.vue index 8547aa570a3548cbd7072fb26f84ed59f554b1b5..5fc7234ecd03fe82dd3019ba18d2997c55673aeb 100644 --- a/front/src/views/content/libraries/Upload.vue +++ b/front/src/views/content/libraries/Upload.vue @@ -1,7 +1,7 @@ <template> <div class="ui vertical aligned stripe segment"> <div v-if="isLoadingLibrary" :class="['ui', {'active': isLoadingLibrary}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading library data…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading library data…</translate></div> </div> <detail-area v-else :library="library"> <file-upload ref="fileupload" :default-import-reference="defaultImportReference" :library="library" /> diff --git a/front/src/views/content/remote/Card.vue b/front/src/views/content/remote/Card.vue index 364783f3eae57fcd94b6b8282c2fdd0dd1e7cb4a..b78f7f220af0b02438d87740e3ab3c1d2d7981be 100644 --- a/front/src/views/content/remote/Card.vue +++ b/front/src/views/content/remote/Card.vue @@ -22,46 +22,50 @@ <human-date :date="library.creation_date" /> </span> </div> + <div class="description"> + {{ library.description }} + <div class="ui hidden divider"></div> + </div> <div class="meta"> <i class="music icon"></i> - <translate :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate> + <translate translate-context="*/*/*" :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate> </div> <div v-if="displayScan && latestScan" class="meta"> <template v-if="latestScan.status === 'pending'"> <i class="hourglass icon"></i> - <translate>Scan waiting</translate> + <translate translate-context="Content/Library/Card.List item">Scan pending</translate> </template> <template v-if="latestScan.status === 'scanning'"> <i class="loading spinner icon"></i> - <translate :translate-params="{progress: scanProgress}">Scanning… (%{ progress }%)</translate> + <translate translate-context="Content/Library/Card.List item" :translate-params="{progress: scanProgress}">Scanning… (%{ progress }%)</translate> </template> <template v-else-if="latestScan.status === 'errored'"> <i class="red download icon"></i> - <translate>Problem during scanning</translate> + <translate translate-context="Content/Library/Card.List item">Problem during scanning</translate> </template> <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files === 0"> <i class="green download icon"></i> - <translate>Scanned</translate> + <translate translate-context="Content/Library/Card.List item">Scanned</translate> </template> <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files > 0"> <i class="yellow download icon"></i> - <translate>Scanned with errors</translate> + <translate translate-context="Content/Library/Card.List item">Scanned with errors</translate> </template> <span class="link right floated" @click="showScan = !showScan"> - <translate>Details</translate> + <translate translate-context="Content/Library/Card.Button.Label/Noun">Details</translate> <i v-if="showScan" class="angle down icon" /> <i v-else class="angle right icon" /> </span> <div v-if="showScan"> <template v-if="latestScan.modification_date"> - <translate>Last update:</translate><human-date :date="latestScan.modification_date" /><br /> + <translate translate-context="Content/Library/Card.List item/Noun">Last update:</translate><human-date :date="latestScan.modification_date" /><br /> </template> - <translate>Failed tracks:</translate> {{ latestScan.errored_files }} + <translate translate-context="Content/Library/Card.List item/Noun">Failed tracks:</translate> {{ latestScan.errored_files }} </div> </div> <div v-if="displayScan && canLaunchScan" class="clearfix"> <span class="right floated link" @click="launchScan"> - <translate>Scan now</translate> <i class="paper plane icon" /> + <translate translate-context="Content/Library/Card.Button.Label/Verb">Scan now</translate> <i class="paper plane icon" /> </span> </div> </div> @@ -71,40 +75,46 @@ <div v-if="displayCopyFid" class="extra content"> <div class="ui form"> <div class="field"> - <label><translate>Sharing link</translate></label> + <label><translate translate-context="Content/Library/Title">Sharing link</translate></label> <copy-input :button-classes="'basic'" :value="library.fid" /> </div> </div> </div> - <div v-if="displayFollow" class="ui bottom attached buttons"> + <div v-if="displayFollow" :class="['ui', 'bottom', {two: library.follow}, 'attached', 'buttons']"> <button v-if="!library.follow" @click="follow()" :class="['ui', 'green', {'loading': isLoadingFollow}, 'button']"> - <translate>Follow</translate> - </button> - <button - v-else-if="!library.follow.approved" - class="ui disabled button"><i class="hourglass icon"></i> - <translate>Follow request pending approval</translate> + <translate translate-context="Content/Library/Card.Button.Label/Verb">Follow</translate> </button> - <button - v-else-if="!library.follow.approved" - class="ui disabled button"><i class="check icon"></i> - <translate>Following</translate> - </button> - <dangerous-button - v-else-if="library.follow.approved" - color="" - :class="['ui', 'button']" - :action="unfollow"> - <translate>Unfollow</translate> - <p slot="modal-header"><translate>Unfollow this library?</translate></p> - <div slot="modal-content"> - <p><translate>By unfollowing this library, you loose access to its content.</translate></p> - </div> - <p slot="modal-confirm"><translate>Unfollow</translate></p> - </dangerous-button> + <template v-else-if="!library.follow.approved"> + <button + class="ui disabled button"><i class="hourglass icon"></i> + <translate translate-context="Content/Library/Card.Paragraph">Follow request pending approval</translate> + </button> + <button + @click="unfollow" + class="ui button"> + <translate translate-context="Content/Library/Card.Paragraph">Cancel follow request</translate> + </button> + </template> + <template v-else-if="library.follow.approved"> + <button + class="ui disabled button"><i class="check icon"></i> + <translate translate-context="Content/Library/Card.Paragraph">Following</translate> + </button> + <dangerous-button + color="" + :class="['ui', 'button']" + :action="unfollow"> + <translate translate-context="*/Library/Button.Label/Verb">Unfollow</translate> + <p slot="modal-header"><translate translate-context="Popup/Library/Title">Unfollow this library?</translate></p> + <div slot="modal-content"> + <p><translate translate-context="Popup/Library/Paragraph">By unfollowing this library, you loose access to its content.</translate></p> + </div> + <div slot="modal-confirm"><translate translate-context="*/Library/Button.Label/Verb">Unfollow</translate></div> + </dangerous-button> + </template> </div> </div> </template> @@ -128,8 +138,8 @@ export default { }, computed: { labels () { - let me = this.$gettext('This library is private and your approval from its owner is needed to access its content') - let everyone = this.$gettext('This library is public and you can access its content freely') + let me = this.$pgettext('Content/Library/Card.Help text', 'This library is private and your approval from its owner is needed to access its content') + let everyone = this.$pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely') return { tooltips: { @@ -162,8 +172,8 @@ export default { methods: { launchScan () { let self = this - let successMsg = this.$gettext('Scan launched') - let skippedMsg = this.$gettext('Scan skipped (previous scan is too recent)') + let successMsg = this.$pgettext('Content/Library/Message', 'Scan launched') + let skippedMsg = this.$pgettext('Content/Library/Message', 'Scan skipped (previous scan is too recent)') axios.post(`federation/libraries/${this.library.uuid}/scan/`).then((response) => { let msg if (response.data.status == 'skipped') { @@ -195,6 +205,7 @@ export default { this.isLoadingFollow = true axios.delete(`federation/follows/library/${this.library.follow.uuid}/`).then((response) => { self.$emit('deleted') + self.library.follow = null self.isLoadingFollow = false }, error => { self.isLoadingFollow = false diff --git a/front/src/views/content/remote/Home.vue b/front/src/views/content/remote/Home.vue index 007fbadcaa51a84a54c44d6d53cef852a3538733..35d56cd95c2642f4b480353739a915020ee51fcf 100644 --- a/front/src/views/content/remote/Home.vue +++ b/front/src/views/content/remote/Home.vue @@ -1,19 +1,19 @@ <template> <div class="ui vertical aligned stripe segment"> <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"> - <div class="ui text loader"><translate>Loading remote libraries…</translate></div> + <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading remote libraries…</translate></div> </div> <div v-else class="ui text container"> - <h1 class="ui header"><translate>Remote libraries</translate></h1> - <p><translate>Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.</translate></p> + <h1 class="ui header"><translate translate-context="Content/Library/Title/Noun">Remote libraries</translate></h1> + <p><translate translate-context="Content/Library/Paragraph">Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.</translate></p> <scan-form @scanned="scanResult = $event"></scan-form> <div class="ui hidden divider"></div> <div v-if="scanResult && scanResult.results.length > 0" class="ui two cards"> <library-card :library="library" v-for="library in scanResult.results" :key="library.fid" /> </div> <template v-if="existingFollows && existingFollows.count > 0"> - <h2><translate>Known libraries</translate></h2> - <i @click="fetch()" :class="['ui', 'circular', 'refresh', 'icon']" /> <translate>Refresh</translate> + <h2><translate translate-context="Content/Library/Title">Known libraries</translate></h2> + <i @click="fetch()" :class="['ui', 'circular', 'refresh', 'icon']" /> <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate> <div class="ui hidden divider"></div> <div class="ui two cards"> <library-card diff --git a/front/src/views/content/remote/ScanForm.vue b/front/src/views/content/remote/ScanForm.vue index dc1b2b78b730a98ed8a7940e69c135a863390389..5c68bf130872da28041ac950a25fe9a063f8280b 100644 --- a/front/src/views/content/remote/ScanForm.vue +++ b/front/src/views/content/remote/ScanForm.vue @@ -1,13 +1,13 @@ <template> <form class="ui form" @submit.prevent="scan"> <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate>Could not fetch remote library</translate></div> + <div class="header"><translate translate-context="Content/Library/Error message.Title">Could not fetch remote library</translate></div> <ul class="list"> <li v-for="error in errors">{{ error }}</li> </ul> </div> <div class="ui field"> - <label><translate>Search a remote library</translate></label> + <label><translate translate-context="Content/Library/Input.Label/Verb">Search a remote library</translate></label> <div :class="['ui', 'action', {loading: isLoading}, 'input']"> <input name="url" v-model="query" :placeholder="labels.placeholder" type="url"> <button type="submit" class="ui icon button"> @@ -47,7 +47,7 @@ export default { }, computed: { labels () { - let placeholder = this.$gettext('Enter a library URL') + let placeholder = this.$pgettext('Content/Library/Input.Placeholder', 'Enter a library URL') return { placeholder } diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue index 69639bd18b54c968ce4a75c0d5392ef31f0ae9bc..6fe7a4c8a1874d588b30363209aaf62ed4fdfcb0 100644 --- a/front/src/views/playlists/Detail.vue +++ b/front/src/views/playlists/Detail.vue @@ -13,7 +13,8 @@ <translate translate-plural="Playlist containing %{ count } tracks, by %{ username }" :translate-n="playlist.tracks_count" - :translate-params="{count: playlist.tracks_count, username: playlist.user.username}"> + :translate-params="{count: playlist.tracks_count, username: playlist.user.username}" + translate-context="Content/Playlist/Header.Subtitle"> Playlist containing %{ count } track, by %{ username } </translate><br> <duration :seconds="playlist.duration" /> @@ -21,22 +22,22 @@ </div> </h2> <div class="ui hidden divider"></div> - <play-button class="orange" :is-playable="playlist.is_playable" :tracks="tracks"><translate>Play all</translate></play-button> + <play-button class="orange" :is-playable="playlist.is_playable" :tracks="tracks"><translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate></play-button> <button - class="ui icon button" + class="ui icon labeled button" v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id" @click="edit = !edit"> <i class="pencil icon"></i> - <template v-if="edit"><translate>End edition</translate></template> - <template v-else><translate>Edit…</translate></template> + <template v-if="edit"><translate translate-context="Content/Playlist/Button.Label/Verb">End edition</translate></template> + <template v-else><translate translate-context="Content/*/Button.Label/Verb">Edit</translate></template> </button> <dangerous-button v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id" class="labeled icon" :action="deletePlaylist"> - <i class="trash icon"></i> <translate>Delete</translate> - <p slot="modal-header" v-translate="{playlist: playlist.name}" :translate-params="{playlist: playlist.name}"> + <i class="trash icon"></i> <translate translate-context="*/*/*/Verb">Delete</translate> + <p slot="modal-header" v-translate="{playlist: playlist.name}" translate-context="Popup/Playlist/Title/Call to action" :translate-params="{playlist: playlist.name}"> Do you want to delete the playlist "%{ playlist }"? </p> - <p slot="modal-content"><translate>This will completely delete this playlist and cannot be undone.</translate></p> - <p slot="modal-confirm"><translate>Delete playlist</translate></p> + <p slot="modal-content"><translate translate-context="Popup/Playlist/Paragraph">This will completely delete this playlist and cannot be undone.</translate></p> + <div slot="modal-confirm"><translate translate-context="Popup/Playlist/Button.Label/Verb">Delete playlist</translate></div> </dangerous-button> </div> </section> @@ -48,7 +49,7 @@ :playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor> </template> <template v-else> - <h2><translate>Tracks</translate></h2> + <h2><translate translate-context="*/*/*/Noun">Tracks</translate></h2> <track-table :display-position="true" :tracks="tracks"></track-table> </template> </section> @@ -87,7 +88,7 @@ export default { computed: { labels() { return { - playlist: this.$gettext("Playlist") + playlist: this.$pgettext('Head/Playlist/Title', 'Playlist') } } }, diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue index 15cc94be431efd6fe67336718b7423c3628d1c67..160b7b83c3785ee44bb3b449c97ef99bc54a756b 100644 --- a/front/src/views/playlists/List.vue +++ b/front/src/views/playlists/List.vue @@ -1,21 +1,21 @@ <template> <main v-title="labels.playlists"> <section class="ui vertical stripe segment"> - <h2 class="ui header"><translate>Browsing playlists</translate></h2> + <h2 class="ui header"><translate translate-context="Content/Playlist/Title">Browsing playlists</translate></h2> <div :class="['ui', {'loading': isLoading}, 'form']"> <template v-if="$store.state.auth.authenticated"> <button @click="$store.commit('playlists/chooseTrack', null)" - class="ui basic green button"><translate>Manage your playlists</translate></button> + class="ui basic green button"><translate translate-context="Content/Playlist/Button.Label/Verb">Manage your playlists</translate></button> <div class="ui hidden divider"></div> </template> <div class="fields"> <div class="field"> - <label><translate>Search</translate></label> + <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/> </div> <div class="field"> - <label><translate>Ordering</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> <option v-for="option in orderingOptions" :value="option[0]"> {{ sharedLabels.filters[option[1]] }} @@ -23,14 +23,14 @@ </select> </div> <div class="field"> - <label><translate>Order</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label> <select class="ui dropdown" v-model="orderingDirection"> - <option value="+"><translate>Ascending</translate></option> - <option value="-"><translate>Descending</translate></option> + <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option> + <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option> </select> </div> <div class="field"> - <label><translate>Results per page</translate></label> + <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label> <select class="ui dropdown" v-model="paginateBy"> <option :value="parseInt(12)">12</option> <option :value="parseInt(25)">25</option> @@ -103,8 +103,8 @@ export default { }, computed: { labels() { - let playlists = this.$gettext("Playlists") - let searchPlaceholder = this.$gettext("Enter playlist name…") + let playlists = this.$pgettext('*/*/*', 'Playlists') + let searchPlaceholder = this.$pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…') return { playlists, searchPlaceholder diff --git a/front/src/views/radios/Detail.vue b/front/src/views/radios/Detail.vue index 61a7553edb3efe22667a20fe35c64f99886bf8fd..d15aaf366a73477d445212ef2210c5d6d4f94a3f 100644 --- a/front/src/views/radios/Detail.vue +++ b/front/src/views/radios/Detail.vue @@ -18,21 +18,21 @@ <div class="ui hidden divider"></div> <radio-button type="custom" :custom-radio-id="radio.id"></radio-button> <template v-if="$store.state.auth.username === radio.user.username"> - <router-link class="ui icon button" :to="{name: 'library.radios.edit', params: {id: radio.id}}" exact> + <router-link class="ui icon labeled button" :to="{name: 'library.radios.edit', params: {id: radio.id}}" exact> <i class="pencil icon"></i> Edit… </router-link> <dangerous-button class="labeled icon" :action="deleteRadio"> <i class="trash icon"></i> Delete - <p slot="modal-header" v-translate="{radio: radio.name}" :translate-params="{radio: radio.name}">Do you want to delete the radio "%{ radio }"?</p> - <p slot="modal-content"><translate>This will completely delete this radio and cannot be undone.</translate></p> - <p slot="modal-confirm"><translate>Delete radio</translate></p> + <p slot="modal-header" v-translate="{radio: radio.name}" translate-context="Popup/Radio/Title" :translate-params="{radio: radio.name}">Do you want to delete the radio "%{ radio }"?</p> + <p slot="modal-content"><translate translate-context="Popup/Radio/Paragraph">This will completely delete this radio and cannot be undone.</translate></p> + <p slot="modal-confirm"><translate translate-context="Popup/Radio/Button.Label/Verb">Delete radio</translate></p> </dangerous-button> </template> </div> </section> <section class="ui vertical stripe segment"> - <h2><translate>Tracks</translate></h2> + <h2><translate translate-context="*/*/*/Noun">Tracks</translate></h2> <track-table :tracks="tracks"></track-table> <div class="ui center aligned basic segment"> <pagination @@ -77,7 +77,7 @@ export default { computed: { labels() { return { - title: this.$gettext("Radio") + title: this.$pgettext('Head/Radio/Title', "Radio") } } }, diff --git a/front/yarn.lock b/front/yarn.lock index db05be080c2249c397b35053a871a58e6162e283..5d20f4ac1bea08bd5ab6eb8cbd1fe27de24d78b4 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2960,6 +2960,11 @@ diff@3.5.0, diff@^3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" + integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" diff --git a/pyproject.toml b/pyproject.toml index 68682d243c231a3fed2f52e5948b9507df9bd42f..21dd7c106dc42ca536762624c3b387610de3824b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,3 +41,6 @@ directory = "misc" name = "Other" showcontent = true + +[tool.black] + exclude = "(.git|.hg|.mypy_cache|.tox|.venv|_build|buck-out|build|dist|migrations)"