Merge commit '9c2d5b534f
' into upstream-20250314
This commit is contained in:
commit
6548462ecb
84 changed files with 1719 additions and 418 deletions
|
@ -86,3 +86,24 @@ S3_ALIAS_HOST=files.example.com
|
|||
# -----------------------
|
||||
IP_RETENTION_PERIOD=31556952
|
||||
SESSION_RETENTION_PERIOD=31556952
|
||||
|
||||
# Fetch All Replies Behavior
|
||||
# --------------------------
|
||||
# When a user expands a post (DetailedStatus view), fetch all of its replies
|
||||
# (default: false)
|
||||
FETCH_REPLIES_ENABLED=false
|
||||
|
||||
# Period to wait between fetching replies (in minutes)
|
||||
FETCH_REPLIES_COOLDOWN_MINUTES=15
|
||||
|
||||
# Period to wait after a post is first created before fetching its replies (in minutes)
|
||||
FETCH_REPLIES_INITIAL_WAIT_MINUTES=5
|
||||
|
||||
# Max number of replies to fetch - total, recursively through a whole reply tree
|
||||
FETCH_REPLIES_MAX_GLOBAL=1000
|
||||
|
||||
# Max number of replies to fetch - for a single post
|
||||
FETCH_REPLIES_MAX_SINGLE=500
|
||||
|
||||
# Max number of replies Collection pages to fetch - total
|
||||
FETCH_REPLIES_MAX_PAGES=500
|
||||
|
|
|
@ -28,12 +28,9 @@ inherit_mode:
|
|||
- Exclude
|
||||
|
||||
plugins:
|
||||
- rubocop-capybara
|
||||
- rubocop-i18n
|
||||
- rubocop-performance
|
||||
- rubocop-rails
|
||||
- rubocop-rspec
|
||||
- rubocop-performance
|
||||
- rubocop-i18n
|
||||
|
||||
require:
|
||||
- rubocop-rspec_rails
|
||||
- rubocop-capybara
|
||||
- ./lib/linter/rubocop_middle_dot
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||
# using RuboCop version 1.73.1.
|
||||
# using RuboCop version 1.73.2.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -2,6 +2,30 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.3.6] - 2025-03-13
|
||||
|
||||
### Security
|
||||
|
||||
- Update dependency `omniauth-saml`
|
||||
- Update dependency `rack`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix Stoplight errors when using `REDIS_NAMESPACE` (#34126 by @ClearlyClaire)
|
||||
|
||||
## [4.3.5] - 2025-03-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Change hashtag suggestion to prefer personal history capitalization (#34070 by @ClearlyClaire)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix processing errors for some HEIF images from iOS 18 (#34086 by @renchap)
|
||||
- Fix streaming server not filtering unknown-language posts from public timelines (#33774 by @ClearlyClaire)
|
||||
- Fix preview cards under Content Warnings not being shown in detailed statuses (#34068 by @ClearlyClaire)
|
||||
- Fix username and display name being hidden on narrow screens in moderation interface (#33064 by @ClearlyClaire)
|
||||
|
||||
## [4.3.4] - 2025-02-27
|
||||
|
||||
### Security
|
||||
|
|
60
Dockerfile
60
Dockerfile
|
@ -14,12 +14,12 @@ ARG BASE_REGISTRY="docker.io"
|
|||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
ARG RUBY_VERSION="3.4.2"
|
||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# renovate: datasource=node-version depName=node
|
||||
ARG NODE_MAJOR_VERSION="22"
|
||||
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
|
||||
ARG DEBIAN_VERSION="bookworm"
|
||||
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
|
||||
# Node.js image to use for base image based on combined variables (ex: 20-bookworm-slim)
|
||||
FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
|
||||
# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
|
||||
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
||||
|
@ -61,7 +61,7 @@ ENV \
|
|||
ENV \
|
||||
# Configure the IP to bind Mastodon to when serving traffic
|
||||
BIND="0.0.0.0" \
|
||||
# Use production settings for Yarn, Node and related nodejs based tools
|
||||
# Use production settings for Yarn, Node.js and related tools
|
||||
NODE_ENV="production" \
|
||||
# Use production settings for Ruby on Rails
|
||||
RAILS_ENV="production" \
|
||||
|
@ -128,13 +128,6 @@ RUN \
|
|||
# Create temporary build layer from base image
|
||||
FROM ruby AS build
|
||||
|
||||
# Copy Node package configuration files into working directory
|
||||
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||
COPY .yarn /opt/mastodon/.yarn
|
||||
|
||||
COPY --from=node /usr/local/bin /usr/local/bin
|
||||
COPY --from=node /usr/local/lib /usr/local/lib
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
|
@ -188,18 +181,12 @@ RUN \
|
|||
libx265-dev \
|
||||
;
|
||||
|
||||
RUN \
|
||||
# Configure Corepack
|
||||
rm /usr/local/bin/yarn*; \
|
||||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
||||
# Create temporary libvips specific build layer from build layer
|
||||
FROM build AS libvips
|
||||
|
||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||
ARG VIPS_VERSION=8.16.0
|
||||
ARG VIPS_VERSION=8.16.1
|
||||
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
||||
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
||||
|
||||
|
@ -284,38 +271,37 @@ RUN \
|
|||
# Download and install required Gems
|
||||
bundle install -j"$(nproc)";
|
||||
|
||||
# Create temporary node specific build layer from build layer
|
||||
FROM build AS yarn
|
||||
# Create temporary assets build layer from build layer
|
||||
FROM build AS precompiler
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy Node package configuration files into working directory
|
||||
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||
COPY streaming/package.json /opt/mastodon/streaming/
|
||||
COPY .yarn /opt/mastodon/.yarn
|
||||
# Copy Mastodon sources into layer
|
||||
COPY . /opt/mastodon/
|
||||
|
||||
# Copy Node.js binaries/libraries into layer
|
||||
COPY --from=node /usr/local/bin /usr/local/bin
|
||||
COPY --from=node /usr/local/lib /usr/local/lib
|
||||
|
||||
RUN \
|
||||
# Configure Corepack
|
||||
rm /usr/local/bin/yarn*; \
|
||||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
||||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||
# Install Node packages
|
||||
# Install Node.js packages
|
||||
yarn workspaces focus --production @mastodon/mastodon;
|
||||
|
||||
# Create temporary assets build layer from build layer
|
||||
FROM build AS precompiler
|
||||
|
||||
# Copy Mastodon sources into precompiler layer
|
||||
COPY . /opt/mastodon/
|
||||
|
||||
# Copy bundler and node packages from build layer to container
|
||||
COPY --from=yarn /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
# Copy libvips components to layer for precompiler
|
||||
# Copy libvips components into layer for precompiler
|
||||
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
|
||||
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# Copy bundler packages into layer for precompiler
|
||||
COPY --from=bundler /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
|
||||
RUN \
|
||||
ldconfig; \
|
||||
|
|
130
Gemfile.lock
130
Gemfile.lock
|
@ -10,29 +10,29 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actioncable (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionmailbox (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionmailer (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionpack (8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
|
@ -40,15 +40,15 @@ GEM
|
|||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actiontext (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionview (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
|
@ -58,22 +58,22 @@ GEM
|
|||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activejob (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activerecord (8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activemodel (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activerecord (8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activestorage (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.1)
|
||||
activesupport (8.0.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
|
@ -333,7 +333,7 @@ GEM
|
|||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.10.1)
|
||||
json (2.10.2)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.16.7)
|
||||
activesupport (>= 4.2)
|
||||
|
@ -453,9 +453,9 @@ GEM
|
|||
omniauth-rails_csrf_protection (1.0.2)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-saml (2.2.2)
|
||||
omniauth-saml (2.2.3)
|
||||
omniauth (~> 2.1)
|
||||
ruby-saml (~> 1.17)
|
||||
ruby-saml (~> 1.18)
|
||||
omniauth_openid_connect (0.8.0)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
|
@ -613,7 +613,7 @@ GEM
|
|||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.12)
|
||||
rack (2.2.13)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.2)
|
||||
|
@ -637,20 +637,20 @@ GEM
|
|||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (8.0.1)
|
||||
actioncable (= 8.0.1)
|
||||
actionmailbox (= 8.0.1)
|
||||
actionmailer (= 8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actiontext (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
rails (8.0.2)
|
||||
actioncable (= 8.0.2)
|
||||
actionmailbox (= 8.0.2)
|
||||
actionmailer (= 8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actiontext (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.1)
|
||||
railties (= 8.0.2)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
|
@ -661,9 +661,9 @@ GEM
|
|||
rails-i18n (8.0.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 8.0.0, < 9)
|
||||
railties (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
railties (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
|
@ -729,7 +729,7 @@ GEM
|
|||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 9)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.73.2)
|
||||
rubocop (1.74.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
|
@ -742,8 +742,9 @@ GEM
|
|||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.38.1)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-capybara (2.22.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-i18n (3.2.3)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1)
|
||||
|
@ -760,12 +761,13 @@ GEM
|
|||
rubocop-rspec (3.5.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec_rails (2.30.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec (~> 3, >= 3.0.1)
|
||||
rubocop-rspec_rails (2.31.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec (~> 3.5)
|
||||
ruby-prof (1.7.1)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.17.0)
|
||||
ruby-saml (1.18.0)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.2.3)
|
||||
|
|
|
@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, account_params, raise_error: true)
|
||||
current_user.update(user_params) if user_params
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: ValidationErrorFormatter.new(e).as_json, status: 422
|
||||
|
|
|
@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
|
|||
def destroy
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
|
|||
def destroy
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,6 +67,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
statuses = [@status] + @context.ancestors + @context.descendants + @context.references
|
||||
|
||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||
|
||||
ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -8,7 +8,7 @@ module Settings
|
|||
def destroy
|
||||
if valid_picture?
|
||||
if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
|
||||
else
|
||||
redirect_to settings_profile_path
|
||||
|
|
|
@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
|
|||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params.except(:settings))
|
||||
current_user.update!(settings_attributes: account_params[:settings])
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :show
|
||||
|
|
|
@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
|
|||
|
||||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
@account.build_fields
|
||||
|
|
|
@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
|
|||
|
||||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :show
|
||||
|
|
|
@ -163,24 +163,49 @@ module JsonLdHelper
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
|
||||
# Fetch the resource given by uri.
|
||||
# @param uri [String]
|
||||
# @param id_is_known [Boolean]
|
||||
# @param on_behalf_of [nil, Account]
|
||||
# @param raise_on_error [Symbol<:all, :temporary, :none>] See {#fetch_resource_without_id_validation} for possible values
|
||||
def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: :none, request_options: {})
|
||||
unless id_is_known
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error)
|
||||
|
||||
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
||||
|
||||
uri = json['id']
|
||||
end
|
||||
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error, request_options: request_options)
|
||||
json.present? && json['id'] == uri ? json : nil
|
||||
end
|
||||
|
||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
|
||||
# Fetch the resource given by uri
|
||||
#
|
||||
# If an error is raised, it contains the response and can be captured for handling like
|
||||
#
|
||||
# begin
|
||||
# fetch_resource_without_id_validation(uri, nil, true)
|
||||
# rescue Mastodon::UnexpectedResponseError => e
|
||||
# e.response
|
||||
# end
|
||||
#
|
||||
# @param uri [String]
|
||||
# @param on_behalf_of [nil, Account]
|
||||
# @param raise_on_error [Symbol<:all, :temporary, :none>]
|
||||
# - +:all+ - raise if response code is not in the 2xx range
|
||||
# - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404
|
||||
# (see {#response_error_unsalvageable} )
|
||||
# - +:none+ - do not raise, return +nil+
|
||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: :none, request_options: {})
|
||||
on_behalf_of ||= Account.representative
|
||||
|
||||
build_request(uri, on_behalf_of, options: request_options).perform do |response|
|
||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
||||
raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && (
|
||||
raise_on_error == :all ||
|
||||
(!response_error_unsalvageable?(response) && raise_on_error == :temporary)
|
||||
)
|
||||
|
||||
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
|
@ -30,30 +30,31 @@ export const ActionBar = () => {
|
|||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleLogoutClick = useCallback(() => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
}, [dispatch]);
|
||||
const menu = useMemo(() => {
|
||||
const handleLogoutClick = () => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
};
|
||||
|
||||
let menu = [];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
|
||||
menu.push({ text: intl.formatMessage(messages.reaction_deck), to: '/reaction_deck' });
|
||||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
||||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
||||
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
|
||||
menu.push({ text: intl.formatMessage(messages.emoji_reactions), to: '/emoji_reactions' });
|
||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.logout), action: handleLogoutClick });
|
||||
return ([
|
||||
{ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' },
|
||||
{ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' },
|
||||
{ text: intl.formatMessage(messages.pins), to: '/pinned' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' },
|
||||
{ text: intl.formatMessage(messages.favourites), to: '/favourites' },
|
||||
{ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
|
||||
{ text: intl.formatMessage(messages.emoji_reactions), to: '/emoji_reactions' },
|
||||
{ text: intl.formatMessage(messages.lists), to: '/lists' },
|
||||
{ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
||||
{ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' },
|
||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.logout), action: handleLogoutClick },
|
||||
]);
|
||||
}, [intl, dispatch]);
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
|
|
|
@ -562,6 +562,7 @@
|
|||
"notification.favourite": "Ffafriodd {name} eich postiad",
|
||||
"notification.favourite.name_and_others_with_link": "Ffafriodd {name} a <a>{count, plural, one {# arall} other {# arall}}</a> eich postiad",
|
||||
"notification.favourite_pm": "Mae {name} wedi ffefrynnu eich cyfeiriad preifat",
|
||||
"notification.favourite_pm.name_and_others_with_link": "Mae {name} a <a>{count, plural, one {# arall} other {# arall}}</a> wedi hoffi'ch crybwylliad preifat",
|
||||
"notification.follow": "Dilynodd {name} chi",
|
||||
"notification.follow.name_and_others": "Mae {name} a <a>{count, plural, zero {}one {# arall} two {# arall} few {# arall} many {# others} other {# arall}}</a> nawr yn eich dilyn chi",
|
||||
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
|
||||
|
@ -696,6 +697,7 @@
|
|||
"poll_button.remove_poll": "Tynnu pleidlais",
|
||||
"privacy.change": "Addasu preifatrwdd y post",
|
||||
"privacy.direct.long": "Pawb sydd â sôn amdanyn nhw yn y postiad",
|
||||
"privacy.direct.short": "Crybwylliad preifat",
|
||||
"privacy.private.long": "Eich dilynwyr yn unig",
|
||||
"privacy.private.short": "Dilynwyr",
|
||||
"privacy.public.long": "Unrhyw ar ac oddi ar Mastodon",
|
||||
|
@ -870,7 +872,9 @@
|
|||
"subscribed_languages.target": "Newid ieithoedd tanysgrifio {target}",
|
||||
"tabs_bar.home": "Cartref",
|
||||
"tabs_bar.notifications": "Hysbysiadau",
|
||||
"terms_of_service.effective_as_of": "Yn effeithiol ers {date}",
|
||||
"terms_of_service.title": "Telerau Gwasanaeth",
|
||||
"terms_of_service.upcoming_changes_on": "Newidiadau i ddod ar {date}",
|
||||
"time_remaining.days": "{number, plural, one {# diwrnod} other {# diwrnod}} ar ôl",
|
||||
"time_remaining.hours": "{number, plural, one {# awr} other {# awr}} ar ôl",
|
||||
"time_remaining.minutes": "{number, plural, one {# munud} other {# munud}} ar ôl",
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
"column.domain_blocks": "Blokerede domæner",
|
||||
"column.edit_list": "Redigér liste",
|
||||
"column.favourites": "Favoritter",
|
||||
"column.firehose": "Realtids-strømme",
|
||||
"column.firehose": "Aktuelt",
|
||||
"column.follow_requests": "Følgeanmodninger",
|
||||
"column.home": "Hjem",
|
||||
"column.list_members": "Håndtér listemedlemmer",
|
||||
|
|
|
@ -874,7 +874,7 @@
|
|||
"tabs_bar.notifications": "Notificações",
|
||||
"terms_of_service.effective_as_of": "Válido a partir de {date}",
|
||||
"terms_of_service.title": "Termos do serviço",
|
||||
"terms_of_service.upcoming_changes_on": "Alterações em {date}",
|
||||
"terms_of_service.upcoming_changes_on": "Próximas aterações em {date}",
|
||||
"time_remaining.days": "{number, plural, one {# dia restante} other {# dias restantes}}",
|
||||
"time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"about.blocks": "Moderirani strežniki",
|
||||
"about.contact": "Stik:",
|
||||
"about.disclaimer": "Mastodon je prosto, odprto-kodno programje in blagovna znamka Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon je prosto, odprtokodno programje in blagovna znamka podjetja Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Razlog ni na voljo",
|
||||
"about.domain_blocks.preamble": "Mastodon vam splošno omogoča ogled vsebin in interakcijo z uporabniki iz vseh drugih strežnikov v fediverzumu. To so izjeme, opravljene na tem strežniku.",
|
||||
"about.domain_blocks.silenced.explanation": "V splošnem ne boste videli profilov in vsebin s tega strežnika, če jih eksplicino ne poiščete ali nanje naročite s sledenjem.",
|
||||
"about.domain_blocks.preamble": "Mastodon vam na splošno omogoča ogled vsebin in interakcijo z uporabniki z vseh drugih strežnikov v fediverzumu. Tu so navedene izjeme, ki jih postavlja ta strežnik.",
|
||||
"about.domain_blocks.silenced.explanation": "V splošnem ne boste videli profilov in vsebin s tega strežnika, razen če jih izrecno poiščete ali jim začnete slediti.",
|
||||
"about.domain_blocks.silenced.title": "Omejeno",
|
||||
"about.domain_blocks.suspended.explanation": "Nobeni podatki s tega strežnika ne bodo obdelani, shranjeni ali izmenjani, zaradi česar je nemogoča kakršna koli interakcija ali komunikacija z uporabniki s tega strežnika.",
|
||||
"about.domain_blocks.suspended.title": "Suspendiran",
|
||||
|
@ -29,11 +29,11 @@
|
|||
"account.endorse": "Izpostavi v profilu",
|
||||
"account.featured_tags.last_status_at": "Zadnja objava {date}",
|
||||
"account.featured_tags.last_status_never": "Ni objav",
|
||||
"account.featured_tags.title": "Izpostavljeni ključniki {name}",
|
||||
"account.featured_tags.title": "Izpostavljeni ključniki osebe {name}",
|
||||
"account.follow": "Sledi",
|
||||
"account.follow_back": "Sledi nazaj",
|
||||
"account.followers": "Sledilci",
|
||||
"account.followers.empty": "Nihče ne sledi temu uporabniku.",
|
||||
"account.followers.empty": "Nihče še ne sledi temu uporabniku.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}",
|
||||
"account.following": "Sledim",
|
||||
"account.following_counter": "{count, plural, one {{counter} sleden} two {{counter} sledena} few {{counter} sledeni} other {{counter} sledenih}}",
|
||||
|
@ -45,9 +45,9 @@
|
|||
"account.languages": "Spremeni naročene jezike",
|
||||
"account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
|
||||
"account.locked_info": "Stanje zasebnosti računa je nastavljeno na zaklenjeno. Lastnik ročno pregleda, kdo ga lahko spremlja.",
|
||||
"account.media": "Mediji",
|
||||
"account.media": "Predstavnosti",
|
||||
"account.mention": "Omeni @{name}",
|
||||
"account.moved_to": "{name} nakazuje, da ima zdaj nov račun:",
|
||||
"account.moved_to": "{name} sporoča, da ima zdaj nov račun:",
|
||||
"account.mute": "Utišaj @{name}",
|
||||
"account.mute_notifications_short": "Utišaj obvestila",
|
||||
"account.mute_short": "Utišaj",
|
||||
|
@ -68,14 +68,14 @@
|
|||
"account.unblock_short": "Odblokiraj",
|
||||
"account.unendorse": "Ne vključi v profil",
|
||||
"account.unfollow": "Ne sledi več",
|
||||
"account.unmute": "Odtišaj @{name}",
|
||||
"account.unmute": "Povrni glas @{name}",
|
||||
"account.unmute_notifications_short": "Izklopi utišanje obvestil",
|
||||
"account.unmute_short": "Odtišaj",
|
||||
"account_note.placeholder": "Kliknite za dodajanje opombe",
|
||||
"account.unmute_short": "Povrni glas",
|
||||
"account_note.placeholder": "Kliknite, da dodate opombo",
|
||||
"admin.dashboard.daily_retention": "Mera ohranjanja uporabnikov po dnevih od registracije",
|
||||
"admin.dashboard.monthly_retention": "Mera ohranjanja uporabnikov po mesecih od registracije",
|
||||
"admin.dashboard.retention.average": "Povprečje",
|
||||
"admin.dashboard.retention.cohort": "Mesec prijave",
|
||||
"admin.dashboard.retention.cohort": "Mesec registracije",
|
||||
"admin.dashboard.retention.cohort_size": "Novi uporabniki",
|
||||
"admin.impact_report.instance_accounts": "Profili računov, ki bi jih s tem izbrisali",
|
||||
"admin.impact_report.instance_followers": "Sledilci, ki bi jih izgubili naši uporabniki",
|
||||
|
@ -87,44 +87,61 @@
|
|||
"alert.unexpected.title": "Ojoj!",
|
||||
"alt_text_badge.title": "Nadomestno besedilo",
|
||||
"alt_text_modal.add_alt_text": "Dodaj nadomestno besedilo",
|
||||
"alt_text_modal.add_text_from_image": "Dodaj besedilo iz slike",
|
||||
"alt_text_modal.cancel": "Prekliči",
|
||||
"alt_text_modal.change_thumbnail": "Spremeni sličico",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Podaj opis za ljudi s težavami s sluhom ...",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Podaj opis za slabovidne ...",
|
||||
"alt_text_modal.done": "Opravljeno",
|
||||
"announcement.announcement": "Obvestilo",
|
||||
"announcement.announcement": "Oznanilo",
|
||||
"annual_report.summary.archetype.booster": "Lovec na trende",
|
||||
"annual_report.summary.archetype.lurker": "Tiholazec",
|
||||
"annual_report.summary.archetype.oracle": "Orakelj",
|
||||
"annual_report.summary.archetype.pollster": "Anketar",
|
||||
"annual_report.summary.archetype.replier": "Priljudnež",
|
||||
"annual_report.summary.followers.followers": "sledilcev",
|
||||
"annual_report.summary.followers.total": "",
|
||||
"annual_report.summary.followers.total": "skupaj {count}",
|
||||
"annual_report.summary.here_it_is": "Tule je povzetek vašega leta {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "- najpriljubljenejša objava",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "- največkrat izpostavljena objava",
|
||||
"annual_report.summary.highlighted_post.by_replies": "- objava z največ odgovori",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "najpogosteje uporabljena aplikacija",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "največkrat uporabljen ključnik",
|
||||
"annual_report.summary.most_used_hashtag.none": "Brez",
|
||||
"annual_report.summary.new_posts.new_posts": "nove objave",
|
||||
"annual_report.summary.percentile.text": "<topLabel>S tem ste se uvrstili med zgornjih </topLabel><percentage></percentage><bottomLabel> uporabnikov domene {domain}.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Živi duši ne bomo povedali.",
|
||||
"annual_report.summary.thanks": "Hvala, ker ste del Mastodona!",
|
||||
"attachments_list.unprocessed": "(neobdelano)",
|
||||
"audio.hide": "Skrij zvok",
|
||||
"block_modal.remote_users_caveat": "Od strežnika {domain} bomo zahtevali, da spoštuje vašo odločitev. Izpolnjevanje zahteve ni zagotovljeno, ker nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.",
|
||||
"block_modal.remote_users_caveat": "Strežnik {domain} bomo pozvali, naj spoštuje vašo odločitev. Kljub temu pa ni gotovo, da bo strežnik prošnjo upošteval, saj nekateri strežniki blokiranja obravnavajo drugače. Javne objave bodo morda še vedno vidne neprijavljenim uporabnikom.",
|
||||
"block_modal.show_less": "Pokaži manj",
|
||||
"block_modal.show_more": "Pokaži več",
|
||||
"block_modal.they_cant_mention": "Ne morejo vas omenjati ali vam slediti.",
|
||||
"block_modal.they_cant_see_posts": "Ne vidijo vaših objav, vi pa ne njihovih.",
|
||||
"block_modal.they_will_know": "Ne morejo videti, da so blokirani.",
|
||||
"block_modal.title": "Blokiraj uporabnika?",
|
||||
"block_modal.you_wont_see_mentions": "Objav, ki jih omenjajo, ne boste videli.",
|
||||
"boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}",
|
||||
"boost_modal.reblog": "Izpostavi objavo?",
|
||||
"block_modal.they_cant_mention": "Ne more vas omenjati ali vam slediti.",
|
||||
"block_modal.they_cant_see_posts": "Ne vidi vaših objav, vi pa ne njegovih.",
|
||||
"block_modal.they_will_know": "Ne more videti, da je blokiran.",
|
||||
"block_modal.title": "Blokiram uporabnika?",
|
||||
"block_modal.you_wont_see_mentions": "Objav, ki ga omenjajo, ne boste videli.",
|
||||
"boost_modal.combo": "Če želite naslednjič to preskočiti, lahko pritisnete {combo}",
|
||||
"boost_modal.reblog": "Izpostavim objavo?",
|
||||
"boost_modal.undo_reblog": "Ali želite preklicati izpostavitev objave?",
|
||||
"bundle_column_error.copy_stacktrace": "Kopiraj poročilo o napaki",
|
||||
"bundle_column_error.error.body": "Zahtevane strani ni mogoče upodobiti. Vzrok težave je morda hrošč v naši kodi ali pa nezdružljivost z brskalnikom.",
|
||||
"bundle_column_error.error.title": "Oh, ne!",
|
||||
"bundle_column_error.network.body": "Pri poskusu nalaganja te strani je prišlo do napake. Vzrok je lahko začasna težava z vašo internetno povezavo ali s tem strežnikom.",
|
||||
"bundle_column_error.network.title": "Napaka v omrežju",
|
||||
"bundle_column_error.network.title": "Omrežna napaka",
|
||||
"bundle_column_error.retry": "Poskusi znova",
|
||||
"bundle_column_error.return": "Nazaj domov",
|
||||
"bundle_column_error.routing.body": "Zahtevane strani ni mogoče najti. Ali ste prepričani, da je naslov URL v naslovni vrstici pravilen?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Zapri",
|
||||
"bundle_modal_error.message": "Med nalaganjem prikaza je prišlo do napake.",
|
||||
"bundle_modal_error.retry": "Poskusi znova",
|
||||
"closed_registrations.other_server_instructions": "Ker je Mastodon decentraliziran, lahko ustvarite račun na drugem strežniku in ste še vedno v interakciji s tem.",
|
||||
"closed_registrations_modal.description": "Odpiranje računa na {domain} trenutno ni možno, upoštevajte pa, da ne potrebujete računa prav na {domain}, da bi uporabljali Mastodon.",
|
||||
"closed_registrations_modal.description": "Odpiranje računa na domeni {domain} trenutno ni možno, upoštevajte pa, da ne potrebujete računa prav na domeni {domain}, da bi uporabljali Mastodon.",
|
||||
"closed_registrations_modal.find_another_server": "Najdi drug strežnik",
|
||||
"closed_registrations_modal.preamble": "Mastodon je decentraliziran, kar pomeni, da ni pomembno, kje ustvarite svoj račun; od koder koli je omogočeno sledenje in interakcija z vsemi s tega strežnika. Strežnik lahko gostite tudi sami!",
|
||||
"closed_registrations_modal.preamble": "Mastodon je decentraliziran, kar pomeni, da ni pomembno, kje ustvarite svoj račun; od koder koli je mogoče slediti in komunicirati z vsemi s tega strežnika. Strežnik lahko gostite tudi sami!",
|
||||
"closed_registrations_modal.title": "Registracija v Mastodon",
|
||||
"column.about": "O programu",
|
||||
"column.blocks": "Blokirani uporabniki",
|
||||
|
@ -137,7 +154,7 @@
|
|||
"column.edit_list": "Uredi seznam",
|
||||
"column.favourites": "Priljubljeni",
|
||||
"column.firehose": "Viri v živo",
|
||||
"column.follow_requests": "Sledi prošnjam",
|
||||
"column.follow_requests": "Prošnje za sledenje",
|
||||
"column.home": "Domov",
|
||||
"column.list_members": "Upravljaj člane seznama",
|
||||
"column.lists": "Seznami",
|
||||
|
@ -155,25 +172,25 @@
|
|||
"column_search.cancel": "Prekliči",
|
||||
"column_subheading.settings": "Nastavitve",
|
||||
"community.column_settings.local_only": "Samo krajevno",
|
||||
"community.column_settings.media_only": "Samo mediji",
|
||||
"community.column_settings.media_only": "Samo predstavnosti",
|
||||
"community.column_settings.remote_only": "Samo oddaljeno",
|
||||
"compose.language.change": "Spremeni jezik",
|
||||
"compose.language.search": "Poišči jezik ...",
|
||||
"compose.language.search": "Poišči jezike ...",
|
||||
"compose.published.body": "Objavljeno.",
|
||||
"compose.published.open": "Odpri",
|
||||
"compose.saved.body": "Objava shranjena.",
|
||||
"compose_form.direct_message_warning_learn_more": "Izvej več",
|
||||
"compose_form.encryption_warning": "Objave na Mastodonu niso šifrirane od kraja do kraja. Prek Mastodona ne delite nobenih občutljivih informacij.",
|
||||
"compose_form.direct_message_warning_learn_more": "Več o tem",
|
||||
"compose_form.encryption_warning": "Objave na Mastodonu niso šifrirane od konca do konca. Prek Mastodona ne delite nobenih občutljivih informacij.",
|
||||
"compose_form.hashtag_warning": "Ta objava ne bo navedena pod nobenim ključnikom, ker ni javna. Samo javne objave lahko iščete s ključniki.",
|
||||
"compose_form.lock_disclaimer": "Vaš račun ni {locked}. Vsakdo vam lahko sledi in si ogleda objave, ki so namenjene samo sledilcem.",
|
||||
"compose_form.lock_disclaimer.lock": "zaklenjen",
|
||||
"compose_form.placeholder": "O čem razmišljate?",
|
||||
"compose_form.poll.duration": "Trajanje ankete",
|
||||
"compose_form.poll.multiple": "Več možnosti",
|
||||
"compose_form.poll.multiple": "Izbira več možnosti",
|
||||
"compose_form.poll.option_placeholder": "Možnost {number}",
|
||||
"compose_form.poll.single": "Ena izbira",
|
||||
"compose_form.poll.switch_to_multiple": "Spremenite anketo, da omogočite več izbir",
|
||||
"compose_form.poll.switch_to_single": "Spremenite anketo, da omogočite eno izbiro",
|
||||
"compose_form.poll.single": "Izbira ene možnosti",
|
||||
"compose_form.poll.switch_to_multiple": "Spremenite anketo, da omogočite izbiro več možnosti",
|
||||
"compose_form.poll.switch_to_single": "Spremenite anketo, da omogočite izbiro ene možnosti",
|
||||
"compose_form.poll.type": "Slog",
|
||||
"compose_form.publish": "Objavi",
|
||||
"compose_form.publish_form": "Objavi",
|
||||
|
@ -191,17 +208,24 @@
|
|||
"confirmations.delete_list.message": "Ali ste prepričani, da želite trajno izbrisati ta seznam?",
|
||||
"confirmations.delete_list.title": "Želite izbrisati seznam?",
|
||||
"confirmations.discard_edit_media.confirm": "Opusti",
|
||||
"confirmations.discard_edit_media.message": "Imate ne shranjene spremembe za medijski opis ali predogled; jih želite kljub temu opustiti?",
|
||||
"confirmations.discard_edit_media.message": "Spremenjenega opisa predstavnosti ali predogleda niste shranili. Želite spremembe kljub temu opustiti?",
|
||||
"confirmations.edit.confirm": "Uredi",
|
||||
"confirmations.edit.message": "Urejanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
|
||||
"confirmations.edit.title": "Želite prepisati objavo?",
|
||||
"confirmations.follow_to_list.confirm": "Sledi in dodaj na seznam",
|
||||
"confirmations.follow_to_list.message": "Osebi {name} morate slediti, preden jo lahko dodate na seznam.",
|
||||
"confirmations.follow_to_list.title": "Naj sledim uporabniku?",
|
||||
"confirmations.logout.confirm": "Odjava",
|
||||
"confirmations.logout.message": "Ali ste prepričani, da se želite odjaviti?",
|
||||
"confirmations.logout.title": "Se želite odjaviti?",
|
||||
"confirmations.mute.confirm": "Utišanje",
|
||||
"confirmations.missing_alt_text.confirm": "Dodaj nadomestno besedilo",
|
||||
"confirmations.missing_alt_text.message": "Vaša objava vsebuje predstavnosti brez nadomestnega besedila. Če jih dodatno opišete, bodo dostopne večji množici ljudi.",
|
||||
"confirmations.missing_alt_text.secondary": "Vseeno objavi",
|
||||
"confirmations.missing_alt_text.title": "Dodam nadomestno besedilo?",
|
||||
"confirmations.mute.confirm": "Utišaj",
|
||||
"confirmations.redraft.confirm": "Izbriši in preoblikuj",
|
||||
"confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati ta status in ga preoblikovati? Vzljubi in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.",
|
||||
"confirmations.redraft.title": "Želite izbrisati in predelati objavo?",
|
||||
"confirmations.redraft.message": "Ali ste prepričani, da želite izbrisati to objavo in jo preoblikovati? Izkazi priljubljenosti in izpostavitve bodo izgubljeni, odgovori na izvirno objavo pa bodo osiroteli.",
|
||||
"confirmations.redraft.title": "Želite izbrisati in preoblikovati objavo?",
|
||||
"confirmations.reply.confirm": "Odgovori",
|
||||
"confirmations.reply.message": "Odgovarjanje bo prepisalo sporočilo, ki ga trenutno sestavljate. Ali ste prepričani, da želite nadaljevati?",
|
||||
"confirmations.reply.title": "Želite prepisati objavo?",
|
||||
|
@ -217,7 +241,7 @@
|
|||
"conversation.with": "Z {names}",
|
||||
"copy_icon_button.copied": "Kopirano v odložišče",
|
||||
"copypaste.copied": "Kopirano",
|
||||
"copypaste.copy_to_clipboard": "Kopiraj na odložišče",
|
||||
"copypaste.copy_to_clipboard": "Kopiraj v odložišče",
|
||||
"directory.federated": "Iz znanega fediverzuma",
|
||||
"directory.local": "Samo iz {domain}",
|
||||
"directory.new_arrivals": "Novi prišleki",
|
||||
|
@ -226,28 +250,34 @@
|
|||
"disabled_account_banner.text": "Vaš račun {disabledAccount} je trenutno onemogočen.",
|
||||
"dismissable_banner.community_timeline": "To so najnovejše javne objave oseb, katerih računi gostujejo na {domain}.",
|
||||
"dismissable_banner.dismiss": "Opusti",
|
||||
"dismissable_banner.explore_links": "Danes po fediverzumu najbolj odmevajo te novice. Višje na seznamu so novejše vesti bolj raznolikih objaviteljev.",
|
||||
"dismissable_banner.explore_statuses": "Danes so po fediverzumu pozornost pritegnile te objave. Višje na seznamu so novejše, bolj izpostavljene in bolj priljubljene objave.",
|
||||
"dismissable_banner.explore_tags": "Danes se po fediverzumu najpogosteje uporabljajo ti ključniki. Višje na seznamu so ključniki, ki jih uporablja več različnih ljudi.",
|
||||
"dismissable_banner.public_timeline": "To so najnovejše javne objave ljudi s fediverzuma, ki jim sledijo ljudje na domeni {domain}.",
|
||||
"domain_block_modal.block": "Blokiraj strežnik",
|
||||
"domain_block_modal.block_account_instead": "Namesto tega blokiraj @{name}",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Osebe s tega strežnika se lahko odzivajo na vaše stare objave.",
|
||||
"domain_block_modal.they_cant_follow": "Nihče s tega strežnika vam ne more slediti.",
|
||||
"domain_block_modal.they_wont_know": "Ne bodo vedeli, da so blokirani.",
|
||||
"domain_block_modal.title": "Blokiraj domeno?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "Izgubili boste {followersCount, plural, one {{followersCountDisplay} sledilca} two {{followersCountDisplay} sledilca} few {{followersCountDisplay} sledilce} other {{followersCountDisplay} sledilcev}} in {followingCount, plural, one {{followingCountDisplay} osebo, ki ji sledite} two {{followingCountDisplay} osebi, ki jima sledite} few {{followingCountDisplay} osebe, ki jim sledite} other {{followingCountDisplay} oseb, ki jim sledite}}.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Izgubili boste vse sledilce in ljudi, ki jim sledite na tem strežniku.",
|
||||
"domain_block_modal.you_wont_see_posts": "Objav ali obvestil uporabnikov s tega strežnika ne boste videli.",
|
||||
"domain_pill.activitypub_lets_connect": "Omogoča vam povezovanje in interakcijo z ljudmi, ki niso samo na Mastodonu, ampak tudi na drugih družabnih platformah.",
|
||||
"domain_pill.activitypub_like_language": "Protokol ActivityPub je kot jezik, s katerim se Mastodon pogovarja z drugimi družabnimi omrežji.",
|
||||
"domain_pill.activitypub_lets_connect": "Omogoča vam povezovanje in interakcijo z ljudmi, ki niso samo na Mastodonu, ampak tudi na drugih družbenih platformah.",
|
||||
"domain_pill.activitypub_like_language": "Protokol ActivityPub je kot jezik, v katerem se Mastodon pogovarja z drugimi družabnimi omrežji.",
|
||||
"domain_pill.server": "Strežnik",
|
||||
"domain_pill.their_handle": "Njihova ročica:",
|
||||
"domain_pill.their_server": "Njihovo digitalno domovanje, kjer bivajo vse njihove objave.",
|
||||
"domain_pill.their_username": "Njihov edinstveni identifikator na njihovem strežniku. Uporabnike z istim uporabniškim imenom lahko najdete na različnih strežnikih.",
|
||||
"domain_pill.their_handle": "Njegova/njena ročica:",
|
||||
"domain_pill.their_server": "Njegovo/njeno digitalno domovanje, kjer bivajo vse njegove/njene objave.",
|
||||
"domain_pill.their_username": "Njegov/njen edinstveni identifikator na njegovem/njenem strežniku. Uporabnike z istim uporabniškim imenom lahko najdete na različnih strežnikih.",
|
||||
"domain_pill.username": "Uporabniško ime",
|
||||
"domain_pill.whats_in_a_handle": "Kaj je v ročici?",
|
||||
"domain_pill.who_they_are": "Ker ročice povedo, kdo je kdo in kje so, ste lahko z osebami v interakciji prek družabnega spleta <button>platform, ki jih poganja ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Ker ročice povedo, kdo ste in kje ste, ste lahko z osebami v interakciji prek družabnega spleta <button>platform, ki jih poganja ActivityPub</button>.",
|
||||
"domain_pill.who_they_are": "Ker ročice povedo, kdo je kdo in kje je, lahko komunicirate z ljudmi po vsem spletu družbenih <button>platform, ki jih poganja ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Ker ročice povedo, kdo ste in kje ste, lahko komunicirate z ljudmi po vsem spletu družbenih <button>platform, ki jih poganja ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Vaša ročica:",
|
||||
"domain_pill.your_server": "Vaše digitalno domovanje, kjer bivajo vse vaše objave. Vam ta ni všeč? Prenesite ga med strežniki kadar koli in z njim tudi svoje sledilce.",
|
||||
"domain_pill.your_server": "Vaše digitalno domovanje, kjer bivajo vse vaše objave. Vam ni všeč? Kadar koli ga prenesite med strežniki in z njim tudi svoje sledilce.",
|
||||
"domain_pill.your_username": "Vaš edinstveni identifikator na tem strežniku. Uporabnike z istim uporabniškim imenom je možno najti na različnih strežnikih.",
|
||||
"embed.instructions": "Vstavite to objavo na svojo spletno stran tako, da kopirate spodnjo kodo.",
|
||||
"embed.preview": "Tako bo izgledalo:",
|
||||
"embed.preview": "Takole bo videti:",
|
||||
"emoji_button.activity": "Dejavnost",
|
||||
"emoji_button.clear": "Počisti",
|
||||
"emoji_button.custom": "Po meri",
|
||||
|
@ -263,32 +293,32 @@
|
|||
"emoji_button.search_results": "Rezultati iskanja",
|
||||
"emoji_button.symbols": "Simboli",
|
||||
"emoji_button.travel": "Potovanja in kraji",
|
||||
"empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo dal na voljo",
|
||||
"empty_column.account_hides_collections": "Ta uporabnik se je odločil, da te informacije ne bo delil",
|
||||
"empty_column.account_suspended": "Račun je suspendiran",
|
||||
"empty_column.account_timeline": "Tukaj ni objav!",
|
||||
"empty_column.account_unavailable": "Profil ni na voljo",
|
||||
"empty_column.blocks": "Niste še blokirali nobenega uporabnika.",
|
||||
"empty_column.bookmarked_statuses": "Zaenkrat še nimate zaznamovanih objav. Ko objavo zaznamujete, se pojavi tukaj.",
|
||||
"empty_column.community": "Krajevna časovnica je prazna. Napišite nekaj javnega, da se bo snežna kepa zakotalila!",
|
||||
"empty_column.community": "Krajevna časovnica je prazna. Napišite nekaj javnega, da se začne polniti!",
|
||||
"empty_column.direct": "Nimate še nobenih zasebnih omemb. Ko jih boste poslali ali prejeli, se bodo prikazale tukaj.",
|
||||
"empty_column.domain_blocks": "Zaenkrat ni blokiranih domen.",
|
||||
"empty_column.explore_statuses": "Trenutno ni nič v trendu. Preverite znova kasneje!",
|
||||
"empty_column.favourited_statuses": "Nimate priljubljenih objav. Ko boste vzljubili kakšno, bo prikazana tukaj.",
|
||||
"empty_column.favourites": "Nihče še ni vzljubil te objave. Ko jo bo nekdo, se bo pojavila tukaj.",
|
||||
"empty_column.explore_statuses": "Trenutno ni novih trendov. Preverite znova kasneje!",
|
||||
"empty_column.favourited_statuses": "Nimate priljubljenih objav. Ko boste kakšno dodali med priljubljene, bo prikazana tukaj.",
|
||||
"empty_column.favourites": "Nihče še ni vzljubil te objave. Ko jo bo nekdo, bo naveden tukaj.",
|
||||
"empty_column.follow_requests": "Nimate prošenj za sledenje. Ko boste prejeli kakšno, se bo prikazala tukaj.",
|
||||
"empty_column.followed_tags": "Zaenkrat ne sledite še nobenemu ključniku. Ko boste, se bodo pojavili tukaj.",
|
||||
"empty_column.hashtag": "V tem ključniku še ni nič.",
|
||||
"empty_column.home": "Vaša domača časovnica je prazna! Sledite več osebam, da jo zapolnite. {suggestions}",
|
||||
"empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama objavili nove statuse, se bodo pojavili tukaj.",
|
||||
"empty_column.followed_tags": "Zaenkrat ne sledite še nobenemu ključniku. Ko boste, se bo pojavil tukaj.",
|
||||
"empty_column.hashtag": "V tem ključniku ni še nič.",
|
||||
"empty_column.home": "Vaša domača časovnica je prazna! Sledite več osebam, da jo zapolnite.",
|
||||
"empty_column.list": "Na tem seznamu ni ničesar. Ko bodo člani tega seznama kaj objavili, se bodo te objave pojavile tukaj.",
|
||||
"empty_column.mutes": "Niste utišali še nobenega uporabnika.",
|
||||
"empty_column.notification_requests": "Vse prebrano! Tu ni ničesar več. Ko prejmete nova obvestila, se bodo pojavila tu glede na vaše nastavitve.",
|
||||
"empty_column.notifications": "Nimate še nobenih obvestil. Povežite se z drugimi, da začnete pogovor.",
|
||||
"empty_column.public": "Tukaj ni ničesar! Da ga napolnite, napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih strežnikov",
|
||||
"empty_column.public": "Tukaj ni ničesar! Napišite nekaj javnega ali pa ročno sledite uporabnikom iz drugih strežnikov, da se bo napolnilo",
|
||||
"error.unexpected_crash.explanation": "Zaradi hrošča v naši kodi ali težave z združljivostjo brskalnika te strani ni mogoče ustrezno prikazati.",
|
||||
"error.unexpected_crash.explanation_addons": "Te strani ni mogoče ustrezno prikazati. To napako najverjetneje povzroča dodatek briskalnika ali samodejna orodja za prevajanje.",
|
||||
"error.unexpected_crash.explanation_addons": "Te strani ni mogoče ustrezno prikazati. To napako najverjetneje povzroča dodatek brskalnika ali samodejna orodja za prevajanje.",
|
||||
"error.unexpected_crash.next_steps": "Poskusite osvežiti stran. Če to ne pomaga, boste morda še vedno lahko uporabljali Mastodon prek drugega brskalnika ali z domorodno aplikacijo.",
|
||||
"error.unexpected_crash.next_steps_addons": "Poskusite jih onemogočiti in osvežiti stran. Če to ne pomaga, boste morda še vedno lahko uporabljali Mastodon prek drugega brskalnika ali z domorodno aplikacijo.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Kopiraj sledenje skladu na odložišče",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Kopiraj sled sklada na odložišče",
|
||||
"errors.unexpected_crash.report_issue": "Prijavi težavo",
|
||||
"explore.suggested_follows": "Ljudje",
|
||||
"explore.title": "Razišči",
|
||||
|
@ -297,7 +327,7 @@
|
|||
"explore.trending_tags": "Ključniki",
|
||||
"filter_modal.added.context_mismatch_explanation": "Ta kategorija filtra ne velja za kontekst, v katerem ste dostopali do te objave. Če želite, da je objava filtrirana tudi v tem kontekstu, morate urediti filter.",
|
||||
"filter_modal.added.context_mismatch_title": "Neujemanje konteksta!",
|
||||
"filter_modal.added.expired_explanation": "Ta kategorija filtra je pretekla, morali boste spremeniti datum veljavnosti, da bo veljal še naprej.",
|
||||
"filter_modal.added.expired_explanation": "Ta kategorija filtra je pretekla. Morali boste spremeniti datum veljavnosti, da bo veljal še naprej.",
|
||||
"filter_modal.added.expired_title": "Filter je pretekel!",
|
||||
"filter_modal.added.review_and_configure": "Če želite pregledati in nadalje prilagoditi kategorijo filtra, obiščite {settings_link}.",
|
||||
"filter_modal.added.review_and_configure_title": "Nastavitve filtra",
|
||||
|
@ -312,6 +342,7 @@
|
|||
"filter_modal.select_filter.title": "Filtriraj to objavo",
|
||||
"filter_modal.title.status": "Filtrirajte objavo",
|
||||
"filter_warning.matches_filter": "Se ujema s filtrom »<span>{title}</span>«",
|
||||
"filtered_notifications_banner.pending_requests": "Od {count, plural, =0 {nikogar, ki bi ga poznali} one {nekoga, ki ga morda poznate} two {dveh ljudi, ki ju morda poznate} other {ljudi, ki jih morda poznate}}",
|
||||
"filtered_notifications_banner.title": "Filtrirana obvestila",
|
||||
"firehose.all": "Vse",
|
||||
"firehose.local": "Ta strežnik",
|
||||
|
@ -360,9 +391,12 @@
|
|||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objav} other {{counter} objav}}",
|
||||
"hashtag.follow": "Sledi ključniku",
|
||||
"hashtag.unfollow": "Nehaj slediti ključniku",
|
||||
"hashtags.and_other": "…in še {count, plural, other {#}}",
|
||||
"hashtags.and_other": "… in še {count, plural, other {#}}",
|
||||
"hints.profiles.followers_may_be_missing": "Sledilci za ta profil morda manjkajo.",
|
||||
"hints.profiles.follows_may_be_missing": "Osebe, ki jim ta profil sledi, morda manjkajo.",
|
||||
"hints.profiles.posts_may_be_missing": "Nekatere objave s tega profila morda manjkajo.",
|
||||
"hints.profiles.see_more_followers": "Pokaži več sledilcev na {domain}",
|
||||
"hints.profiles.see_more_follows": "Pokaži več sledenih ljudi na zbirališču {domain}",
|
||||
"hints.profiles.see_more_posts": "Pokaži več objav na {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Odgovori z drugih strežnikov morda manjkajo.",
|
||||
"hints.threads.see_more": "Pokaži več odgovorov na {domain}",
|
||||
|
@ -373,9 +407,25 @@
|
|||
"home.pending_critical_update.link": "Glejte posodobitve",
|
||||
"home.pending_critical_update.title": "Na voljo je kritična varnostna posodobbitev!",
|
||||
"home.show_announcements": "Pokaži obvestila",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ne more obveščati uporabnikov, da ste prezrli njihova obvestila. Tudi če jih prezrete, jih lahko uporabniki še vedno pošiljajo.",
|
||||
"ignore_notifications_modal.filter_instead": "Raje filtriraj",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Še vedno boste lahko sprejeli, zavrnili ali prijavili uporabnike",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtriranje pomaga pri izogibanju morebitni zmedi",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Filtrirana obvestila lahko pregledate ločeno",
|
||||
"ignore_notifications_modal.ignore": "Prezri obvestila",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Naj prezrem obvestila moderiranih računov?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Naj prezrem obvestila novih računov?",
|
||||
"ignore_notifications_modal.not_followers_title": "Naj prezrem obvestila ljudi, ki vam ne sledijo?",
|
||||
"ignore_notifications_modal.not_following_title": "Naj prezrem obvestila ljudi, ki jim ne sledite?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Naj prezrem obvestila od nezaželenih zasebnih omemb?",
|
||||
"info_button.label": "Pomoč",
|
||||
"info_button.what_is_alt_text": "<h1>Kaj je nadomestno besedilo?</h1> <p>Z nadomestnim besedilom dodatno opišemo sliko in tako pomagamo slabovidnim, ljudem s slabo internetno povezavo in tistim, ki jim manjka kontekst.</p> <p>Vaša objava bo veliko bolj dostopna in razumljiva, če boste napisali jasno, jedrnato in nepristransko nadomestno besedilo.</p> <ul> <li>Izpostavite pomembne elemente.</li> <li>Povzemite besedilo v slikah.</li> <li>Pišite v celih stavkih.</li> <li>Zajemite bistvo, ne dolgovezite.</li> <li>Opišite težnje in ključna odkritja, ki ste jih razbrali iz zapletenih grafik (npr. diagramov ali zemljevidov).</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Med priljubljene lahko dodate, ko se vpišete v svoj račun.",
|
||||
"interaction_modal.action.follow": "Sledite lahko šele, ko se vpišete v svoj račun.",
|
||||
"interaction_modal.action.reblog": "Izpostavite lahko šele, ko se vpišete v svoj račun.",
|
||||
"interaction_modal.action.reply": "Odgovorite lahko šele, ko se vpišete v svoj račun.",
|
||||
"interaction_modal.action.vote": "Glasujete lahko šele, ko se vpišete v svoj račun.",
|
||||
"interaction_modal.go": "Naprej",
|
||||
"interaction_modal.no_account_yet": "Še nimate računa?",
|
||||
"interaction_modal.on_another_server": "Na drugem strežniku",
|
||||
"interaction_modal.on_this_server": "Na tem strežniku",
|
||||
|
@ -383,6 +433,8 @@
|
|||
"interaction_modal.title.follow": "Sledi {name}",
|
||||
"interaction_modal.title.reblog": "Izpostavi objavo {name}",
|
||||
"interaction_modal.title.reply": "Odgovori na objavo {name}",
|
||||
"interaction_modal.title.vote": "Izpolni anketo uporabnika/ce {name}",
|
||||
"interaction_modal.username_prompt": "Npr. {example}",
|
||||
"intervals.full.days": "{number, plural, one {# dan} two {# dni} few {# dni} other {# dni}}",
|
||||
"intervals.full.hours": "{number, plural, one {# ura} two {# uri} few {# ure} other {# ur}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuta} two {# minuti} few {# minute} other {# minut}}",
|
||||
|
@ -407,7 +459,7 @@
|
|||
"keyboard_shortcuts.muted": "Odpri seznam utišanih uporabnikov",
|
||||
"keyboard_shortcuts.my_profile": "Odprite svoj profil",
|
||||
"keyboard_shortcuts.notifications": "Odpri stolpec z obvestili",
|
||||
"keyboard_shortcuts.open_media": "Odpri medij",
|
||||
"keyboard_shortcuts.open_media": "Odpri predstavnost",
|
||||
"keyboard_shortcuts.pinned": "Odpri seznam pripetih objav",
|
||||
"keyboard_shortcuts.profile": "Odpri avtorjev profil",
|
||||
"keyboard_shortcuts.reply": "Odgovori na objavo",
|
||||
|
@ -416,26 +468,35 @@
|
|||
"keyboard_shortcuts.spoilers": "Pokaži/skrij polje CW",
|
||||
"keyboard_shortcuts.start": "Odpri stolpec \"začni\"",
|
||||
"keyboard_shortcuts.toggle_hidden": "Pokaži/skrij besedilo za CW",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Pokaži/skrij medije",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Pokaži/skrij predstavnosti",
|
||||
"keyboard_shortcuts.toot": "Začni povsem novo objavo",
|
||||
"keyboard_shortcuts.translate": "za prevod objave",
|
||||
"keyboard_shortcuts.unfocus": "Odstrani pozornost z območja za sestavljanje besedila/iskanje",
|
||||
"keyboard_shortcuts.up": "Premakni navzgor po seznamu",
|
||||
"lightbox.close": "Zapri",
|
||||
"lightbox.next": "Naslednji",
|
||||
"lightbox.previous": "Prejšnji",
|
||||
"lightbox.zoom_in": "Približaj na dejansko velikost",
|
||||
"lightbox.zoom_out": "Čez cel prikaz",
|
||||
"limited_account_hint.action": "Vseeno pokaži profil",
|
||||
"limited_account_hint.title": "Profil so moderatorji strežnika {domain} skrili.",
|
||||
"link_preview.author": "Avtor_ica {name}",
|
||||
"link_preview.author": "Avtor/ica {name}",
|
||||
"link_preview.more_from_author": "Več od {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}",
|
||||
"lists.add_member": "Dodaj",
|
||||
"lists.add_to_list": "Dodaj na seznam",
|
||||
"lists.add_to_lists": "Dodaj {name} na sezname",
|
||||
"lists.create": "Ustvari",
|
||||
"lists.create_a_list_to_organize": "Uredite si domači vir z novim seznamom",
|
||||
"lists.create_list": "Ustvari seznam",
|
||||
"lists.delete": "Izbriši seznam",
|
||||
"lists.done": "Opravljeno",
|
||||
"lists.edit": "Uredi seznam",
|
||||
"lists.exclusive": "Skrij člane v domovanju",
|
||||
"lists.exclusive_hint": "Objave vseh, ki so na tem seznamu, se ne pokažejo v vašem domačem viru. Tako se izognete podvojenim objavam.",
|
||||
"lists.find_users_to_add": "Poišči člane za dodajanje",
|
||||
"lists.list_members": "Člani seznama",
|
||||
"lists.list_members_count": "{count, plural, one {# član} two {# člana} few {# člani} other {# članov}}",
|
||||
"lists.list_name": "Ime seznama",
|
||||
"lists.new_list_name": "Novo ime seznama",
|
||||
"lists.no_lists_yet": "Ni seznamov.",
|
||||
|
@ -446,6 +507,8 @@
|
|||
"lists.replies_policy.list": "Članom seznama",
|
||||
"lists.replies_policy.none": "Nikomur",
|
||||
"lists.save": "Shrani",
|
||||
"lists.search": "Iskanje",
|
||||
"lists.show_replies_to": "Vključi odgovore, katerih pošiljatelji so člani seznama in prejemniki",
|
||||
"load_pending": "{count, plural, one {# nov element} two {# nova elementa} few {# novi elementi} other {# novih elementov}}",
|
||||
"loading_indicator.label": "Nalaganje …",
|
||||
"media_gallery.hide": "Skrij",
|
||||
|
@ -493,9 +556,17 @@
|
|||
"notification.admin.report_statuses": "{name} je prijavil/a {target} zaradi {category}",
|
||||
"notification.admin.report_statuses_other": "{name} je prijavil/a {target}",
|
||||
"notification.admin.sign_up": "{name} se je vpisal/a",
|
||||
"notification.admin.sign_up.name_and_others": "Prijavili so se {name} in {count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}",
|
||||
"notification.annual_report.message": "Čaka vas vaš #Wrapstodon {year}! Razkrijte svoje letošnje nepozabne trenutke na Mastodonu!",
|
||||
"notification.annual_report.view": "Pokaži #Wrapstodon",
|
||||
"notification.favourite": "{name} je vzljubil/a vašo objavo",
|
||||
"notification.favourite.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}</a> je dodalo vašo objavo med priljubljene",
|
||||
"notification.favourite_pm": "{name} je dodalo vašo zasebno omembo med priljubljene",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba} two {# drugi osebi} few {# druge osebe} other {# drugih oseb}}</a> je dodalo vašo zasebno omembo med priljubljene",
|
||||
"notification.follow": "{name} vam sledi",
|
||||
"notification.follow.name_and_others": "{name} in {count, plural, one {<a># druga oseba</a> sta ti sledila} two {<a># drugi osebi</a> so ti sledili} few {<a># druge osebe</a> so ti sledili} other {<a># drugih oseb</a> ti je sledilo}}",
|
||||
"notification.follow_request": "{name} vam želi slediti",
|
||||
"notification.follow_request.name_and_others": "{name} in {count, plural, one {# druga oseba bi ti rada sledila} two {# drugi osebi bi ti radi sledili} few {# druge osebe bi ti radi sledili} other {# drugih oseb bi ti radi sledili}}",
|
||||
"notification.label.mention": "Omemba",
|
||||
"notification.label.private_mention": "Zasebna omemba",
|
||||
"notification.label.private_reply": "Zasebni odgovor",
|
||||
|
@ -514,6 +585,7 @@
|
|||
"notification.own_poll": "Vaša anketa je zaključena",
|
||||
"notification.poll": "Anketa, v kateri ste sodelovali, je zaključena",
|
||||
"notification.reblog": "{name} je izpostavila/a vašo objavo",
|
||||
"notification.reblog.name_and_others_with_link": "{name} in <a>{count, plural, one {# druga oseba</a> sta izpostavila tvojo objavo} two {# drugi osebi</a> so izpostavili tvojo objavo} few {# druge osebe</a> so izpostavili tvojo objavo} other {# drugih oseb</a> so izpostavili tvojo objavo}}",
|
||||
"notification.relationships_severance_event": "Povezave z {name} prekinjene",
|
||||
"notification.relationships_severance_event.account_suspension": "Skrbnik na {from} je suspendiral račun {target}, kar pomeni, da od računa ne morete več prejemati posodobitev ali imeti z njim interakcij.",
|
||||
"notification.relationships_severance_event.domain_block": "Skrbnik na {from} je blokiral domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
|
||||
|
@ -522,12 +594,21 @@
|
|||
"notification.status": "{name} je pravkar objavil/a",
|
||||
"notification.update": "{name} je uredil(a) objavo",
|
||||
"notification_requests.accept": "Sprejmi",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Sprejmi # prošnjo …} two {Sprejmi # prošnji …} few {Sprejmi # prošnje …} other {Sprejmi # prošenj …}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Sprejmi prošnjo} two {Sprejmi prošnji} other {Sprejmi prošnje}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "Sprejeti nameravate {count, plural, one {eno prošnjo za obvestila} two {dve prošnji za obvestila} few {# prošnje za obvestila} other {# prošenj za obvestila}}. Ali ste prepričani?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Ali želite sprejeti zahteve za obvestila?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Zavrni prošnjo} two {Zavrni prošnji} other {Zavrni prošnje}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Zavrniti nameravate {count, plural, one {eno prošnjo za obvestila} two {dve prošnji za obvestila} few {# prošnje za obvestila} other {# prošenj za obvestila}}. Do {count, plural, one {nje} two {njiju} other {njih}} ne boste več mogli dostopati. Ali ste prepričani?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Želite opustiti zahteve za obvestila?",
|
||||
"notification_requests.dismiss": "Zavrni",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Zavrni # prošnjo …} two {Zavrni # prošnji …} few {Zavrni # prošnje …} other {Zavrni # prošenj …}}",
|
||||
"notification_requests.edit_selection": "Uredi",
|
||||
"notification_requests.exit_selection": "Opravljeno",
|
||||
"notification_requests.explainer_for_limited_account": "Obvestila za ta račun so bila filtrirana, ker je ta račun omejil moderator.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Obvestila za ta račun so bila filtrirana, ker je račun ali njegov strežnik omejil moderator.",
|
||||
"notification_requests.maximize": "Maksimiraj",
|
||||
"notification_requests.minimize_banner": "Zloži pasico filtriranih obvestil",
|
||||
"notification_requests.notifications_from": "Obvestila od {name}",
|
||||
"notification_requests.title": "Filtrirana obvestila",
|
||||
"notification_requests.view": "Pokaži obvestila",
|
||||
|
@ -542,6 +623,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Vrstica za hitro filtriranje",
|
||||
"notifications.column_settings.follow": "Novi sledilci:",
|
||||
"notifications.column_settings.follow_request": "Nove prošnje za sledenje:",
|
||||
"notifications.column_settings.group": "Združi",
|
||||
"notifications.column_settings.mention": "Omembe:",
|
||||
"notifications.column_settings.poll": "Rezultati ankete:",
|
||||
"notifications.column_settings.push": "Potisna obvestila",
|
||||
|
@ -568,6 +650,9 @@
|
|||
"notifications.policy.accept": "Sprejmi",
|
||||
"notifications.policy.accept_hint": "Pokaži med obvestili",
|
||||
"notifications.policy.drop": "Prezri",
|
||||
"notifications.policy.drop_hint": "Pošlji v pozabo, od koder se nikdar nič ne vrne",
|
||||
"notifications.policy.filter": "Filtriraj",
|
||||
"notifications.policy.filter_hint": "Pošlji med filtrirana prejeta obvestila",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Omejeno s strani moderatorjev strežnika",
|
||||
"notifications.policy.filter_limited_accounts_title": "Moderirani računi",
|
||||
"notifications.policy.filter_new_accounts.hint": "Ustvarjen v {days, plural, one {zadnjem # dnevu} two {zadnjih # dnevih} few {zadnjih # dnevih} other {zadnjih # dnevih}}",
|
||||
|
@ -585,12 +670,14 @@
|
|||
"onboarding.follows.back": "Nazaj",
|
||||
"onboarding.follows.done": "Opravljeno",
|
||||
"onboarding.follows.empty": "Žal trenutno ni mogoče prikazati nobenih rezultatov. Lahko poskusite z iskanjem ali brskanjem po strani za raziskovanje, da poiščete osebe, ki jim želite slediti, ali poskusite znova pozneje.",
|
||||
"onboarding.follows.search": "Išči",
|
||||
"onboarding.follows.title": "Vaš prvi korak je, da sledite ljudem",
|
||||
"onboarding.profile.discoverable": "Naj bo moj profil mogoče najti",
|
||||
"onboarding.profile.discoverable_hint": "Ko se odločite za razkrivanje na Mastodonu, se lahko vaše objave pojavijo v rezultatih iskanja in trendih, vaš profil pa se lahko predlaga ljudem, ki imajo podobne interese kot vi.",
|
||||
"onboarding.profile.display_name": "Pojavno ime",
|
||||
"onboarding.profile.display_name_hint": "Vaše polno ime ali lažno ime ...",
|
||||
"onboarding.profile.note": "Biografija",
|
||||
"onboarding.profile.note_hint": "Druge osebe lahko @omenite ali #ključite ...",
|
||||
"onboarding.profile.note_hint": "Lahko @omenite druge osebe ali dodate #ključnike ...",
|
||||
"onboarding.profile.save_and_continue": "Shrani in nadaljuj",
|
||||
"onboarding.profile.title": "Nastavitev profila",
|
||||
"onboarding.profile.upload_avatar": "Naloži sliko profila",
|
||||
|
@ -610,6 +697,7 @@
|
|||
"poll_button.remove_poll": "Odstrani anketo",
|
||||
"privacy.change": "Spremeni zasebnost objave",
|
||||
"privacy.direct.long": "Vsem omenjenim v objavi",
|
||||
"privacy.direct.short": "Zasebna omemba",
|
||||
"privacy.private.long": "Samo vašim sledilcem",
|
||||
"privacy.private.short": "Sledilcem",
|
||||
"privacy.public.long": "Vsem, ki so ali niso na Mastodonu",
|
||||
|
@ -621,6 +709,8 @@
|
|||
"privacy_policy.title": "Pravilnik o zasebnosti",
|
||||
"recommended": "Priporočeno",
|
||||
"refresh": "Osveži",
|
||||
"regeneration_indicator.please_stand_by": "Prosimo, počakajte.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Pripravljamo vaš domači vir …",
|
||||
"relative_time.days": "{number} d",
|
||||
"relative_time.full.days": "{number, plural, one {pred # dnem} two {pred # dnevoma} few {pred # dnevi} other {pred # dnevi}}",
|
||||
"relative_time.full.hours": "{number, plural, one {pred # uro} two {pred # urama} few {pred # urami} other {pred # urami}}",
|
||||
|
@ -656,7 +746,7 @@
|
|||
"report.reasons.dislike": "Ni mi všeč",
|
||||
"report.reasons.dislike_description": "To ni tisto, kar želim videti",
|
||||
"report.reasons.legal": "To ni legalno",
|
||||
"report.reasons.legal_description": "Ste mnenja, da krši zakonodajo vaše države ali države strežnika",
|
||||
"report.reasons.legal_description": "Sem mnenja, da krši zakonodajo moje države ali države strežnika",
|
||||
"report.reasons.other": "Gre za nekaj drugega",
|
||||
"report.reasons.other_description": "Težava ne sodi v druge kategorije",
|
||||
"report.reasons.spam": "To je neželena vsebina",
|
||||
|
@ -668,10 +758,10 @@
|
|||
"report.statuses.subtitle": "Izberite vse, kar ustreza",
|
||||
"report.statuses.title": "Ali so kakšne objave, ki dokazujejo trditve iz te prijave?",
|
||||
"report.submit": "Pošlji",
|
||||
"report.target": "Prijavi {target}",
|
||||
"report.target": "Prijavljate {target}",
|
||||
"report.thanks.take_action": "Tukaj so vaše možnosti za nadzor tistega, kar vidite na Mastodonu:",
|
||||
"report.thanks.take_action_actionable": "Medtem, ko to pregledujemo, lahko proti @{name} ukrepate:",
|
||||
"report.thanks.title": "Ali ne želite tega videti?",
|
||||
"report.thanks.title": "Ali ne želite videti tega?",
|
||||
"report.thanks.title_actionable": "Hvala za prijavo, bomo preverili.",
|
||||
"report.unfollow": "Ne sledi več @{name}",
|
||||
"report.unfollow_explanation": "Temu računu sledite. Da ne boste več videli njegovih objav v svojem domačem viru, mu prenehajte slediti.",
|
||||
|
@ -695,7 +785,7 @@
|
|||
"search.search_or_paste": "Iščite ali prilepite URL",
|
||||
"search_popout.full_text_search_disabled_message": "Ni dostopno na {domain}.",
|
||||
"search_popout.full_text_search_logged_out_message": "Na voljo le, če ste prijavljeni.",
|
||||
"search_popout.language_code": "Koda ISO jezika",
|
||||
"search_popout.language_code": "Jezikovna koda ISO",
|
||||
"search_popout.options": "Možnosti iskanja",
|
||||
"search_popout.quick_actions": "Hitra dejanja",
|
||||
"search_popout.recent": "Nedavna iskanja",
|
||||
|
@ -705,8 +795,10 @@
|
|||
"search_results.all": "Vse",
|
||||
"search_results.hashtags": "Ključniki",
|
||||
"search_results.no_results": "Ni rezultatov.",
|
||||
"search_results.no_search_yet": "Pobrskajte med objavami, profili in ključniki.",
|
||||
"search_results.see_all": "Poglej vse",
|
||||
"search_results.statuses": "Objave",
|
||||
"search_results.title": "Zadetki za \"{q}\"",
|
||||
"server_banner.about_active_users": "Osebe, ki so uporabljale ta strežnik zadnjih 30 dni (dejavni uporabniki meseca)",
|
||||
"server_banner.active_users": "dejavnih uporabnikov",
|
||||
"server_banner.administered_by": "Upravlja:",
|
||||
|
@ -724,6 +816,7 @@
|
|||
"status.bookmark": "Dodaj med zaznamke",
|
||||
"status.cancel_reblog_private": "Prekliči izpostavitev",
|
||||
"status.cannot_reblog": "Te objave ni mogoče izpostaviti",
|
||||
"status.continued_thread": "Nadaljevanje niti",
|
||||
"status.copy": "Kopiraj povezavo do objave",
|
||||
"status.delete": "Izbriši",
|
||||
"status.detailed_status": "Podroben pogled pogovora",
|
||||
|
@ -733,7 +826,7 @@
|
|||
"status.edited": "Zadnje urejanje {date}",
|
||||
"status.edited_x_times": "Urejeno {count, plural, one {#-krat} two {#-krat} few {#-krat} other {#-krat}}",
|
||||
"status.embed": "Pridobite kodo za vgradnjo",
|
||||
"status.favourite": "Priljubljen_a",
|
||||
"status.favourite": "Priljubljen/a",
|
||||
"status.favourites": "{count, plural, one {priljubitev} two {priljubitvi} few {priljubitve} other {priljubitev}}",
|
||||
"status.filter": "Filtriraj to objavo",
|
||||
"status.history.created": "{name}: ustvarjeno {date}",
|
||||
|
@ -741,7 +834,7 @@
|
|||
"status.load_more": "Naloži več",
|
||||
"status.media.open": "Kliknite za odpiranje",
|
||||
"status.media.show": "Kliknite za prikaz",
|
||||
"status.media_hidden": "Mediji so skriti",
|
||||
"status.media_hidden": "Predstavnosti so skrite",
|
||||
"status.mention": "Omeni @{name}",
|
||||
"status.more": "Več",
|
||||
"status.mute": "Utišaj @{name}",
|
||||
|
@ -758,6 +851,7 @@
|
|||
"status.redraft": "Izbriši in preoblikuj",
|
||||
"status.remove_bookmark": "Odstrani zaznamek",
|
||||
"status.remove_favourite": "Odstrani iz priljubljenih",
|
||||
"status.replied_in_thread": "Odgovor iz niti",
|
||||
"status.replied_to": "Odgovoril/a {name}",
|
||||
"status.reply": "Odgovori",
|
||||
"status.replyAll": "Odgovori na nit",
|
||||
|
@ -767,7 +861,7 @@
|
|||
"status.show_less_all": "Prikaži manj za vse",
|
||||
"status.show_more_all": "Pokaži več za vse",
|
||||
"status.show_original": "Pokaži izvirnik",
|
||||
"status.title.with_attachments": "{user} je objavil_a {attachmentCount, plural, one {{attachmentCount} priponko} two {{attachmentCount} priponki} few {{attachmentCount} priponke} other {{attachmentCount} priponk}}",
|
||||
"status.title.with_attachments": "{user} je objavil/a {attachmentCount, plural, one {{attachmentCount} priponko} two {{attachmentCount} priponki} few {{attachmentCount} priponke} other {{attachmentCount} priponk}}",
|
||||
"status.translate": "Prevedi",
|
||||
"status.translated_from_with": "Prevedeno iz {lang} s pomočjo {provider}",
|
||||
"status.uncached_media_warning": "Predogled ni na voljo",
|
||||
|
@ -778,7 +872,9 @@
|
|||
"subscribed_languages.target": "Spremeni naročene jezike za {target}",
|
||||
"tabs_bar.home": "Domov",
|
||||
"tabs_bar.notifications": "Obvestila",
|
||||
"terms_of_service.effective_as_of": "Veljavno od {date}",
|
||||
"terms_of_service.title": "Pogoji uporabe",
|
||||
"terms_of_service.upcoming_changes_on": "Spremembe začnejo veljati {date}",
|
||||
"time_remaining.days": "{number, plural, one {preostaja # dan} two {preostajata # dneva} few {preostajajo # dnevi} other {preostaja # dni}}",
|
||||
"time_remaining.hours": "{number, plural, one {# ura} other {# ur}} je ostalo",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuta} other {# minut}} je ostalo",
|
||||
|
@ -794,6 +890,11 @@
|
|||
"upload_button.label": "Dodajte slike, video ali zvočno datoteko",
|
||||
"upload_error.limit": "Omejitev prenosa datoteke je presežena.",
|
||||
"upload_error.poll": "Prenos datoteke z anketami ni dovoljen.",
|
||||
"upload_form.drag_and_drop.instructions": "Predstavnostno priponko lahko poberete tako, da pritisnete preslednico ali vnašalko. S puščicami na tipkovnici premikate priponko v posamezno smer. Priponko lahko odložite na novem položaju s ponovnim pritiskom na preslednico ali vnašalko ali pa dejanje prekličete s tipko ubežnica.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Premikanje priponke je preklicano. Predstavnostna priponka {item} je padla nazaj na prejšnje mesto.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Predstavnostna priponka {item} je padla nazaj.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Priponka {item} je bila premaknjena.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Pobrana priponka {item}.",
|
||||
"upload_form.edit": "Uredi",
|
||||
"upload_progress.label": "Pošiljanje ...",
|
||||
"upload_progress.processing": "Obdelovanje …",
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"account.block_short": "Блокла",
|
||||
"account.blocked": "Блокланган",
|
||||
"account.cancel_follow_request": "Киләсе сорау",
|
||||
"account.copy": "Профиль сылтамасын күчереп ал",
|
||||
"account.disable_notifications": "@{name} язулары өчен белдерүләр сүндерү",
|
||||
"account.domain_blocked": "Домен блокланган",
|
||||
"account.edit_profile": "Профильне үзгәртү",
|
||||
|
@ -43,6 +44,8 @@
|
|||
"account.mention": "@{name} искәртү",
|
||||
"account.moved_to": "{name} аларның яңа счеты хәзер күрсәтте:",
|
||||
"account.mute": "@{name} кулланучыга әһәмият бирмәү",
|
||||
"account.mute_notifications_short": "Искәртүләрне сүндер",
|
||||
"account.mute_short": "Тавышсыз",
|
||||
"account.muted": "Әһәмият бирмәнгән",
|
||||
"account.open_original_page": "Чыганак битен ачу",
|
||||
"account.posts": "Язма",
|
||||
|
@ -58,6 +61,7 @@
|
|||
"account.unendorse": "Профильдә тәкъдим итмәү",
|
||||
"account.unfollow": "Язылуны туктату",
|
||||
"account.unmute": "Kабызыгыз @{name}",
|
||||
"account.unmute_notifications_short": "Искәртүләрне кабыз",
|
||||
"account.unmute_short": "Kабызыгыз",
|
||||
"account_note.placeholder": "Click to add a note",
|
||||
"admin.dashboard.daily_retention": "Теркәлгәннән соң икенче көнне кулланучыларны тоту коэффициенты",
|
||||
|
|
|
@ -874,6 +874,7 @@
|
|||
"tabs_bar.notifications": "Сповіщення",
|
||||
"terms_of_service.effective_as_of": "Ефективний на {date}",
|
||||
"terms_of_service.title": "Умови використання",
|
||||
"terms_of_service.upcoming_changes_on": "Майбутні зміни {date}",
|
||||
"time_remaining.days": "{number, plural, one {# день} few {# дні} other {# днів}}",
|
||||
"time_remaining.hours": "{number, plural, one {# година} few {# години} other {# годин}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}",
|
||||
|
|
|
@ -435,7 +435,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
collection = @object['replies']
|
||||
return if collection.blank?
|
||||
|
||||
replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
||||
replies = ActivityPub::FetchRepliesService.new.call(status.account.uri, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
||||
return unless replies.nil?
|
||||
|
||||
uri = value_or_id(collection)
|
||||
|
|
43
app/models/concerns/status/fetch_replies_concern.rb
Normal file
43
app/models/concerns/status/fetch_replies_concern.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Status::FetchRepliesConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# enable/disable fetching all replies
|
||||
FETCH_REPLIES_ENABLED = ENV['FETCH_REPLIES_ENABLED'] == 'true'
|
||||
|
||||
# debounce fetching all replies to minimize DoS
|
||||
FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes
|
||||
FETCH_REPLIES_INITIAL_WAIT_MINUTES = (ENV['FETCH_REPLIES_INITIAL_WAIT_MINUTES'] || 5).to_i.minutes
|
||||
|
||||
included do
|
||||
scope :created_recently, -> { where(created_at: FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago..) }
|
||||
scope :not_created_recently, -> { where(created_at: ..FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago) }
|
||||
scope :fetched_recently, -> { where(fetched_replies_at: FETCH_REPLIES_COOLDOWN_MINUTES.ago..) }
|
||||
scope :not_fetched_recently, -> { where(fetched_replies_at: [nil, ..FETCH_REPLIES_COOLDOWN_MINUTES.ago]) }
|
||||
|
||||
scope :should_not_fetch_replies, -> { local.or(created_recently.or(fetched_recently)) }
|
||||
scope :should_fetch_replies, -> { remote.not_created_recently.not_fetched_recently }
|
||||
|
||||
# statuses for which we won't receive update or deletion actions,
|
||||
# and should update when fetching replies
|
||||
# Status from an account which either
|
||||
# a) has only remote followers
|
||||
# b) has local follows that were created after the last update time, or
|
||||
# c) has no known followers
|
||||
scope :unsubscribed, lambda {
|
||||
remote.merge(
|
||||
Status.left_outer_joins(account: :followers).where.not(followers_accounts: { domain: nil })
|
||||
.or(where.not('follows.created_at < statuses.updated_at'))
|
||||
.or(where(follows: { id: nil }))
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def should_fetch_replies?
|
||||
# we aren't brand new, and we haven't fetched replies since the debounce window
|
||||
FETCH_REPLIES_ENABLED && !local? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && (
|
||||
fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago
|
||||
)
|
||||
end
|
||||
end
|
|
@ -31,6 +31,7 @@
|
|||
# markdown :boolean default(FALSE)
|
||||
# limited_scope :integer
|
||||
# quote_of_id :bigint(8)
|
||||
# fetched_replies_at :datetime
|
||||
#
|
||||
|
||||
require 'ostruct'
|
||||
|
@ -41,6 +42,7 @@ class Status < ApplicationRecord
|
|||
include Paginable
|
||||
include RateLimitable
|
||||
include Status::DomainBlockConcern
|
||||
include Status::FetchRepliesConcern
|
||||
include Status::SafeReblogInsert
|
||||
include Status::SearchConcern
|
||||
include Status::SnapshotConcern
|
||||
|
|
54
app/services/activitypub/fetch_all_replies_service.rb
Normal file
54
app/services/activitypub/fetch_all_replies_service.rb
Normal file
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService
|
||||
include JsonLdHelper
|
||||
|
||||
# Limit of replies to fetch per status
|
||||
MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i
|
||||
|
||||
def call(status_uri, collection_or_uri, max_pages: 1, request_id: nil)
|
||||
@status_uri = status_uri
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_replies(items)
|
||||
# Find all statuses that we *shouldn't* update the replies for, and use that as a filter.
|
||||
# We don't assume that we have the statuses before they're created,
|
||||
# hence the negative filter -
|
||||
# "keep all these uris except the ones we already have"
|
||||
# instead of
|
||||
# "keep all these uris that match some conditions on existing Status objects"
|
||||
#
|
||||
# Typically we assume the number of replies we *shouldn't* fetch is smaller than the
|
||||
# replies we *should* fetch, so we also minimize the number of uris we should load here.
|
||||
uris = items.map { |item| value_or_id(item) }
|
||||
|
||||
# Expand collection to get replies in the DB that were
|
||||
# - not included in the collection,
|
||||
# - that we have locally
|
||||
# - but we have no local followers and thus don't get updates/deletes for
|
||||
parent_id = Status.where(uri: @status_uri).pick(:id)
|
||||
unless parent_id.nil?
|
||||
unsubscribed_replies = Status
|
||||
.where.not(uri: uris)
|
||||
.where(in_reply_to_id: parent_id)
|
||||
.unsubscribed
|
||||
.pluck(:uri)
|
||||
uris.concat(unsubscribed_replies)
|
||||
end
|
||||
|
||||
dont_update = Status.where(uri: uris).should_not_fetch_replies.pluck(:uri)
|
||||
|
||||
# touch all statuses that already exist and that we're about to update
|
||||
Status.where(uri: uris).should_fetch_replies.touch_all(:fetched_replies_at)
|
||||
|
||||
# Reject all statuses that we already have in the db
|
||||
uris = (uris - dont_update).take(MAX_REPLIES)
|
||||
|
||||
Rails.logger.debug { "FetchAllRepliesService - #{@collection_or_uri}: Fetching filtered statuses: #{uris}" }
|
||||
uris
|
||||
end
|
||||
end
|
|
@ -33,7 +33,7 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
|
|||
return collection_or_uri if collection_or_uri.is_a?(Hash)
|
||||
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
|
||||
|
||||
fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
|
||||
fetch_resource_without_id_validation(collection_or_uri, local_follower, raise_on_error: :temporary)
|
||||
end
|
||||
|
||||
def process_items(items)
|
||||
|
|
|
@ -45,7 +45,7 @@ class ActivityPub::FetchFeaturedTagsCollectionService < BaseService
|
|||
return collection_or_uri if collection_or_uri.is_a?(Hash)
|
||||
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
|
||||
|
||||
fetch_resource_without_id_validation(collection_or_uri, local_follower, true)
|
||||
fetch_resource_without_id_validation(collection_or_uri, local_follower, raise_on_error: :temporary)
|
||||
end
|
||||
|
||||
def process_items(items)
|
||||
|
|
|
@ -13,7 +13,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
|||
|
||||
@request_id = request_id || "#{Time.now.utc.to_i}-status-#{uri}"
|
||||
@json = if prefetched_body.nil?
|
||||
fetch_resource(uri, true, on_behalf_of)
|
||||
fetch_status(uri, true, on_behalf_of)
|
||||
else
|
||||
body_to_json(prefetched_body, compare_id: uri)
|
||||
end
|
||||
|
@ -80,4 +80,20 @@ class ActivityPub::FetchRemoteStatusService < BaseService
|
|||
def expected_object_type?
|
||||
equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
|
||||
end
|
||||
|
||||
def fetch_status(uri, id_is_known, on_behalf_of = nil)
|
||||
begin
|
||||
fetch_resource(uri, id_is_known, on_behalf_of, raise_on_error: :all)
|
||||
rescue Mastodon::UnexpectedResponseError => e
|
||||
return unless e.response.code == 404
|
||||
|
||||
# If this is a 404 from a public status from a remote account, delete it
|
||||
existing_status = Status.remote.find_by(uri: uri)
|
||||
if existing_status&.distributable?
|
||||
Rails.logger.debug { "FetchRemoteStatusService - Got 404 for orphaned status with URI #{uri}, deleting" }
|
||||
Tombstone.find_or_create_by(uri: uri, account: existing_status.account)
|
||||
RemoveStatusService.new.call(existing_status, redraft: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,39 +3,59 @@
|
|||
class ActivityPub::FetchRepliesService < BaseService
|
||||
include JsonLdHelper
|
||||
|
||||
def call(parent_status, collection_or_uri, allow_synchronous_requests: true, request_id: nil)
|
||||
@account = parent_status.account
|
||||
# Limit of fetched replies
|
||||
MAX_REPLIES = 5
|
||||
|
||||
def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, request_id: nil)
|
||||
@reference_uri = reference_uri
|
||||
@allow_synchronous_requests = allow_synchronous_requests
|
||||
|
||||
@items = collection_items(collection_or_uri)
|
||||
@items, n_pages = collection_items(collection_or_uri, max_pages: max_pages)
|
||||
return if @items.nil?
|
||||
|
||||
FetchReplyWorker.push_bulk(filtered_replies) { |reply_uri| [reply_uri, { 'request_id' => request_id }] }
|
||||
@items = filter_replies(@items)
|
||||
FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id }] }
|
||||
|
||||
@items
|
||||
[@items, n_pages]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def collection_items(collection_or_uri)
|
||||
def collection_items(collection_or_uri, max_pages: 1)
|
||||
collection = fetch_collection(collection_or_uri)
|
||||
return unless collection.is_a?(Hash)
|
||||
|
||||
collection = fetch_collection(collection['first']) if collection['first'].present?
|
||||
return unless collection.is_a?(Hash)
|
||||
|
||||
items = []
|
||||
n_pages = 1
|
||||
while collection.is_a?(Hash)
|
||||
items.concat(as_array(collection_page_items(collection)))
|
||||
|
||||
break if items.size >= MAX_REPLIES
|
||||
break if n_pages >= max_pages
|
||||
|
||||
collection = collection['next'].present? ? fetch_collection(collection['next']) : nil
|
||||
n_pages += 1
|
||||
end
|
||||
|
||||
[items, n_pages]
|
||||
end
|
||||
|
||||
def collection_page_items(collection)
|
||||
case collection['type']
|
||||
when 'Collection', 'CollectionPage'
|
||||
as_array(collection['items'])
|
||||
collection['items']
|
||||
when 'OrderedCollection', 'OrderedCollectionPage'
|
||||
as_array(collection['orderedItems'])
|
||||
collection['orderedItems']
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_collection(collection_or_uri)
|
||||
return collection_or_uri if collection_or_uri.is_a?(Hash)
|
||||
return unless @allow_synchronous_requests
|
||||
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
|
||||
return if non_matching_uri_hosts?(@reference_uri, collection_or_uri)
|
||||
|
||||
# NOTE: For backward compatibility reasons, Mastodon signs outgoing
|
||||
# queries incorrectly by default.
|
||||
|
@ -45,19 +65,19 @@ class ActivityPub::FetchRepliesService < BaseService
|
|||
#
|
||||
# Therefore, retry with correct signatures if this fails.
|
||||
begin
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, true)
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
|
||||
rescue Mastodon::UnexpectedResponseError => e
|
||||
raise unless e.response && e.response.code == 401 && Addressable::URI.parse(collection_or_uri).query.present?
|
||||
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, true, request_options: { omit_query_string: false })
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary, request_options: { omit_query_string: false })
|
||||
end
|
||||
end
|
||||
|
||||
def filtered_replies
|
||||
def filter_replies(items)
|
||||
# Only fetch replies to the same server as the original status to avoid
|
||||
# amplification attacks.
|
||||
|
||||
# Also limit to 5 fetched replies to limit potential for DoS.
|
||||
@items.map { |item| value_or_id(item) }.reject { |uri| non_matching_uri_hosts?(@account.uri, uri) }.take(5)
|
||||
items.map { |item| value_or_id(item) }.reject { |uri| non_matching_uri_hosts?(@reference_uri, uri) }.take(MAX_REPLIES)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -69,6 +69,6 @@ class ActivityPub::SynchronizeFollowersService < BaseService
|
|||
return collection_or_uri if collection_or_uri.is_a?(Hash)
|
||||
return if non_matching_uri_hosts?(@account.uri, collection_or_uri)
|
||||
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, true)
|
||||
fetch_resource_without_id_validation(collection_or_uri, nil, raise_on_error: :temporary)
|
||||
end
|
||||
end
|
||||
|
|
68
app/workers/activitypub/fetch_all_replies_worker.rb
Normal file
68
app/workers/activitypub/fetch_all_replies_worker.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Fetch all replies to a status, querying recursively through
|
||||
# ActivityPub replies collections, fetching any statuses that
|
||||
# we either don't already have or we haven't checked for new replies
|
||||
# in the Status::FETCH_REPLIES_COOLDOWN_MINUTES interval
|
||||
class ActivityPub::FetchAllRepliesWorker
|
||||
include Sidekiq::Worker
|
||||
include ExponentialBackoff
|
||||
include JsonLdHelper
|
||||
|
||||
sidekiq_options queue: 'pull', retry: 3
|
||||
|
||||
# Global max replies to fetch per request (all replies, recursively)
|
||||
MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_GLOBAL'] || 1000).to_i
|
||||
MAX_PAGES = (ENV['FETCH_REPLIES_MAX_PAGES'] || 500).to_i
|
||||
|
||||
def perform(root_status_id, options = {})
|
||||
@root_status = Status.remote.find_by(id: root_status_id)
|
||||
return unless @root_status&.should_fetch_replies?
|
||||
|
||||
@root_status.touch(:fetched_replies_at)
|
||||
Rails.logger.debug { "FetchAllRepliesWorker - #{@root_status.uri}: Fetching all replies for status: #{@root_status}" }
|
||||
|
||||
uris_to_fetch, n_pages = get_replies(@root_status.uri, MAX_PAGES, options)
|
||||
return if uris_to_fetch.nil?
|
||||
|
||||
fetched_uris = uris_to_fetch.clone.to_set
|
||||
|
||||
until uris_to_fetch.empty? || fetched_uris.length >= MAX_REPLIES || n_pages >= MAX_PAGES
|
||||
next_reply = uris_to_fetch.pop
|
||||
next if next_reply.nil?
|
||||
|
||||
new_reply_uris, new_n_pages = get_replies(next_reply, MAX_PAGES - n_pages, options)
|
||||
next if new_reply_uris.nil?
|
||||
|
||||
new_reply_uris = new_reply_uris.reject { |uri| fetched_uris.include?(uri) }
|
||||
|
||||
uris_to_fetch.concat(new_reply_uris)
|
||||
fetched_uris = fetched_uris.merge(new_reply_uris)
|
||||
n_pages += new_n_pages
|
||||
end
|
||||
|
||||
Rails.logger.debug { "FetchAllRepliesWorker - #{@root_status.uri}: fetched #{fetched_uris.length} replies" }
|
||||
|
||||
# Workers shouldn't be returning anything, but this is used in tests
|
||||
fetched_uris
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_replies(status_uri, max_pages, options = {})
|
||||
replies_collection_or_uri = get_replies_uri(status_uri)
|
||||
return if replies_collection_or_uri.nil?
|
||||
|
||||
ActivityPub::FetchAllRepliesService.new.call(status_uri, replies_collection_or_uri, max_pages: max_pages, **options.deep_symbolize_keys)
|
||||
end
|
||||
|
||||
def get_replies_uri(parent_status_uri)
|
||||
fetch_resource(parent_status_uri, true)&.fetch('replies', nil)
|
||||
rescue => e
|
||||
Rails.logger.info { "FetchAllRepliesWorker - #{@root_status.uri}: Caught exception while resolving replies URI #{parent_status_uri}: #{e} - #{e.message}" }
|
||||
# Raise if we can't get the collection for top-level status to trigger retry
|
||||
raise e if parent_status_uri == @root_status.uri
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ class ActivityPub::FetchRepliesWorker
|
|||
sidekiq_options queue: 'pull', retry: 3
|
||||
|
||||
def perform(parent_status_id, replies_uri, options = {})
|
||||
ActivityPub::FetchRepliesService.new.call(Status.find(parent_status_id), replies_uri, **options.deep_symbolize_keys)
|
||||
ActivityPub::FetchRepliesService.new.call(Status.find(parent_status_id).account.uri, replies_uri, **options.deep_symbolize_keys)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker
|
||||
DEBOUNCE_DELAY = 5.seconds
|
||||
|
||||
sidekiq_options queue: 'push', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||
|
||||
# Distribute an profile update to servers that might have a copy
|
||||
|
|
|
@ -34,11 +34,11 @@ require_relative '../lib/paperclip/transcoder'
|
|||
require_relative '../lib/paperclip/type_corrector'
|
||||
require_relative '../lib/paperclip/response_with_limit_adapter'
|
||||
require_relative '../lib/terrapin/multi_pipe_extensions'
|
||||
require_relative '../lib/mastodon/middleware/public_file_server'
|
||||
require_relative '../lib/mastodon/middleware/socket_cleanup'
|
||||
require_relative '../lib/mastodon/snowflake'
|
||||
require_relative '../lib/mastodon/feature'
|
||||
require_relative '../lib/mastodon/version'
|
||||
require_relative '../lib/mastodon/rack_middleware'
|
||||
require_relative '../lib/public_file_server_middleware'
|
||||
require_relative '../lib/devise/strategies/two_factor_ldap_authenticatable'
|
||||
require_relative '../lib/devise/strategies/two_factor_pam_authenticatable'
|
||||
require_relative '../lib/elasticsearch/client_extensions'
|
||||
|
@ -88,9 +88,9 @@ module Mastodon
|
|||
# We use our own middleware for this
|
||||
config.public_file_server.enabled = false
|
||||
|
||||
config.middleware.use PublicFileServerMiddleware if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
|
||||
config.middleware.use Mastodon::Middleware::PublicFileServer if Rails.env.local? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
|
||||
config.middleware.use Rack::Attack
|
||||
config.middleware.use Mastodon::RackMiddleware
|
||||
config.middleware.use Mastodon::Middleware::SocketCleanup
|
||||
|
||||
config.before_configuration do
|
||||
require 'mastodon/redis_configuration'
|
||||
|
|
|
@ -49,6 +49,10 @@ cy:
|
|||
attributes:
|
||||
reblog:
|
||||
taken: o'r statws yn bodoli'n barod
|
||||
terms_of_service:
|
||||
attributes:
|
||||
effective_date:
|
||||
too_soon: yn rhy fuan, rhaid iddo fod yn hwyrach na %{date}
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -49,6 +49,10 @@ el:
|
|||
attributes:
|
||||
reblog:
|
||||
taken: της ανάρτησης υπάρχει ήδη
|
||||
terms_of_service:
|
||||
attributes:
|
||||
effective_date:
|
||||
too_soon: είναι πολύ σύντομα, πρέπει να είναι μετά από %{date}
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -52,7 +52,7 @@ nl:
|
|||
terms_of_service:
|
||||
attributes:
|
||||
effective_date:
|
||||
too_soon: is te vroeg, moet later zijn dan %{date}
|
||||
too_soon: is te vroeg, moet na %{date} zijn
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -18,9 +18,13 @@ sl:
|
|||
attributes:
|
||||
domain:
|
||||
invalid: ni veljavno ime domene
|
||||
messages:
|
||||
invalid_domain_on_line: "%{value} ni veljavno ime domene"
|
||||
models:
|
||||
account:
|
||||
attributes:
|
||||
fields:
|
||||
fields_with_values_missing_labels: vsebuje vrednosti, ki niso kategorizirane
|
||||
username:
|
||||
invalid: samo črke, številke in podčrtaji
|
||||
reserved: je rezerviran
|
||||
|
@ -40,10 +44,15 @@ sl:
|
|||
attributes:
|
||||
account_id:
|
||||
taken: je že na seznamu
|
||||
must_be_following: mora biti račun, ki mu sledite
|
||||
status:
|
||||
attributes:
|
||||
reblog:
|
||||
taken: od objave že obstajajo
|
||||
terms_of_service:
|
||||
attributes:
|
||||
effective_date:
|
||||
too_soon: je prekmalu, naj bo kasneje od %{date}
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -5,10 +5,51 @@ tt:
|
|||
poll:
|
||||
options: Сайлаулар
|
||||
user:
|
||||
email: Почта адресы
|
||||
email: Эл. почта адресы
|
||||
locale: Тел
|
||||
password: Серсүз
|
||||
user/account:
|
||||
username: Кулланучы исеме
|
||||
user/invite_request:
|
||||
text: Сәбәп
|
||||
errors:
|
||||
attributes:
|
||||
domain:
|
||||
invalid: бу домен исеме гамәлдә түгел
|
||||
messages:
|
||||
invalid_domain_on_line: "%{value} дөрес домен исеме түгел"
|
||||
models:
|
||||
account:
|
||||
attributes:
|
||||
username:
|
||||
invalid: хәрефләр, цифрлар һәм ассызыклау билгеләре генә ярый
|
||||
admin/webhook:
|
||||
attributes:
|
||||
url:
|
||||
invalid: рөхсәт ителгән URL түгел
|
||||
doorkeeper/application:
|
||||
attributes:
|
||||
website:
|
||||
invalid: рөхсәт ителгән URL түгел
|
||||
import:
|
||||
attributes:
|
||||
data:
|
||||
malformed: формат дөрес түгел
|
||||
list_account:
|
||||
attributes:
|
||||
account_id:
|
||||
taken: инде исемлектә
|
||||
status:
|
||||
attributes:
|
||||
reblog:
|
||||
taken: язма инде бар
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
blocked: ярамаган эл. почта провайдерын куллана
|
||||
role_id:
|
||||
elevated: сезнең хәзерге ролегездән югарырак була алмый
|
||||
user_role:
|
||||
attributes:
|
||||
position:
|
||||
elevated: сезнең хәзерге ролегездән югарырак була алмый
|
||||
|
|
|
@ -321,6 +321,7 @@ cy:
|
|||
title: Cofnod archwilio
|
||||
unavailable_instance: "(nid yw enw'r parth ar gael)"
|
||||
announcements:
|
||||
back: Nôl i'r cyhoeddiadau
|
||||
destroyed_msg: Cyhoeddiad wedi'i ddileu'n llwyddiannus!
|
||||
edit:
|
||||
title: Golygu cyhoeddiad
|
||||
|
@ -329,6 +330,9 @@ cy:
|
|||
new:
|
||||
create: Creu cyhoeddiad
|
||||
title: Cyhoeddiad newydd
|
||||
preview:
|
||||
explanation_html: 'Bydd yr e-bost yn cael ei anfon at <strong>%{display_count} defnyddiwr</strong> . Bydd y testun canlynol yn cael ei gynnwys yn yr e-bost:'
|
||||
title: Hysbysiad rhagolwg cyhoeddiad
|
||||
publish: Cyhoeddi
|
||||
published_msg: Cyhoeddiad wedi'i gyhoeddi'n llwyddiannus!
|
||||
scheduled_for: Wedi'i amserlenni ar gyfer %{time}
|
||||
|
@ -995,6 +999,7 @@ cy:
|
|||
chance_to_review_html: "<strong>Ni fydd y telerau gwasanaeth sy'n cael eu cynhyrchu'n cael eu cyhoeddi'n awtomatig.</strong> Bydd cyfle i chi adolygu'r canlyniadau. Cwblhewch y manylion angenrheidiol i symud ymlaen."
|
||||
explanation_html: Mae'r templed telerau gwasanaeth sy'n cael eu darparu at ddibenion gwybodaeth yn unig, ac ni ddylid ei ddehongli fel cyngor cyfreithiol ar unrhyw bwnc. Ymgynghorwch â'ch cyngor cyfreithiol eich hun ar eich sefyllfa ag unrhyw gwestiynau cyfreithiol penodol sydd gennych.
|
||||
title: Telerau Gosod Gwasanaeth
|
||||
going_live_on_html: Byw, effeithiol %{date}
|
||||
history: Hanes
|
||||
live: Byw
|
||||
no_history: Nid oes unrhyw newidiadau i delerau gwasanaeth wedi'u cofnodi eto.
|
||||
|
@ -2076,6 +2081,10 @@ cy:
|
|||
recovery_instructions_html: Os ydych chi'n colli mynediad i'ch ffôn, mae modd i chi ddefnyddio un o'r codau adfer isod i gael mynediad at eich cyfrif. <strong>Cadwch y codau adfer yn breifat</strong>. Er enghraifft, gallwch chi eu argraffu a'u cadw gyda dogfennau eraill pwysig.
|
||||
webauthn: Allweddi diogelwch
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Mae gweinyddwyr %{domain} yn gwneud cyhoeddiad:'
|
||||
subject: Cyhoeddiad gwasanaeth
|
||||
title: Cyhoeddiad gwasanaeth %{domain}
|
||||
appeal_approved:
|
||||
action: Gosodiadau Cyfrif
|
||||
explanation: Mae apêl y rhybudd yn erbyn eich cyfrif ar %{strike_date} a gyflwynwyd gennych ar %{appeal_date} wedi'i chymeradwyo. Mae eich cyfrif unwaith eto yn gadarnhaol.
|
||||
|
@ -2108,6 +2117,8 @@ cy:
|
|||
terms_of_service_changed:
|
||||
agreement: Drwy barhau i ddefnyddio %{domain}, rydych yn cytuno i'r telerau hyn. Os ydych yn anghytuno â'r telerau a ddiweddarwyd, gallwch derfynu eich cytundeb â %{domain} ar unrhyw adeg drwy ddileu eich cyfrif.
|
||||
changelog: 'Yn fyr, dyma beth mae''r diweddariad hwn yn ei olygu i chi:'
|
||||
description: 'Rydych yn derbyn yr e-bost hwn oherwydd ein bod yn gwneud rhai newidiadau i''n telerau gwasanaeth yn %{domain}. Bydd y diweddariadau hyn yn dod i rym ar %{date}. Rydym yn eich annog i adolygu''r telerau diweddaraf yn llawn yma:'
|
||||
description_html: Rydych yn derbyn yr e-bost hwn oherwydd ein bod yn gwneud rhai newidiadau i'n telerau gwasanaeth yn %{domain}. Bydd y diweddariadau hyn yn dod i rym ar <strong>%{date}</strong> . Rydym yn eich annog i adolygu'r <a href="%{path}" target="_blank">telerau diweddaraf yn llawn yma</a> .
|
||||
sign_off: Tîm %{domain}
|
||||
subject: Diweddariadau i'n telerau gwasanaeth
|
||||
subtitle: Mae telerau gwasanaeth %{domain} yn newid
|
||||
|
|
|
@ -3,7 +3,19 @@ tt:
|
|||
devise:
|
||||
confirmations:
|
||||
confirmed: Сезнең э. почта адресыгыз уңышлы расланган.
|
||||
failure:
|
||||
already_authenticated: Сез кердегез инде.
|
||||
inactive: Сезнең аккаунтыгыз әле активламаган.
|
||||
invalid: "%{authentication_keys} яки серсүз дөрес кертелмәгән."
|
||||
locked: Сезнең хисапъязмагыз блокланган.
|
||||
not_found_in_database: "%{authentication_keys} яки серсүз дөрес кертелмәгән."
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
action: Email адресын расла
|
||||
action_with_app: Расла һәм %{app} эченә кайт
|
||||
title: Email адресын раслагыз
|
||||
email_changed:
|
||||
explanation: 'Сезнең аккаунтыгызның email адресы моңа үзгәртеләчәк:'
|
||||
reset_password_instructions:
|
||||
action: Серсүзне үзгәртү
|
||||
title: Серсүзне алыштыру
|
||||
|
|
|
@ -60,6 +60,7 @@ sl:
|
|||
error:
|
||||
title: Prišlo je do napake
|
||||
new:
|
||||
prompt_html: "%{client_name} želi dostopati do vašega računa. <strong>To prošnjo odobrite le, če tega odjemalca prepoznate in mu zaupate.</strong>"
|
||||
review_permissions: Preglej dovoljenja
|
||||
title: Potrebna je odobritev
|
||||
show:
|
||||
|
@ -82,6 +83,7 @@ sl:
|
|||
access_denied: Lastnik virov ali odobritveni strežnik je zavrnil zahtevo.
|
||||
credential_flow_not_configured: Pretok geselskih pooblastil lastnika virov ni uspel, ker Doorkeeper.configure.resource_owner_from_credentials ni nastavljen.
|
||||
invalid_client: Odobritev odjemalca ni uspela zaradi neznanega odjemalca, zaradi nevključitve odobritve odjemalca ali zaradi nepodprte metode odobritve.
|
||||
invalid_code_challenge_method: Metoda za kodo mora biti S256, čistopis ni podprt.
|
||||
invalid_grant: Predložena odobritev je neveljavna, je potekla, je preklicana, se ne ujema z URI-jem za preusmeritev uporabljenim v zahtevi za odobritev, ali pa je bila izdana drugemu odjemalcu.
|
||||
invalid_redirect_uri: URI za preusmeritev ni veljaven.
|
||||
invalid_request:
|
||||
|
|
|
@ -309,6 +309,7 @@ el:
|
|||
title: Αρχείο ελέγχου
|
||||
unavailable_instance: "(μη διαθέσιμο όνομα τομέα)"
|
||||
announcements:
|
||||
back: Επιστροφή στις ανακοινώσεις
|
||||
destroyed_msg: Επιτυχής διαγραφή ανακοίνωσης!
|
||||
edit:
|
||||
title: Ενημέρωση ανακοίνωσης
|
||||
|
|
|
@ -1159,6 +1159,7 @@ et:
|
|||
set_new_password: Uue salasõna määramine
|
||||
setup:
|
||||
email_below_hint_html: Kontrolli rämpsposti kausta või taotle uut. Saad oma e-posti aadressi parandada, kui see on vale.
|
||||
email_settings_hint_html: Klõpsa aadressile %{email} saadetud linki, et alustada Mastodoni kasutamist. Me oleme ootel.
|
||||
link_not_received: Kas ei saanud linki?
|
||||
new_confirmation_instructions_sent: Saad mõne minuti pärast uue kinnituslingiga e-kirja!
|
||||
title: Kontrolli sisendkasti
|
||||
|
@ -1167,6 +1168,7 @@ et:
|
|||
title: Logi sisse kohta %{domain}
|
||||
sign_up:
|
||||
manual_review: Liitumised kohas %{domain} vaadatakse meie moderaatorite poolt käsitsi läbi. Aitamaks meil sinu taotlust läbi vaadata, kirjuta palun natuke endast ja miks soovid kontot kohas %{domain}.
|
||||
preamble: Selle Mastodoni serveri kontoga saad jälgida mistahes teist isikut fediversumis, sõltumata sellest, kus ta konto on majutatud.
|
||||
title: Loo konto serverisse %{domain}.
|
||||
status:
|
||||
account_status: Konto olek
|
||||
|
|
|
@ -320,7 +320,7 @@ nl:
|
|||
title: Nieuwe mededeling
|
||||
preview:
|
||||
explanation_html: 'De e-mail wordt verzonden naar <strong>%{display_count} gebruikers</strong>. De volgende tekst wordt in het bericht opgenomen:'
|
||||
title: Voorbeeld aankondiging notificatie
|
||||
title: Voorbeeld van mededeling
|
||||
publish: Inschakelen
|
||||
published_msg: Publiceren van mededeling geslaagd!
|
||||
scheduled_for: Ingepland voor %{time}
|
||||
|
@ -943,7 +943,7 @@ nl:
|
|||
chance_to_review_html: "<strong>De gegenereerde gebruiksvoorwaarden worden niet automatisch gepubliceerd.</strong> Je krijgt de gelegenheid om de resultaten eerst te bekijken. Vul de benodigde gegevens in om verder te gaan."
|
||||
explanation_html: Het sjabloon voor de gebruiksvoorwaarden is uitsluitend bedoeld voor informatieve doeleinden en mag niet worden opgevat als juridisch advies over welk onderwerp dan ook. Raadpleeg een eigen juridisch adviseur over jouw situatie en voor specifieke juridische vragen.
|
||||
title: Gebruiksvoorwaarden instellen
|
||||
going_live_on_html: Actueel, met ingang van %{date}
|
||||
going_live_on_html: Actueel met ingang van %{date}
|
||||
history: Geschiedenis
|
||||
live: Actueel
|
||||
no_history: Er zijn nog geen opgeslagen wijzigingen van de gebruiksvoorwaarden.
|
||||
|
@ -1911,7 +1911,7 @@ nl:
|
|||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'De beheerders van %{domain} doen een mededeling:'
|
||||
subject: Service aankondiging
|
||||
subject: Service-aankondiging
|
||||
title: "%{domain} service aankondiging"
|
||||
appeal_approved:
|
||||
action: Accountinstellingen
|
||||
|
@ -1945,8 +1945,8 @@ nl:
|
|||
terms_of_service_changed:
|
||||
agreement: Door %{domain} te blijven gebruiken, ga je akkoord met deze voorwaarden. Als je het niet eens bent met de bijgewerkte voorwaarden, kun je je overeenkomst met %{domain} op elk gewenst moment beëindigen door je account te verwijderen.
|
||||
changelog: 'In een oogopslag betekent deze update voor jou:'
|
||||
description: 'Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden bij %{domain}. Deze aanpassingen komen van kracht op %{date}. We raden je aan om de bijgewerkte voorwaarden hier volledig te bekijken:'
|
||||
description_html: Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden bij %{domain}. Deze aanpassingen komen van kracht op <strong>%{date}</strong>. We raden je aan om de <a href="%{path}" target="_blank">bijgewerkte voorwaarden hier</a> volledig te bestuderen.
|
||||
description: 'Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden op %{domain}. Deze aanpassingen worden van kracht op %{date}. We raden je aan om de bijgewerkte voorwaarden hier volledig te bekijken:'
|
||||
description_html: Je ontvangt dit bericht, omdat we enkele wijzigingen aanbrengen in onze gebruiksvoorwaarden op %{domain}. Deze aanpassingen worden van kracht op <strong>%{date}</strong>. We raden je aan om de <a href="%{path}" target="_blank">bijgewerkte voorwaarden hier</a> volledig te bestuderen.
|
||||
sign_off: Het %{domain}-team
|
||||
subject: Onze bijgewerkte gebruiksvoorwaarden
|
||||
subtitle: De gebruiksvoorwaarden van %{domain} veranderen
|
||||
|
|
|
@ -309,6 +309,7 @@ pt-PT:
|
|||
title: Registo de auditoria
|
||||
unavailable_instance: "(nome de domínio indisponível)"
|
||||
announcements:
|
||||
back: Voltar às mensagem de manutenção
|
||||
destroyed_msg: Mensagem de manutenção eliminada com sucesso!
|
||||
edit:
|
||||
title: Editar mensagem de manutenção
|
||||
|
@ -317,6 +318,9 @@ pt-PT:
|
|||
new:
|
||||
create: Criar mensagem de manutenção
|
||||
title: Nova mensagem de manutenção
|
||||
preview:
|
||||
explanation_html: 'O e-mail será enviado para <strong>%{display_count} utilizadores</strong>. O texto seguinte será incluído na mensagem de e-mail:'
|
||||
title: Pré-visualização da mensagem de manutenção
|
||||
publish: Publicar
|
||||
published_msg: Mensagem de manutenção publicada com sucesso!
|
||||
scheduled_for: Agendado para %{time}
|
||||
|
@ -1905,6 +1909,10 @@ pt-PT:
|
|||
recovery_instructions_html: Se perderes o seu telemóvel, poderás usar um dos códigos de recuperação para voltares a ter acesso à tua conta. <strong>Guarda os códigos de recuperação em lugar seguro</strong>. Por exemplo, podes imprimi-los e guardá-los junto a outros documentos importantes.
|
||||
webauthn: Chaves de segurança
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Os administradores do %{domain} estão a fazer um anúncio:'
|
||||
subject: Mensagem de manutenção do serviço
|
||||
title: 'Mensagem de manutenção de %{domain} '
|
||||
appeal_approved:
|
||||
action: Configurações da conta
|
||||
explanation: A contestação à reprimenda contra a tua conta em %{strike_date}, enviada a %{appeal_date}, foi aceite. A tua conta encontra-se novamente em situação regular.
|
||||
|
|
|
@ -3,7 +3,7 @@ cy:
|
|||
simple_form:
|
||||
hints:
|
||||
account:
|
||||
attribution_domains: Un i bob llinell. Yn amddiffyn rhag priodoli ffug.
|
||||
attribution_domains: Un i bob llinell. Yn diogelu rhag priodoli ffug.
|
||||
discoverable: Mae'n bosibl y bydd eich postiadau cyhoeddus a'ch proffil yn cael sylw neu'n cael eu hargymell mewn gwahanol feysydd o Mastodon ac efallai y bydd eich proffil yn cael ei awgrymu i ddefnyddwyr eraill.
|
||||
display_name: Eich enw llawn neu'ch enw hwyl.
|
||||
fields: Eich tudalen cartref, rhagenwau, oed, neu unrhyw beth.
|
||||
|
@ -132,12 +132,18 @@ cy:
|
|||
name: Dim ond er mwyn ei gwneud yn fwy darllenadwy y gallwch chi newid y llythrennau, er enghraifft
|
||||
terms_of_service:
|
||||
changelog: Mae modd ei strwythuro gyda chystrawen Markdown.
|
||||
effective_date: Gall amserlen resymol amrywio rhwng 10 a 30 diwrnod o'r dyddiad y byddwch yn hysbysu'ch defnyddwyr.
|
||||
text: Mae modd ei strwythuro gyda chystrawen Markdown.
|
||||
terms_of_service_generator:
|
||||
admin_email: Mae hysbysiadau cyfreithiol yn cynnwys gwrth-hysbysiadau, gorchmynion llys, ceisiadau tynnu i lawr, a cheisiadau gorfodi'r gyfraith.
|
||||
arbitration_address: Gall fod yr un peth â'r cyfeiriad corfforol uchod, neu “D/A” os ydych chi'n defnyddio e-bost.
|
||||
arbitration_website: Gall fod yn ffurflen we, neu “D/A” os ydych chi'n defnyddio e-bost.
|
||||
choice_of_law: Dinas, rhanbarth, tiriogaeth neu wladwriaeth y bydd ei deddfau mewnol sylweddol yn llywodraethu unrhyw hawliadau a phob hawliad.
|
||||
dmca_address: Ar gyfer gweithredwyr yr Unol Daleithiau, defnyddiwch y cyfeiriad sydd wedi'i gofrestru yn Designated Agent Directory y DMCA. Mae rhestriad blychau post ar gael ar gais uniongyrchol, defnyddiwch gais Designated Agent Post Office Box Waiver Request y DMCA i anfon e-bost at y Swyddfa Hawlfraint a disgrifiwch eich bod yn gymedrolwr cynnwys yn y cartref sy'n ofni dial neu ddialedd am eich gweithredoedd ac sydd angen defnyddio Blwch P.O. i dynnu eich cyfeiriad cartref o olwg y cyhoedd.
|
||||
dmca_email: Gall fod yr un e-bost ag sy'n cael ei ddefnyddir ar gyfer “Cyfeiriad e-bost ar gyfer hysbysiadau cyfreithiol” uchod.
|
||||
domain: Dynodiad unigryw o'r gwasanaeth ar-lein rydych chi'n ei ddarparu.
|
||||
jurisdiction: Rhestrwch y wlad lle mae pwy bynnag sy'n talu'r biliau yn byw. Os yw'n gwmni neu'n endid arall, rhestrwch y wlad lle mae wedi'i ymgorffori, a'r ddinas, rhanbarth, tiriogaeth neu wladwriaeth fel y bo'n briodol.
|
||||
min_age: Ni ddylai fod yn is na'r isafswm oedran sy'n ofynnol gan gyfreithiau eich awdurdodaeth.
|
||||
user:
|
||||
chosen_languages: Wedi eu dewis, dim ond tŵtiau yn yr ieithoedd hyn bydd yn cael eu harddangos mewn ffrydiau cyhoeddus
|
||||
role: Mae'r rôl yn rheoli pa ganiatâd sydd gan y defnyddiwr.
|
||||
|
@ -330,15 +336,18 @@ cy:
|
|||
usable: Caniatáu i bostiadau ddefnyddio'r hashnod hwn yn lleol
|
||||
terms_of_service:
|
||||
changelog: Beth sydd wedi newid?
|
||||
effective_date: Dyddiad effeithiol
|
||||
text: Telerau Gwasanaeth
|
||||
terms_of_service_generator:
|
||||
admin_email: Cyfeiriad e-bost ar gyfer hysbysiadau cyfreithiol
|
||||
arbitration_address: Cyfeiriad ffisegol ar gyfer hysbysiadau cyflafareddu
|
||||
arbitration_website: Gwefan ar gyfer cyflwyno hysbysiadau cyflafareddu
|
||||
choice_of_law: Dewis Cyfraith
|
||||
dmca_address: Cyfeiriad ffisegol ar gyfer DMCA/hysbysiadau hawlfraint
|
||||
dmca_email: Cyfeiriad e-bost ar gyfer DMCA/hysbysiadau hawlfraint
|
||||
domain: Parth
|
||||
jurisdiction: Awdurdodaeth gyfreithiol
|
||||
min_age: Isafswm oedran
|
||||
user:
|
||||
role: Rôl
|
||||
time_zone: Cylchfa amser
|
||||
|
|
|
@ -136,10 +136,14 @@ is:
|
|||
text: Er hægt að sníða með Markdown-málskipan.
|
||||
terms_of_service_generator:
|
||||
admin_email: Löglegar tilkynningar ná yfir andsvör, dómsúrskurði, lokunarbeiðnir og beiðnir frá lögregluembættum.
|
||||
arbitration_address: Má vera það sama og raunverulegt heimilisfang eða “N/A” ef tölvupóstur er notaður.
|
||||
arbitration_website: Má vera innfyllingarform á vefsíðu eða “N/A” ef tölvupóstur er notaður.
|
||||
choice_of_law: Sveitarfélög, héruð eða ríki þar sem ríkjandi lög og reglugerðir skulu stýra meðhöndlun á öllum kröfum.
|
||||
dmca_address: Fyrir rekstraraðila í BNA ætti að nota heimilisfang sem skráð er í DMCA Designated Agent Directory. Hægt er að verða sér úti um A P.O. pósthólfsskráningu með beinni beiðni; notaðu DMCA Designated Agent Post Office Box Waiver Request til að senda tölvupóst á Copyright Office og lýstu því yfir að þú sért heimavinnandi efnismiðlari (home-based content moderator) sem átt á hættu refsingar eða hefndir vegna þess sem þú miðlar og þurfir því á slíku pósthólfi að halda svo þitt eigið heimilisfang sé ekki gert opinbert.
|
||||
dmca_email: Má vera sama tölvupóstfang og það sem notað er í “Tölvupóstfang vegna löglegra tilkynninga” hér að ofan.
|
||||
domain: Einstakt auðkenni á netþjónustunni sem þú býður.
|
||||
jurisdiction: Settu inn landið þar sem sá býr sem borgar reikningana. Ef það er fyrirtæki eða samtök, skaltu hafa það landið þar sem lögheimili þess er, auk borgar, héraðs, svæðis eða fylkis eins og við á.
|
||||
min_age: Ætti ekki að vera lægri en sá lágmarksaldur sek kveðið er á um í lögum þíns lögsagnarumdæmis.
|
||||
user:
|
||||
chosen_languages: Þegar merkt er við þetta, birtast einungis færslur á völdum tungumálum á opinberum tímalínum
|
||||
role: Hlutverk stýrir hvaða heimildir notandinn hefur.
|
||||
|
@ -343,6 +347,7 @@ is:
|
|||
dmca_email: Tölvupóstfang tilkynninga vegna DMCA/höfundaréttar
|
||||
domain: Lén
|
||||
jurisdiction: Lögsagnarumdæmi
|
||||
min_age: Lágmarksaldur
|
||||
user:
|
||||
role: Hlutverk
|
||||
time_zone: Tímabelti
|
||||
|
|
|
@ -132,8 +132,11 @@ lv:
|
|||
name: Tu vari mainīt tikai burtu lielumu, piemēram, lai tie būtu vieglāk lasāmi
|
||||
terms_of_service:
|
||||
changelog: Var veidot ar Markdown pierakstu.
|
||||
effective_date: Saprātīgs laika logs var būt no 10 līdz 30 dienām no dienas, kad lietotāji tiek apziņoti.
|
||||
text: Var veidot ar Markdown pierakstu.
|
||||
terms_of_service_generator:
|
||||
arbitration_address: Var būt tāda pati kā augstāk esošā fiziskā adrese vai "N/A", ja tiek izmantota e-pasta adrese.
|
||||
arbitration_website: Var būt tīmekļa veidlapa vai "N/A", ja tiek izmantots e-pasts.
|
||||
domain: Sniegtā tiešsaistas pakalpojuma neatkārtojama identifikācija.
|
||||
user:
|
||||
chosen_languages: Ja ieķeksēts, publiskos laika grafikos tiks parādītas tikai ziņas noteiktajās valodās
|
||||
|
@ -327,11 +330,13 @@ lv:
|
|||
usable: Ļaut ierakstos vietēji izmantot šo tēmturi
|
||||
terms_of_service:
|
||||
changelog: Kas ir mainījies?
|
||||
effective_date: Spēkā stāšanās datums
|
||||
text: Pakalpojuma izmantošanas nosacījumi
|
||||
terms_of_service_generator:
|
||||
admin_email: E-pasta adrese juridiskiem paziņojumiem
|
||||
choice_of_law: Likuma izvēle
|
||||
domain: Domēna vārds
|
||||
min_age: Mazākais pieļaujamais vecums
|
||||
user:
|
||||
role: Loma
|
||||
time_zone: Laika josla
|
||||
|
|
|
@ -132,18 +132,18 @@ nl:
|
|||
name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken
|
||||
terms_of_service:
|
||||
changelog: Kan worden opgemaakt met Markdown.
|
||||
effective_date: Een redelijke periode kan variëren van 10 tot 30 dagen vanaf de datum waarop u uw gebruikers op de hoogte stelt.
|
||||
effective_date: Een redelijke periode kan variëren van 10 tot 30 dagen vanaf de datum waarop je jouw gebruikers op de hoogte stelt.
|
||||
text: Kan worden opgemaakt met Markdown.
|
||||
terms_of_service_generator:
|
||||
admin_email: Juridische mededelingen zijn o. a. counter-notices, gerechterlijke bevelen, takedown-requests en handhavingsverzoeken.
|
||||
arbitration_address: Kan hetzelfde zijn als bovenstaand vestigingsadres of "N/A" bij gebruik van e-mail.
|
||||
arbitration_website: Kan een webformulier zijn, of "N/A" als e-mail wordt gebruikt.
|
||||
choice_of_law: Stad, regio, grondgebied of staat waar de interne grondwetten van toepassing zijn op alle claims.
|
||||
arbitration_website: Kan een webformulier zijn, of "N/A" wanneer e-mail wordt gebruikt.
|
||||
choice_of_law: Stad, regio, grondgebied of staat waar de interne materiële wetten van toepassing zijn op alle aanspraken.
|
||||
dmca_address: 'Gebruik voor beheerders in de VS: het adres dat is geregistreerd in de DMCA Designated Agent Directory. Op verzoek is er een postbuslijst beschikbaar. Gebruik het DMCA Designated Agent Post Office Box Waiver Request om het Copyright Office te e-mailen en te beschrijven dat je een vanaf huis opererende inhoudsmoderator bent, die wraak of vergelding vreest voor je moderator-acties en daarom een postbus moet gebruiken om jouw huisadres uit het publieke domein te houden.'
|
||||
dmca_email: Kan hetzelfde e-mailadres zijn dat gebruikt wordt voor "E-mailadres voor juridische berichten" hierboven.
|
||||
domain: Een unieke identificatie van de online dienst die je levert.
|
||||
jurisdiction: Vermeld het land waar de persoon woont die de rekeningen betaalt. Is het een bedrijf of iets dergelijks, vermeld dan het land waar het ingeschreven staat en de stad, de regio, het grondgebied of de staat, voor zover van toepassing.
|
||||
min_age: Mag niet lager zijn dan de minimale vereiste leeftijd volgens de wetten van uw jurisdictie.
|
||||
min_age: Mag niet lager zijn dan de minimale vereiste leeftijd volgens de wetten van jouw jurisdictie.
|
||||
user:
|
||||
chosen_languages: Alleen berichten in de aangevinkte talen worden op de openbare tijdlijnen getoond
|
||||
role: De rol bepaalt welke rechten de gebruiker heeft.
|
||||
|
@ -342,7 +342,7 @@ nl:
|
|||
admin_email: E-mailadres voor juridische meldingen
|
||||
arbitration_address: Vestigingsadres voor arbitrage-mededelingen
|
||||
arbitration_website: Website voor het indienen van arbitrage-mededelingen
|
||||
choice_of_law: Keuze van recht
|
||||
choice_of_law: Keuze van rechtsgebied
|
||||
dmca_address: Vestigingsadres voor DMCA/auteursrecht-mededelingen
|
||||
dmca_email: E-mailadres voor DMCA/auteursrecht-mededelingen
|
||||
domain: Domein
|
||||
|
|
|
@ -136,10 +136,14 @@ pt-PT:
|
|||
text: Pode ser estruturado com sintaxe Markdown.
|
||||
terms_of_service_generator:
|
||||
admin_email: Os avisos legais incluem contra-avisos, ordens judiciais, pedidos de remoção e pedidos de aplicação da lei.
|
||||
arbitration_address: Pode ser o mesmo que o endereço físico acima ou “N/A” se utilizar e-mail.
|
||||
arbitration_website: Pode ser um formulário web ou “N/A” se for utilizado o e-mail.
|
||||
choice_of_law: A legislação interna do estado, território, região ou cidade que deve regular qualquer conflito.
|
||||
dmca_address: Para operadores dos EUA, utilize o endereço registado no Diretório de Agentes Designados DMCA. A listagem de uma caixa postal está disponível mediante pedido direto. Utilize o DMCA Designated Agent Post Office Box Waiver Request para enviar uma mensagem de correio eletrónico ao Copyright Office e descreva que é um moderador de conteúdos baseado em casa que receia vingança ou represálias pelas suas ações e que necessita de utilizar uma caixa postal para retirar o seu endereço de casa da vista do público.
|
||||
dmca_email: Pode ser o mesmo e-mail utilizado para “Endereço de e-mail para avisos legais” acima.
|
||||
domain: Identificação única do serviço online que está a prestar.
|
||||
jurisdiction: Indique o país de residência de quem paga as contas. Se se tratar de uma empresa ou outra entidade, indique o país onde está constituída, bem como a cidade, região, território ou estado, consoante o caso.
|
||||
min_age: Não deve ter menos do que a idade mínima exigida pela legislação da sua jurisdição.
|
||||
user:
|
||||
chosen_languages: Quando selecionado, só serão mostradas nas cronologias públicas as publicações nos idiomas escolhidos
|
||||
role: A função controla as permissões que o utilizador tem.
|
||||
|
@ -343,6 +347,7 @@ pt-PT:
|
|||
dmca_email: Endereço de e-mail para avisos DMCA/direitos autorais
|
||||
domain: Domínio
|
||||
jurisdiction: Jurisdição legal
|
||||
min_age: Idade mínima
|
||||
user:
|
||||
role: Função
|
||||
time_zone: Fuso horário
|
||||
|
|
|
@ -3,12 +3,14 @@ sl:
|
|||
simple_form:
|
||||
hints:
|
||||
account:
|
||||
attribution_domains: Ena na vrstico. Ščiti pred napačno navedbo avtorstva.
|
||||
discoverable: Vaše javne objave in profil so lahko predstavljeni ali priporočeni v različnih delih Mastodona, vaš profil pa je lahko predlagan drugim uporabnikom.
|
||||
display_name: Vaše polno ime ali lažno ime.
|
||||
fields: Vaša domača stran, starost, kar koli.
|
||||
indexable: Vaše javne objave se lahko pojavijo v rezultatih iskanja na Mastodonu. Ljudje, ki so bili v interakciji z vašimi objavami, jih bodo lahko iskali ne glede na to.
|
||||
note: 'Druge osebe lahko @omenite ali #ključnite.'
|
||||
show_collections: Ljudje bodo lahko brskali po vaših sledilcih in sledenih. Ljudje, ki jim sledite, bodo videli, da jim sledite ne glede na to.
|
||||
unlocked: Ljudje vam bodo lahko sledili, ne da bi zahtevali odobritev. Ne potrdite, če želite pregledati prošnje za sledenje in izbrati, ali želite nove sledilce sprejeti ali zavrniti.
|
||||
account_alias:
|
||||
acct: Določite uporabniškoime@domena računa, od katerega se želite preseliti
|
||||
account_migration:
|
||||
|
@ -58,6 +60,7 @@ sl:
|
|||
setting_display_media_default: Skrij medij, ki je označen kot občutljiv
|
||||
setting_display_media_hide_all: Vedno skrij vse medije
|
||||
setting_display_media_show_all: Vedno pokaži medij, ki je označen kot občutljiv
|
||||
setting_system_scrollbars_ui: Velja zgolj za namizne brskalnike, ki temeljijo na Safariju in Chromeu
|
||||
setting_use_blurhash: Prelivi temeljijo na barvah skrite vizualne slike, vendar zakrivajo vse podrobnosti
|
||||
setting_use_pending_items: Skrij posodobitev časovnice za klikom namesto samodejnega posodabljanja
|
||||
username: Uporabite lahko črke, števke in podčrtaje.
|
||||
|
@ -127,8 +130,22 @@ sl:
|
|||
show_application: Ne glede na to boste vedno lahko pogledali, katera aplikacija je objavila vašo objavo.
|
||||
tag:
|
||||
name: Spremenite lahko le npr. velikost črk (velike/male), da je bolj berljivo
|
||||
terms_of_service:
|
||||
changelog: Uporabite lahko oblikovanje z Markdown.
|
||||
effective_date: Razumen čas do uveljavitve je navadno nekje med 10 in 30 dni od datuma, ko ste obvestili svoje uporabnike.
|
||||
text: Uporabite lahko oblikovanje z Markdown.
|
||||
terms_of_service_generator:
|
||||
admin_email: Pravna obvestila vključujejo odgovore na obvestila, sodne naloge, zahteve za odstranitev in zahteve organov pregona.
|
||||
arbitration_address: Lahko je enak kot fizični naslov zgoraj ali „N/A“, če uporabljate e-pošto.
|
||||
arbitration_website: Lahko je spletni obrazec ali „N/A“, če uporabljate e-pošto.
|
||||
choice_of_law: Mesto, regija, teritorij ali zvezna država, katere področni zakoni urejajo vse tožbene zahtevke.
|
||||
dmca_email: Lahko je enak e-poštni naslov kot v polju „E-poštni naslov za pravna obvestila“ zgoraj.
|
||||
domain: Edinstvena identifikacija spletne storitve, ki jo ponujate.
|
||||
jurisdiction: Navedite državo, kjer živi tisti, ki plačuje račune. Če je to podjetje ali druga entiteta, navedite državo, kjer je bila ustanovljena. Po potrebi dopišite tudi mesto, regijo, teritorij ali zvezno državo.
|
||||
min_age: Ne smete biti mlajši od starostne omejitve, ki jo postavljajo zakoni vašega pravosodnega sistema.
|
||||
user:
|
||||
chosen_languages: Ko je označeno, bodo v javnih časovnicah prikazane samo objave v izbranih jezikih
|
||||
role: Vloga določa, katera dovoljenja ima uporabnik.
|
||||
user_role:
|
||||
color: Barva, uporabljena za vlogo po celem up. vmesniku, podana v šestnajstiškem zapisu RGB
|
||||
highlighted: S tem je vloga javno vidna
|
||||
|
@ -141,6 +158,7 @@ sl:
|
|||
url: Kam bodo poslani dogodki
|
||||
labels:
|
||||
account:
|
||||
attribution_domains: Spletna mesta, ki vas smejo navajati kot avtorja/ico
|
||||
discoverable: Izpostavljaj profile in objave v algoritmih odkrivanja
|
||||
fields:
|
||||
name: Oznaka
|
||||
|
@ -217,8 +235,10 @@ sl:
|
|||
setting_display_media_show_all: Prikaži vse
|
||||
setting_expand_spoilers: Vedno razširi objave, označene z opozorili o vsebini
|
||||
setting_hide_network: Skrij svoje omrežje
|
||||
setting_missing_alt_text_modal: Pred objavo predstavnosti brez nadomestnega besedila pokaži potrditveno okno
|
||||
setting_reduce_motion: Zmanjšanje premikanja v animacijah
|
||||
setting_system_font_ui: Uporabi privzeto pisavo sistema
|
||||
setting_system_scrollbars_ui: Uporabi privzeti drsni trak sistema
|
||||
setting_theme: Tema strani
|
||||
setting_trends: Pokaži današnje trende
|
||||
setting_unfollow_modal: Pokaži potrditveno okno, preden nekoga prenehamo slediti
|
||||
|
@ -313,6 +333,19 @@ sl:
|
|||
name: Ključnik
|
||||
trendable: Dovoli, da se ta ključnik pojavi med trendi
|
||||
usable: Dovoli, da objave krajevno uporabljajo ta ključnik
|
||||
terms_of_service:
|
||||
changelog: Kaj je novega?
|
||||
effective_date: Datum začetka veljavnosti
|
||||
text: Pogoji uporabe
|
||||
terms_of_service_generator:
|
||||
admin_email: E-poštni naslov za pravna obvestila
|
||||
arbitration_address: Fizični naslov za arbitražna obvestila
|
||||
arbitration_website: Spletišče za vložitev arbitražnih obvestil
|
||||
dmca_address: Fizični naslov za obvestila DMCA ali o avtorskih pravicah
|
||||
dmca_email: E-poštni naslov za obvestila DMCA ali o avtorskih pravicah
|
||||
domain: Domena
|
||||
jurisdiction: Pravna pristojnost
|
||||
min_age: Najmanjša starost
|
||||
user:
|
||||
role: Vloga
|
||||
time_zone: Časovni pas
|
||||
|
|
|
@ -25,9 +25,12 @@ sl:
|
|||
other: Objav
|
||||
two: Objavi
|
||||
posts_tab_heading: Objave
|
||||
self_follow_error: Ni dovoljeno slediti lastnemu računu
|
||||
admin:
|
||||
account_actions:
|
||||
action: Izvedi dejanje
|
||||
already_silenced: Ta račun je že omejen.
|
||||
already_suspended: Ta račun je že suspendiran.
|
||||
title: Izvedi moderirano dejanje za %{acct}
|
||||
account_moderation_notes:
|
||||
create: Pusti opombo
|
||||
|
@ -49,6 +52,7 @@ sl:
|
|||
title: Spremeni e-naslov za %{username}
|
||||
change_role:
|
||||
changed_msg: Vloga uspešno spremenjena!
|
||||
edit_roles: Upravljaj z uporabniškimi vlogami
|
||||
label: Spremeni vlogo
|
||||
no_role: Brez vloge
|
||||
title: Spremeni vlogo za %{username}
|
||||
|
@ -189,6 +193,7 @@ sl:
|
|||
create_domain_block: Ustvari blokado domene
|
||||
create_email_domain_block: Ustvari blokado domene e-pošte
|
||||
create_ip_block: Ustvari pravilo IP
|
||||
create_relay: Ustvari rele
|
||||
create_unavailable_domain: Ustvari domeno, ki ni na voljo
|
||||
create_user_role: Ustvari vlogo
|
||||
demote_user: Ponižaj uporabnika
|
||||
|
@ -200,18 +205,22 @@ sl:
|
|||
destroy_email_domain_block: Izbriši blokado domene e-pošte
|
||||
destroy_instance: Očisti domeno
|
||||
destroy_ip_block: Izbriši pravilo IP
|
||||
destroy_relay: Izbriši rele
|
||||
destroy_status: Izbriši objavo
|
||||
destroy_unavailable_domain: Izbriši nedosegljivo domeno
|
||||
destroy_user_role: Uniči vlogo
|
||||
disable_2fa_user: Onemogoči
|
||||
disable_custom_emoji: Onemogoči emotikon po meri
|
||||
disable_relay: Onemogoči rele
|
||||
disable_sign_in_token_auth_user: Onemogoči overjanje z žetonom po e-pošti za uporabnika
|
||||
disable_user: Onemogoči uporabnika
|
||||
enable_custom_emoji: Omogoči emotikon po meri
|
||||
enable_relay: Omogoči rele
|
||||
enable_sign_in_token_auth_user: Omogoči overjanje z žetonom po e-pošti za uporabnika
|
||||
enable_user: Omogoči uporabnika
|
||||
memorialize_account: Spomenificiraj račun
|
||||
promote_user: Povišaj uporabnika
|
||||
publish_terms_of_service: Objavi pogoje uporabe
|
||||
reject_appeal: Zavrni pritožbo
|
||||
reject_user: Zavrni uporabnika
|
||||
remove_avatar_user: Odstrani avatar
|
||||
|
@ -249,6 +258,7 @@ sl:
|
|||
create_domain_block_html: "%{name} je blokiral/a domeno %{target}"
|
||||
create_email_domain_block_html: "%{name} je dal/a na črni seznam e-pošto domene %{target}"
|
||||
create_ip_block_html: "%{name} je ustvaril/a pravilo za IP %{target}"
|
||||
create_relay_html: "%{name} je ustvaril/a rele %{target}"
|
||||
create_unavailable_domain_html: "%{name} je prekinil/a dostavo v domeno %{target}"
|
||||
create_user_role_html: "%{name} je ustvaril/a vlogo %{target}"
|
||||
demote_user_html: "%{name} je ponižal/a uporabnika %{target}"
|
||||
|
@ -260,18 +270,22 @@ sl:
|
|||
destroy_email_domain_block_html: "%{name} je odblokiral/a e-pošto domene %{target}"
|
||||
destroy_instance_html: "%{name} je očistil/a domeno %{target}"
|
||||
destroy_ip_block_html: "%{name} je izbrisal/a pravilo za IP %{target}"
|
||||
destroy_relay_html: "%{name} je izbrisal/a rele %{target}"
|
||||
destroy_status_html: "%{name} je odstranil/a objavo uporabnika %{target}"
|
||||
destroy_unavailable_domain_html: "%{name} je nadaljeval/a dostav v domeno %{target}"
|
||||
destroy_user_role_html: "%{name} je izbrisal/a vlogo %{target}"
|
||||
disable_2fa_user_html: "%{name} je onemogočil/a dvofaktorsko zahtevo za uporabnika %{target}"
|
||||
disable_custom_emoji_html: "%{name} je onemogočil/a emotikone %{target}"
|
||||
disable_relay_html: "%{name} je onemogočil/a rele %{target}"
|
||||
disable_sign_in_token_auth_user_html: "%{name} je onemogočil/a overjanje z žetonom po e-pošti za uporabnika %{target}"
|
||||
disable_user_html: "%{name} je onemogočil/a prijavo za uporabnika %{target}"
|
||||
enable_custom_emoji_html: "%{name} je omogočil/a emotikone %{target}"
|
||||
enable_relay_html: "%{name} je omogočil rele %{target}"
|
||||
enable_sign_in_token_auth_user_html: "%{name} je omogočil/a overjanje z žetonom po e-pošti za uporabnika %{target}"
|
||||
enable_user_html: "%{name} je omogočil/a prijavo za uporabnika %{target}"
|
||||
memorialize_account_html: "%{name} je spremenil/a račun uporabnika %{target} v spominsko stran"
|
||||
promote_user_html: "%{name} je povišal/a uporabnika %{target}"
|
||||
publish_terms_of_service_html: "%{name} je posodobil/a pogoje uporabe"
|
||||
reject_appeal_html: "%{name} je zavrnil/a pritožbo uporabnika %{target} na moderatorsko odločitev"
|
||||
reject_user_html: "%{name} je zavrnil/a registracijo iz %{target}"
|
||||
remove_avatar_user_html: "%{name} je odstranil podobo (avatar) uporabnika %{target}"
|
||||
|
@ -301,6 +315,7 @@ sl:
|
|||
title: Dnevnik revizije
|
||||
unavailable_instance: "(ime domene ni na voljo)"
|
||||
announcements:
|
||||
back: Nazaj na oznanila
|
||||
destroyed_msg: Obvestilo je bilo uspešno izbrisano!
|
||||
edit:
|
||||
title: Uredi obvestilo
|
||||
|
@ -309,6 +324,9 @@ sl:
|
|||
new:
|
||||
create: Ustvari obvestilo
|
||||
title: Novo obvestilo
|
||||
preview:
|
||||
explanation_html: 'E-poštno sporočilo bo poslano <strong>%{display_count} uporabnikom</strong>. Priloženo bo naslednje besedilo:'
|
||||
title: Pokaži predogled oznanila
|
||||
publish: Objavi
|
||||
published_msg: Obvestilo je bilo uspešno objavljeno!
|
||||
scheduled_for: Načrtovano ob %{time}
|
||||
|
@ -486,6 +504,9 @@ sl:
|
|||
title: Sledi priporočilom
|
||||
unsuppress: Obnovi sledenje priporočilom
|
||||
instances:
|
||||
audit_log:
|
||||
title: Nedavni revizijski zapisi
|
||||
view_all: Prikaži ves revizijski dnevnik
|
||||
availability:
|
||||
description_html:
|
||||
few: Če dostava v domeno spodleti <strong>%{count} različne dni</strong> brez uspeha, ne bo nadaljnjih poskusov dostopa, razen če je prejeta dostava <em>iz</em> domene.
|
||||
|
@ -622,6 +643,7 @@ sl:
|
|||
suspend_description_html: Račun in vsa njegova vsebina ne bo dostopna in bo postopoma izbrisana, interakcija z njim pa ne bo več možna. Dejanje je moč povrniti v roku 30 dni. Zaključi vse prijave zoper ta račun.
|
||||
actions_description_html: Odločite se, katere ukrepe boste sprejeli za rešitev te prijave. Če sprejmete kazenski ukrep proti prijavljenemu računu, mu bo poslano e-poštno obvestilo, razen če je izbrana kategorija <strong>Neželena pošta</strong>.
|
||||
actions_description_remote_html: Odločite se za dejanje, ki bo odločilo o tej prijavi. To bo vplivalo le na to, kako <strong>vaš</strong> strežnik komunicira s tem oddaljenim računom in obravnava njegovo vsebino.
|
||||
actions_no_posts: To poročilo ni vezano na nobene objave, ki bi jih lahko izbrisali
|
||||
add_to_report: Dodaj več v prijavo
|
||||
already_suspended_badges:
|
||||
local: Že suspendiran na tem strežniku
|
||||
|
@ -838,6 +860,7 @@ sl:
|
|||
back_to_account: Nazaj na stran računa
|
||||
back_to_report: Nazaj na stran prijave
|
||||
batch:
|
||||
add_to_report: 'Dodaj poročilu #%{id}'
|
||||
remove_from_report: Odstrani iz prijave
|
||||
report: Poročaj
|
||||
contents: Vsebina
|
||||
|
@ -849,12 +872,17 @@ sl:
|
|||
media:
|
||||
title: Mediji
|
||||
metadata: Metapodatki
|
||||
no_history: Ta objava ni bila spremenjena
|
||||
no_status_selected: Nobena objava ni bila spremenjena, ker ni bila nobena izbrana
|
||||
open: Odpri objavo
|
||||
original_status: Izvorna objava
|
||||
reblogs: Ponovljeni blogi
|
||||
replied_to_html: V odgovor %{acct_link}
|
||||
status_changed: Objava spremenjena
|
||||
status_title: Avtor/ica objave @%{name}
|
||||
title: Objave računa - @%{name}
|
||||
trending: V trendu
|
||||
view_publicly: Prikaži javno
|
||||
visibility: Vidnost
|
||||
with_media: Z mediji
|
||||
strikes:
|
||||
|
@ -896,6 +924,9 @@ sl:
|
|||
message_html: Nobenih pravil strežnika niste določili.
|
||||
sidekiq_process_check:
|
||||
message_html: Noben proces Sidekiq ne poteka za %{value} vrst. Preglejte svojo prilagoditev Sidekiq
|
||||
software_version_check:
|
||||
action: Oglejte si razpoložljive posodobitve
|
||||
message_html: Na voljo je posodobitev Mastodona.
|
||||
software_version_critical_check:
|
||||
action: Glejte razpoložljive posodobitve
|
||||
message_html: Na voljo je kritična posodobitev Mastodona. Posodobite čim prej.
|
||||
|
@ -922,16 +953,42 @@ sl:
|
|||
name: Ime
|
||||
newest: Najnovejše
|
||||
oldest: Najstarejše
|
||||
open: Prikaži javno
|
||||
reset: Ponastavi
|
||||
review: Stanje pregleda
|
||||
search: Išči
|
||||
title: Ključniki
|
||||
updated_msg: Nastavitve ključnikov uspešno posodobljene
|
||||
terms_of_service:
|
||||
back: Nazaj na pogoje uporabe
|
||||
changelog: Kaj je novega
|
||||
create: Uporabi svoje
|
||||
current: Trenutni
|
||||
draft: Osnutek
|
||||
generate: Uporabi predlogo
|
||||
generates:
|
||||
action: Generiraj
|
||||
chance_to_review_html: "<strong>Generirani pogoji uporabe ne bodo objavljeni samodejno,</strong> tako da jih boste imeli čas preveriti. Vnesite vse potrebne podatke."
|
||||
explanation_html: Predloga pogojev uporabe je informativne narave in naj ne služi kot pravno vodilo. O pravnih vprašanjih in specifikah se posvetujte s pravnim strokovnjakom.
|
||||
title: Postavitev pogojev uporabe
|
||||
going_live_on_html: Objavljeni, začnejo veljati %{date}
|
||||
history: Zgodovina
|
||||
live: Objavljeni
|
||||
no_history: V pogojih uporabe še ni zabeleženih sprememb.
|
||||
no_terms_of_service_html: Trenutno nimate nastavljenih pogojev uporabe. Ti naj bi razjasnili pravno razmerje in dodeljevanje odgovornosti med vami in vašimi uporabniki v primeru spora.
|
||||
notified_on_html: Uporabniki so obveščeni %{date}
|
||||
notify_users: Obvesti uporabnike
|
||||
preview:
|
||||
explanation_html: 'E-poštno sporočilo bo poslano <strong>%{display_count} uporabnikom</strong>, ki so se registrirali pred %{date}. Priloženo bo naslednje besedilo:'
|
||||
send_preview: Pošlji predogled na %{email}
|
||||
send_to_all:
|
||||
few: Pošlji %{display_count} e-poštna sporočila
|
||||
one: Pošlji %{display_count} e-poštno sporočilo
|
||||
other: Pošlji %{display_count} e-poštnih sporočil
|
||||
two: Pošlji %{display_count} e-poštni sporočili
|
||||
title: Prikaži predogled obvestila pogojev uporabe
|
||||
publish: Objavi
|
||||
published_on_html: Objavljeno %{date}
|
||||
save_draft: Shrani osnutek
|
||||
title: Pogoji uporabe
|
||||
title: Upravljanje
|
||||
|
@ -1173,6 +1230,7 @@ sl:
|
|||
set_new_password: Nastavi novo geslo
|
||||
setup:
|
||||
email_below_hint_html: Poglejte v mapo neželene pošte ali zaprosite za novega. Če ste podali napačen e-naslov, ga lahko popravite.
|
||||
email_settings_hint_html: Kliknite na povezavo, ki smo vam jo poslali na %{email}, pa boste lahko začeli uporabljati Mastodon. Tukajle bomo počakali.
|
||||
link_not_received: Ali ste prejeli povezavo?
|
||||
new_confirmation_instructions_sent: Čez nekaj minut boste prejeli novo e-sporočilo s potrditveno povezavo!
|
||||
title: Preverite svojo dohodno e-pošto
|
||||
|
@ -1181,6 +1239,7 @@ sl:
|
|||
title: Vpiši se v %{domain}
|
||||
sign_up:
|
||||
manual_review: Registracije na %{domain} ročno pregledajo naši moderatorji. Da nam olajšate obdelavo vaše prijave, zapišite kaj o sebi in zakaj si želite račun na %{domain}.
|
||||
preamble: Če ustvarite račun na tem strežniku Mastodona, boste lahko sledili komur koli v fediverzumu, ne glede na to, kje gostuje njegov/njen račun.
|
||||
title: Naj vas namestimo na %{domain}.
|
||||
status:
|
||||
account_status: Stanje računa
|
||||
|
@ -1192,8 +1251,16 @@ sl:
|
|||
view_strikes: Pokaži pretekle ukrepe proti mojemu računu
|
||||
too_fast: Obrazec oddan prehitro, poskusite znova.
|
||||
use_security_key: Uporabi varnostni ključ
|
||||
user_agreement_html: Prebral/a sem <a href="%{terms_of_service_path}" target="_blank">pogoje uporabe</a> in <a href="%{privacy_policy_path}" target="_blank">politiko zasebnosti</a> in z obojim soglašam
|
||||
user_privacy_agreement_html: Prebral sem <a href="%{privacy_policy_path}" target="_blank">politiko zasebnosti</a> in soglašam z njo
|
||||
author_attribution:
|
||||
example_title: Vzorčno besedilo
|
||||
hint_html: Ali pišete novičke ali spletni dnevnik kje drugje poleg Mastodona? Poskrbite, da bo vaše avtorstvo pravilno navedeno, ko bo kdo delil vaše delo na Mastodonu.
|
||||
instructions: 'Poskrbite, da bo v dokumentu HTML vašega prispevka naslednja koda:'
|
||||
more_from_html: Več od %{name}
|
||||
s_blog: Spletni dnevnik %{name}
|
||||
then_instructions: Nato dodajte ime domene, kamor objavljate, v spodnje polje.
|
||||
title: Priznanje avtorstva
|
||||
challenge:
|
||||
confirm: Nadaljuj
|
||||
hint_html: "<strong>Namig:</strong> naslednjo uro vas ne bomo več vprašali po vašem geslu."
|
||||
|
@ -1404,6 +1471,68 @@ sl:
|
|||
merge_long: Ohrani obstoječe zapise in dodaj nove
|
||||
overwrite: Prepiši
|
||||
overwrite_long: Zamenjaj trenutne zapise z novimi
|
||||
overwrite_preambles:
|
||||
blocking_html:
|
||||
few: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
|
||||
one: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
|
||||
other: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
|
||||
two: Kaže, da želite <strong>zamenjati svoj seznam blokiranih</strong> z do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
|
||||
bookmarks_html:
|
||||
few: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavami</strong> iz <strong>%{filename}</strong>.
|
||||
one: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavo</strong> iz <strong>%{filename}</strong>.
|
||||
other: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavami</strong> iz <strong>%{filename}</strong>.
|
||||
two: Kaže, da želite <strong>zamenjati svoje zaznamke</strong> z do <strong>%{count} objavama</strong> iz <strong>%{filename}</strong>.
|
||||
domain_blocking_html:
|
||||
few: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenami</strong> iz <strong>%{filename}</strong>.
|
||||
one: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domeno</strong> iz <strong>%{filename}</strong>.
|
||||
other: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenami</strong> iz <strong>%{filename}</strong>.
|
||||
two: Kaže, da želite <strong>zamenjati svoj seznam blokiranih domen</strong> z do <strong>%{count} domenama</strong> iz <strong>%{filename}</strong>.
|
||||
following_html:
|
||||
few: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računom</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
|
||||
one: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računu</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
|
||||
other: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računom</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
|
||||
two: Kaže, da želite <strong>slediti</strong> do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong> in <strong>nehati slediti komur koli drugemu</strong>.
|
||||
lists_html:
|
||||
few: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bodo dodani do <strong>%{count} računi</strong>.
|
||||
one: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bo dodan do <strong>%{count} račun</strong>.
|
||||
other: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bo dodanih do <strong>%{count} računov</strong>.
|
||||
two: Kaže, da nameravate <strong>zamenjati svoje sezname</strong> z vsebino iz <strong>%{filename}</strong>. V nove sezname bosta dodana do <strong>%{count} računa</strong>.
|
||||
muting_html:
|
||||
few: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
|
||||
one: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
|
||||
other: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računi</strong> iz <strong>%{filename}</strong>.
|
||||
two: Kaže, da nameravate <strong>zamenjati svoj seznam utišanih računov</strong> z do <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
|
||||
preambles:
|
||||
blocking_html:
|
||||
few: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} račune</strong> iz <strong>%{filename}</strong>.
|
||||
one: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} račun</strong> iz <strong>%{filename}</strong>.
|
||||
other: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} računov</strong> iz <strong>%{filename}</strong>.
|
||||
two: Kaže, da nameravate <strong>blokirati</strong> <strong>%{count} računa</strong> iz <strong>%{filename}</strong>.
|
||||
bookmarks_html:
|
||||
few: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objave</strong> iz <strong>%{filename}</strong>.
|
||||
one: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objavo</strong> iz <strong>%{filename}</strong>.
|
||||
other: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objav</strong> iz <strong>%{filename}</strong>.
|
||||
two: V svoje <strong>zaznamke</strong> boste dodali <strong>%{count} objavi</strong> iz <strong>%{filename}</strong>.
|
||||
domain_blocking_html:
|
||||
few: "<strong>Blokirali</strong> boste <strong>%{count} domene</strong> iz <strong>%{filename}</strong>."
|
||||
one: "<strong>Blokirali</strong> boste <strong>%{count} domeno</strong> iz <strong>%{filename}</strong>."
|
||||
other: "<strong>Blokirali</strong> boste <strong>%{count} domen</strong> iz <strong>%{filename}</strong>."
|
||||
two: "<strong>Blokirali</strong> boste <strong>%{count} domeni</strong> iz <strong>%{filename}</strong>."
|
||||
following_html:
|
||||
few: Začeli boste <strong>slediti</strong> <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
|
||||
one: Začeli boste <strong>slediti</strong> <strong>%{count} računu</strong> iz <strong>%{filename}</strong>.
|
||||
other: Začeli boste <strong>slediti</strong> <strong>%{count} računom</strong> iz <strong>%{filename}</strong>.
|
||||
two: Začeli boste <strong>slediti</strong> <strong>%{count} računoma</strong> iz <strong>%{filename}</strong>.
|
||||
lists_html:
|
||||
few: V svoje <strong>sezname</strong> boste dodali <strong>%{count} račune</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
|
||||
one: V svoje <strong>sezname</strong> boste dodali <strong>%{count} račun</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
|
||||
other: V svoje <strong>sezname</strong> boste dodali <strong>%{count} računov</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
|
||||
two: V svoje <strong>sezname</strong> boste dodali <strong>%{count} računa</strong> iz <strong>%{filename}</strong>. Če seznami manjkajo, bodo ustvarjeni.
|
||||
muting_html:
|
||||
few: "<strong>Utišali</strong> boste <strong>%{count} račune</strong> iz <strong>%{filename}</strong>."
|
||||
one: "<strong>Utišali</strong> boste <strong>%{count} račun</strong> iz <strong>%{filename}</strong>."
|
||||
other: "<strong>Utišali</strong> boste <strong>%{count} računov</strong> iz <strong>%{filename}</strong>."
|
||||
two: "<strong>Utišali</strong> boste <strong>%{count} računa</strong> iz <strong>%{filename}</strong>."
|
||||
preface: Podatke, ki ste jih izvozili iz drugega strežnika, lahko uvozite. Na primer seznam oseb, ki jih spremljate ali blokirate.
|
||||
recent_imports: Nedavni uvozi
|
||||
states:
|
||||
|
@ -1490,6 +1619,7 @@ sl:
|
|||
media_attachments:
|
||||
validations:
|
||||
images_and_video: Videoposnetka ni mogoče priložiti objavi, ki že vsebuje slike
|
||||
not_found: Predstavnosti %{ids} ne najdem ali pa je že pripeta k drugi objavi
|
||||
not_ready: Datotek, katerih obdelava ni dokončana, ni mogoče pripeti. Poskusite znova kmalu!
|
||||
too_many: Ni možno priložiti več kot 4 datoteke
|
||||
migrations:
|
||||
|
@ -1661,6 +1791,7 @@ sl:
|
|||
scheduled_statuses:
|
||||
over_daily_limit: Za ta dan ste presegli omejitev %{limit} načrtovanih objav
|
||||
over_total_limit: Presegli ste omejitev %{limit} načrtovanih objav
|
||||
too_soon: datum mora biti v prihodnosti
|
||||
self_destruct:
|
||||
lead_html: Na žalost se <strong>%{domain}</strong> za vedno zapira. Če ste tu imeli svoj račun, ga v prihodnje ne boste mogli več uporabljati. Zahtevate lahko kopijo svojih podatkov.
|
||||
title: Ta strežnik se zapira
|
||||
|
@ -1864,6 +1995,10 @@ sl:
|
|||
recovery_instructions_html: Če kdaj izgubite dostop do telefona, lahko uporabite eno od spodnjih obnovitvenih kod, da ponovno pridobite dostop do svojega računa. <strong>Shranite obnovitvene kode</strong>. Lahko jih natisnete in shranite z drugimi pomembnimi dokumenti.
|
||||
webauthn: Varnostni ključi
|
||||
user_mailer:
|
||||
announcement_published:
|
||||
description: 'Skrbniki domene %{domain} oznanjajo:'
|
||||
subject: Storitveno oznanilo
|
||||
title: Storitveno oznanilo %{domain}
|
||||
appeal_approved:
|
||||
action: Nastavitve računa
|
||||
explanation: Pritožbi na ukrep proti vašemu računu z dne %{strike_date}, ki ste jo oddali dne %{appeal_date}, je bilo ugodeno. Vaš račun je znova nesporen.
|
||||
|
@ -1894,6 +2029,13 @@ sl:
|
|||
subject: Do vašega računa je bil opravljen dostop z novega naslova IP
|
||||
title: Nova prijava
|
||||
terms_of_service_changed:
|
||||
agreement: Če boste še naprej uporabljali %{domain}, se strinjate s temi pogoji. Če se ne, lahko kadarkoli odstopite od dogovora z domeno %{domain} tako, da izbrišete svoj račun.
|
||||
changelog: 'Kratek povzetek tega, kaj ta posodobitev pomeni za vas:'
|
||||
description: 'To e-poštno sporočilo ste prejeli, ker smo spremenili pogoje uporabe v domeni %{domain}. Posodobitve bodo začele veljati %{date}. Vabimo vas, da si posodobljene pogoje preberete tukaj:'
|
||||
description_html: To e-poštno sporočilo ste prejeli, ker smo spremenili pogoje uporabe v domeni %{domain}. Posodobitve bodo začele veljati <strong>%{date}</strong>. Vabimo vas, da si posodobljene pogoje preberete <a href="%{path}" target="_blank">na tej povezavi</a>.
|
||||
sign_off: Ekipa %{domain}
|
||||
subject: Posodobitve naših pogojev uporabe
|
||||
subtitle: Spreminjajo se pogoji uporabe domene %{domain}
|
||||
title: Pomembna posodobitev
|
||||
warning:
|
||||
appeal: Pošlji pritožbo
|
||||
|
@ -1984,6 +2126,7 @@ sl:
|
|||
instructions_html: Spodnjo kodo kopirajte in prilepite v HTML svojega spletnega mesta. Nato dodajte naslov svoje spletne strani v eno od dodatnih polj v svojem profilu v zavihku »Uredi profil« in shranite spremembe.
|
||||
verification: Potrditev
|
||||
verified_links: Vaše preverjene povezave
|
||||
website_verification: Overitev spletišča
|
||||
webauthn_credentials:
|
||||
add: Dodaj nov varnostni ključ
|
||||
create:
|
||||
|
|
|
@ -325,7 +325,8 @@ uk:
|
|||
create: Створити оголошення
|
||||
title: Нове оголошення
|
||||
preview:
|
||||
title: Попередній перегляд повідомлення
|
||||
explanation_html: 'Електронний лист буде надіслано <strong>%{display_count} користувачам</strong>. До електронного листа буде включено такий текст:'
|
||||
title: Попередній перегляд сповіщення
|
||||
publish: Опублікувати
|
||||
published_msg: Оголошення успішно опубліковано!
|
||||
scheduled_for: Заплановано на %{time}
|
||||
|
|
|
@ -10,3 +10,5 @@ shared:
|
|||
version:
|
||||
metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil) %>
|
||||
prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %>
|
||||
test:
|
||||
experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', nil), 'testing_only'].compact.join(',') %>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFetchedRepliesAtToStatus < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :statuses, :fetched_replies_at, :datetime, null: true
|
||||
end
|
||||
end
|
|
@ -1420,6 +1420,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_05_074104) do
|
|||
t.boolean "markdown", default: false
|
||||
t.integer "limited_scope"
|
||||
t.bigint "quote_of_id"
|
||||
t.datetime "fetched_replies_at"
|
||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
||||
t.index ["account_id", "reblog_of_id", "deleted_at", "searchability"], name: "index_statuses_for_get_following_accounts_to_search", where: "((deleted_at IS NULL) AND (reblog_of_id IS NULL) AND (searchability = ANY (ARRAY[0, 10, 1])))"
|
||||
t.index ["account_id"], name: "index_statuses_on_account_id"
|
||||
|
|
|
@ -19,8 +19,8 @@ module Mastodon::Feature
|
|||
super
|
||||
end
|
||||
|
||||
def respond_to_missing?(name)
|
||||
name.to_s.end_with?('_enabled?')
|
||||
def respond_to_missing?(name, include_all = false)
|
||||
name.to_s.end_with?('_enabled?') || super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
52
lib/mastodon/middleware/public_file_server.rb
Normal file
52
lib/mastodon/middleware/public_file_server.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'action_dispatch/middleware/static'
|
||||
|
||||
module Mastodon
|
||||
module Middleware
|
||||
class PublicFileServer
|
||||
SERVICE_WORKER_TTL = 7.days.to_i
|
||||
CACHE_TTL = 28.days.to_i
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
file = @file_handler.attempt(env)
|
||||
|
||||
# If the request is not a static file, move on!
|
||||
return @app.call(env) if file.nil?
|
||||
|
||||
status, headers, response = file
|
||||
|
||||
# Set cache headers on static files. Some paths require different cache headers
|
||||
headers['Cache-Control'] = begin
|
||||
request_path = env['REQUEST_PATH']
|
||||
|
||||
if request_path.start_with?('/sw.js')
|
||||
"public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
|
||||
elsif request_path.start_with?(paperclip_root_url)
|
||||
"public, max-age=#{CACHE_TTL}, immutable"
|
||||
else
|
||||
"public, max-age=#{CACHE_TTL}, must-revalidate"
|
||||
end
|
||||
end
|
||||
|
||||
# Override the default CSP header set by the CSP middleware
|
||||
headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
|
||||
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paperclip_root_url
|
||||
ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
lib/mastodon/middleware/socket_cleanup.rb
Normal file
34
lib/mastodon/middleware/socket_cleanup.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mastodon
|
||||
module Middleware
|
||||
class SocketCleanup
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@app.call(env)
|
||||
ensure
|
||||
clean_up_sockets!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_up_sockets!
|
||||
clean_up_redis_socket!
|
||||
clean_up_statsd_socket!
|
||||
end
|
||||
|
||||
def clean_up_redis_socket!
|
||||
RedisConnection.pool.checkin if Thread.current[:redis]
|
||||
Thread.current[:redis] = nil
|
||||
end
|
||||
|
||||
def clean_up_statsd_socket!
|
||||
Thread.current[:statsd_socket]&.close
|
||||
Thread.current[:statsd_socket] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Mastodon::RackMiddleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@app.call(env)
|
||||
ensure
|
||||
clean_up_sockets!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clean_up_sockets!
|
||||
clean_up_redis_socket!
|
||||
clean_up_statsd_socket!
|
||||
end
|
||||
|
||||
def clean_up_redis_socket!
|
||||
RedisConnection.pool.checkin if Thread.current[:redis]
|
||||
Thread.current[:redis] = nil
|
||||
end
|
||||
|
||||
def clean_up_statsd_socket!
|
||||
Thread.current[:statsd_socket]&.close
|
||||
Thread.current[:statsd_socket] = nil
|
||||
end
|
||||
end
|
|
@ -35,7 +35,7 @@ module Mastodon
|
|||
end
|
||||
|
||||
def default_prerelease
|
||||
'alpha.3'
|
||||
'alpha.4'
|
||||
end
|
||||
|
||||
def prerelease
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'action_dispatch/middleware/static'
|
||||
|
||||
class PublicFileServerMiddleware
|
||||
SERVICE_WORKER_TTL = 7.days.to_i
|
||||
CACHE_TTL = 28.days.to_i
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@file_handler = ActionDispatch::FileHandler.new(Rails.application.paths['public'].first)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
file = @file_handler.attempt(env)
|
||||
|
||||
# If the request is not a static file, move on!
|
||||
return @app.call(env) if file.nil?
|
||||
|
||||
status, headers, response = file
|
||||
|
||||
# Set cache headers on static files. Some paths require different cache headers
|
||||
headers['Cache-Control'] = begin
|
||||
request_path = env['REQUEST_PATH']
|
||||
|
||||
if request_path.start_with?('/sw.js')
|
||||
"public, max-age=#{SERVICE_WORKER_TTL}, must-revalidate"
|
||||
elsif request_path.start_with?(paperclip_root_url)
|
||||
"public, max-age=#{CACHE_TTL}, immutable"
|
||||
else
|
||||
"public, max-age=#{CACHE_TTL}, must-revalidate"
|
||||
end
|
||||
end
|
||||
|
||||
# Override the default CSP header set by the CSP middleware
|
||||
headers['Content-Security-Policy'] = "default-src 'none'; form-action 'none'" if request_path.start_with?(paperclip_root_url)
|
||||
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
[status, headers, response]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paperclip_root_url
|
||||
ENV.fetch('PAPERCLIP_ROOT_URL', '/system')
|
||||
end
|
||||
end
|
|
@ -5,6 +5,10 @@ class Redis
|
|||
def exists?(...)
|
||||
call_with_namespace('exists?', ...)
|
||||
end
|
||||
|
||||
def with
|
||||
yield self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::Settings::BrandingController do
|
||||
render_views
|
||||
|
||||
describe 'When signed in as an admin' do
|
||||
before do
|
||||
sign_in Fabricate(:admin_user), scope: :user
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'cannot create a setting value for a non-admin key' do
|
||||
expect(Setting.new_setting_key).to be_blank
|
||||
|
||||
patch :update, params: { form_admin_settings: { new_setting_key: 'New key value' } }
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(400)
|
||||
expect(Setting.new_setting_key).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ RSpec.describe Api::Web::PushSubscriptionsController do
|
|||
p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
|
||||
auth: 'eH_C8rq2raXqlcBVDa1gLg==',
|
||||
},
|
||||
standard: standard,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
@ -36,6 +37,7 @@ RSpec.describe Api::Web::PushSubscriptionsController do
|
|||
},
|
||||
}
|
||||
end
|
||||
let(:standard) { '1' }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
@ -51,14 +53,27 @@ RSpec.describe Api::Web::PushSubscriptionsController do
|
|||
|
||||
user.reload
|
||||
|
||||
expect(created_push_subscription).to have_attributes(
|
||||
endpoint: eq(create_payload[:subscription][:endpoint]),
|
||||
key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]),
|
||||
key_auth: eq(create_payload[:subscription][:keys][:auth])
|
||||
)
|
||||
expect(created_push_subscription)
|
||||
.to have_attributes(
|
||||
endpoint: eq(create_payload[:subscription][:endpoint]),
|
||||
key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]),
|
||||
key_auth: eq(create_payload[:subscription][:keys][:auth])
|
||||
)
|
||||
.and be_standard
|
||||
expect(user.session_activations.first.web_push_subscription).to eq(created_push_subscription)
|
||||
end
|
||||
|
||||
context 'when standard is provided as false value' do
|
||||
let(:standard) { '0' }
|
||||
|
||||
it 'saves push subscription with standard as false' do
|
||||
post :create, format: :json, params: create_payload
|
||||
|
||||
expect(created_push_subscription)
|
||||
.to_not be_standard
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a user who has a session with a prior subscription' do
|
||||
let!(:prior_subscription) { Fabricate(:web_push_subscription, session_activation: user.session_activations.last) }
|
||||
|
||||
|
|
|
@ -3,28 +3,23 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Mastodon::Feature do
|
||||
around do |example|
|
||||
original_value = Rails.configuration.x.mastodon.experimental_features
|
||||
Rails.configuration.x.mastodon.experimental_features = 'fasp,fetch_all_replies'
|
||||
example.run
|
||||
Rails.configuration.x.mastodon.experimental_features = original_value
|
||||
end
|
||||
|
||||
describe '::fasp_enabled?' do
|
||||
subject { described_class.fasp_enabled? }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
describe '::fetch_all_replies_enabled?' do
|
||||
subject { described_class.fetch_all_replies_enabled? }
|
||||
describe '::testing_only_enabled?' do
|
||||
subject { described_class.testing_only_enabled? }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
|
||||
describe '::unspecified_feature_enabled?' do
|
||||
subject { described_class.unspecified_feature_enabled? }
|
||||
context 'when example is not tagged with a feature' do
|
||||
subject { described_class.unspecified_feature_enabled? }
|
||||
|
||||
it { is_expected.to be false }
|
||||
it { is_expected.to be false }
|
||||
end
|
||||
|
||||
context 'when example is tagged with a feature', feature: 'unspecified_feature' do
|
||||
subject { described_class.unspecified_feature_enabled? }
|
||||
|
||||
it { is_expected.to be true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
127
spec/models/concerns/status/fetch_replies_concern_spec.rb
Normal file
127
spec/models/concerns/status/fetch_replies_concern_spec.rb
Normal file
|
@ -0,0 +1,127 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Status::FetchRepliesConcern do
|
||||
ActiveRecord.verbose_query_logs = true
|
||||
|
||||
let!(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let!(:bob) { Fabricate(:account, username: 'bob', domain: 'other.com') }
|
||||
|
||||
let!(:account) { alice }
|
||||
let!(:status_old) { Fabricate(:status, account: account, fetched_replies_at: 1.year.ago, created_at: 1.year.ago) }
|
||||
let!(:status_fetched_recently) { Fabricate(:status, account: account, fetched_replies_at: 1.second.ago, created_at: 1.year.ago) }
|
||||
let!(:status_created_recently) { Fabricate(:status, account: account, created_at: 1.second.ago) }
|
||||
let!(:status_never_fetched) { Fabricate(:status, account: account, created_at: 1.year.ago) }
|
||||
|
||||
describe 'should_fetch_replies' do
|
||||
let!(:statuses) { Status.should_fetch_replies.all }
|
||||
|
||||
context 'with a local status' do
|
||||
it 'never fetches local replies' do
|
||||
expect(statuses).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote status' do
|
||||
let(:account) { bob }
|
||||
|
||||
it 'fetches old statuses' do
|
||||
expect(statuses).to include(status_old)
|
||||
end
|
||||
|
||||
it 'fetches statuses that have never been fetched and weren\'t created recently' do
|
||||
expect(statuses).to include(status_never_fetched)
|
||||
end
|
||||
|
||||
it 'does not fetch statuses that were fetched recently' do
|
||||
expect(statuses).to_not include(status_fetched_recently)
|
||||
end
|
||||
|
||||
it 'does not fetch statuses that were created recently' do
|
||||
expect(statuses).to_not include(status_created_recently)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'should_not_fetch_replies' do
|
||||
let!(:statuses) { Status.should_not_fetch_replies.all }
|
||||
|
||||
context 'with a local status' do
|
||||
it 'does not fetch local statuses' do
|
||||
expect(statuses).to include(status_old, status_never_fetched, status_fetched_recently, status_never_fetched)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote status' do
|
||||
let(:account) { bob }
|
||||
|
||||
it 'fetches old statuses' do
|
||||
expect(statuses).to_not include(status_old)
|
||||
end
|
||||
|
||||
it 'fetches statuses that have never been fetched and weren\'t created recently' do
|
||||
expect(statuses).to_not include(status_never_fetched)
|
||||
end
|
||||
|
||||
it 'does not fetch statuses that were fetched recently' do
|
||||
expect(statuses).to include(status_fetched_recently)
|
||||
end
|
||||
|
||||
it 'does not fetch statuses that were created recently' do
|
||||
expect(statuses).to include(status_created_recently)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'unsubscribed' do
|
||||
let!(:spike) { Fabricate(:account, username: 'spike', domain: 'other.com') }
|
||||
let!(:status) { Fabricate(:status, account: bob, updated_at: 1.day.ago) }
|
||||
|
||||
context 'when the status is from an account with only remote followers after last update' do
|
||||
before do
|
||||
Fabricate(:follow, account: spike, target_account: bob)
|
||||
end
|
||||
|
||||
it 'shows the status as unsubscribed' do
|
||||
expect(Status.unsubscribed).to eq([status])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status is from an account with only remote followers before last update' do
|
||||
before do
|
||||
Fabricate(:follow, account: spike, target_account: bob, created_at: 2.days.ago)
|
||||
end
|
||||
|
||||
it 'shows the status as unsubscribed' do
|
||||
expect(Status.unsubscribed).to eq([status])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is from account with local followers after last update' do
|
||||
before do
|
||||
Fabricate(:follow, account: alice, target_account: bob)
|
||||
end
|
||||
|
||||
it 'shows the status as unsubscribed' do
|
||||
expect(Status.unsubscribed).to eq([status])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status is from account with local followers before last update' do
|
||||
before do
|
||||
Fabricate(:follow, account: alice, target_account: bob, created_at: 2.days.ago)
|
||||
end
|
||||
|
||||
it 'does not show the status as unsubscribed' do
|
||||
expect(Status.unsubscribed).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status has no followers' do
|
||||
it 'shows the status as unsubscribed' do
|
||||
expect(Status.unsubscribed).to eq([status])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
spec/requests/admin/settings/branding_spec.rb
Normal file
19
spec/requests/admin/settings/branding_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Admin Settings Branding' do
|
||||
describe 'When signed in as an admin' do
|
||||
before { sign_in Fabricate(:admin_user) }
|
||||
|
||||
describe 'PUT /admin/settings/branding' do
|
||||
it 'cannot create a setting value for a non-admin key' do
|
||||
expect { put admin_settings_branding_path, params: { form_admin_settings: { new_setting_key: 'New key value' } } }
|
||||
.to_not change(Setting, :new_setting_key).from(nil)
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -53,8 +53,6 @@ RSpec.describe 'credentials API' do
|
|||
patch '/api/v1/accounts/update_credentials', headers: headers, params: params
|
||||
end
|
||||
|
||||
before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
avatar: fixture_file_upload('avatar.gif', 'image/gif'),
|
||||
|
@ -113,7 +111,7 @@ RSpec.describe 'credentials API' do
|
|||
})
|
||||
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to have_received(:perform_async).with(user.account_id)
|
||||
.to have_enqueued_sidekiq_job(user.account_id)
|
||||
end
|
||||
|
||||
def expect_account_updates
|
||||
|
|
|
@ -15,10 +15,6 @@ RSpec.describe 'Deleting profile images' do
|
|||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
describe 'DELETE /api/v1/profile' do
|
||||
before do
|
||||
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
context 'when deleting an avatar' do
|
||||
context 'with wrong scope' do
|
||||
before do
|
||||
|
@ -38,7 +34,8 @@ RSpec.describe 'Deleting profile images' do
|
|||
account.reload
|
||||
expect(account.avatar).to_not exist
|
||||
expect(account.header).to exist
|
||||
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to have_enqueued_sidekiq_job(account.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,7 +58,8 @@ RSpec.describe 'Deleting profile images' do
|
|||
account.reload
|
||||
expect(account.avatar).to exist
|
||||
expect(account.header).to_not exist
|
||||
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id)
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to have_enqueued_sidekiq_job(account.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ RSpec.describe 'API V1 Push Subscriptions' do
|
|||
subscription: {
|
||||
endpoint: endpoint,
|
||||
keys: keys,
|
||||
standard: standard,
|
||||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
@ -36,6 +37,7 @@ RSpec.describe 'API V1 Push Subscriptions' do
|
|||
},
|
||||
}.with_indifferent_access
|
||||
end
|
||||
let(:standard) { '1' }
|
||||
let(:scopes) { 'push' }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
@ -66,6 +68,7 @@ RSpec.describe 'API V1 Push Subscriptions' do
|
|||
user_id: eq(user.id),
|
||||
access_token_id: eq(token.id)
|
||||
)
|
||||
.and be_standard
|
||||
|
||||
expect(response.parsed_body.with_indifferent_access)
|
||||
.to include(
|
||||
|
@ -73,6 +76,17 @@ RSpec.describe 'API V1 Push Subscriptions' do
|
|||
)
|
||||
end
|
||||
|
||||
context 'when standard is provided as false value' do
|
||||
let(:standard) { '0' }
|
||||
|
||||
it 'saves push subscription with standard as false' do
|
||||
subject
|
||||
|
||||
expect(endpoint_push_subscription)
|
||||
.to_not be_standard
|
||||
end
|
||||
end
|
||||
|
||||
it 'replaces old subscription on repeat calls' do
|
||||
2.times { subject }
|
||||
|
||||
|
|
124
spec/services/activitypub/fetch_all_replies_service_spec.rb
Normal file
124
spec/services/activitypub/fetch_all_replies_service_spec.rb
Normal file
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::FetchAllRepliesService do
|
||||
subject { described_class.new }
|
||||
|
||||
let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
|
||||
let(:status) { Fabricate(:status, account: actor) }
|
||||
let(:collection_uri) { 'http://example.com/replies/1' }
|
||||
|
||||
let(:items) do
|
||||
%w(
|
||||
http://example.com/self-reply-1
|
||||
http://example.com/self-reply-2
|
||||
http://example.com/self-reply-3
|
||||
http://other.com/other-reply-1
|
||||
http://other.com/other-reply-2
|
||||
http://other.com/other-reply-3
|
||||
http://example.com/self-reply-4
|
||||
http://example.com/self-reply-5
|
||||
http://example.com/self-reply-6
|
||||
)
|
||||
end
|
||||
|
||||
let(:payload) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
type: 'Collection',
|
||||
id: collection_uri,
|
||||
items: items,
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
it 'fetches more than the default maximum and from multiple domains' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(
|
||||
%w(
|
||||
http://example.com/self-reply-1
|
||||
http://example.com/self-reply-2
|
||||
http://example.com/self-reply-3
|
||||
http://other.com/other-reply-1
|
||||
http://other.com/other-reply-2
|
||||
http://other.com/other-reply-3
|
||||
http://example.com/self-reply-4
|
||||
http://example.com/self-reply-5
|
||||
http://example.com/self-reply-6
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'with a recent status' do
|
||||
before do
|
||||
Fabricate(:status, uri: 'http://example.com/self-reply-2', fetched_replies_at: 1.second.ago, local: false)
|
||||
end
|
||||
|
||||
it 'skips statuses that have been updated recently' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(
|
||||
%w(
|
||||
http://example.com/self-reply-1
|
||||
http://example.com/self-reply-3
|
||||
http://other.com/other-reply-1
|
||||
http://other.com/other-reply-2
|
||||
http://other.com/other-reply-3
|
||||
http://example.com/self-reply-4
|
||||
http://example.com/self-reply-5
|
||||
http://example.com/self-reply-6
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an old status' do
|
||||
before do
|
||||
Fabricate(:status, uri: 'http://other.com/other-reply-1', fetched_replies_at: 1.year.ago, created_at: 1.year.ago, account: actor)
|
||||
end
|
||||
|
||||
it 'updates the time that fetched statuses were last fetched' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status.uri, payload)
|
||||
|
||||
expect(Status.find_by(uri: 'http://other.com/other-reply-1').fetched_replies_at).to be >= 1.minute.ago
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unsubscribed replies' do
|
||||
before do
|
||||
remote_actor = Fabricate(:account, domain: 'other.com', uri: 'http://other.com/account')
|
||||
# reply not in the collection from the remote instance, but we know about anyway without anyone following the account
|
||||
Fabricate(:status, account: remote_actor, in_reply_to_id: status.id, uri: 'http://other.com/account/unsubscribed', fetched_replies_at: 1.year.ago, created_at: 1.year.ago)
|
||||
end
|
||||
|
||||
it 'updates the unsubscribed replies' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(
|
||||
%w(
|
||||
http://example.com/self-reply-1
|
||||
http://example.com/self-reply-2
|
||||
http://example.com/self-reply-3
|
||||
http://other.com/other-reply-1
|
||||
http://other.com/other-reply-2
|
||||
http://other.com/other-reply-3
|
||||
http://example.com/self-reply-4
|
||||
http://example.com/self-reply-5
|
||||
http://example.com/self-reply-6
|
||||
http://other.com/account/unsubscribed
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,6 +9,9 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
|
|||
|
||||
let!(:sender) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar') }
|
||||
|
||||
let(:follower) { Fabricate(:account, username: 'alice') }
|
||||
let(:follow) { nil }
|
||||
let(:response) { { body: Oj.dump(object), headers: { 'content-type': 'application/activity+json' } } }
|
||||
let(:existing_status) { nil }
|
||||
|
||||
let(:note) do
|
||||
|
@ -23,13 +26,14 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
|
|||
|
||||
before do
|
||||
stub_request(:get, 'https://foo.bar/watch?v=12345').to_return(status: 404, body: '')
|
||||
stub_request(:get, object[:id]).to_return(body: Oj.dump(object))
|
||||
stub_request(:get, object[:id]).to_return(**response)
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
before do
|
||||
follow
|
||||
existing_status
|
||||
subject.call(object[:id], prefetched_body: Oj.dump(object))
|
||||
subject.call(object[:id])
|
||||
end
|
||||
|
||||
context 'with Note object' do
|
||||
|
@ -254,6 +258,45 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
|
|||
expect(existing_status.text).to eq 'Lorem ipsum'
|
||||
expect(existing_status.edits).to_not be_empty
|
||||
end
|
||||
|
||||
context 'when the status appears to have been deleted at source' do
|
||||
let(:response) { { status: 404, body: '' } }
|
||||
|
||||
shared_examples 'no delete' do
|
||||
it 'does not delete the status' do
|
||||
existing_status.reload
|
||||
expect(existing_status.text).to eq 'Foo'
|
||||
expect(existing_status.edits).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status is orphaned/unsubscribed' do
|
||||
it 'deletes the orphaned status' do
|
||||
expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the status is from an account with only remote followers' do
|
||||
let(:follower) { Fabricate(:account, username: 'alice', domain: 'foo.bar') }
|
||||
let(:follow) { Fabricate(:follow, account: follower, target_account: sender, created_at: 2.days.ago) }
|
||||
|
||||
it 'deletes the orphaned status' do
|
||||
expect { existing_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
context 'when the status is private' do
|
||||
let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :private) }
|
||||
|
||||
it_behaves_like 'no delete'
|
||||
end
|
||||
|
||||
context 'when the status is direct' do
|
||||
let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id], visibility: :direct) }
|
||||
|
||||
it_behaves_like 'no delete'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a Create activity' do
|
||||
|
|
|
@ -40,7 +40,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'queues the expected worker' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, payload)
|
||||
subject.call(status.account.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1'])
|
||||
end
|
||||
|
@ -50,7 +50,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, payload)
|
||||
subject.call(status.account.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
@ -64,7 +64,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, collection_uri)
|
||||
subject.call(status.account.uri, collection_uri)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
@ -85,7 +85,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, payload)
|
||||
subject.call(status.account.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
@ -99,7 +99,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, collection_uri)
|
||||
subject.call(status.account.uri, collection_uri)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
@ -124,7 +124,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, payload)
|
||||
subject.call(status.account.uri, payload)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
@ -138,7 +138,7 @@ RSpec.describe ActivityPub::FetchRepliesService do
|
|||
it 'spawns workers for up to 5 replies on the same server' do
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
|
||||
subject.call(status, collection_uri)
|
||||
subject.call(status.account.uri, collection_uri)
|
||||
|
||||
expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5'])
|
||||
end
|
||||
|
|
8
spec/support/feature_flags.rb
Normal file
8
spec/support/feature_flags.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.before(:example, :feature) do |example|
|
||||
feature = example.metadata[:feature]
|
||||
allow(Mastodon::Feature).to receive(:"#{feature}_enabled?").and_return(true)
|
||||
end
|
||||
end
|
|
@ -23,7 +23,11 @@ module ProfileStories
|
|||
def as_a_logged_in_user
|
||||
as_a_registered_user
|
||||
visit new_user_session_path
|
||||
expect(page)
|
||||
.to have_title(I18n.t('auth.login'))
|
||||
fill_in_auth_details(email, password)
|
||||
expect(page)
|
||||
.to have_css('.app-holder')
|
||||
end
|
||||
|
||||
def as_a_logged_in_admin
|
||||
|
|
|
@ -5,8 +5,6 @@ require 'rails_helper'
|
|||
RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
|
||||
include ProfileStories
|
||||
|
||||
subject { page }
|
||||
|
||||
let(:email) { 'test@example.com' }
|
||||
let(:password) { 'password' }
|
||||
let(:confirmed_at) { Time.zone.now }
|
||||
|
@ -14,13 +12,11 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
|
|||
|
||||
before do
|
||||
as_a_logged_in_user
|
||||
visit root_path
|
||||
page.driver.browser.manage.window.resize_to(1600, 1050)
|
||||
end
|
||||
|
||||
it 'can be posted' do
|
||||
expect(subject).to have_css('div.app-holder')
|
||||
|
||||
visit_homepage
|
||||
status_text = 'This is a new status!'
|
||||
|
||||
within('.compose-form') do
|
||||
|
@ -28,12 +24,12 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
|
|||
click_on 'Post'
|
||||
end
|
||||
|
||||
expect(subject).to have_css('.status__content__text', text: status_text)
|
||||
expect(page)
|
||||
.to have_css('.status__content__text', text: status_text)
|
||||
end
|
||||
|
||||
it 'can be posted again' do
|
||||
expect(subject).to have_css('div.app-holder')
|
||||
|
||||
visit_homepage
|
||||
status_text = 'This is a second status!'
|
||||
|
||||
within('.compose-form') do
|
||||
|
@ -41,6 +37,15 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
|
|||
click_on 'Post'
|
||||
end
|
||||
|
||||
expect(subject).to have_css('.status__content__text', text: status_text)
|
||||
expect(page)
|
||||
.to have_css('.status__content__text', text: status_text)
|
||||
end
|
||||
|
||||
def visit_homepage
|
||||
visit root_path
|
||||
|
||||
expect(page)
|
||||
.to have_css('div.app-holder')
|
||||
.and have_css('form.compose-form')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,5 +40,7 @@ RSpec.describe 'report interface', :attachment_processing, :js, :streaming do
|
|||
within '.report-actions' do
|
||||
click_on I18n.t('admin.reports.mark_as_resolved')
|
||||
end
|
||||
expect(page)
|
||||
.to have_content(I18n.t('admin.reports.resolved_msg'))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,8 +11,6 @@ RSpec.describe 'Settings Privacy' do
|
|||
before { user.account.update(discoverable: false) }
|
||||
|
||||
context 'with a successful update' do
|
||||
before { allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) }
|
||||
|
||||
it 'updates user profile information' do
|
||||
# View settings page
|
||||
visit settings_privacy_path
|
||||
|
@ -29,14 +27,13 @@ RSpec.describe 'Settings Privacy' do
|
|||
.to have_content(I18n.t('privacy.title'))
|
||||
.and have_content(success_message)
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to have_received(:perform_async).with(user.account.id)
|
||||
.to have_enqueued_sidekiq_job(user.account.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a failed update' do
|
||||
before do
|
||||
allow(UpdateAccountService).to receive(:new).and_return(failing_update_service)
|
||||
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
|
||||
end
|
||||
|
||||
it 'updates user profile information' do
|
||||
|
@ -54,7 +51,7 @@ RSpec.describe 'Settings Privacy' do
|
|||
expect(page)
|
||||
.to have_content(I18n.t('privacy.title'))
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to_not have_received(:perform_async)
|
||||
.to_not have_enqueued_sidekiq_job(anything)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -7,7 +7,6 @@ RSpec.describe 'Settings profile page' do
|
|||
let(:account) { user.account }
|
||||
|
||||
before do
|
||||
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async)
|
||||
sign_in user
|
||||
end
|
||||
|
||||
|
@ -24,7 +23,7 @@ RSpec.describe 'Settings profile page' do
|
|||
.to change { account.reload.display_name }.to('New name')
|
||||
.and(change { account.reload.avatar.instance.avatar_file_name }.from(nil).to(be_present))
|
||||
expect(ActivityPub::UpdateDistributionWorker)
|
||||
.to have_received(:perform_async).with(account.id)
|
||||
.to have_enqueued_sidekiq_job(account.id)
|
||||
end
|
||||
|
||||
def display_name_field
|
||||
|
|
281
spec/workers/activitypub/fetch_all_replies_worker_spec.rb
Normal file
281
spec/workers/activitypub/fetch_all_replies_worker_spec.rb
Normal file
|
@ -0,0 +1,281 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ActivityPub::FetchAllRepliesWorker do
|
||||
subject { described_class.new }
|
||||
|
||||
let(:top_items) do
|
||||
[
|
||||
'http://example.com/self-reply-1',
|
||||
'http://other.com/other-reply-2',
|
||||
'http://example.com/self-reply-3',
|
||||
]
|
||||
end
|
||||
|
||||
let(:top_items_paged) do
|
||||
[
|
||||
'http://example.com/self-reply-4',
|
||||
'http://other.com/other-reply-5',
|
||||
'http://example.com/self-reply-6',
|
||||
]
|
||||
end
|
||||
|
||||
let(:nested_items) do
|
||||
[
|
||||
'http://example.com/nested-self-reply-1',
|
||||
'http://other.com/nested-other-reply-2',
|
||||
'http://example.com/nested-self-reply-3',
|
||||
]
|
||||
end
|
||||
|
||||
let(:nested_items_paged) do
|
||||
[
|
||||
'http://example.com/nested-self-reply-4',
|
||||
'http://other.com/nested-other-reply-5',
|
||||
'http://example.com/nested-self-reply-6',
|
||||
]
|
||||
end
|
||||
|
||||
let(:all_items) do
|
||||
top_items + top_items_paged + nested_items + nested_items_paged
|
||||
end
|
||||
|
||||
let(:top_note_uri) do
|
||||
'http://example.com/top-post'
|
||||
end
|
||||
|
||||
let(:top_collection_uri) do
|
||||
'http://example.com/top-post/replies'
|
||||
end
|
||||
|
||||
# The reply uri that has the nested replies under it
|
||||
let(:reply_note_uri) do
|
||||
'http://other.com/other-reply-2'
|
||||
end
|
||||
|
||||
# The collection uri of nested replies
|
||||
let(:reply_collection_uri) do
|
||||
'http://other.com/other-reply-2/replies'
|
||||
end
|
||||
|
||||
let(:replies_top) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: top_collection_uri,
|
||||
type: 'Collection',
|
||||
items: top_items + top_items_paged,
|
||||
}
|
||||
end
|
||||
|
||||
let(:replies_nested) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: reply_collection_uri,
|
||||
type: 'Collection',
|
||||
items: nested_items + nested_items_paged,
|
||||
}
|
||||
end
|
||||
|
||||
# The status resource for the top post
|
||||
let(:top_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: top_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: replies_top,
|
||||
attributedTo: 'https://example.com',
|
||||
}
|
||||
end
|
||||
|
||||
# The status resource that has the uri to the replies collection
|
||||
let(:reply_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: reply_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: replies_nested,
|
||||
attributedTo: 'https://other.com',
|
||||
}
|
||||
end
|
||||
|
||||
let(:empty_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: 'https://example.com/empty',
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: [],
|
||||
attributedTo: 'https://example.com',
|
||||
}
|
||||
end
|
||||
|
||||
let(:account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:status) do
|
||||
Fabricate(
|
||||
:status,
|
||||
account: account,
|
||||
uri: top_note_uri,
|
||||
created_at: 1.day.ago - Status::FetchRepliesConcern::FETCH_REPLIES_INITIAL_WAIT_MINUTES
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_ENABLED', true)
|
||||
allow(FetchReplyWorker).to receive(:push_bulk)
|
||||
all_items.each do |item|
|
||||
next if [top_note_uri, reply_note_uri].include? item
|
||||
|
||||
stub_request(:get, item).to_return(status: 200, body: Oj.dump(empty_object), headers: { 'Content-Type': 'application/activity+json' })
|
||||
end
|
||||
|
||||
stub_request(:get, top_note_uri).to_return(status: 200, body: Oj.dump(top_object), headers: { 'Content-Type': 'application/activity+json' })
|
||||
stub_request(:get, reply_note_uri).to_return(status: 200, body: Oj.dump(reply_object), headers: { 'Content-Type': 'application/activity+json' })
|
||||
end
|
||||
|
||||
shared_examples 'fetches all replies' do
|
||||
it 'fetches statuses recursively' do
|
||||
got_uris = subject.perform(status.id)
|
||||
expect(got_uris).to match_array(all_items)
|
||||
end
|
||||
|
||||
it 'respects the maximum limits set by not recursing after the max is reached' do
|
||||
stub_const('ActivityPub::FetchAllRepliesWorker::MAX_REPLIES', 5)
|
||||
got_uris = subject.perform(status.id)
|
||||
expect(got_uris).to match_array(top_items + top_items_paged)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'perform' do
|
||||
context 'when the payload is a Note with replies as a Collection of inlined replies' do
|
||||
it_behaves_like 'fetches all replies'
|
||||
end
|
||||
|
||||
context 'when the payload is a Note with replies as a URI to a Collection' do
|
||||
let(:top_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: top_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: top_collection_uri,
|
||||
attributedTo: 'https://example.com',
|
||||
}
|
||||
end
|
||||
let(:reply_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: reply_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: reply_collection_uri,
|
||||
attributedTo: 'https://other.com',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, top_collection_uri).to_return(status: 200, body: Oj.dump(replies_top), headers: { 'Content-Type': 'application/activity+json' })
|
||||
stub_request(:get, reply_collection_uri).to_return(status: 200, body: Oj.dump(replies_nested), headers: { 'Content-Type': 'application/activity+json' })
|
||||
end
|
||||
|
||||
it_behaves_like 'fetches all replies'
|
||||
end
|
||||
|
||||
context 'when the payload is a Note with replies as a paginated collection' do
|
||||
let(:top_page_2_uri) do
|
||||
"#{top_collection_uri}/2"
|
||||
end
|
||||
|
||||
let(:reply_page_2_uri) do
|
||||
"#{reply_collection_uri}/2"
|
||||
end
|
||||
|
||||
let(:top_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: top_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
id: top_collection_uri,
|
||||
first: {
|
||||
type: 'CollectionPage',
|
||||
partOf: top_collection_uri,
|
||||
items: top_items,
|
||||
next: top_page_2_uri,
|
||||
},
|
||||
},
|
||||
attributedTo: 'https://example.com',
|
||||
}
|
||||
end
|
||||
let(:reply_object) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: reply_note_uri,
|
||||
type: 'Note',
|
||||
content: 'Lorem ipsum',
|
||||
replies: {
|
||||
type: 'Collection',
|
||||
id: reply_collection_uri,
|
||||
first: {
|
||||
type: 'CollectionPage',
|
||||
partOf: reply_collection_uri,
|
||||
items: nested_items,
|
||||
next: reply_page_2_uri,
|
||||
},
|
||||
},
|
||||
attributedTo: 'https://other.com',
|
||||
}
|
||||
end
|
||||
|
||||
let(:top_page_two) do
|
||||
{
|
||||
type: 'CollectionPage',
|
||||
id: top_page_2_uri,
|
||||
partOf: top_collection_uri,
|
||||
items: top_items_paged,
|
||||
}
|
||||
end
|
||||
|
||||
let(:reply_page_two) do
|
||||
{
|
||||
type: 'CollectionPage',
|
||||
id: reply_page_2_uri,
|
||||
partOf: reply_collection_uri,
|
||||
items: nested_items_paged,
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:get, top_page_2_uri).to_return(status: 200, body: Oj.dump(top_page_two), headers: { 'Content-Type': 'application/activity+json' })
|
||||
stub_request(:get, reply_page_2_uri).to_return(status: 200, body: Oj.dump(reply_page_two), headers: { 'Content-Type': 'application/activity+json' })
|
||||
end
|
||||
|
||||
it_behaves_like 'fetches all replies'
|
||||
|
||||
it 'limits by max pages' do
|
||||
stub_const('ActivityPub::FetchAllRepliesWorker::MAX_PAGES', 3)
|
||||
got_uris = subject.perform(status.id)
|
||||
expect(got_uris).to match_array(top_items + top_items_paged + nested_items)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when replies should not be fetched' do
|
||||
# ensure that we should not fetch by setting the status to be created in the debounce window
|
||||
let(:status) { Fabricate(:status, account: account, uri: top_note_uri, created_at: DateTime.now) }
|
||||
|
||||
before do
|
||||
stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_INITIAL_WAIT_MINUTES', 1.week)
|
||||
end
|
||||
|
||||
it 'returns nil without fetching' do
|
||||
got_uris = subject.perform(status.id)
|
||||
expect(got_uris).to be_nil
|
||||
assert_not_requested :get, top_note_uri
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -13205,7 +13205,14 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:*, pg-protocol@npm:^1.7.1":
|
||||
"pg-protocol@npm:*":
|
||||
version: 1.8.0
|
||||
resolution: "pg-protocol@npm:1.8.0"
|
||||
checksum: 10c0/2be784955599d84b564795952cee52cc2b8eab0be43f74fc1061506353801e282c1d52c9e0691a9b72092c1f3fde370e9b181e80fef6bb82a9b8d1618bfa91e6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:^1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "pg-protocol@npm:1.7.1"
|
||||
checksum: 10c0/3168d407ddc4c0fa2403eb9b49205399d4bc53dadbafdfcc5d25fa61b860a31c25df25704cf14c8140c80f0a41061d586e5fd5ce9bf800dfb91e9ce810bc2c37
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue