mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-21 21:57:19 +00:00
Merge branch 'main' into add-scrollbar-style-option
This commit is contained in:
commit
5e41d9b472
59
.annotaterb.yml
Normal file
59
.annotaterb.yml
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
:position: before
|
||||
:position_in_additional_file_patterns: before
|
||||
:position_in_class: before
|
||||
:position_in_factory: before
|
||||
:position_in_fixture: before
|
||||
:position_in_routes: before
|
||||
:position_in_serializer: before
|
||||
:position_in_test: before
|
||||
:classified_sort: true
|
||||
:exclude_controllers: true
|
||||
:exclude_factories: true
|
||||
:exclude_fixtures: true
|
||||
:exclude_helpers: true
|
||||
:exclude_scaffolds: true
|
||||
:exclude_serializers: true
|
||||
:exclude_sti_subclasses: true
|
||||
:exclude_tests: true
|
||||
:force: false
|
||||
:format_markdown: false
|
||||
:format_rdoc: false
|
||||
:format_yard: false
|
||||
:frozen: false
|
||||
:ignore_model_sub_dir: false
|
||||
:ignore_unknown_models: false
|
||||
:include_version: false
|
||||
:show_complete_foreign_keys: false
|
||||
:show_foreign_keys: false
|
||||
:show_indexes: false
|
||||
:simple_indexes: false
|
||||
:sort: false
|
||||
:timestamp: false
|
||||
:trace: false
|
||||
:with_comment: true
|
||||
:with_column_comments: true
|
||||
:with_table_comments: true
|
||||
:active_admin: false
|
||||
:command:
|
||||
:debug: false
|
||||
:hide_default_column_types: ''
|
||||
:hide_limit_column_types: 'integer,boolean'
|
||||
:ignore_columns:
|
||||
:ignore_routes:
|
||||
:models: true
|
||||
:routes: false
|
||||
:skip_on_db_migrate: false
|
||||
:target_action: :do_annotations
|
||||
:wrapper:
|
||||
:wrapper_close:
|
||||
:wrapper_open:
|
||||
:classes_default_to_s: []
|
||||
:additional_file_patterns: []
|
||||
:model_dir:
|
||||
- app/models
|
||||
:require: []
|
||||
:root_dir:
|
||||
- ''
|
||||
|
||||
:show_check_constraints: false
|
|
@ -69,7 +69,7 @@ services:
|
|||
hard: -1
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:v1.6.1
|
||||
image: libretranslate/libretranslate:v1.6.2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- lt-data:/home/libretranslate/.local
|
||||
|
|
1
.github/workflows/build-container-image.yml
vendored
1
.github/workflows/build-container-image.yml
vendored
|
@ -92,6 +92,7 @@ jobs:
|
|||
build-args: |
|
||||
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
|
||||
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
|
||||
SOURCE_COMMIT=${{ github.sha }}
|
||||
platforms: ${{ inputs.platforms }}
|
||||
provenance: false
|
||||
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
|
||||
|
|
307
Dockerfile
307
Dockerfile
|
@ -29,6 +29,8 @@ FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
|||
ARG MASTODON_VERSION_PRERELEASE=""
|
||||
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
|
||||
ARG MASTODON_VERSION_METADATA=""
|
||||
# Will be available as Mastodon::Version.source_commit
|
||||
ARG SOURCE_COMMIT=""
|
||||
|
||||
# Allow Ruby on Rails to serve static files
|
||||
# See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
|
||||
|
@ -45,30 +47,31 @@ ARG GID="991"
|
|||
|
||||
# Apply Mastodon build options based on options above
|
||||
ENV \
|
||||
# Apply Mastodon version information
|
||||
# Apply Mastodon version information
|
||||
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
|
||||
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
|
||||
# Apply Mastodon static files and YJIT options
|
||||
SOURCE_COMMIT="${SOURCE_COMMIT}" \
|
||||
# Apply Mastodon static files and YJIT options
|
||||
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
|
||||
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
|
||||
# Apply timezone
|
||||
# Apply timezone
|
||||
TZ=${TZ}
|
||||
|
||||
ENV \
|
||||
# Configure the IP to bind Mastodon to when serving traffic
|
||||
# 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 and related nodejs based tools
|
||||
NODE_ENV="production" \
|
||||
# Use production settings for Ruby on Rails
|
||||
# Use production settings for Ruby on Rails
|
||||
RAILS_ENV="production" \
|
||||
# Add Ruby and Mastodon installation to the PATH
|
||||
# Add Ruby and Mastodon installation to the PATH
|
||||
DEBIAN_FRONTEND="noninteractive" \
|
||||
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
|
||||
# Optimize jemalloc 5.x performance
|
||||
# Optimize jemalloc 5.x performance
|
||||
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
|
||||
# Enable libvips, should not be changed
|
||||
# Enable libvips, should not be changed
|
||||
MASTODON_USE_LIBVIPS=true \
|
||||
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
|
||||
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
|
||||
MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
|
||||
|
||||
# Set default shell used for running commands
|
||||
|
@ -79,14 +82,14 @@ ARG TARGETPLATFORM
|
|||
RUN echo "Target platform is $TARGETPLATFORM"
|
||||
|
||||
RUN \
|
||||
# Remove automatic apt cache Docker cleanup scripts
|
||||
# Remove automatic apt cache Docker cleanup scripts
|
||||
rm -f /etc/apt/apt.conf.d/docker-clean; \
|
||||
# Sets timezone
|
||||
# Sets timezone
|
||||
echo "${TZ}" > /etc/localtime; \
|
||||
# Creates mastodon user/group and sets home directory
|
||||
# Creates mastodon user/group and sets home directory
|
||||
groupadd -g "${GID}" mastodon; \
|
||||
useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \
|
||||
# Creates /mastodon symlink to /opt/mastodon
|
||||
# Creates /mastodon symlink to /opt/mastodon
|
||||
ln -s /opt/mastodon /mastodon;
|
||||
|
||||
# Set /opt/mastodon as working directory
|
||||
|
@ -94,28 +97,28 @@ WORKDIR /opt/mastodon
|
|||
|
||||
# hadolint ignore=DL3008,DL3005
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Apt update & upgrade to check for security updates to Debian image
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Apt update & upgrade to check for security updates to Debian image
|
||||
apt-get update; \
|
||||
apt-get dist-upgrade -yq; \
|
||||
# Install jemalloc, curl and other necessary components
|
||||
# Install jemalloc, curl and other necessary components
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
file \
|
||||
libjemalloc2 \
|
||||
patchelf \
|
||||
procps \
|
||||
tini \
|
||||
tzdata \
|
||||
wget \
|
||||
curl \
|
||||
file \
|
||||
libjemalloc2 \
|
||||
patchelf \
|
||||
procps \
|
||||
tini \
|
||||
tzdata \
|
||||
wget \
|
||||
; \
|
||||
# Patch Ruby to use jemalloc
|
||||
# Patch Ruby to use jemalloc
|
||||
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
|
||||
# Discard patchelf after use
|
||||
# Discard patchelf after use
|
||||
apt-get purge -y \
|
||||
patchelf \
|
||||
patchelf \
|
||||
;
|
||||
|
||||
# Create temporary build layer from base image
|
||||
|
@ -132,56 +135,56 @@ ARG TARGETPLATFORM
|
|||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Install build tools and bundler dependencies from APT
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Install build tools and bundler dependencies from APT
|
||||
apt-get install -y --no-install-recommends \
|
||||
autoconf \
|
||||
automake \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libgdbm-dev \
|
||||
libglib2.0-dev \
|
||||
libgmp-dev \
|
||||
libicu-dev \
|
||||
libidn-dev \
|
||||
libpq-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
meson \
|
||||
nasm \
|
||||
pkg-config \
|
||||
shared-mime-info \
|
||||
xz-utils \
|
||||
# libvips components
|
||||
libcgif-dev \
|
||||
libexif-dev \
|
||||
libexpat1-dev \
|
||||
libgirepository1.0-dev \
|
||||
libheif-dev \
|
||||
libimagequant-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
liblcms2-dev \
|
||||
liborc-dev \
|
||||
libspng-dev \
|
||||
libtiff-dev \
|
||||
libwebp-dev \
|
||||
autoconf \
|
||||
automake \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
libgdbm-dev \
|
||||
libglib2.0-dev \
|
||||
libgmp-dev \
|
||||
libicu-dev \
|
||||
libidn-dev \
|
||||
libpq-dev \
|
||||
libssl-dev \
|
||||
libtool \
|
||||
meson \
|
||||
nasm \
|
||||
pkg-config \
|
||||
shared-mime-info \
|
||||
xz-utils \
|
||||
# libvips components
|
||||
libcgif-dev \
|
||||
libexif-dev \
|
||||
libexpat1-dev \
|
||||
libgirepository1.0-dev \
|
||||
libheif-dev \
|
||||
libimagequant-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
liblcms2-dev \
|
||||
liborc-dev \
|
||||
libspng-dev \
|
||||
libtiff-dev \
|
||||
libwebp-dev \
|
||||
# ffmpeg components
|
||||
libdav1d-dev \
|
||||
liblzma-dev \
|
||||
libmp3lame-dev \
|
||||
libopus-dev \
|
||||
libsnappy-dev \
|
||||
libvorbis-dev \
|
||||
libvpx-dev \
|
||||
libx264-dev \
|
||||
libx265-dev \
|
||||
libdav1d-dev \
|
||||
liblzma-dev \
|
||||
libmp3lame-dev \
|
||||
libopus-dev \
|
||||
libsnappy-dev \
|
||||
libvorbis-dev \
|
||||
libvpx-dev \
|
||||
libx264-dev \
|
||||
libx265-dev \
|
||||
;
|
||||
|
||||
RUN \
|
||||
# Configure Corepack
|
||||
# Configure Corepack
|
||||
rm /usr/local/bin/yarn*; \
|
||||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
@ -228,28 +231,28 @@ WORKDIR /usr/local/ffmpeg/src/ffmpeg-${FFMPEG_VERSION}
|
|||
# Configure and compile ffmpeg
|
||||
RUN \
|
||||
./configure \
|
||||
--prefix=/usr/local/ffmpeg \
|
||||
--toolchain=hardened \
|
||||
--disable-debug \
|
||||
--disable-devices \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--disable-network \
|
||||
--disable-static \
|
||||
--enable-ffmpeg \
|
||||
--enable-ffprobe \
|
||||
--enable-gpl \
|
||||
--enable-libdav1d \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libsnappy \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libx264 \
|
||||
--enable-libx265 \
|
||||
--enable-shared \
|
||||
--enable-version3 \
|
||||
--prefix=/usr/local/ffmpeg \
|
||||
--toolchain=hardened \
|
||||
--disable-debug \
|
||||
--disable-devices \
|
||||
--disable-doc \
|
||||
--disable-ffplay \
|
||||
--disable-network \
|
||||
--disable-static \
|
||||
--enable-ffmpeg \
|
||||
--enable-ffprobe \
|
||||
--enable-gpl \
|
||||
--enable-libdav1d \
|
||||
--enable-libmp3lame \
|
||||
--enable-libopus \
|
||||
--enable-libsnappy \
|
||||
--enable-libvorbis \
|
||||
--enable-libvpx \
|
||||
--enable-libwebp \
|
||||
--enable-libx264 \
|
||||
--enable-libx265 \
|
||||
--enable-shared \
|
||||
--enable-version3 \
|
||||
; \
|
||||
make -j$(nproc); \
|
||||
make install;
|
||||
|
@ -263,17 +266,17 @@ ARG TARGETPLATFORM
|
|||
COPY Gemfile* /opt/mastodon/
|
||||
|
||||
RUN \
|
||||
# Mount Ruby Gem caches
|
||||
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
|
||||
# Configure bundle to prevent changes to Gemfile and Gemfile.lock
|
||||
# Mount Ruby Gem caches
|
||||
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
|
||||
# Configure bundle to prevent changes to Gemfile and Gemfile.lock
|
||||
bundle config set --global frozen "true"; \
|
||||
# Configure bundle to not cache downloaded Gems
|
||||
# Configure bundle to not cache downloaded Gems
|
||||
bundle config set --global cache_all "false"; \
|
||||
# Configure bundle to only process production Gems
|
||||
# Configure bundle to only process production Gems
|
||||
bundle config set --local without "development test"; \
|
||||
# Configure bundle to not warn about root user
|
||||
# Configure bundle to not warn about root user
|
||||
bundle config set silence_root_warning "true"; \
|
||||
# Download and install required Gems
|
||||
# Download and install required Gems
|
||||
bundle install -j"$(nproc)";
|
||||
|
||||
# Create temporary node specific build layer from build layer
|
||||
|
@ -288,9 +291,9 @@ COPY .yarn /opt/mastodon/.yarn
|
|||
|
||||
# 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
|
||||
--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
|
||||
yarn workspaces focus --production @mastodon/mastodon;
|
||||
|
||||
# Create temporary assets build layer from build layer
|
||||
|
@ -311,10 +314,10 @@ ARG TARGETPLATFORM
|
|||
|
||||
RUN \
|
||||
ldconfig; \
|
||||
# Use Ruby on Rails to create Mastodon assets
|
||||
# Use Ruby on Rails to create Mastodon assets
|
||||
SECRET_KEY_BASE_DUMMY=1 \
|
||||
bundle exec rails assets:precompile; \
|
||||
# Cleanup temporary files
|
||||
# Cleanup temporary files
|
||||
rm -fr /opt/mastodon/tmp;
|
||||
|
||||
# Prep final Mastodon Ruby layer
|
||||
|
@ -324,49 +327,49 @@ ARG TARGETPLATFORM
|
|||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Mount Corepack and Yarn caches from Docker buildx caches
|
||||
--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 \
|
||||
# Apt update install non-dev versions of necessary components
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Mount Corepack and Yarn caches from Docker buildx caches
|
||||
--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 \
|
||||
# Apt update install non-dev versions of necessary components
|
||||
apt-get install -y --no-install-recommends \
|
||||
libexpat1 \
|
||||
libglib2.0-0 \
|
||||
libicu72 \
|
||||
libidn12 \
|
||||
libpq5 \
|
||||
libreadline8 \
|
||||
libssl3 \
|
||||
libyaml-0-2 \
|
||||
libexpat1 \
|
||||
libglib2.0-0 \
|
||||
libicu72 \
|
||||
libidn12 \
|
||||
libpq5 \
|
||||
libreadline8 \
|
||||
libssl3 \
|
||||
libyaml-0-2 \
|
||||
# libvips components
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
liborc-0.4-0 \
|
||||
libspng0 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
liborc-0.4-0 \
|
||||
libspng0 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
# ffmpeg components
|
||||
libdav1d6 \
|
||||
libmp3lame0 \
|
||||
libopencore-amrnb0 \
|
||||
libopencore-amrwb0 \
|
||||
libopus0 \
|
||||
libsnappy1v5 \
|
||||
libtheora0 \
|
||||
libvorbis0a \
|
||||
libvorbisenc2 \
|
||||
libvorbisfile3 \
|
||||
libvpx7 \
|
||||
libx264-164 \
|
||||
libx265-199 \
|
||||
libdav1d6 \
|
||||
libmp3lame0 \
|
||||
libopencore-amrnb0 \
|
||||
libopencore-amrwb0 \
|
||||
libopus0 \
|
||||
libsnappy1v5 \
|
||||
libtheora0 \
|
||||
libvorbis0a \
|
||||
libvorbisenc2 \
|
||||
libvorbisfile3 \
|
||||
libvpx7 \
|
||||
libx264-164 \
|
||||
libx265-199 \
|
||||
;
|
||||
|
||||
# Copy Mastodon sources into final layer
|
||||
|
@ -386,7 +389,7 @@ COPY --from=ffmpeg /usr/local/ffmpeg/lib /usr/local/lib
|
|||
|
||||
RUN \
|
||||
ldconfig; \
|
||||
# Smoketest media processors
|
||||
# Smoketest media processors
|
||||
vips -v; \
|
||||
ffmpeg -version; \
|
||||
ffprobe -version;
|
||||
|
@ -396,10 +399,10 @@ RUN \
|
|||
bundle exec bootsnap precompile --gemfile app/ lib/;
|
||||
|
||||
RUN \
|
||||
# Pre-create and chown system volume to Mastodon user
|
||||
# Pre-create and chown system volume to Mastodon user
|
||||
mkdir -p /opt/mastodon/public/system; \
|
||||
chown mastodon:mastodon /opt/mastodon/public/system; \
|
||||
# Set Mastodon user as owner of tmp folder
|
||||
# Set Mastodon user as owner of tmp folder
|
||||
chown -R mastodon:mastodon /opt/mastodon/tmp;
|
||||
|
||||
# Set the running user for resulting container
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -114,7 +114,7 @@ group :opentelemetry do
|
|||
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
||||
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
||||
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
||||
gem 'opentelemetry-instrumentation-rails', '~> 0.32.0', require: false
|
||||
gem 'opentelemetry-instrumentation-rails', '~> 0.33.0', require: false
|
||||
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
||||
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
||||
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
||||
|
@ -172,7 +172,7 @@ group :development do
|
|||
gem 'rubocop-rspec_rails', require: false
|
||||
|
||||
# Annotates modules with schema
|
||||
gem 'annotate', '~> 3.2'
|
||||
gem 'annotaterb', '~> 4.13'
|
||||
|
||||
# Enhanced error message pages for development
|
||||
gem 'better_errors', '~> 2.9'
|
||||
|
|
64
Gemfile.lock
64
Gemfile.lock
|
@ -90,29 +90,27 @@ GEM
|
|||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
android_key_attestation (0.3.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
annotaterb (4.13.0)
|
||||
ast (2.4.2)
|
||||
attr_required (1.0.2)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1001.0)
|
||||
aws-sdk-core (3.212.0)
|
||||
aws-partitions (1.1009.0)
|
||||
aws-sdk-core (3.213.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.95.0)
|
||||
aws-sdk-kms (1.96.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.170.1)
|
||||
aws-sdk-s3 (1.172.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-blob (0.5.2)
|
||||
azure-blob (0.5.3)
|
||||
rexml
|
||||
base64 (0.2.0)
|
||||
bcp47_spec (0.2.1)
|
||||
|
@ -131,7 +129,7 @@ GEM
|
|||
msgpack (~> 1.2)
|
||||
brakeman (6.2.2)
|
||||
racc
|
||||
browser (6.0.0)
|
||||
browser (6.1.0)
|
||||
brpoplpush-redis_script (0.1.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
redis (>= 1.0, < 6)
|
||||
|
@ -178,7 +176,7 @@ GEM
|
|||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.4)
|
||||
date (3.4.0)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
|
@ -189,10 +187,10 @@ GEM
|
|||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (6.0.0)
|
||||
activesupport (~> 7.0)
|
||||
devise-two-factor (6.1.0)
|
||||
activesupport (>= 7.0, < 8.1)
|
||||
devise (~> 4.0)
|
||||
railties (~> 7.0)
|
||||
railties (>= 7.0, < 8.1)
|
||||
rotp (~> 6.0)
|
||||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
|
@ -330,7 +328,7 @@ GEM
|
|||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.4)
|
||||
json (2.8.1)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3.1)
|
||||
activesupport (>= 4.2)
|
||||
|
@ -348,7 +346,7 @@ GEM
|
|||
json-ld-preloaded (3.3.1)
|
||||
json-ld (~> 3.3)
|
||||
rdf (~> 3.3)
|
||||
json-schema (5.0.1)
|
||||
json-schema (5.1.0)
|
||||
addressable (~> 2.8)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.7.1)
|
||||
|
@ -407,16 +405,16 @@ GEM
|
|||
mime-types (3.6.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.1001)
|
||||
mime-types-data (3.2024.1105)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.25.1)
|
||||
msgpack (1.7.3)
|
||||
msgpack (1.7.5)
|
||||
multi_json (1.15.0)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.5.0)
|
||||
uri
|
||||
net-imap (0.5.0)
|
||||
net-imap (0.5.1)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.19.0)
|
||||
|
@ -480,13 +478,13 @@ GEM
|
|||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-action_pack (0.9.0)
|
||||
opentelemetry-instrumentation-action_pack (0.10.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-rack (~> 0.21)
|
||||
opentelemetry-instrumentation-action_view (0.7.2)
|
||||
opentelemetry-instrumentation-action_view (0.7.3)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||
opentelemetry-instrumentation-active_support (~> 0.6)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-active_job (0.7.8)
|
||||
opentelemetry-api (~> 1.0)
|
||||
|
@ -529,10 +527,10 @@ GEM
|
|||
opentelemetry-instrumentation-rack (0.25.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-rails (0.32.0)
|
||||
opentelemetry-instrumentation-rails (0.33.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.2.0)
|
||||
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
||||
opentelemetry-instrumentation-action_pack (~> 0.10.0)
|
||||
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
||||
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
||||
|
@ -554,10 +552,10 @@ GEM
|
|||
opentelemetry-semantic_conventions (1.10.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.0)
|
||||
ostruct (0.6.1)
|
||||
ox (2.14.18)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.5.0)
|
||||
parser (3.3.6.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
parslet (2.0.0)
|
||||
|
@ -579,7 +577,7 @@ GEM
|
|||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
railties (>= 7.0.0)
|
||||
psych (5.1.2)
|
||||
psych (5.2.0)
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.3)
|
||||
|
@ -664,7 +662,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.10)
|
||||
reline (0.5.11)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.6.0)
|
||||
rack (>= 1.4)
|
||||
|
@ -673,7 +671,7 @@ GEM
|
|||
railties (>= 5.2)
|
||||
rexml (3.3.9)
|
||||
rotp (6.3.0)
|
||||
rouge (4.4.0)
|
||||
rouge (4.5.1)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
|
@ -693,7 +691,7 @@ GEM
|
|||
rspec-mocks (3.13.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (7.0.1)
|
||||
rspec-rails (7.1.0)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
|
@ -794,7 +792,7 @@ GEM
|
|||
stackprof (0.2.26)
|
||||
stoplight (4.1.0)
|
||||
redlock (~> 1.0)
|
||||
stringio (3.1.1)
|
||||
stringio (3.1.2)
|
||||
strong_migrations (2.1.0)
|
||||
activerecord (>= 6.1)
|
||||
swd (1.3.0)
|
||||
|
@ -867,7 +865,7 @@ GEM
|
|||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.8.2)
|
||||
webrick (1.9.0)
|
||||
websocket (1.2.11)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
|
@ -884,7 +882,7 @@ PLATFORMS
|
|||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10)
|
||||
addressable (~> 2.8)
|
||||
annotate (~> 3.2)
|
||||
annotaterb (~> 4.13)
|
||||
aws-sdk-s3 (~> 1.123)
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
|
@ -968,7 +966,7 @@ DEPENDENCIES
|
|||
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
||||
opentelemetry-instrumentation-pg (~> 0.29.0)
|
||||
opentelemetry-instrumentation-rack (~> 0.25.0)
|
||||
opentelemetry-instrumentation-rails (~> 0.32.0)
|
||||
opentelemetry-instrumentation-rails (~> 0.33.0)
|
||||
opentelemetry-instrumentation-redis (~> 0.25.3)
|
||||
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
||||
opentelemetry-sdk (~> 1.4)
|
||||
|
|
|
@ -58,10 +58,7 @@ module Admin
|
|||
private
|
||||
|
||||
def set_resolved_records
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
|
||||
end
|
||||
@resolved_records = DomainResource.new(@email_domain_block.domain).mx
|
||||
end
|
||||
|
||||
def resource_params
|
||||
|
|
|
@ -5,6 +5,8 @@ module Admin
|
|||
before_action :set_instances, only: :index
|
||||
before_action :set_instance, except: :index
|
||||
|
||||
LOGS_LIMIT = 5
|
||||
|
||||
def index
|
||||
authorize :instance, :index?
|
||||
preload_delivery_failures!
|
||||
|
@ -13,7 +15,7 @@ module Admin
|
|||
def show
|
||||
authorize :instance, :show?
|
||||
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5)
|
||||
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
|
@ -16,6 +16,8 @@ module Admin
|
|||
|
||||
def show
|
||||
authorize [:admin, @status], :show?
|
||||
|
||||
@status_batch_action = Admin::StatusBatchAction.new
|
||||
end
|
||||
|
||||
def batch
|
||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections')
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select(:id, :hide_collections)
|
||||
end
|
||||
|
||||
def familiar_followers
|
||||
|
|
|
@ -5,6 +5,8 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
before_action :set_recently_used_tags, only: :index
|
||||
|
||||
RECENT_TAGS_LIMIT = 10
|
||||
|
||||
def index
|
||||
render json: @recently_used_tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@recently_used_tags, current_user&.account_id)
|
||||
end
|
||||
|
@ -12,6 +14,6 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_recently_used_tags
|
||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
|
||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,17 +15,12 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
ApplicationRecord.transaction do
|
||||
list_accounts.each do |account|
|
||||
@list.accounts << account
|
||||
end
|
||||
end
|
||||
|
||||
AddAccountsToListService.new.call(@list, Account.find(account_ids))
|
||||
render_empty
|
||||
end
|
||||
|
||||
def destroy
|
||||
ListAccount.where(list: @list, account_id: account_ids).destroy_all
|
||||
RemoveAccountsFromListService.new.call(@list, Account.where(id: account_ids))
|
||||
render_empty
|
||||
end
|
||||
|
||||
|
@ -43,10 +38,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||
end
|
||||
end
|
||||
|
||||
def list_accounts
|
||||
Account.find(account_ids)
|
||||
end
|
||||
|
||||
def account_ids
|
||||
Array(resource_params[:account_ids])
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ module WebAppControllerConcern
|
|||
vary_by 'Accept, Accept-Language, Cookie'
|
||||
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
|
||||
content_security_policy do |p|
|
||||
policy = ContentSecurityPolicy.new
|
||||
|
@ -24,10 +23,6 @@ module WebAppControllerConcern
|
|||
!(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
|
||||
end
|
||||
|
||||
def set_app_body_class
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
|
||||
def redirect_unauthenticated_to_permalinks!
|
||||
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
||||
|
||||
|
|
|
@ -35,12 +35,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
end
|
||||
|
||||
def set_last_used_at_by_app
|
||||
@last_used_at_by_app = Doorkeeper::AccessToken
|
||||
.select('DISTINCT ON (application_id) application_id, last_used_at')
|
||||
.where(resource_owner_id: current_resource_owner.id)
|
||||
.where.not(last_used_at: nil)
|
||||
.order(application_id: :desc, last_used_at: :desc)
|
||||
.pluck(:application_id, :last_used_at)
|
||||
.to_h
|
||||
@last_used_at_by_app = current_resource_owner.applications_last_used
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||
before_action :set_featured_tag, except: [:index, :create]
|
||||
before_action :set_recently_used_tags, only: :index
|
||||
|
||||
RECENT_TAGS_LIMIT = 10
|
||||
|
||||
def index
|
||||
@featured_tag = FeaturedTag.new
|
||||
end
|
||||
|
@ -38,7 +40,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def set_recently_used_tags
|
||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
|
||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
|
||||
end
|
||||
|
||||
def featured_tag_params
|
||||
|
|
|
@ -24,6 +24,8 @@ class Settings::ImportsController < Settings::BaseController
|
|||
lists: false,
|
||||
}.freeze
|
||||
|
||||
RECENT_IMPORTS_LIMIT = 10
|
||||
|
||||
def index
|
||||
@import = Form::Import.new(current_account: current_account)
|
||||
end
|
||||
|
@ -96,6 +98,6 @@ class Settings::ImportsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def set_recent_imports
|
||||
@recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(10)
|
||||
@recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(RECENT_IMPORTS_LIMIT)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,12 +12,12 @@ module Admin::AccountModerationNotesHelper
|
|||
)
|
||||
end
|
||||
|
||||
def admin_account_inline_link_to(account)
|
||||
def admin_account_inline_link_to(account, path: nil)
|
||||
return if account.nil?
|
||||
|
||||
link_to(
|
||||
account_inline_text(account),
|
||||
admin_account_path(account.id),
|
||||
path || admin_account_path(account.id),
|
||||
class: class_names('inline-name-tag', suspended: suspended_account?(account)),
|
||||
title: account.acct
|
||||
)
|
||||
|
|
|
@ -79,7 +79,7 @@ module ApplicationHelper
|
|||
|
||||
def html_title
|
||||
safe_join(
|
||||
[content_for(:page_title).to_s.chomp, title]
|
||||
[content_for(:page_title), title]
|
||||
.compact_blank,
|
||||
' - '
|
||||
)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SelfDestructHelper
|
||||
VERIFY_PURPOSE = 'self-destruct'
|
||||
|
||||
def self.self_destruct?
|
||||
value = ENV.fetch('SELF_DESTRUCT', nil)
|
||||
value.present? && Rails.application.message_verifier('self-destruct').verify(value) == ENV['LOCAL_DOMAIN']
|
||||
value = Rails.configuration.x.mastodon.self_destruct_value
|
||||
value.present? && Rails.application.message_verifier(VERIFY_PURPOSE).verify(value) == ENV['LOCAL_DOMAIN']
|
||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||
false
|
||||
end
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module StatusesHelper
|
||||
EMBEDDED_CONTROLLER = 'statuses'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
VISIBLITY_ICONS = {
|
||||
public: 'globe',
|
||||
unlisted: 'lock_open',
|
||||
|
@ -60,18 +57,10 @@ module StatusesHelper
|
|||
components.compact_blank.join("\n\n")
|
||||
end
|
||||
|
||||
def stream_link_target
|
||||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
||||
def visibility_icon(status)
|
||||
VISIBLITY_ICONS[status.visibility.to_sym]
|
||||
end
|
||||
|
||||
def embedded_view?
|
||||
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
|
||||
end
|
||||
|
||||
def prefers_autoplay?
|
||||
ActiveModel::Type::Boolean.new.cast(params[:autoplay]) || current_user&.setting_auto_play_gif
|
||||
end
|
||||
|
|
|
@ -141,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
|||
|
||||
return { notifications };
|
||||
},
|
||||
{
|
||||
useLoadingBar: false,
|
||||
},
|
||||
);
|
||||
|
||||
export const processNewNotificationForGroups = createAppAsyncThunk(
|
||||
|
|
|
@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
|
|||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
@ -164,32 +166,18 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
handleClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.handleHotkeyOpen(e);
|
||||
};
|
||||
|
||||
handleMouseUp = e => {
|
||||
// Only handle clicks on the empty space above the content
|
||||
|
||||
if (e.target !== e.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.handleHotkeyOpen();
|
||||
};
|
||||
|
||||
handlePrependAccountClick = e => {
|
||||
this.handleAccountClick(e, false);
|
||||
};
|
||||
|
||||
handleAccountClick = (e, proper = true) => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
this._openProfile(proper);
|
||||
this.handleClick(e);
|
||||
};
|
||||
|
||||
handleExpandedToggle = () => {
|
||||
|
@ -287,7 +275,7 @@ class Status extends ImmutablePureComponent {
|
|||
this.props.onMention(this._properStatus().get('account'));
|
||||
};
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
handleHotkeyOpen = (e) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
|
@ -300,7 +288,13 @@ class Status extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
const path = `/@${status.getIn(['account', 'acct'])}/${status.get('id')}`;
|
||||
|
||||
if (e?.button === 0 && !(e?.ctrlKey || e?.metaKey)) {
|
||||
history.push(path);
|
||||
} else if (e?.button === 1 || (e?.button === 0 && (e?.ctrlKey || e?.metaKey))) {
|
||||
window.open(path, '_blank', 'noreferrer noopener');
|
||||
}
|
||||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
|
@ -393,24 +387,11 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
let media, statusAvatar, prepend, rebloggedByText;
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{expanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
|
||||
if (featured) {
|
||||
prepend = (
|
||||
|
@ -425,7 +406,7 @@ class Status extends ImmutablePureComponent {
|
|||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend__icon'><Icon id='retweet' icon={RepeatIcon} /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <Link data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} to={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></Link> }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -446,6 +427,20 @@ class Status extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{expanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
|
@ -549,20 +544,19 @@ class Status extends ImmutablePureComponent {
|
|||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<div onMouseUp={this.handleMouseUp} className='status__info'>
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'>
|
||||
<span className='status__visibility-icon'><VisibilityIcon visibility={status.get('visibility')} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<Link to={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{matchedFilters && <FilterWarning title={matchedFilters.join(', ')} expanded={this.state.showDespiteFilter} onClick={this.handleFilterToggle} />}
|
||||
|
|
|
@ -204,8 +204,8 @@ class StatusContent extends PureComponent {
|
|||
element = element.parentNode;
|
||||
}
|
||||
|
||||
if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
|
||||
this.props.onClick();
|
||||
if (deltaX + deltaY < 5 && (e.button === 0 || e.button === 1) && this.props.onClick) {
|
||||
this.props.onClick(e);
|
||||
}
|
||||
|
||||
this.startXY = null;
|
||||
|
|
|
@ -43,7 +43,7 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
|||
);
|
||||
|
||||
const handleMouseUp = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
||||
({ clientX, clientY, target, button }) => {
|
||||
({ clientX, clientY, target, button, ctrlKey, metaKey }) => {
|
||||
const [startX, startY] = clickCoordinatesRef.current ?? [0, 0];
|
||||
const [deltaX, deltaY] = [
|
||||
Math.abs(clientX - startX),
|
||||
|
@ -64,8 +64,14 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
|||
element = element.parentNode as HTMLDivElement | null;
|
||||
}
|
||||
|
||||
if (deltaX + deltaY < 5 && button === 0 && account) {
|
||||
history.push(`/@${account.acct}/${statusId}`);
|
||||
if (deltaX + deltaY < 5 && account) {
|
||||
const path = `/@${account.acct}/${statusId}`;
|
||||
|
||||
if (button === 0 && !(ctrlKey || metaKey)) {
|
||||
history.push(path);
|
||||
} else if (button === 1 || (button === 0 && (ctrlKey || metaKey))) {
|
||||
window.open(path, '_blank', 'noreferrer noopener');
|
||||
}
|
||||
}
|
||||
|
||||
clickCoordinatesRef.current = null;
|
||||
|
|
|
@ -128,6 +128,8 @@ export const BoostModal: React.FC<{
|
|||
? messages.cancel_reblog
|
||||
: messages.reblog,
|
||||
)}
|
||||
/* eslint-disable-next-line jsx-a11y/no-autofocus -- We are in the modal */
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"alert.unexpected.title": "المعذرة!",
|
||||
"alt_text_badge.title": "نص بديل",
|
||||
"announcement.announcement": "إعلان",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"attachments_list.unprocessed": "(غير معالَج)",
|
||||
"audio.hide": "إخفاء المقطع الصوتي",
|
||||
"block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
|
||||
|
@ -489,6 +490,7 @@
|
|||
"notification.label.private_reply": "رد خاص",
|
||||
"notification.label.reply": "ردّ",
|
||||
"notification.mention": "إشارة",
|
||||
"notification.mentioned_you": "{name} mentioned you",
|
||||
"notification.moderation-warning.learn_more": "اعرف المزيد",
|
||||
"notification.moderation_warning": "لقد تلقيت تحذيرًا بالإشراف",
|
||||
"notification.moderation_warning.action_delete_statuses": "تم حذف بعض من منشوراتك.",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Опаа!",
|
||||
"alt_text_badge.title": "Алтернативен текст",
|
||||
"announcement.announcement": "Оповестяване",
|
||||
"annual_report.summary.archetype.booster": "Якият подсилвател",
|
||||
"annual_report.summary.archetype.lurker": "Дебнещото",
|
||||
"annual_report.summary.archetype.oracle": "Оракул",
|
||||
"annual_report.summary.archetype.pollster": "Анкетьорче",
|
||||
"annual_report.summary.archetype.replier": "Социална пеперуда",
|
||||
"annual_report.summary.followers.followers": "последователи",
|
||||
"annual_report.summary.followers.total": "{count} общо",
|
||||
"annual_report.summary.here_it_is": "Ето преглед на вашата {year} година:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "най-правено като любима публикация",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "най-подсилваната публикация",
|
||||
"annual_report.summary.highlighted_post.by_replies": "публикации с най-много отговори",
|
||||
"annual_report.summary.highlighted_post.possessive": "на {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "най-употребявано приложение",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "най-употребяван хаштаг",
|
||||
"annual_report.summary.new_posts.new_posts": "нови публикации",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Това ви слага най-отгоре</topLabel><percentage></percentage><bottomLabel>сред потребителите на Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Няма да кажем на Бърни Сандърс.",
|
||||
"annual_report.summary.thanks": "Благодарим, че сте част от Mastodon!",
|
||||
"attachments_list.unprocessed": "(необработено)",
|
||||
"audio.hide": "Скриване на звука",
|
||||
"block_modal.remote_users_caveat": "Ще поискаме сървърът {domain} да почита решението ви. Съгласието обаче не се гарантира откак някои сървъри могат да боравят с блоковете по различен начин. Обществените публикации още може да се виждат от невлезли в системата потребители.",
|
||||
|
@ -158,6 +176,7 @@
|
|||
"compose_form.poll.duration": "Времетраене на анкетата",
|
||||
"compose_form.poll.multiple": "Множествен избор",
|
||||
"compose_form.poll.option_placeholder": "Избор {number}",
|
||||
"compose_form.poll.single": "Единичен избор",
|
||||
"compose_form.poll.switch_to_multiple": "Промяна на анкетата, за да се позволят множество възможни избора",
|
||||
"compose_form.poll.switch_to_single": "Промяна на анкетата, за да се позволи един възможен избор",
|
||||
"compose_form.poll.type": "Стил",
|
||||
|
@ -195,6 +214,8 @@
|
|||
"confirmations.unfollow.message": "Наистина ли искате да не следвате {name}?",
|
||||
"confirmations.unfollow.title": "Спирате ли да следвате потребителя?",
|
||||
"content_warning.hide": "Скриване на публ.",
|
||||
"content_warning.show": "Нека се покаже",
|
||||
"content_warning.show_more": "Показване на още",
|
||||
"conversation.delete": "Изтриване на разговора",
|
||||
"conversation.mark_as_read": "Маркиране като прочетено",
|
||||
"conversation.open": "Преглед на разговора",
|
||||
|
@ -365,6 +386,9 @@
|
|||
"home.pending_critical_update.link": "Преглед на обновяванията",
|
||||
"home.pending_critical_update.title": "Налично критично обновяване на сигурността!",
|
||||
"home.show_announcements": "Показване на оповестяванията",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon не може да осведоми потребители, че сте пренебрегнали известията им. Пренебрегването на известията няма да спре самите съобщения да не бъдат изпращани.",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Вие все още ще може да приемате, отхвърляте или докладвате потребители",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Прецеждането помага за избягване на възможно объркване",
|
||||
"interaction_modal.description.favourite": "Имайки акаунт в Mastodon, може да сложите тази публикации в любими, за да позволите на автора да узнае, че я цените и да я запазите за по-късно.",
|
||||
"interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
|
||||
"interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
|
||||
|
@ -380,6 +404,7 @@
|
|||
"interaction_modal.title.follow": "Последване на {name}",
|
||||
"interaction_modal.title.reblog": "Подсилване на публикацията на {name}",
|
||||
"interaction_modal.title.reply": "Отговаряне на публикацията на {name}",
|
||||
"interaction_modal.title.vote": "Гласувайте в анкетата на {name}",
|
||||
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
|
||||
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# минута} other {# минути}}",
|
||||
|
@ -420,6 +445,8 @@
|
|||
"lightbox.close": "Затваряне",
|
||||
"lightbox.next": "Напред",
|
||||
"lightbox.previous": "Назад",
|
||||
"lightbox.zoom_in": "Увеличение до действителната големина",
|
||||
"lightbox.zoom_out": "Увеличение до побиране",
|
||||
"limited_account_hint.action": "Показване на профила въпреки това",
|
||||
"limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.",
|
||||
"link_preview.author": "От {name}",
|
||||
|
@ -441,6 +468,7 @@
|
|||
"lists.subheading": "Вашите списъци",
|
||||
"load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
|
||||
"loading_indicator.label": "Зареждане…",
|
||||
"media_gallery.hide": "Скриване",
|
||||
"moved_to_account_banner.text": "Вашият акаунт {disabledAccount} сега е изключен, защото се преместихте в {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Скриване от известията",
|
||||
"mute_modal.hide_options": "Скриване на възможностите",
|
||||
|
@ -489,6 +517,7 @@
|
|||
"notification.favourite": "{name} направи любима публикацията ви",
|
||||
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
||||
"notification.follow": "{name} ви последва",
|
||||
"notification.follow.name_and_others": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> ви последваха",
|
||||
"notification.follow_request": "{name} поиска да ви последва",
|
||||
"notification.follow_request.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} поискаха да ви последват",
|
||||
"notification.label.mention": "Споменаване",
|
||||
|
@ -496,6 +525,7 @@
|
|||
"notification.label.private_reply": "Личен отговор",
|
||||
"notification.label.reply": "Отговор",
|
||||
"notification.mention": "Споменаване",
|
||||
"notification.mentioned_you": "{name} ви спомена",
|
||||
"notification.moderation-warning.learn_more": "Научете повече",
|
||||
"notification.moderation_warning": "Получихте предупреждение за модериране",
|
||||
"notification.moderation_warning.action_delete_statuses": "Някои от публикациите ви са премахнати.",
|
||||
|
@ -611,7 +641,7 @@
|
|||
"onboarding.steps.follow_people.title": "Персонализиране на началния ви инфоканал",
|
||||
"onboarding.steps.publish_status.body": "Поздравете целия свят.",
|
||||
"onboarding.steps.publish_status.title": "Направете първата си публикация",
|
||||
"onboarding.steps.setup_profile.body": "Други са по-вероятно да взаимодействат с вас с попълнения профил.",
|
||||
"onboarding.steps.setup_profile.body": "Подсилете взаимодействията си, имайки изчерпателен профил.",
|
||||
"onboarding.steps.setup_profile.title": "Пригодете профила си",
|
||||
"onboarding.steps.share_profile.body": "Позволете на приятелите си да знаят как да ви намират в Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Споделяне на профила ви",
|
||||
|
@ -752,6 +782,7 @@
|
|||
"status.bookmark": "Отмятане",
|
||||
"status.cancel_reblog_private": "Край на подсилването",
|
||||
"status.cannot_reblog": "Публикацията не може да се подсилва",
|
||||
"status.continued_thread": "Продължена нишка",
|
||||
"status.copy": "Копиране на връзката към публикация",
|
||||
"status.delete": "Изтриване",
|
||||
"status.detailed_status": "Подробен изглед на разговора",
|
||||
|
@ -760,6 +791,7 @@
|
|||
"status.edit": "Редактиране",
|
||||
"status.edited": "Последно редактирано на {date}",
|
||||
"status.edited_x_times": "Редактирано {count, plural,one {{count} път} other {{count} пъти}}",
|
||||
"status.embed": "Вземане на кода за вграждане",
|
||||
"status.favourite": "Любимо",
|
||||
"status.favourites": "{count, plural, one {любимо} other {любими}}",
|
||||
"status.filter": "Филтриране на публ.",
|
||||
|
@ -784,6 +816,7 @@
|
|||
"status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
|
||||
"status.redraft": "Изтриване и преработване",
|
||||
"status.remove_bookmark": "Премахване на отметката",
|
||||
"status.replied_in_thread": "Отговорено в нишката",
|
||||
"status.replied_to": "В отговор до {name}",
|
||||
"status.reply": "Отговор",
|
||||
"status.replyAll": "Отговор на нишка",
|
||||
|
@ -821,6 +854,7 @@
|
|||
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
|
||||
"upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
|
||||
"upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
|
||||
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
||||
"upload_form.edit": "Редактиране",
|
||||
"upload_form.thumbnail": "Промяна на миниобраза",
|
||||
"upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "l'aplicació més utilitzada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "l'etiqueta més utilitzada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Cap",
|
||||
"annual_report.summary.new_posts.new_posts": "publicacions noves",
|
||||
"annual_report.summary.thanks": "Gràcies per formar part de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sense processar)",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "ap a ddefnyddiwyd fwyaf",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashnod a ddefnyddiwyd fwyaf",
|
||||
"annual_report.summary.most_used_hashtag.none": "Dim",
|
||||
"annual_report.summary.new_posts.new_posts": "postiadau newydd",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Rydych chi yn y </topLabel><percentage></percentage><bottomLabel>mwyaf o ddefnyddwyr Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ni fyddwn yn dweud wrth Bernie.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest benyttede app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest benyttede hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Intet",
|
||||
"annual_report.summary.new_posts.new_posts": "nye indlæg",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Det betyder, at man er i top</topLabel><percentage></percentage><bottomLabel>af Mastodon-brugere.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi fortæller det ikke til Bernie.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "am häufigsten verwendete App",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "am häufigsten verwendeter Hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Keiner",
|
||||
"annual_report.summary.new_posts.new_posts": "neue Beiträge",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Damit gehörst du zu den obersten</topLabel><percentage></percentage><bottomLabel>der Mastodon-Nutzer*innen.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Wir werden Bernie nichts verraten.",
|
||||
|
@ -687,7 +688,7 @@
|
|||
"poll_button.remove_poll": "Umfrage entfernen",
|
||||
"privacy.change": "Sichtbarkeit anpassen",
|
||||
"privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile",
|
||||
"privacy.direct.short": "Bestimmte Profile",
|
||||
"privacy.direct.short": "Ausgewählte Profile",
|
||||
"privacy.private.long": "Nur deine Follower",
|
||||
"privacy.private.short": "Follower",
|
||||
"privacy.public.long": "Alle in und außerhalb von Mastodon",
|
||||
|
|
|
@ -90,6 +90,8 @@
|
|||
"annual_report.summary.archetype.replier": "La plej societema",
|
||||
"annual_report.summary.followers.followers": "sekvantoj",
|
||||
"annual_report.summary.highlighted_post.by_replies": "afiŝo kun la plej multaj respondoj",
|
||||
"annual_report.summary.most_used_app.most_used_app": "plej uzita apo",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nenio",
|
||||
"annual_report.summary.new_posts.new_posts": "novaj afiŝoj",
|
||||
"annual_report.summary.thanks": "Dankon pro esti parto de Mastodon!",
|
||||
"attachments_list.unprocessed": "(neprilaborita)",
|
||||
|
@ -219,7 +221,7 @@
|
|||
"dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
|
||||
"dismissable_banner.dismiss": "Eksigi",
|
||||
"dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
|
||||
"dismissable_banner.explore_statuses": "Ĉi tiuj estas afiŝoj de la tuta socia reto, kiuj populariĝas hodiaŭ. Pli novaj afiŝoj kun pli da diskonigoj kaj plej ŝatataj estas rangigitaj pli alte.",
|
||||
"dismissable_banner.explore_statuses": "Jen afiŝoj en la socia reto kiuj populariĝis hodiaŭ. Novaj afiŝoj kun pli da diskonigoj kaj stelumoj aperas pli alte.",
|
||||
"dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
|
||||
"dismissable_banner.public_timeline": "Ĉi tiuj estas la plej lastatempaj publikaj afiŝoj de homoj en la socia reto, kiujn homoj sur {domain} sekvas.",
|
||||
"domain_block_modal.block": "Bloki servilon",
|
||||
|
@ -338,7 +340,7 @@
|
|||
"followed_tags": "Sekvataj kradvortoj",
|
||||
"footer.about": "Pri",
|
||||
"footer.directory": "Profilujo",
|
||||
"footer.get_app": "Akiru la Programon",
|
||||
"footer.get_app": "Akiri la apon",
|
||||
"footer.invite": "Inviti homojn",
|
||||
"footer.keyboard_shortcuts": "Fulmoklavoj",
|
||||
"footer.privacy_policy": "Politiko de privateco",
|
||||
|
@ -387,7 +389,7 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ĉu ignori sciigojn de homoj, kiuj ne sekvas vin?",
|
||||
"ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
|
||||
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
|
||||
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumi ĉi tiun afiŝon por sciigi la afiŝanton ke vi sâtas kaj konservas ĝin por poste.",
|
||||
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povas sekvi {name} por ricevi iliajn afiŝojn en via hejma fluo.",
|
||||
"interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
|
||||
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu afiŝo.",
|
||||
|
@ -646,7 +648,7 @@
|
|||
"onboarding.start.lead": "Vi nun estas parto de Mastodon, unika, malcentralizita socia amaskomunikilara platformo, kie vi—ne algoritmo—zorgas vian propran sperton. Ni komencu vin sur ĉi tiu nova socia limo:",
|
||||
"onboarding.start.skip": "Ĉu vi ne bezonas helpon por komenci?",
|
||||
"onboarding.start.title": "Vi atingas ĝin!",
|
||||
"onboarding.steps.follow_people.body": "Sekvi interesajn homojn estas pri kio Mastodonto temas.",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Agordu vian hejman fluon",
|
||||
"onboarding.steps.publish_status.body": "Salutu la mondon per teksto, fotoj, filmetoj aŭ balotenketoj {emoji}",
|
||||
"onboarding.steps.publish_status.title": "Fari vian unuan afiŝon",
|
||||
|
@ -780,9 +782,9 @@
|
|||
"server_banner.is_one_of_many": "{domain} estas unu el la multaj sendependaj Mastodon-serviloj, kiujn vi povas uzi por partopreni en la fediverso.",
|
||||
"server_banner.server_stats": "Statistikoj de la servilo:",
|
||||
"sign_in_banner.create_account": "Krei konton",
|
||||
"sign_in_banner.follow_anyone": "Sekvi iun ajn tra la fediverso kaj vidi ĉion en kronologia ordo. Neniuj algoritmoj, reklamoj aŭ klakbetoj videblas.",
|
||||
"sign_in_banner.mastodon_is": "Mastodonto estas la plej bona maniero por resti flank-al-flanke kun kio okazas.",
|
||||
"sign_in_banner.sign_in": "Saluti",
|
||||
"sign_in_banner.follow_anyone": "Sekvu iun ajn tra la fediverso kaj vidu ĉion laŭ templinio. Nul algoritmo, reklamo aŭ kliklogilo ĉeestas.",
|
||||
"sign_in_banner.mastodon_is": "Mastodon estas la plej bona maniero resti ĝisdata pri aktualaĵoj.",
|
||||
"sign_in_banner.sign_in": "Ensaluti",
|
||||
"sign_in_banner.sso_redirect": "Ensalutu aŭ Registriĝi",
|
||||
"status.admin_account": "Malfermi fasadon de moderigado por @{name}",
|
||||
"status.admin_domain": "Malfermu moderigan interfacon por {domain}",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "la aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevos mensajes",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en la cima</topLabel><percentage></percentage><bottomLabel>de los usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
|
|
|
@ -89,9 +89,9 @@
|
|||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "El cazador de tendencias",
|
||||
"annual_report.summary.archetype.lurker": "El acechador",
|
||||
"annual_report.summary.archetype.oracle": "El oráculo",
|
||||
"annual_report.summary.archetype.oracle": "El oraculo",
|
||||
"annual_report.summary.archetype.pollster": "El encuestador",
|
||||
"annual_report.summary.archetype.replier": "El más sociable",
|
||||
"annual_report.summary.archetype.replier": "La mariposa sociable",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
"account.following": "Seuratut",
|
||||
"account.following_counter": "{count, plural, one {{counter} seurattu} other {{counter} seurattua}}",
|
||||
"account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
|
||||
"account.go_to_profile": "Mene profiiliin",
|
||||
"account.go_to_profile": "Siirry profiiliin",
|
||||
"account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset",
|
||||
"account.in_memoriam": "Muistoissamme.",
|
||||
"account.joined_short": "Liittynyt",
|
||||
|
@ -87,10 +87,23 @@
|
|||
"alert.unexpected.title": "Hups!",
|
||||
"alt_text_badge.title": "Vaihtoehtoinen teksti",
|
||||
"announcement.announcement": "Tiedote",
|
||||
"annual_report.summary.archetype.booster": "Tehostaja",
|
||||
"annual_report.summary.archetype.lurker": "Lymyilijä",
|
||||
"annual_report.summary.archetype.oracle": "Oraakkeli",
|
||||
"annual_report.summary.archetype.pollster": "Mielipidetutkija",
|
||||
"annual_report.summary.archetype.replier": "Sosiaalinen perhonen",
|
||||
"annual_report.summary.followers.followers": "seuraajaa",
|
||||
"annual_report.summary.followers.total": "{count} yhteensä",
|
||||
"annual_report.summary.here_it_is": "Tässä on katsaus vuoteesi {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "suosikkeihin lisätyin julkaisu",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "tehostetuin julkaisu",
|
||||
"annual_report.summary.highlighted_post.by_replies": "julkaisu, jolla on eniten vastauksia",
|
||||
"annual_report.summary.highlighted_post.possessive": "Käyttäjän {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "käytetyin sovellus",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "käytetyin aihetunniste",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ei mitään",
|
||||
"annual_report.summary.new_posts.new_posts": "uutta julkaisua",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Olet osa huippujoukkoa, johon kuuluu</topLabel><percentage></percentage><bottomLabel>Mastodon-käyttäjistä.</bottomLabel>",
|
||||
"annual_report.summary.thanks": "Kiitos, että olet osa Mastodonia!",
|
||||
"attachments_list.unprocessed": "(käsittelemätön)",
|
||||
"audio.hide": "Piilota ääni",
|
||||
|
@ -513,6 +526,7 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportoi käyttäjän {target}",
|
||||
"notification.admin.sign_up": "{name} rekisteröityi",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} rekisteröityivät",
|
||||
"notification.annual_report.message": "Vuoden {year} #Wrapstodon odottaa! Paljasta vuotesi kohokohdat ikimuistoiset hetket Mastodonissa!",
|
||||
"notification.annual_report.view": "Näytä #Wrapstodon",
|
||||
"notification.favourite": "{name} lisäsi julkaisusi suosikkeihinsa",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> lisäsivät julkaisusi suosikkeihinsa",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "hjá {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest brúkta app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brúkta frámerki",
|
||||
"annual_report.summary.most_used_hashtag.none": "Einki",
|
||||
"annual_report.summary.new_posts.new_posts": "nýggir postar",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Tað fær teg í topp</topLabel><percentage></percentage><bottomLabel>av Mastodon brúkarum.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vit fara ikki at fortelja Bernie tað.",
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
"alert.unexpected.title": "Oups!",
|
||||
"alt_text_badge.title": "Texte Alt",
|
||||
"announcement.announcement": "Annonce",
|
||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
"alert.unexpected.title": "Oups !",
|
||||
"alt_text_badge.title": "Texte Alt",
|
||||
"announcement.announcement": "Annonce",
|
||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aip is mó a úsáidtear",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag is mó a úsáidtear",
|
||||
"annual_report.summary.most_used_hashtag.none": "Dada",
|
||||
"annual_report.summary.new_posts.new_posts": "postanna nua",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Cuireann sé sin i mbarr</topLabel><percentage></percentage><bottomLabel> úsáideoirí Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ní inseoidh muid do Bernie.",
|
||||
|
|
|
@ -87,6 +87,11 @@
|
|||
"alert.unexpected.title": "Vaites!",
|
||||
"alt_text_badge.title": "Texto Alt",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "A axencia de noticias",
|
||||
"annual_report.summary.archetype.lurker": "Volleur",
|
||||
"annual_report.summary.archetype.oracle": "Sabichón/e",
|
||||
"annual_report.summary.archetype.pollster": "O INE",
|
||||
"annual_report.summary.archetype.replier": "Lareteire",
|
||||
"annual_report.summary.followers.followers": "seguidoras",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Este é o resumo do teu {year}:",
|
||||
|
@ -96,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "app que mais usaches",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "o cancelo mais utilizado",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nada",
|
||||
"annual_report.summary.new_posts.new_posts": "novas publicacións",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Sitúante no top</topLabel><percentage></percentage><bottomLabel> das usuarias de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.thanks": "Grazas por ser parte de Mastodon!",
|
||||
|
@ -520,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} denunciou a {target}",
|
||||
"notification.admin.sign_up": "{name} rexistrouse",
|
||||
"notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# máis} other {# máis}} crearon unha conta",
|
||||
"notification.annual_report.message": "A #VidaEnMastodon de {year} agarda por ti! Desvela os momentos máis destacados e historias reseñables en Mastodon!",
|
||||
"notification.annual_report.view": "Ver #VidaEnMastodon",
|
||||
"notification.favourite": "{name} marcou como favorita a túa publicación",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# máis} other {# máis}}</a> favoreceron a túa publicación",
|
||||
"notification.follow": "{name} comezou a seguirte",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "של {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "היישומון שהכי בשימוש",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "התג בשימוש הרב ביותר",
|
||||
"annual_report.summary.most_used_hashtag.none": "אף אחד",
|
||||
"annual_report.summary.new_posts.new_posts": "הודעות חדשות",
|
||||
"annual_report.summary.percentile.text": "<topLabel>ממקם אותך באחוזון </topLabel><percentage></percentage><bottomLabel>של משמשי מסטודון.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "לא נגלה לברני.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name} fióktól",
|
||||
"annual_report.summary.most_used_app.most_used_app": "legtöbbet használt app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "legtöbbet használt hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nincs",
|
||||
"annual_report.summary.new_posts.new_posts": "új bejegyzés",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Ezzel a</topLabel><percentage></percentage><bottomLabel>csúcs Mastodon felhasználó között vagy.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Nem mondjuk el Bernie-nek.",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Ups!",
|
||||
"alt_text_badge.title": "Texto alt",
|
||||
"announcement.announcement": "Annuncio",
|
||||
"annual_report.summary.archetype.booster": "Le impulsator",
|
||||
"annual_report.summary.archetype.lurker": "Le lector",
|
||||
"annual_report.summary.archetype.oracle": "Le oraculo",
|
||||
"annual_report.summary.archetype.pollster": "Le sondagista",
|
||||
"annual_report.summary.archetype.replier": "Le responditor",
|
||||
"annual_report.summary.followers.followers": "sequitores",
|
||||
"annual_report.summary.followers.total": "{count} in total",
|
||||
"annual_report.summary.here_it_is": "Ecce tu summario de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "message le plus favorite",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "message le plus impulsate",
|
||||
"annual_report.summary.highlighted_post.by_replies": "message le plus respondite",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}, ecce tu…",
|
||||
"annual_report.summary.most_used_app.most_used_app": "application le plus usate",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag le plus usate",
|
||||
"annual_report.summary.most_used_hashtag.none": "Necun",
|
||||
"annual_report.summary.new_posts.new_posts": "nove messages",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Isto te pone in le prime</topLabel><percentage></percentage><bottomLabel>del usatores de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Tu es un primo inter pares.",
|
||||
"annual_report.summary.thanks": "Gratias pro facer parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(non processate)",
|
||||
"audio.hide": "Celar audio",
|
||||
"block_modal.remote_users_caveat": "Nos demandera al servitor {domain} de respectar tu decision. Nonobstante, le conformitate non es garantite perque alcun servitores pote tractar le blocadas de maniera differente. Le messages public pote esser totevia visibile pro le usatores non authenticate.",
|
||||
|
@ -158,6 +177,7 @@
|
|||
"compose_form.poll.duration": "Durata del sondage",
|
||||
"compose_form.poll.multiple": "Selection multiple",
|
||||
"compose_form.poll.option_placeholder": "Option {number}",
|
||||
"compose_form.poll.single": "Option singule",
|
||||
"compose_form.poll.switch_to_multiple": "Cambiar le sondage pro permitter selectiones multiple",
|
||||
"compose_form.poll.switch_to_single": "Cambiar le sondage pro permitter selection singule",
|
||||
"compose_form.poll.type": "Stilo",
|
||||
|
@ -196,6 +216,7 @@
|
|||
"confirmations.unfollow.title": "Cessar de sequer le usator?",
|
||||
"content_warning.hide": "Celar le message",
|
||||
"content_warning.show": "Monstrar in omne caso",
|
||||
"content_warning.show_more": "Monstrar plus",
|
||||
"conversation.delete": "Deler conversation",
|
||||
"conversation.mark_as_read": "Marcar como legite",
|
||||
"conversation.open": "Vider conversation",
|
||||
|
@ -304,6 +325,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Usa un categoria existente o crea un nove",
|
||||
"filter_modal.select_filter.title": "Filtrar iste message",
|
||||
"filter_modal.title.status": "Filtrar un message",
|
||||
"filter_warning.matches_filter": "Corresponde al filtro “<span>{title}</span>”",
|
||||
"filtered_notifications_banner.pending_requests": "De {count, plural, =0 {nemo} one {un persona} other {# personas}} que tu pote cognoscer",
|
||||
"filtered_notifications_banner.title": "Notificationes filtrate",
|
||||
"firehose.all": "Toto",
|
||||
|
@ -383,6 +405,7 @@
|
|||
"interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.",
|
||||
"interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.",
|
||||
"interaction_modal.description.reply": "Con un conto sur Mastodon, tu pote responder a iste message.",
|
||||
"interaction_modal.description.vote": "Con un conto sur Mastodon, tu pote votar in iste sondage.",
|
||||
"interaction_modal.login.action": "Porta me a casa",
|
||||
"interaction_modal.login.prompt": "Dominio de tu servitor, p.ex. mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Non sur Mstodon?",
|
||||
|
@ -394,6 +417,7 @@
|
|||
"interaction_modal.title.follow": "Sequer {name}",
|
||||
"interaction_modal.title.reblog": "Impulsar le message de {name}",
|
||||
"interaction_modal.title.reply": "Responder al message de {name}",
|
||||
"interaction_modal.title.vote": "Votar in le sondage de {name}",
|
||||
"intervals.full.days": "{number, plural, one {# die} other {# dies}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hora} other {# horas}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuta} other {# minutas}}",
|
||||
|
@ -503,9 +527,12 @@
|
|||
"notification.admin.report_statuses_other": "{name} ha reportate {target}",
|
||||
"notification.admin.sign_up": "{name} se ha inscribite",
|
||||
"notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# altere usator} other {altere # usatores}} se inscribeva",
|
||||
"notification.annual_report.message": "Tu summario #Wrapstodon pro {year} attende! Revela le momentos saliente e memorabile de tu anno sur Mastodon!",
|
||||
"notification.annual_report.view": "Visitar summario #Wrapstodon",
|
||||
"notification.favourite": "{name} ha marcate tu message como favorite",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# altere} other {# alteres}}</a> favoriva tu message",
|
||||
"notification.follow": "{name} te ha sequite",
|
||||
"notification.follow.name_and_others": "{name} e <a>{count, plural, one {# other} other {# alteres}}</a> te ha sequite",
|
||||
"notification.follow_request": "{name} ha requestate de sequer te",
|
||||
"notification.follow_request.name_and_others": "{name} e {count, plural, one {# altere} other {# alteres}} ha demandate de sequer te",
|
||||
"notification.label.mention": "Mention",
|
||||
|
@ -564,6 +591,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
|
||||
"notifications.column_settings.follow": "Nove sequitores:",
|
||||
"notifications.column_settings.follow_request": "Nove requestas de sequimento:",
|
||||
"notifications.column_settings.group": "Gruppo",
|
||||
"notifications.column_settings.mention": "Mentiones:",
|
||||
"notifications.column_settings.poll": "Resultatos del sondage:",
|
||||
"notifications.column_settings.push": "Notificationes push",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest notaða forrit",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest notaða myllumerki",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ekkert",
|
||||
"annual_report.summary.new_posts.new_posts": "nýjar færslur",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Það setur þig á meðal efstu</topLabel><percentage></percentage><bottomLabel>notenda Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Við förum ekkert að raupa um þetta.",
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
"alt_text_badge.title": "Testo alternativo",
|
||||
"announcement.announcement": "Annuncio",
|
||||
"annual_report.summary.archetype.oracle": "L'oracolo",
|
||||
"annual_report.summary.archetype.pollster": "Sondaggista",
|
||||
"annual_report.summary.followers.followers": "seguaci",
|
||||
"annual_report.summary.followers.total": "{count} in totale",
|
||||
"annual_report.summary.here_it_is": "Ecco il tuo {year} in sintesi:",
|
||||
|
@ -97,6 +98,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "di {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "l'app più utilizzata",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "l'hashtag più usato",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nessuno",
|
||||
"annual_report.summary.new_posts.new_posts": "nuovi post",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Questo ti colloca tra il</topLabel><percentage></percentage><bottomLabel>dei migliori utenti Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Non lo diremo a Bernie.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name} 님의",
|
||||
"annual_report.summary.most_used_app.most_used_app": "가장 많이 사용한 앱",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "가장 많이 사용한 해시태그",
|
||||
"annual_report.summary.most_used_hashtag.none": "없음",
|
||||
"annual_report.summary.new_posts.new_posts": "새 게시물",
|
||||
"annual_report.summary.percentile.text": "<topLabel>마스토돈 사용자의 상위</topLabel><percentage></percentage><bottomLabel>입니다.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "엄마한테 말 안 할게요.",
|
||||
|
|
|
@ -87,6 +87,17 @@
|
|||
"alert.unexpected.title": "Atyo!",
|
||||
"alt_text_badge.title": "Teksto alternativo",
|
||||
"announcement.announcement": "Pregon",
|
||||
"annual_report.summary.archetype.pollster": "El anketero",
|
||||
"annual_report.summary.followers.followers": "suivantes",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "la puvlikasyon mas favoritada",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "la puvlikasyon mas repartajada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "la puvlikasyon kon mas repuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "la aplikasyon mas uzada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiketa mas uzada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Dinguno",
|
||||
"annual_report.summary.new_posts.new_posts": "puvlikasyones muevas",
|
||||
"annual_report.summary.thanks": "Mersi por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(no prosesado)",
|
||||
"audio.hide": "Eskonde audio",
|
||||
"block_modal.show_less": "Amostra manko",
|
||||
|
@ -97,6 +108,7 @@
|
|||
"block_modal.you_wont_see_mentions": "No veras publikasyones ke lo enmentan.",
|
||||
"boost_modal.combo": "Puedes klikar {combo} para ometer esto la proksima vez",
|
||||
"boost_modal.reblog": "Repartajar puvlikasyon?",
|
||||
"boost_modal.undo_reblog": "Departajar puvlikasyon?",
|
||||
"bundle_column_error.copy_stacktrace": "Kopia el raporto de yerro",
|
||||
"bundle_column_error.error.body": "La pajina solisitada no pudo ser renderada. Podria ser por un yerro en muestro kodiche o un problem de kompatibilita kon el navigador.",
|
||||
"bundle_column_error.error.title": "Atyo, no!",
|
||||
|
@ -155,6 +167,7 @@
|
|||
"compose_form.poll.duration": "Durasion de anketa",
|
||||
"compose_form.poll.multiple": "Multiples opsyones",
|
||||
"compose_form.poll.option_placeholder": "Opsyon {number}",
|
||||
"compose_form.poll.single": "Opsyon unika",
|
||||
"compose_form.poll.switch_to_multiple": "Troka anketa para permeter a eskojer mas ke una opsyon",
|
||||
"compose_form.poll.switch_to_single": "Troka anketa para permeter a eskojer solo una opsyon",
|
||||
"compose_form.poll.type": "Estilo",
|
||||
|
@ -213,6 +226,7 @@
|
|||
"dismissable_banner.public_timeline": "Estas son las publikasyones publikas mas resientes de personas en la red sosyala a las kualas la djente de {domain} sige.",
|
||||
"domain_block_modal.block": "Bloka sirvidor",
|
||||
"domain_block_modal.block_account_instead": "Bloka @{name} en su lugar",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Las personas de este sirvidor pueden enteraktuar kon tus puvlikasyones viejas.",
|
||||
"domain_block_modal.they_cant_follow": "Dingun de este sirvidor puede segirte.",
|
||||
"domain_block_modal.they_wont_know": "No savra ke tiene sido blokado.",
|
||||
"domain_block_modal.title": "Bloka el domeno?",
|
||||
|
@ -307,6 +321,7 @@
|
|||
"follow_suggestions.personalized_suggestion": "Sujestion personalizada",
|
||||
"follow_suggestions.popular_suggestion": "Sujestion populara",
|
||||
"follow_suggestions.popular_suggestion_longer": "Popular en {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Similares a los profils ke tienes segido resyentemente",
|
||||
"follow_suggestions.view_all": "Ve todos",
|
||||
"follow_suggestions.who_to_follow": "A ken segir",
|
||||
"followed_tags": "Etiketas segidas",
|
||||
|
@ -335,6 +350,9 @@
|
|||
"hashtag.follow": "Sige etiketa",
|
||||
"hashtag.unfollow": "Desige etiketa",
|
||||
"hashtags.and_other": "…i {count, plural, one {}other {# mas}}",
|
||||
"hints.profiles.followers_may_be_missing": "Puede ser ke algunos suivantes de este profil no se amostren.",
|
||||
"hints.profiles.follows_may_be_missing": "Puede ser ke algunos kuentos segidos por este profil no se amostren.",
|
||||
"hints.profiles.posts_may_be_missing": "Puede ser ke algunas puvlikasyones de este profil no se amostren.",
|
||||
"hints.profiles.see_more_followers": "Ve mas suivantes en {domain}",
|
||||
"hints.profiles.see_more_follows": "Ve mas segidos en {domain}",
|
||||
"hints.profiles.see_more_posts": "Ve mas puvlikasyones en {domain}",
|
||||
|
@ -352,10 +370,12 @@
|
|||
"ignore_notifications_modal.new_accounts_title": "Inyorar avizos de kuentos muevos?",
|
||||
"ignore_notifications_modal.not_followers_title": "Inyorar avizos de personas a las kualas no te sigen?",
|
||||
"ignore_notifications_modal.not_following_title": "Inyorar avizos de personas a las kualas no siges?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorar avizos de mensyones privadas no solisitadas?",
|
||||
"interaction_modal.description.favourite": "Kon un kuento en Mastodon, puedes markar esta publikasyon komo favorita para ke el autor sepa ke te plaze i para guadrarla para dempues.",
|
||||
"interaction_modal.description.follow": "Kon un kuento en Mastodon, puedes segir a {name} para risivir sus publikasyones en tu linya temporal prinsipala.",
|
||||
"interaction_modal.description.reblog": "Kon un kuento en Mastodon, puedes repartajar esta publikasyon para amostrarla a tus suivantes.",
|
||||
"interaction_modal.description.reply": "Kon un kuento en Mastodon, puedes arispondir a esta publikasyon.",
|
||||
"interaction_modal.description.vote": "Kon un kuento en Mastodon, puedes votar en esta anketa.",
|
||||
"interaction_modal.login.action": "Va a tu sirvidor",
|
||||
"interaction_modal.login.prompt": "Domeno del sirvidor de tu kuento, por enshemplo mastodon.social",
|
||||
"interaction_modal.no_account_yet": "No tyenes kuento de Mastodon?",
|
||||
|
@ -367,6 +387,7 @@
|
|||
"interaction_modal.title.follow": "Sige a {name}",
|
||||
"interaction_modal.title.reblog": "Repartaja publikasyon de {name}",
|
||||
"interaction_modal.title.reply": "Arisponde a publikasyon de {name}",
|
||||
"interaction_modal.title.vote": "Vota en la anketa de {name}",
|
||||
"intervals.full.days": "{number, plural, one {# diya} other {# diyas}}",
|
||||
"intervals.full.hours": "{number, plural, one {# ora} other {# oras}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
|
||||
|
@ -466,13 +487,16 @@
|
|||
"navigation_bar.security": "Segurita",
|
||||
"not_signed_in_indicator.not_signed_in": "Nesesitas konektarse kon tu kuento para akseder este rekurso.",
|
||||
"notification.admin.report": "{name} raporto {target}",
|
||||
"notification.admin.report_statuses": "{name} raporto {target} por {category}",
|
||||
"notification.admin.report_statuses_other": "{name} raporto {target}",
|
||||
"notification.admin.sign_up": "{name} kriyo un konto",
|
||||
"notification.annual_report.view": "Ve #Wrapstodon",
|
||||
"notification.favourite": "A {name} le plaze tu publikasyon",
|
||||
"notification.follow": "{name} te ampeso a segir",
|
||||
"notification.follow_request": "{name} tiene solisitado segirte",
|
||||
"notification.label.mention": "Enmenta",
|
||||
"notification.label.private_mention": "Enmentadura privada",
|
||||
"notification.label.private_reply": "Repuesta privada",
|
||||
"notification.label.reply": "Arisponde",
|
||||
"notification.mention": "Enmenta",
|
||||
"notification.mentioned_you": "{name} te enmento",
|
||||
|
@ -536,6 +560,7 @@
|
|||
"notifications.policy.accept_hint": "Amostra en avizos",
|
||||
"notifications.policy.drop": "Inyora",
|
||||
"notifications.policy.filter": "Filtra",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Limitadas por moderadores del sirvidor",
|
||||
"notifications.policy.filter_limited_accounts_title": "Kuentos moderados",
|
||||
"notifications.policy.filter_new_accounts.hint": "Kriyadas durante {days, plural, one {el ultimo diya} other {los ultimos # diyas}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Muevos kuentos",
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
"account.unendorse": "Nerodyti profilyje",
|
||||
"account.unfollow": "Nebesekti",
|
||||
"account.unmute": "Atšaukti nutildymą @{name}",
|
||||
"account.unmute_notifications_short": "Atšaukti nutildymą pranešimams",
|
||||
"account.unmute_notifications_short": "Atšaukti pranešimų nutildymą",
|
||||
"account.unmute_short": "Atšaukti nutildymą",
|
||||
"account_note.placeholder": "Spustelėk, kad pridėtum pastabą.",
|
||||
"admin.dashboard.daily_retention": "Naudotojų pasilikimo rodiklis pagal dieną po registracijos",
|
||||
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "labiausiai naudota programa",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "labiausiai naudotas saitažodis",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nieko",
|
||||
"annual_report.summary.new_posts.new_posts": "nauji įrašai",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Tai reiškia, kad esate tarp</topLabel><percentage></percentage><bottomLabel>populiariausių „Mastodon“ naudotojų.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Mes nesakysime Bernie.",
|
||||
|
@ -525,6 +526,7 @@
|
|||
"notification.admin.report_statuses": "{name} pranešė {target} kategorijai {category}",
|
||||
"notification.admin.report_statuses_other": "{name} pranešė {target}",
|
||||
"notification.admin.sign_up": "{name} užsiregistravo",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ir {count, plural, one {# kitas} few {# kiti} many {# kito} other {# kitų}} užsiregistravo",
|
||||
"notification.annual_report.message": "Jūsų laukia {year} #Wrapstodon! Atskleiskite savo metų svarbiausius įvykius ir įsimintinas akimirkas platformoje „Mastodon“.",
|
||||
"notification.annual_report.view": "Peržiūrėti #Wrapstodon",
|
||||
"notification.favourite": "{name} pamėgo tavo įrašą",
|
||||
|
@ -536,6 +538,7 @@
|
|||
"notification.label.private_reply": "Privatus atsakymas",
|
||||
"notification.label.reply": "Atsakymas",
|
||||
"notification.mention": "Paminėjimas",
|
||||
"notification.mentioned_you": "{name} paminėjo jus",
|
||||
"notification.moderation-warning.learn_more": "Sužinoti daugiau",
|
||||
"notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą",
|
||||
"notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.",
|
||||
|
|
|
@ -86,6 +86,14 @@
|
|||
"alert.unexpected.message": "Radās negaidīta kļūda.",
|
||||
"alert.unexpected.title": "Ups!",
|
||||
"announcement.announcement": "Paziņojums",
|
||||
"annual_report.summary.followers.followers": "sekotāji",
|
||||
"annual_report.summary.followers.total": "pavisam {count}",
|
||||
"annual_report.summary.here_it_is": "Šeit ir {year}. gada pārskats:",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "visizmantotākā lietotne",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "visizmantotākais tēmturis",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nav",
|
||||
"annual_report.summary.new_posts.new_posts": "jauni ieraksti",
|
||||
"attachments_list.unprocessed": "(neapstrādāti)",
|
||||
"audio.hide": "Slēpt audio",
|
||||
"block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
|
||||
|
@ -325,7 +333,11 @@
|
|||
"hashtag.follow": "Sekot tēmturim",
|
||||
"hashtag.unfollow": "Pārstāt sekot tēmturim",
|
||||
"hashtags.and_other": "… un {count, plural, other {vēl #}}",
|
||||
"hints.profiles.see_more_followers": "Skatīt vairāk sekotāju {domain}",
|
||||
"hints.profiles.see_more_follows": "Skatīt vairāk sekojumu {domain}",
|
||||
"hints.profiles.see_more_posts": "Skatīt vairāk ierakstu {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Var trūkt atbildes no citiem serveriem.",
|
||||
"hints.threads.see_more": "Skatīt vairāk atbilžu {domain}",
|
||||
"home.column_settings.show_reblogs": "Rādīt pastiprinātos ierakstus",
|
||||
"home.column_settings.show_replies": "Rādīt atbildes",
|
||||
"home.hide_announcements": "Slēpt paziņojumus",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "meest gebruikte app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "meest gebruikte hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Geen",
|
||||
"annual_report.summary.new_posts.new_posts": "nieuwe berichten",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Dat zet je in de top</topLabel><percentage></percentage><bottomLabel>van Mastodon-gebruikers.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "We zullen Bernie niets vertellen.",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Oi sann!",
|
||||
"alt_text_badge.title": "Alternativ tekst",
|
||||
"announcement.announcement": "Kunngjering",
|
||||
"annual_report.summary.archetype.booster": "Den som jaktar på noko kult",
|
||||
"annual_report.summary.archetype.lurker": "Den som heng på hjørnet",
|
||||
"annual_report.summary.archetype.oracle": "Orakelet",
|
||||
"annual_report.summary.archetype.pollster": "Meiningsmålaren",
|
||||
"annual_report.summary.archetype.replier": "Den sosiale sumarfuglen",
|
||||
"annual_report.summary.followers.followers": "fylgjarar",
|
||||
"annual_report.summary.followers.total": "{count} i alt",
|
||||
"annual_report.summary.here_it_is": "Her er eit gjensyn med {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "det mest omtykte innlegget",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "det mest framheva innlegget",
|
||||
"annual_report.summary.highlighted_post.by_replies": "innlegget med flest svar",
|
||||
"annual_report.summary.highlighted_post.possessive": "som {name} laga",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest brukte app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brukte emneknagg",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ingen",
|
||||
"annual_report.summary.new_posts.new_posts": "nye innlegg",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Du er av dei</topLabel><percentage></percentage><bottomLabel>ivrigaste Mastodon-brukarane.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ikkje eit ord til pressa.",
|
||||
"annual_report.summary.thanks": "Takk for at du er med i Mastodon!",
|
||||
"attachments_list.unprocessed": "(ubehandla)",
|
||||
"audio.hide": "Gøym lyd",
|
||||
"block_modal.remote_users_caveat": "Me vil be tenaren {domain} om å respektere di avgjerd. Me kan ikkje garantera at det vert gjort, sidan nokre tenarar kan handtera blokkering ulikt. Offentlege innlegg kan framleis vera synlege for ikkje-innlogga brukarar.",
|
||||
|
@ -197,6 +216,7 @@
|
|||
"confirmations.unfollow.title": "Slutt å fylgja brukaren?",
|
||||
"content_warning.hide": "Gøym innlegg",
|
||||
"content_warning.show": "Vis likevel",
|
||||
"content_warning.show_more": "Vis meir",
|
||||
"conversation.delete": "Slett samtale",
|
||||
"conversation.mark_as_read": "Marker som lesen",
|
||||
"conversation.open": "Sjå samtale",
|
||||
|
@ -305,6 +325,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Bruk ein eksisterande kategori eller opprett ein ny",
|
||||
"filter_modal.select_filter.title": "Filtrer dette innlegget",
|
||||
"filter_modal.title.status": "Filtrer eit innlegg",
|
||||
"filter_warning.matches_filter": "Passar med filteret «<span>{title}</span>»",
|
||||
"filtered_notifications_banner.pending_requests": "Frå {count, plural, =0 {ingen} one {éin person} other {# personar}} du kanskje kjenner",
|
||||
"filtered_notifications_banner.title": "Filtrerte varslingar",
|
||||
"firehose.all": "Alle",
|
||||
|
@ -506,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} rapporterte {target}",
|
||||
"notification.admin.sign_up": "{name} er registrert",
|
||||
"notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# annan} other {# andre}} vart med",
|
||||
"notification.annual_report.message": "#Året ditt for {year} ventar! Sjå kva som skjedde i løpet av Mastodon-året ditt!",
|
||||
"notification.annual_report.view": "Sjå #Året ditt",
|
||||
"notification.favourite": "{name} markerte innlegget ditt som favoritt",
|
||||
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# annan} other {# andre}}</a> favorittmerka innlegget ditt",
|
||||
"notification.follow": "{name} fylgde deg",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Ups!",
|
||||
"alt_text_badge.title": "Tekst alternatywny",
|
||||
"announcement.announcement": "Ogłoszenie",
|
||||
"annual_report.summary.archetype.booster": "Łowca treści",
|
||||
"annual_report.summary.archetype.lurker": "Czyhający",
|
||||
"annual_report.summary.archetype.oracle": "Wyrocznia",
|
||||
"annual_report.summary.archetype.pollster": "Ankieter",
|
||||
"annual_report.summary.archetype.replier": "Motyl społeczny",
|
||||
"annual_report.summary.followers.followers": "obserwujących",
|
||||
"annual_report.summary.followers.total": "łącznie {count}",
|
||||
"annual_report.summary.here_it_is": "Oto przegląd Twojego {year} roku:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "najbardziej ulubiony wpis",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "najbardziej promowany wpis",
|
||||
"annual_report.summary.highlighted_post.by_replies": "wpis z największą liczbą odpowiedzi",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "najczęściej używana aplikacja",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "najczęściej używany hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Brak",
|
||||
"annual_report.summary.new_posts.new_posts": "nowe wpisy",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Nie powiemy Berniemu.",
|
||||
"annual_report.summary.thanks": "Dziękujemy, że jesteś częścią Mastodona!",
|
||||
"attachments_list.unprocessed": "(nieprzetworzone)",
|
||||
"audio.hide": "Ukryj dźwięk",
|
||||
"block_modal.remote_users_caveat": "Poprosimy serwer {domain} o uszanowanie twojej decyzji. Zgodność nie jest jednak gwarantowana, bo niektóre serwery mogą inaczej obsługiwać blokowanie. Wpisy publiczne mogą być widoczne dla niezalogowanych użytkowników.",
|
||||
|
@ -508,6 +526,7 @@
|
|||
"notification.admin.report_statuses_other": "{name} zgłosił(a) {target}",
|
||||
"notification.admin.sign_up": "Użytkownik {name} zarejestrował się",
|
||||
"notification.admin.sign_up.name_and_others": "zarejestrował(-a) się {name} i {count, plural, one {# inna osoba} few {# inne osoby} other {# innych osób}}",
|
||||
"notification.annual_report.view": "Zobacz #Wrapstodon",
|
||||
"notification.favourite": "{name} dodaje Twój wpis do ulubionych",
|
||||
"notification.favourite.name_and_others_with_link": "{name} i <a>{count, plural, one {# inna osoba</a> polubiła twój wpis} few {# inne osoby</a> polubiły twój wpis} other {# innych osób</a> polubiło twój wpis}}",
|
||||
"notification.follow": "{name} obserwuje Cię",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicativo mais usado",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag mais usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nenhuma",
|
||||
"annual_report.summary.new_posts.new_posts": "novas publicações",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Isso o coloca no topo</topLabel></percentage><bottomLabel>dos usuários de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Não contaremos à Bernie.",
|
||||
|
|
|
@ -89,9 +89,11 @@
|
|||
"announcement.announcement": "Объявление",
|
||||
"annual_report.summary.archetype.booster": "Репостер",
|
||||
"annual_report.summary.archetype.lurker": "Молчун",
|
||||
"annual_report.summary.archetype.oracle": "Шаман",
|
||||
"annual_report.summary.archetype.pollster": "Опросчик",
|
||||
"annual_report.summary.archetype.replier": "Душа компании",
|
||||
"annual_report.summary.followers.followers": "подписчиков",
|
||||
"annual_report.summary.followers.total": "{count} за всё время",
|
||||
"annual_report.summary.here_it_is": "Вот ваши итоги {year} года:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "пост с наибольшим количеством звёздочек",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "пост с наибольшим количеством продвижений",
|
||||
|
@ -99,7 +101,9 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "наиболее часто используемое приложение",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "наиболее часто используемый хэштег",
|
||||
"annual_report.summary.most_used_hashtag.none": "Нет",
|
||||
"annual_report.summary.new_posts.new_posts": "новых постов",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Всё это помещает вас в топ</topLabel><percentage></percentage><bottomLabel>пользователей Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.thanks": "Спасибо за то, что были вместе с Mastodon!",
|
||||
"attachments_list.unprocessed": "(не обработан)",
|
||||
"audio.hide": "Скрыть аудио",
|
||||
|
|
|
@ -348,6 +348,8 @@
|
|||
"hashtag.follow": "Sledovať hashtag",
|
||||
"hashtag.unfollow": "Prestať sledovať hashtag",
|
||||
"hashtags.and_other": "…a {count, plural, other {# ďalších}}",
|
||||
"hints.profiles.see_more_posts": "Pozri viac príspevkov na {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Odpovede z ostatných serverov môžu chýbať.",
|
||||
"hints.threads.see_more": "Pozri viac odpovedí na {domain}",
|
||||
"home.column_settings.show_reblogs": "Zobraziť zdieľania",
|
||||
"home.column_settings.show_replies": "Zobraziť odpovede",
|
||||
|
@ -360,6 +362,10 @@
|
|||
"ignore_notifications_modal.filter_to_act_users": "Stále budeš môcť akceptovať, odmietnuť, alebo nahlásiť užívateľov",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Triedenie pomáha vyvarovať sa možnému zmäteniu",
|
||||
"ignore_notifications_modal.ignore": "Ignoruj upozornenia",
|
||||
"ignore_notifications_modal.new_accounts_title": "Nevšímať si oznámenia z nových účtov?",
|
||||
"ignore_notifications_modal.not_followers_title": "Nevšímať si oznámenia od ľudí, ktorí ťa nenasledujú?",
|
||||
"ignore_notifications_modal.not_following_title": "Nevšímať si oznámenia od ľudí, ktorých nenasleduješ?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Nevšímať si oznámenia o nevyžiadaných súkromných spomínaniach?",
|
||||
"interaction_modal.description.favourite": "S účtom na Mastodone môžete tento príspevok ohviezdičkovať, tak dať autorovi vedieť, že sa vám páči, a uložiť si ho na neskôr.",
|
||||
"interaction_modal.description.follow": "S účtom na Mastodone môžete {name} sledovať a vidieť ich príspevky vo svojom domovskom kanáli.",
|
||||
"interaction_modal.description.reblog": "S účtom na Mastodone môžete tento príspevok zdeľať so svojimi sledovateľmi.",
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
"alert.unexpected.message": "Zgodila se je nepričakovana napaka.",
|
||||
"alert.unexpected.title": "Ojoj!",
|
||||
"announcement.announcement": "Obvestilo",
|
||||
"annual_report.summary.most_used_hashtag.none": "Brez",
|
||||
"annual_report.summary.new_posts.new_posts": "nove objave",
|
||||
"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.",
|
||||
|
@ -433,6 +435,7 @@
|
|||
"lists.subheading": "Vaši seznami",
|
||||
"load_pending": "{count, plural, one {# nov element} two {# nova elementa} few {# novi elementi} other {# novih elementov}}",
|
||||
"loading_indicator.label": "Nalaganje …",
|
||||
"media_gallery.hide": "Skrij",
|
||||
"moved_to_account_banner.text": "Vaš račun {disabledAccount} je trenutno onemogočen, ker ste se prestavili na {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Skrijte se pred obvestili",
|
||||
"mute_modal.hide_options": "Skrij možnosti",
|
||||
|
@ -444,6 +447,7 @@
|
|||
"mute_modal.you_wont_see_mentions": "Objav, ki jih omenjajo, ne boste videli.",
|
||||
"mute_modal.you_wont_see_posts": "Še vedno vidijo vaše objave, vi pa ne njihovih.",
|
||||
"navigation_bar.about": "O Mastodonu",
|
||||
"navigation_bar.administration": "Upravljanje",
|
||||
"navigation_bar.advanced_interface": "Odpri v naprednem spletnem vmesniku",
|
||||
"navigation_bar.blocks": "Blokirani uporabniki",
|
||||
"navigation_bar.bookmarks": "Zaznamki",
|
||||
|
@ -478,10 +482,12 @@
|
|||
"notification.favourite": "{name} je vzljubil/a vašo objavo",
|
||||
"notification.follow": "{name} vam sledi",
|
||||
"notification.follow_request": "{name} vam želi slediti",
|
||||
"notification.label.mention": "Omemba",
|
||||
"notification.label.private_mention": "Zasebna omemba",
|
||||
"notification.label.private_reply": "Zasebni odgovor",
|
||||
"notification.label.reply": "Odgovori",
|
||||
"notification.mention": "Omemba",
|
||||
"notification.mentioned_you": "{name} vas je omenil/a",
|
||||
"notification.moderation-warning.learn_more": "Več o tem",
|
||||
"notification.moderation_warning": "Prejeli ste opozorilo moderatorjev",
|
||||
"notification.moderation_warning.action_delete_statuses": "Nekatere vaše objave so odstranjene.",
|
||||
|
@ -502,6 +508,7 @@
|
|||
"notification.status": "{name} je pravkar objavil/a",
|
||||
"notification.update": "{name} je uredil(a) objavo",
|
||||
"notification_requests.accept": "Sprejmi",
|
||||
"notification_requests.confirm_accept_multiple.title": "Ali želite sprejeti zahteve za obvestila?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Želite opustiti zahteve za obvestila?",
|
||||
"notification_requests.dismiss": "Zavrni",
|
||||
"notification_requests.edit_selection": "Uredi",
|
||||
|
@ -740,6 +747,7 @@
|
|||
"status.edit": "Uredi",
|
||||
"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.favourites": "{count, plural, one {priljubitev} two {priljubitvi} few {priljubitve} other {priljubitev}}",
|
||||
"status.filter": "Filtriraj to objavo",
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "nga {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplikacioni më i përdorur",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag-u më i përdorur",
|
||||
"annual_report.summary.most_used_hashtag.none": "Asnjë",
|
||||
"annual_report.summary.new_posts.new_posts": "postime të reja",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Kjo ju vë në krye</topLabel><percentage></percentage><bottomLabel>të përdoruesve të Mastodon-it.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Nuk do t’ia themi Bernit.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest använda app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest använda hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Inga",
|
||||
"annual_report.summary.new_posts.new_posts": "nya inlägg",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Det placerar dig i topp</topLabel><percentage></percentage><bottomLabel>av Mastodon-användare.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi berättar inte för Bernie.",
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
"admin.impact_report.instance_followers": "Kullanıcılarımızın kaybedeceği takipçiler",
|
||||
"admin.impact_report.instance_follows": "Kullanıcılarının kaybedeceği takipçiler",
|
||||
"admin.impact_report.title": "Etki özeti",
|
||||
"alert.rate_limited.message": "Lütfen {retry_time, time, medium} saatinden sonra tekrar deneyin.",
|
||||
"alert.rate_limited.message": "Lütfen sonra tekrar deneyin {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Aşırı istek gönderildi",
|
||||
"alert.unexpected.message": "Beklenmedik bir hata oluştu.",
|
||||
"alert.unexpected.title": "Hay aksi!",
|
||||
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "en çok kullanılan uygulama",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "en çok kullanılan etiket",
|
||||
"annual_report.summary.most_used_hashtag.none": "Yok",
|
||||
"annual_report.summary.new_posts.new_posts": "yeni gönderiler",
|
||||
"annual_report.summary.percentile.text": "<bottomLabel>Mastodon kullanıcılarının</bottomLabel><percentage></percentage><topLabel>üst dilimindesiniz</topLabel>.",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Bernie'ye söylemeyiz.",
|
||||
|
@ -357,7 +358,7 @@
|
|||
"footer.privacy_policy": "Gizlilik politikası",
|
||||
"footer.source_code": "Kaynak kodu görüntüle",
|
||||
"footer.status": "Durum",
|
||||
"generic.saved": "Kaydedildi",
|
||||
"generic.saved": "Kaydet",
|
||||
"getting_started.heading": "Başlarken",
|
||||
"hashtag.column_header.tag_mode.all": "ve {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "ya da {additional}",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "найчастіше використовуваний застосунок",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "найчастіший хештег",
|
||||
"annual_report.summary.most_used_hashtag.none": "Немає",
|
||||
"annual_report.summary.new_posts.new_posts": "нові дописи",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Це виводить вас в топ</topLabel><percentage></percentage><bottomLabel> користувачів Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ми не скажемо Bernie.",
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "app dùng nhiều nhất",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag dùng nhiều nhất",
|
||||
"annual_report.summary.most_used_hashtag.none": "Không có",
|
||||
"annual_report.summary.new_posts.new_posts": "tút mới",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Bạn nằm trong top</topLabel><percentage></percentage><bottomLabel>thành viên Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Chúng tôi sẽ không kể cho Bernie.",
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
"announcement.announcement": "公告",
|
||||
"annual_report.summary.archetype.booster": "潮流捕手",
|
||||
"annual_report.summary.archetype.lurker": "吃瓜群众",
|
||||
"annual_report.summary.archetype.oracle": "预言家",
|
||||
"annual_report.summary.archetype.oracle": "无所不在",
|
||||
"annual_report.summary.archetype.pollster": "投票狂魔",
|
||||
"annual_report.summary.archetype.replier": "评论区原住民",
|
||||
"annual_report.summary.followers.followers": "关注者",
|
||||
|
@ -101,13 +101,14 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name} 的",
|
||||
"annual_report.summary.most_used_app.most_used_app": "最常用的应用",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "最常用的话题",
|
||||
"annual_report.summary.new_posts.new_posts": "新发嘟",
|
||||
"annual_report.summary.most_used_hashtag.none": "无",
|
||||
"annual_report.summary.new_posts.new_posts": "新嘟嘟",
|
||||
"annual_report.summary.percentile.text": "<topLabel>这使你跻身 Mastodon 用户的前</topLabel><percentage></percentage><bottomLabel></bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉Bernie。",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我们打死也不会告诉扣税国王的(他知道的话要来收你发嘟税了)。",
|
||||
"annual_report.summary.thanks": "感谢你这一年与 Mastodon 一路同行!",
|
||||
"attachments_list.unprocessed": "(未处理)",
|
||||
"audio.hide": "隐藏音频",
|
||||
"block_modal.remote_users_caveat": "我们将要求服务器 {domain} 尊重您的决定。然而,我们无法保证对方一定遵从,因为某些服务器可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录的用户可见。",
|
||||
"block_modal.remote_users_caveat": "我们将要求服务器 {domain} 尊重你的决定。然而,我们无法保证对方一定遵从,因为某些服务器可能会以不同的方案处理屏蔽操作。公开嘟文仍然可能对未登录的用户可见。",
|
||||
"block_modal.show_less": "隐藏",
|
||||
"block_modal.show_more": "显示更多",
|
||||
"block_modal.they_cant_mention": "他们不能提及或关注你。",
|
||||
|
@ -131,7 +132,7 @@
|
|||
"bundle_modal_error.message": "载入这个组件时发生了错误。",
|
||||
"bundle_modal_error.retry": "重试",
|
||||
"closed_registrations.other_server_instructions": "基于 Mastodon 去中心化的特性,你可以在其它服务器上创建账号并继续与此服务器互动。",
|
||||
"closed_registrations_modal.description": "您目前无法在 {domain} 上创建账户,但请注意,使用 Mastodon 并非需要专门在 {domain} 上注册账户。",
|
||||
"closed_registrations_modal.description": "你目前无法在 {domain} 上创建账户,但请注意,使用 Mastodon 并非需要专门在 {domain} 上注册账户。",
|
||||
"closed_registrations_modal.find_another_server": "查找其他服务器",
|
||||
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以无论在哪个实例创建账号,都可以关注本服务器上的账号并与之交流。 或者你还可以自己搭建实例!",
|
||||
"closed_registrations_modal.title": "注册 Mastodon 账号",
|
||||
|
@ -160,8 +161,8 @@
|
|||
"column_header.unpin": "取消置顶",
|
||||
"column_subheading.settings": "设置",
|
||||
"community.column_settings.local_only": "仅限本站",
|
||||
"community.column_settings.media_only": "仅限媒体",
|
||||
"community.column_settings.remote_only": "仅限外部",
|
||||
"community.column_settings.media_only": "仅媒体",
|
||||
"community.column_settings.remote_only": "仅外站",
|
||||
"compose.language.change": "更改语言",
|
||||
"compose.language.search": "搜索语言...",
|
||||
"compose.published.body": "嘟文已发布。",
|
||||
|
@ -179,11 +180,11 @@
|
|||
"compose_form.poll.single": "单选",
|
||||
"compose_form.poll.switch_to_multiple": "将投票改为多选",
|
||||
"compose_form.poll.switch_to_single": "将投票改为单选",
|
||||
"compose_form.poll.type": "样式",
|
||||
"compose_form.poll.type": "类型",
|
||||
"compose_form.publish": "发布",
|
||||
"compose_form.publish_form": "发布",
|
||||
"compose_form.publish_form": "新嘟文",
|
||||
"compose_form.reply": "回复",
|
||||
"compose_form.save_changes": "更新",
|
||||
"compose_form.save_changes": "更改",
|
||||
"compose_form.spoiler.marked": "移除内容警告",
|
||||
"compose_form.spoiler.unmarked": "添加内容警告",
|
||||
"compose_form.spoiler_placeholder": "内容警告 (可选)",
|
||||
|
@ -196,7 +197,7 @@
|
|||
"confirmations.delete_list.message": "确定永久删除这个列表吗?",
|
||||
"confirmations.delete_list.title": "确认删除列表?",
|
||||
"confirmations.discard_edit_media.confirm": "丢弃",
|
||||
"confirmations.discard_edit_media.message": "您还有未保存的媒体描述或预览修改,仍要丢弃吗?",
|
||||
"confirmations.discard_edit_media.message": "你还有未保存的媒体描述或预览修改,仍要丢弃吗?",
|
||||
"confirmations.edit.confirm": "编辑",
|
||||
"confirmations.edit.message": "编辑此消息将会覆盖当前正在撰写的信息。仍要继续吗?",
|
||||
"confirmations.edit.title": "确认覆盖嘟文?",
|
||||
|
@ -228,7 +229,7 @@
|
|||
"directory.new_arrivals": "新来者",
|
||||
"directory.recently_active": "最近活跃",
|
||||
"disabled_account_banner.account_settings": "账号设置",
|
||||
"disabled_account_banner.text": "您的账号 {disabledAccount} 目前已被禁用。",
|
||||
"disabled_account_banner.text": "你的账号 {disabledAccount} 目前已被禁用。",
|
||||
"dismissable_banner.community_timeline": "这些是来自 {domain} 用户的最新公开嘟文。",
|
||||
"dismissable_banner.dismiss": "忽略",
|
||||
"dismissable_banner.explore_links": "这些新闻故事正被本站和分布式网络上其他站点的用户谈论。",
|
||||
|
@ -263,7 +264,7 @@
|
|||
"emoji_button.clear": "清除",
|
||||
"emoji_button.custom": "自定义",
|
||||
"emoji_button.flags": "旗帜",
|
||||
"emoji_button.food": "食物和饮料",
|
||||
"emoji_button.food": "食物与饮料",
|
||||
"emoji_button.label": "插入表情符号",
|
||||
"emoji_button.nature": "自然",
|
||||
"emoji_button.not_found": "未找到匹配的表情符号",
|
||||
|
@ -287,13 +288,13 @@
|
|||
"empty_column.favourited_statuses": "你没有喜欢过任何嘟文。喜欢过的嘟文会显示在这里。",
|
||||
"empty_column.favourites": "没有人喜欢过这条嘟文。如果有人喜欢了,就会显示在这里。",
|
||||
"empty_column.follow_requests": "你还没有收到任何关注请求。当你收到一个关注请求时,它会出现在这里。",
|
||||
"empty_column.followed_tags": "您还没有关注任何话题标签。 当您关注后,它们会出现在这里。",
|
||||
"empty_column.followed_tags": "你还没有关注任何话题标签。 当你关注后,它们会出现在这里。",
|
||||
"empty_column.hashtag": "这个话题标签下暂时没有内容。",
|
||||
"empty_column.home": "你的主页时间线是空的!快去关注更多人吧。 {suggestions}",
|
||||
"empty_column.list": "列表中还没有任何内容。当列表成员发布新嘟文时,它们将出现在这里。",
|
||||
"empty_column.lists": "你还没有创建过列表。你创建的列表会在这里显示。",
|
||||
"empty_column.mutes": "你没有隐藏任何用户。",
|
||||
"empty_column.notification_requests": "都看完了!这里没有任何未读通知。当收到新的通知时,它们将根据您的设置显示在这里。",
|
||||
"empty_column.notification_requests": "都看完了!这里没有任何未读通知。当收到新的通知时,它们将根据你的设置显示在这里。",
|
||||
"empty_column.notifications": "你还没有收到过任何通知,快和其他用户互动吧。",
|
||||
"empty_column.public": "这里什么都没有!写一些公开的嘟文,或者关注其他服务器的用户后,这里就会有嘟文出现了",
|
||||
"error.unexpected_crash.explanation": "此页面无法正确显示,这可能是因为我们的代码中有错误,也可能是因为浏览器兼容问题。",
|
||||
|
@ -332,7 +333,7 @@
|
|||
"firehose.remote": "其他服务器",
|
||||
"follow_request.authorize": "同意",
|
||||
"follow_request.reject": "拒绝",
|
||||
"follow_requests.unlocked_explanation": "尽管你没有锁嘟,但是 {domain} 的工作人员认为你也许会想手动审核审核这些账号的关注请求。",
|
||||
"follow_requests.unlocked_explanation": "尽管你没有锁嘟,但是 {domain} 的站务人员认为你也许会想手动审核审核这些账号的关注请求。",
|
||||
"follow_suggestions.curated_suggestion": "站务人员精选",
|
||||
"follow_suggestions.dismiss": "不再显示",
|
||||
"follow_suggestions.featured_longer": "由 {domain} 管理团队精选",
|
||||
|
@ -353,7 +354,7 @@
|
|||
"footer.directory": "用户目录",
|
||||
"footer.get_app": "获取应用",
|
||||
"footer.invite": "邀请",
|
||||
"footer.keyboard_shortcuts": "快捷键列表",
|
||||
"footer.keyboard_shortcuts": "快捷键",
|
||||
"footer.privacy_policy": "隐私政策",
|
||||
"footer.source_code": "查看源代码",
|
||||
"footer.status": "状态",
|
||||
|
@ -400,18 +401,18 @@
|
|||
"ignore_notifications_modal.not_followers_title": "是否忽略未关注你的人的通知?",
|
||||
"ignore_notifications_modal.not_following_title": "是否忽略你未关注的人的通知?",
|
||||
"ignore_notifications_modal.private_mentions_title": "是否忽略不请自来的私下提及?",
|
||||
"interaction_modal.description.favourite": "只需一个 Mastodon 账号,即可喜欢这条嘟文,对嘟文的作者展示您欣赏的态度,并保存嘟文以供日后使用。",
|
||||
"interaction_modal.description.favourite": "只需一个 Mastodon 账号,即可喜欢这条嘟文,对嘟文的作者展示你欣赏的态度,并保存嘟文以供日后使用。",
|
||||
"interaction_modal.description.follow": "拥有一个 Mastodon 账号,你就可以关注 {name} 并在自己的主页上接收对方的新嘟文。",
|
||||
"interaction_modal.description.reblog": "拥有一个 Mastodon 账号,你就可以向自己的关注者们转发此嘟文。",
|
||||
"interaction_modal.description.reply": "拥有一个 Mastodon 账号,你就可以回复此嘟文。",
|
||||
"interaction_modal.description.vote": "拥有一个 Mastodon 账号,你就可以参与此投票。",
|
||||
"interaction_modal.login.action": "转到主页",
|
||||
"interaction_modal.login.prompt": "您所入驻的服务器域名,如:mastodon.social",
|
||||
"interaction_modal.login.prompt": "你所入驻的服务器域名,如:mastodon.social",
|
||||
"interaction_modal.no_account_yet": "不在 Mastodon 上?",
|
||||
"interaction_modal.on_another_server": "在另一服务器",
|
||||
"interaction_modal.on_this_server": "在此服务器",
|
||||
"interaction_modal.sign_in": "您尚未登录此服务器,您的账号托管在哪?",
|
||||
"interaction_modal.sign_in_hint": "提示:这是您注册的网站,如果您不记得了,请在邮箱的收件箱中查找欢迎邮件。您还可以输入完整的用户名!(例如 @Mastodon@mastodon.social)",
|
||||
"interaction_modal.sign_in": "你尚未登录此服务器,你的账号托管在哪?",
|
||||
"interaction_modal.sign_in_hint": "提示:这是你注册的网站,如果你不记得了,请在邮箱的收件箱中查找欢迎邮件。你还可以输入完整的用户名!(例如 @Mastodon@mastodon.social)",
|
||||
"interaction_modal.title.favourite": "喜欢 {name} 的嘟文",
|
||||
"interaction_modal.title.follow": "关注 {name}",
|
||||
"interaction_modal.title.reblog": "转发 {name} 的嘟文",
|
||||
|
@ -423,8 +424,8 @@
|
|||
"keyboard_shortcuts.back": "返回上一页",
|
||||
"keyboard_shortcuts.blocked": "打开被屏蔽用户列表",
|
||||
"keyboard_shortcuts.boost": "转嘟",
|
||||
"keyboard_shortcuts.column": "选择某栏",
|
||||
"keyboard_shortcuts.compose": "选择输入框",
|
||||
"keyboard_shortcuts.column": "选中某栏",
|
||||
"keyboard_shortcuts.compose": "选中输入框",
|
||||
"keyboard_shortcuts.description": "说明",
|
||||
"keyboard_shortcuts.direct": "打开私下提及栏",
|
||||
"keyboard_shortcuts.down": "在列表中让光标下移",
|
||||
|
@ -446,7 +447,7 @@
|
|||
"keyboard_shortcuts.profile": "打开作者的个人资料",
|
||||
"keyboard_shortcuts.reply": "回复嘟文",
|
||||
"keyboard_shortcuts.requests": "打开关注请求列表",
|
||||
"keyboard_shortcuts.search": "选择搜索框",
|
||||
"keyboard_shortcuts.search": "选中搜索框",
|
||||
"keyboard_shortcuts.spoilers": "显示或隐藏被折叠的正文",
|
||||
"keyboard_shortcuts.start": "打开“开始使用”栏",
|
||||
"keyboard_shortcuts.toggle_hidden": "显示或隐藏被折叠的正文",
|
||||
|
@ -472,16 +473,16 @@
|
|||
"lists.exclusive": "在主页中隐藏这些嘟文",
|
||||
"lists.new.create": "新建列表",
|
||||
"lists.new.title_placeholder": "新列表的标题",
|
||||
"lists.replies_policy.followed": "任何被关注的用户",
|
||||
"lists.replies_policy.followed": "所有我关注的用户",
|
||||
"lists.replies_policy.list": "列表成员",
|
||||
"lists.replies_policy.none": "无人",
|
||||
"lists.replies_policy.none": "不显示",
|
||||
"lists.replies_policy.title": "显示回复:",
|
||||
"lists.search": "搜索你关注的人",
|
||||
"lists.subheading": "你的列表",
|
||||
"load_pending": "{count} 项",
|
||||
"loading_indicator.label": "加载中…",
|
||||
"media_gallery.hide": "隐藏",
|
||||
"moved_to_account_banner.text": "您的账号 {disabledAccount} 已禁用,因为您已迁移到 {movedToAccount}。",
|
||||
"moved_to_account_banner.text": "你的账号 {disabledAccount} 已禁用,因为你已迁移到 {movedToAccount}。",
|
||||
"mute_modal.hide_from_notifications": "从通知中隐藏",
|
||||
"mute_modal.hide_options": "隐藏选项",
|
||||
"mute_modal.indefinite": "直到我取消隐藏他们",
|
||||
|
@ -506,19 +507,19 @@
|
|||
"navigation_bar.filters": "忽略的关键词",
|
||||
"navigation_bar.follow_requests": "关注请求",
|
||||
"navigation_bar.followed_tags": "关注的话题标签",
|
||||
"navigation_bar.follows_and_followers": "关注和粉丝",
|
||||
"navigation_bar.follows_and_followers": "关注与关注者",
|
||||
"navigation_bar.lists": "列表",
|
||||
"navigation_bar.logout": "退出登录",
|
||||
"navigation_bar.moderation": "运营",
|
||||
"navigation_bar.moderation": "审核",
|
||||
"navigation_bar.mutes": "已隐藏的用户",
|
||||
"navigation_bar.opened_in_classic_interface": "嘟文、账户和其他特定页面默认在经典网页界面中打开。",
|
||||
"navigation_bar.personal": "个人",
|
||||
"navigation_bar.pins": "置顶嘟文",
|
||||
"navigation_bar.preferences": "首选项",
|
||||
"navigation_bar.preferences": "偏好设置",
|
||||
"navigation_bar.public_timeline": "跨站公共时间轴",
|
||||
"navigation_bar.search": "搜索",
|
||||
"navigation_bar.security": "安全",
|
||||
"not_signed_in_indicator.not_signed_in": "您需要登录才能访问此资源。",
|
||||
"not_signed_in_indicator.not_signed_in": "你需要登录才能访问此资源。",
|
||||
"notification.admin.report": "{name} 举报了 {target}",
|
||||
"notification.admin.report_account": "{name} 举报了来自 {target} 的 {count, plural, other {# 条嘟文}},原因为 {category}",
|
||||
"notification.admin.report_account_other": "{name} 举报了来自 {target} 的 {count, plural, other {# 条嘟文}}",
|
||||
|
@ -527,10 +528,10 @@
|
|||
"notification.admin.sign_up": "{name} 注册了",
|
||||
"notification.admin.sign_up.name_and_others": "{name} 和 {count, plural, other {另外 # 人}}注册了",
|
||||
"notification.annual_report.message": "你的 {year} #Wrapstodon 年度回顾来啦!快来看看这一年你在 Mastodon 上的精彩瞬间!",
|
||||
"notification.annual_report.view": "查看 #Wrapstodon",
|
||||
"notification.annual_report.view": "查看 #Wrapstodon 年度回顾",
|
||||
"notification.favourite": "{name} 喜欢了你的嘟文",
|
||||
"notification.favourite.name_and_others_with_link": "{name} 和 <a>{count, plural, other {另外 # 人}}</a> 喜欢了你的嘟文",
|
||||
"notification.follow": "{name} 开始关注你",
|
||||
"notification.follow": "{name} 关注了你",
|
||||
"notification.follow.name_and_others": "{name} 和 <a>{count, plural, other {另外 # 人}}</a> 关注了你",
|
||||
"notification.follow_request": "{name} 向你发送了关注请求",
|
||||
"notification.follow_request.name_and_others": "{name} 和 {count, plural, other {另外 # 人}} 向你发送了关注请求",
|
||||
|
@ -568,7 +569,7 @@
|
|||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, other {拒绝请求}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "你将要拒绝 {count, plural, other {# 个通知请求}}。你将无法再轻易访问{count, plural, other {它们}}。是否继续?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "是否拒绝通知请求?",
|
||||
"notification_requests.dismiss": "拒绝",
|
||||
"notification_requests.dismiss": "忽略",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, other {拒绝 # 个请求…}}",
|
||||
"notification_requests.edit_selection": "编辑",
|
||||
"notification_requests.exit_selection": "完成",
|
||||
|
@ -596,7 +597,7 @@
|
|||
"notifications.column_settings.push": "推送通知",
|
||||
"notifications.column_settings.reblog": "转嘟:",
|
||||
"notifications.column_settings.show": "在通知栏显示",
|
||||
"notifications.column_settings.sound": "播放音效",
|
||||
"notifications.column_settings.sound": "播放提示音",
|
||||
"notifications.column_settings.status": "新嘟文:",
|
||||
"notifications.column_settings.unread_notifications.category": "未读通知",
|
||||
"notifications.column_settings.unread_notifications.highlight": "高亮显示未读通知",
|
||||
|
@ -629,7 +630,7 @@
|
|||
"notifications.policy.filter_not_following_hint": "直到你手动批准",
|
||||
"notifications.policy.filter_not_following_title": "你没有关注的人",
|
||||
"notifications.policy.filter_private_mentions_hint": "过滤通知,除非通知是在回复提及你自己的内容,或发送者是你关注的人",
|
||||
"notifications.policy.filter_private_mentions_title": "不请自来的提及",
|
||||
"notifications.policy.filter_private_mentions_title": "不请自来的私下提及",
|
||||
"notifications.policy.title": "管理来自 … 的通知",
|
||||
"notifications_permission_banner.enable": "启用桌面通知",
|
||||
"notifications_permission_banner.how_to_control": "启用桌面通知以在 Mastodon 未打开时接收通知。你可以通过交互通过上面的 {icon} 按钮来精细控制可以发送桌面通知的交互类型。",
|
||||
|
@ -639,9 +640,9 @@
|
|||
"onboarding.actions.go_to_explore": "看看有什么新鲜事",
|
||||
"onboarding.actions.go_to_home": "转到主页动态",
|
||||
"onboarding.compose.template": "你好 #Mastodon!",
|
||||
"onboarding.follows.empty": "很抱歉,现在无法显示任何结果。您可以尝试使用搜索或浏览探索页面来查找要关注的人,或稍后再试。",
|
||||
"onboarding.follows.empty": "很抱歉,现在无法显示任何结果。你可以尝试使用搜索或浏览探索页面来查找要关注的人,或稍后再试。",
|
||||
"onboarding.follows.lead": "你管理你自己的家庭饲料。你关注的人越多,它将越活跃和有趣。 这些配置文件可能是一个很好的起点——你可以随时取消关注它们!",
|
||||
"onboarding.follows.title": "定制您的主页动态",
|
||||
"onboarding.follows.title": "定制你的主页动态",
|
||||
"onboarding.profile.discoverable": "让我的资料卡可被他人发现",
|
||||
"onboarding.profile.discoverable_hint": "当你选择在 Mastodon 上启用发现功能时,你的嘟文可能会出现在搜索结果和热门中,你的账户可能会被推荐给与你兴趣相似的人。",
|
||||
"onboarding.profile.display_name": "昵称",
|
||||
|
@ -652,28 +653,28 @@
|
|||
"onboarding.profile.save_and_continue": "保存并继续",
|
||||
"onboarding.profile.title": "设置个人资料",
|
||||
"onboarding.profile.upload_avatar": "上传头像",
|
||||
"onboarding.profile.upload_header": "上传资料卡头图",
|
||||
"onboarding.profile.upload_header": "上传个人资料背景横幅",
|
||||
"onboarding.share.lead": "让人们知道他们如何在Mastodon找到你!",
|
||||
"onboarding.share.message": "我是来自 #Mastodon 的 {username}!请在 {url} 关注我。",
|
||||
"onboarding.share.next_steps": "可能的下一步:",
|
||||
"onboarding.share.title": "分享你的个人资料",
|
||||
"onboarding.start.lead": "你的新 Mastodon 帐户已准备好。下面是如何最大限度地利用它:",
|
||||
"onboarding.start.lead": "你的新 Mastodon 账户已准备好。下面是如何最大限度地利用它:",
|
||||
"onboarding.start.skip": "想要在前面跳过吗?",
|
||||
"onboarding.start.title": "你已经成功了!",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "定制您的主页动态",
|
||||
"onboarding.steps.follow_people.title": "定制你的主页动态",
|
||||
"onboarding.steps.publish_status.body": "向世界问声好吧。",
|
||||
"onboarding.steps.publish_status.title": "发布你的第一篇嘟文",
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
"onboarding.steps.setup_profile.title": "自定义你的个人资料",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
"onboarding.steps.share_profile.body": "让你的朋友知道怎样在 Mastodon 找到你",
|
||||
"onboarding.steps.share_profile.title": "分享你的个人资料",
|
||||
"onboarding.tips.2fa": "<strong>你知道吗?</strong>你可以在账户设置中配置双因素认证来保护账户安全。可以使用你选择的任何 TOTP 应用,无需电话号码!",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>你知道吗?</strong> 既然Mastodon是去中心化的,你所看到的一些账户将被托管在你以外的服务器上。 但你可以无缝地与他们交互!他们的服务器在他们的用户名的后半部分!",
|
||||
"onboarding.tips.migration": "<strong>您知道吗?</strong> 如果你觉得你喜欢 {domain} 不是您未来的一个伟大的服务器选择。 您可以移动到另一个 Mastodon 服务器而不失去您的关注者。 您甚至可以主持您自己的服务器!",
|
||||
"onboarding.tips.verification": "<strong>您知道吗?</strong> 您可以通过在自己的网站上放置一个链接到您的 Mastodon 个人资料并将网站添加到您的个人资料来验证您的帐户。 无需收费或文书工作!",
|
||||
"onboarding.tips.migration": "<strong>你知道吗?</strong>如果你将来觉得 {domain} 不再符合您的需求,你可以在保留现有关注者的情况下迁移至其他 Mastodon 服务器。你甚至可以部署自己的服务器!",
|
||||
"onboarding.tips.verification": "<strong>你知道吗?</strong> 你可以通过在自己的网站上放置一个链接到你的 Mastodon 个人资料并将网站添加到你的个人资料来验证你的账户。 无需收费或文书工作!",
|
||||
"password_confirmation.exceeds_maxlength": "密码确认超过最大密码长度",
|
||||
"password_confirmation.mismatching": "密码确认不匹配",
|
||||
"password_confirmation.mismatching": "确认密码与密码不一致。",
|
||||
"picture_in_picture.restore": "恢复",
|
||||
"poll.closed": "已关闭",
|
||||
"poll.refresh": "刷新",
|
||||
|
@ -686,9 +687,9 @@
|
|||
"poll_button.add_poll": "发起投票",
|
||||
"poll_button.remove_poll": "移除投票",
|
||||
"privacy.change": "设置嘟文的可见范围",
|
||||
"privacy.direct.long": "帖子中提到的每个人",
|
||||
"privacy.direct.long": "嘟文中提到的每个人",
|
||||
"privacy.direct.short": "特定的人",
|
||||
"privacy.private.long": "仅限您的关注者",
|
||||
"privacy.private.long": "仅限你的关注者",
|
||||
"privacy.private.short": "关注者",
|
||||
"privacy.public.long": "所有 Mastodon 内外的人",
|
||||
"privacy.public.short": "公开",
|
||||
|
@ -744,12 +745,12 @@
|
|||
"report.reasons.violation": "违反服务器规则",
|
||||
"report.reasons.violation_description": "你清楚它违反了特定的规则",
|
||||
"report.rules.subtitle": "选择所有适用选项",
|
||||
"report.rules.title": "哪些规则被违反了?",
|
||||
"report.rules.title": "违反了哪些规则?",
|
||||
"report.statuses.subtitle": "选择所有适用选项",
|
||||
"report.statuses.title": "是否有任何嘟文可以支持这一报告?",
|
||||
"report.submit": "提交",
|
||||
"report.target": "举报 {target}",
|
||||
"report.thanks.take_action": "以下是您控制您在 Mastodon 上能看到哪些内容的选项:",
|
||||
"report.thanks.take_action": "以下是你控制你在 Mastodon 上能看到哪些内容的选项:",
|
||||
"report.thanks.take_action_actionable": "在我们审阅这个问题时,你可以对 @{name} 采取行动",
|
||||
"report.thanks.title": "不想看到这个内容?",
|
||||
"report.thanks.title_actionable": "感谢提交举报,我们将会进行处理。",
|
||||
|
@ -779,7 +780,7 @@
|
|||
"search_popout.options": "搜索选项",
|
||||
"search_popout.quick_actions": "快捷操作",
|
||||
"search_popout.recent": "最近搜索",
|
||||
"search_popout.specific_date": "指定日期",
|
||||
"search_popout.specific_date": "具体日期",
|
||||
"search_popout.user": "用户",
|
||||
"search_results.accounts": "用户",
|
||||
"search_results.all": "全部",
|
||||
|
@ -788,7 +789,7 @@
|
|||
"search_results.see_all": "查看全部",
|
||||
"search_results.statuses": "嘟文",
|
||||
"search_results.title": "搜索 {q}",
|
||||
"server_banner.about_active_users": "过去 30 天内使用此服务器的人(每月活跃用户)",
|
||||
"server_banner.about_active_users": "过去 30 天内使用此服务器的人(月活跃用户)",
|
||||
"server_banner.active_users": "活跃用户",
|
||||
"server_banner.administered_by": "本站管理员:",
|
||||
"server_banner.is_one_of_many": "{domain} 是可用于参与联邦宇宙的众多独立 Mastodon 服务器之一。",
|
||||
|
@ -813,7 +814,7 @@
|
|||
"status.direct_indicator": "私下提及",
|
||||
"status.edit": "编辑",
|
||||
"status.edited": "最后编辑于 {date}",
|
||||
"status.edited_x_times": "共编辑 {count, plural, one {{count} 次} other {{count} 次}}",
|
||||
"status.edited_x_times": "共编辑 {count, plural, other {{count} 次}}",
|
||||
"status.embed": "获取嵌入代码",
|
||||
"status.favourite": "喜欢",
|
||||
"status.favourites": "{count, plural, other {次喜欢}}",
|
||||
|
@ -833,7 +834,7 @@
|
|||
"status.pinned": "置顶嘟文",
|
||||
"status.read_more": "查看更多",
|
||||
"status.reblog": "转嘟",
|
||||
"status.reblog_private": "转嘟(可见者不变)",
|
||||
"status.reblog_private": "以相同可见性转嘟",
|
||||
"status.reblogged_by": "{name} 转嘟了",
|
||||
"status.reblogs": "{count, plural, other {次转嘟}}",
|
||||
"status.reblogs.empty": "没有人转嘟过此条嘟文。如果有人转嘟了,就会显示在这里。",
|
||||
|
@ -855,7 +856,7 @@
|
|||
"status.uncached_media_warning": "预览不可用",
|
||||
"status.unmute_conversation": "恢复此对话的通知提醒",
|
||||
"status.unpin": "在个人资料页面取消置顶",
|
||||
"subscribed_languages.lead": "更改此选择后,仅选定语言的嘟文会出现在您的主页和列表时间轴上。选择「无」将接收所有语言的嘟文。",
|
||||
"subscribed_languages.lead": "更改此选择后,仅选定语言的嘟文会出现在你的主页和列表时间轴上。选择「无」将接收所有语言的嘟文。",
|
||||
"subscribed_languages.save": "保存更改",
|
||||
"subscribed_languages.target": "更改 {target} 的订阅语言",
|
||||
"tabs_bar.home": "主页",
|
||||
|
@ -885,7 +886,7 @@
|
|||
"upload_form.edit": "编辑",
|
||||
"upload_form.thumbnail": "更改缩略图",
|
||||
"upload_form.video_description": "为听障人士和视障人士添加文字描述",
|
||||
"upload_modal.analyzing_picture": "分析图片…",
|
||||
"upload_modal.analyzing_picture": "正在分析图片…",
|
||||
"upload_modal.apply": "应用",
|
||||
"upload_modal.applying": "正在应用…",
|
||||
"upload_modal.choose_image": "选择图像",
|
||||
|
@ -907,5 +908,5 @@
|
|||
"video.mute": "静音",
|
||||
"video.pause": "暂停",
|
||||
"video.play": "播放",
|
||||
"video.unmute": "解除禁音"
|
||||
"video.unmute": "取消静音"
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"annual_report.summary.highlighted_post.possessive": "{name} 的",
|
||||
"annual_report.summary.most_used_app.most_used_app": "最常使用的應用程式",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "最常使用的主題標籤",
|
||||
"annual_report.summary.most_used_hashtag.none": "無最常用之主題標籤",
|
||||
"annual_report.summary.new_posts.new_posts": "新嘟文",
|
||||
"annual_report.summary.percentile.text": "<topLabel>這讓您成為前</topLabel><percentage></percentage><bottomLabel>Mastodon 的使用者。</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "我們不會告訴 Bernie。",
|
||||
|
|
|
@ -1922,3 +1922,31 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__card {
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: $ui-base-color;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
font-weight: 400;
|
||||
border: 1px solid lighten($ui-base-color, 4%);
|
||||
color: $primary-text-color;
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
|
||||
.status__prepend {
|
||||
padding: 0 0 15px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status__content {
|
||||
padding-top: 0;
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,16 +339,12 @@ a.table-action-link {
|
|||
}
|
||||
}
|
||||
|
||||
.status__content {
|
||||
padding-top: 0;
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
// Reset the status card to not have borders, background or padding when
|
||||
// inline in the table of statuses
|
||||
.status__card {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nothing-here {
|
||||
|
|
|
@ -17,6 +17,6 @@ class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
|
|||
private
|
||||
|
||||
def commonly_interacted_with_accounts
|
||||
report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total'))
|
||||
report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,6 @@ class AnnualReport::MostRebloggedAccounts < AnnualReport::Source
|
|||
private
|
||||
|
||||
def most_reblogged_accounts
|
||||
report_statuses.where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total'))
|
||||
report_statuses.where.not(reblog_of_id: nil).joins(reblog: :account).group(accounts: [:id]).having('count(*) > 1').order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,6 @@ class AnnualReport::MostUsedApps < AnnualReport::Source
|
|||
private
|
||||
|
||||
def most_used_apps
|
||||
report_statuses.joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total'))
|
||||
report_statuses.joins(:application).group(oauth_applications: [:name]).order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,6 +17,12 @@ class AnnualReport::TopHashtags < AnnualReport::Source
|
|||
private
|
||||
|
||||
def top_hashtags
|
||||
Tag.joins(:statuses).where(statuses: { id: report_statuses.select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total'))
|
||||
Tag.joins(:statuses).where(statuses: { id: report_statuses.select(:id) }).group(coalesced_tag_names).having('count(*) > 1').order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
|
||||
def coalesced_tag_names
|
||||
Arel.sql(<<~SQL.squish)
|
||||
COALESCE(tags.display_name, tags.name)
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
22
app/lib/domain_resource.rb
Normal file
22
app/lib/domain_resource.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DomainResource
|
||||
attr_reader :domain
|
||||
|
||||
RESOLVE_TIMEOUT = 5
|
||||
|
||||
def initialize(domain)
|
||||
@domain = domain
|
||||
end
|
||||
|
||||
def mx
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = RESOLVE_TIMEOUT
|
||||
dns
|
||||
.getresources(domain, Resolv::DNS::Resource::IN::MX)
|
||||
.to_a
|
||||
.map { |mx| mx.exchange.to_s }
|
||||
.compact_blank
|
||||
end
|
||||
end
|
||||
end
|
|
@ -162,7 +162,7 @@ class FeedManager
|
|||
timeline_key = key(:home, into_account.id)
|
||||
timeline_status_ids = redis.zrange(timeline_key, 0, -1)
|
||||
|
||||
from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status|
|
||||
from_account.statuses.select(:id, :reblog_of_id).where(id: timeline_status_ids).reorder(nil).find_each do |status|
|
||||
remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?)
|
||||
end
|
||||
end
|
||||
|
@ -175,7 +175,7 @@ class FeedManager
|
|||
timeline_key = key(:list, list.id)
|
||||
timeline_status_ids = redis.zrange(timeline_key, 0, -1)
|
||||
|
||||
from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status|
|
||||
from_account.statuses.select(:id, :reblog_of_id).where(id: timeline_status_ids).reorder(nil).find_each do |status|
|
||||
remove_from_feed(:list, list.id, status, aggregate_reblogs: list.account.user&.aggregates_reblogs?)
|
||||
end
|
||||
end
|
||||
|
@ -196,7 +196,7 @@ class FeedManager
|
|||
.where.not(account: into_account.following)
|
||||
.tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id))
|
||||
|
||||
scope.select('id, reblog_of_id').reorder(nil).find_each do |status|
|
||||
scope.select(:id, :reblog_of_id).reorder(nil).find_each do |status|
|
||||
remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,7 +71,7 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter
|
|||
end
|
||||
|
||||
def local_votes_scope
|
||||
Poll.joins(:votes).where(votes: { account: Account.local }).select('polls.id, polls.status_id')
|
||||
Poll.joins(:votes).where(votes: { account: Account.local }).select(polls: [:id, :status_id])
|
||||
end
|
||||
|
||||
def local_statuses_scope
|
||||
|
|
|
@ -144,6 +144,8 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
end
|
||||
|
||||
class PrefixClause
|
||||
EPOCH_RE = /\A\d+\z/
|
||||
|
||||
attr_reader :operator, :prefix, :term
|
||||
|
||||
def initialize(prefix, operator, term, options = {})
|
||||
|
@ -168,15 +170,15 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
when 'before'
|
||||
@filter = :created_at
|
||||
@type = :range
|
||||
@term = { lt: TermValidator.validate_date!(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
@term = { lt: date_from_term(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
when 'after'
|
||||
@filter = :created_at
|
||||
@type = :range
|
||||
@term = { gt: TermValidator.validate_date!(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
@term = { gt: date_from_term(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
when 'during'
|
||||
@filter = :created_at
|
||||
@type = :range
|
||||
@term = { gte: TermValidator.validate_date!(term), lte: TermValidator.validate_date!(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
@term = { gte: date_from_term(term), lte: date_from_term(term), time_zone: @options[:current_account]&.user_time_zone.presence || 'UTC' }
|
||||
when 'in'
|
||||
@operator = :flag
|
||||
@term = term
|
||||
|
@ -222,16 +224,10 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
|
||||
term
|
||||
end
|
||||
end
|
||||
|
||||
class TermValidator
|
||||
STRICT_DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/ # yyyy-MM-dd
|
||||
EPOCH_MILLIS_REGEX = /\A\d{1,19}\z/
|
||||
|
||||
def self.validate_date!(value)
|
||||
return value if value.match?(STRICT_DATE_REGEX) || value.match?(EPOCH_MILLIS_REGEX)
|
||||
|
||||
raise Mastodon::FilterValidationError, "Invalid date #{value}"
|
||||
def date_from_term(term)
|
||||
DateTime.iso8601(term) unless term.match?(EPOCH_RE) # This will raise Date::Error if the date is invalid
|
||||
term
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class AccountFilter
|
|||
when 'email'
|
||||
accounts_with_users.merge(User.matches_email(value.to_s.strip))
|
||||
when 'ip'
|
||||
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none
|
||||
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group(users: [:id], accounts: [:id])) : Account.none
|
||||
when 'invited_by'
|
||||
invited_by_scope(value)
|
||||
when 'order'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
primary_abstract_class
|
||||
|
||||
include Remotable
|
||||
|
||||
|
|
|
@ -4,75 +4,68 @@ module Account::Associations
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Local users
|
||||
has_one :user, inverse_of: :account, dependent: :destroy
|
||||
# Core associations
|
||||
with_options dependent: :destroy do
|
||||
# Association where account owns record
|
||||
with_options inverse_of: :account do
|
||||
has_many :account_moderation_notes
|
||||
has_many :account_pins
|
||||
has_many :account_warnings
|
||||
has_many :aliases, class_name: 'AccountAlias'
|
||||
has_many :bookmarks
|
||||
has_many :conversations, class_name: 'AccountConversation'
|
||||
has_many :custom_filters
|
||||
has_many :favourites
|
||||
has_many :featured_tags, -> { includes(:tag) }
|
||||
has_many :list_accounts
|
||||
has_many :media_attachments
|
||||
has_many :mentions
|
||||
has_many :migrations, class_name: 'AccountMigration'
|
||||
has_many :notification_permissions
|
||||
has_many :notification_requests
|
||||
has_many :notifications
|
||||
has_many :owned_lists, class_name: 'List'
|
||||
has_many :polls
|
||||
has_many :report_notes
|
||||
has_many :reports
|
||||
has_many :scheduled_statuses
|
||||
has_many :status_pins
|
||||
has_many :statuses
|
||||
|
||||
# Timelines
|
||||
has_many :statuses, inverse_of: :account, dependent: :destroy
|
||||
has_many :favourites, inverse_of: :account, dependent: :destroy
|
||||
has_many :bookmarks, inverse_of: :account, dependent: :destroy
|
||||
has_many :mentions, inverse_of: :account, dependent: :destroy
|
||||
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
|
||||
has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy
|
||||
has_one :deletion_request, class_name: 'AccountDeletionRequest'
|
||||
has_one :follow_recommendation_suppression
|
||||
has_one :notification_policy
|
||||
has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy'
|
||||
has_one :user
|
||||
end
|
||||
|
||||
# Notifications
|
||||
has_many :notifications, inverse_of: :account, dependent: :destroy
|
||||
has_one :notification_policy, inverse_of: :account, dependent: :destroy
|
||||
has_many :notification_permissions, inverse_of: :account, dependent: :destroy
|
||||
has_many :notification_requests, inverse_of: :account, dependent: :destroy
|
||||
# Association where account is targeted by record
|
||||
with_options foreign_key: :target_account_id, inverse_of: :target_account do
|
||||
has_many :strikes, class_name: 'AccountWarning'
|
||||
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote'
|
||||
has_many :targeted_reports, class_name: 'Report'
|
||||
end
|
||||
end
|
||||
|
||||
# Pinned statuses
|
||||
has_many :status_pins, inverse_of: :account, dependent: :destroy
|
||||
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
|
||||
# Status records pinned by the account
|
||||
has_many :pinned_statuses, -> { reorder(status_pins: { created_at: :desc }) }, through: :status_pins, class_name: 'Status', source: :status
|
||||
|
||||
# Endorsements
|
||||
has_many :account_pins, inverse_of: :account, dependent: :destroy
|
||||
# Account records endorsed (pinned) by the account
|
||||
has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account
|
||||
|
||||
# Media
|
||||
has_many :media_attachments, dependent: :destroy
|
||||
has_many :polls, dependent: :destroy
|
||||
|
||||
# Report relationships
|
||||
has_many :reports, dependent: :destroy, inverse_of: :account
|
||||
has_many :targeted_reports, class_name: 'Report', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||
|
||||
has_many :report_notes, dependent: :destroy
|
||||
has_many :custom_filters, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Moderation notes
|
||||
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
||||
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||
has_many :account_warnings, dependent: :destroy, inverse_of: :account
|
||||
has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||
|
||||
# Lists (that the account is on, not owned by the account)
|
||||
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
||||
# List records the account has been added to (not owned by the account)
|
||||
has_many :lists, through: :list_accounts
|
||||
|
||||
# Lists (owned by the account)
|
||||
has_many :owned_lists, class_name: 'List', dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Account migrations
|
||||
# Account record where account has been migrated
|
||||
belongs_to :moved_to_account, class_name: 'Account', optional: true
|
||||
has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
|
||||
has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Hashtags
|
||||
# Tag records applied to account
|
||||
has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany
|
||||
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Account deletion requests
|
||||
has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Follow recommendations
|
||||
# FollowRecommendation for account (surfaced via view)
|
||||
has_one :follow_recommendation, inverse_of: :account, dependent: nil
|
||||
has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Account statuses cleanup policy
|
||||
has_one :statuses_cleanup_policy, class_name: 'AccountStatusesCleanupPolicy', inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Imports
|
||||
# BulkImport records owned by account
|
||||
has_many :bulk_imports, inverse_of: :account, dependent: :delete_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -80,8 +80,8 @@ module Account::Interactions
|
|||
has_many :passive_relationships, foreign_key: 'target_account_id', inverse_of: :target_account
|
||||
end
|
||||
|
||||
has_many :following, -> { order('follows.id desc') }, through: :active_relationships, source: :target_account
|
||||
has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
|
||||
has_many :following, -> { order(follows: { id: :desc }) }, through: :active_relationships, source: :target_account
|
||||
has_many :followers, -> { order(follows: { id: :desc }) }, through: :passive_relationships, source: :account
|
||||
|
||||
with_options class_name: 'SeveredRelationship', dependent: :destroy do
|
||||
has_many :severed_relationships, foreign_key: 'local_account_id', inverse_of: :local_account
|
||||
|
@ -96,16 +96,16 @@ module Account::Interactions
|
|||
has_many :block_relationships, foreign_key: 'account_id', inverse_of: :account
|
||||
has_many :blocked_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
|
||||
end
|
||||
has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
|
||||
has_many :blocked_by, -> { order('blocks.id desc') }, through: :blocked_by_relationships, source: :account
|
||||
has_many :blocking, -> { order(blocks: { id: :desc }) }, through: :block_relationships, source: :target_account
|
||||
has_many :blocked_by, -> { order(blocks: { id: :desc }) }, through: :blocked_by_relationships, source: :account
|
||||
|
||||
# Mute relationships
|
||||
with_options class_name: 'Mute', dependent: :destroy do
|
||||
has_many :mute_relationships, foreign_key: 'account_id', inverse_of: :account
|
||||
has_many :muted_by_relationships, foreign_key: :target_account_id, inverse_of: :target_account
|
||||
end
|
||||
has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
|
||||
has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
|
||||
has_many :muting, -> { order(mutes: { id: :desc }) }, through: :mute_relationships, source: :target_account
|
||||
has_many :muted_by, -> { order(mutes: { id: :desc }) }, through: :muted_by_relationships, source: :account
|
||||
has_many :conversation_mutes, dependent: :destroy
|
||||
has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
|
||||
has_many :announcement_mutes, dependent: :destroy
|
||||
|
|
123
app/models/concerns/notification/groups.rb
Normal file
123
app/models/concerns/notification/groups.rb
Normal file
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Notification::Groups
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# `set_group_key!` needs to be updated if this list changes
|
||||
GROUPABLE_NOTIFICATION_TYPES = %i(favourite reblog follow).freeze
|
||||
MAXIMUM_GROUP_SPAN_HOURS = 12
|
||||
|
||||
def set_group_key!
|
||||
return if filtered? || GROUPABLE_NOTIFICATION_TYPES.exclude?(type)
|
||||
|
||||
type_prefix = case type
|
||||
when :favourite, :reblog
|
||||
[type, target_status&.id].join('-')
|
||||
when :follow
|
||||
type
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
redis_key = "notif-group/#{account.id}/#{type_prefix}"
|
||||
hour_bucket = activity.created_at.utc.to_i / 1.hour.to_i
|
||||
|
||||
# Reuse previous group if it does not span too large an amount of time
|
||||
previous_bucket = redis.get(redis_key).to_i
|
||||
hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS
|
||||
|
||||
# We do not concern ourselves with race conditions since we use hour buckets
|
||||
redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_SPAN_HOURS.hours.to_i)
|
||||
|
||||
self.group_key = "#{type_prefix}-#{hour_bucket}"
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def paginate_groups(limit, pagination_order, grouped_types: nil)
|
||||
raise ArgumentError unless %i(asc desc).include?(pagination_order)
|
||||
|
||||
query = reorder(id: pagination_order)
|
||||
|
||||
# Ideally `:types` would be a bind rather than part of the SQL itself, but that does not
|
||||
# seem to be possible to do with Rails, considering that the expression would occur in
|
||||
# multiple places, including in a `select`
|
||||
group_key_sql = begin
|
||||
if grouped_types.present?
|
||||
# Normalize `grouped_types` so the number of different SQL query shapes remains small, and
|
||||
# the queries can be analyzed in monitoring/telemetry tools
|
||||
grouped_types = (grouped_types.map(&:to_sym) & GROUPABLE_NOTIFICATION_TYPES).sort
|
||||
|
||||
sanitize_sql_array([<<~SQL.squish, { types: grouped_types }])
|
||||
COALESCE(
|
||||
CASE
|
||||
WHEN notifications.type IN (:types) THEN notifications.group_key
|
||||
ELSE NULL
|
||||
END,
|
||||
'ungrouped-' || notifications.id
|
||||
)
|
||||
SQL
|
||||
else
|
||||
"COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)"
|
||||
end
|
||||
end
|
||||
|
||||
unscoped
|
||||
.with_recursive(
|
||||
grouped_notifications: [
|
||||
# Base case: fetching one notification and annotating it with visited groups
|
||||
query
|
||||
.select('notifications.*', "ARRAY[#{group_key_sql}] AS groups")
|
||||
.limit(1),
|
||||
# Recursive case, always yielding at most one annotated notification
|
||||
unscoped
|
||||
.from(
|
||||
[
|
||||
# Expose the working table as `wt`, but quit early if we've reached the limit
|
||||
unscoped
|
||||
.select('id', 'groups')
|
||||
.from('grouped_notifications')
|
||||
.where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
|
||||
.arel.as('wt'),
|
||||
# Recursive query, using `LATERAL` so we can refer to `wt`
|
||||
query
|
||||
.where(pagination_order == :desc ? 'notifications.id < wt.id' : 'notifications.id > wt.id')
|
||||
.where.not("#{group_key_sql} = ANY(wt.groups)")
|
||||
.limit(1)
|
||||
.arel.lateral('notifications'),
|
||||
]
|
||||
)
|
||||
.select('notifications.*', "array_append(wt.groups, #{group_key_sql}) AS groups"),
|
||||
]
|
||||
)
|
||||
.from('grouped_notifications AS notifications')
|
||||
.order(id: pagination_order)
|
||||
.limit(limit)
|
||||
end
|
||||
|
||||
# This returns notifications from the request page, but with at most one notification per group.
|
||||
# Notifications that have no `group_key` each count as a separate group.
|
||||
def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil, grouped_types: nil)
|
||||
query = reorder(id: :desc)
|
||||
query = query.where(id: ...(max_id.to_i)) if max_id.present?
|
||||
query = query.where(id: (since_id.to_i + 1)...) if since_id.present?
|
||||
query.paginate_groups(limit, :desc, grouped_types: grouped_types)
|
||||
end
|
||||
|
||||
# Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id,
|
||||
# whereas since_id gives the items with largest id, but with since_id as a cutoff.
|
||||
# Results will be in ascending order by id.
|
||||
def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil, grouped_types: nil)
|
||||
query = reorder(id: :asc)
|
||||
query = query.where(id: (min_id.to_i + 1)...) if min_id.present?
|
||||
query = query.where(id: ...(max_id.to_i)) if max_id.present?
|
||||
query.paginate_groups(limit, :asc, grouped_types: grouped_types)
|
||||
end
|
||||
|
||||
def to_a_grouped_paginated_by_id(limit, options = {})
|
||||
if options[:min_id].present?
|
||||
paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id], grouped_types: options[:grouped_types]).reverse
|
||||
else
|
||||
paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id], grouped_types: options[:grouped_types]).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -33,8 +33,15 @@ class FollowRequest < ApplicationRecord
|
|||
|
||||
def authorize!
|
||||
follow = account.follow!(target_account, reblogs: show_reblogs, notify: notify, languages: languages, uri: uri, bypass_limit: true)
|
||||
ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id)
|
||||
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
||||
|
||||
if account.local?
|
||||
ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id)
|
||||
MergeWorker.perform_async(target_account.id, account.id, 'home')
|
||||
MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
|
||||
[target_account.id, list_id, 'list']
|
||||
end
|
||||
end
|
||||
|
||||
destroy!
|
||||
end
|
||||
|
||||
|
|
|
@ -28,9 +28,9 @@ class InstanceFilter
|
|||
def scope_for(key, value)
|
||||
case key.to_s
|
||||
when 'limited'
|
||||
Instance.joins(:domain_block).reorder(Arel.sql('domain_blocks.id desc'))
|
||||
Instance.joins(:domain_block).reorder(domain_blocks: { id: :desc })
|
||||
when 'allowed'
|
||||
Instance.joins(:domain_allow).reorder(Arel.sql('domain_allows.id desc'))
|
||||
Instance.joins(:domain_allow).reorder(domain_allows: { id: :desc })
|
||||
when 'by_domain'
|
||||
Instance.matches_domain(value)
|
||||
when 'availability'
|
||||
|
|
|
@ -34,7 +34,7 @@ class List < ApplicationRecord
|
|||
private
|
||||
|
||||
def validate_account_lists_limit
|
||||
errors.add(:base, I18n.t('lists.errors.limit')) if account.lists.count >= PER_ACCOUNT_LIMIT
|
||||
errors.add(:base, I18n.t('lists.errors.limit')) if account.owned_lists.count >= PER_ACCOUNT_LIMIT
|
||||
end
|
||||
|
||||
def clean_feed_manager
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
class Notification < ApplicationRecord
|
||||
self.inheritance_column = nil
|
||||
|
||||
include Notification::Groups
|
||||
include Paginable
|
||||
include Redisable
|
||||
|
||||
|
@ -31,10 +32,6 @@ class Notification < ApplicationRecord
|
|||
'Poll' => :poll,
|
||||
}.freeze
|
||||
|
||||
# `set_group_key!` needs to be updated if this list changes
|
||||
GROUPABLE_NOTIFICATION_TYPES = %i(favourite reblog follow).freeze
|
||||
MAXIMUM_GROUP_SPAN_HOURS = 12
|
||||
|
||||
# Please update app/javascript/api_types/notification.ts if you change this
|
||||
PROPERTIES = {
|
||||
mention: {
|
||||
|
@ -130,30 +127,6 @@ class Notification < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def set_group_key!
|
||||
return if filtered? || Notification::GROUPABLE_NOTIFICATION_TYPES.exclude?(type)
|
||||
|
||||
type_prefix = case type
|
||||
when :favourite, :reblog
|
||||
[type, target_status&.id].join('-')
|
||||
when :follow
|
||||
type
|
||||
else
|
||||
raise NotImplementedError
|
||||
end
|
||||
redis_key = "notif-group/#{account.id}/#{type_prefix}"
|
||||
hour_bucket = activity.created_at.utc.to_i / 1.hour.to_i
|
||||
|
||||
# Reuse previous group if it does not span too large an amount of time
|
||||
previous_bucket = redis.get(redis_key).to_i
|
||||
hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS
|
||||
|
||||
# We do not concern ourselves with race conditions since we use hour buckets
|
||||
redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_SPAN_HOURS.hours.to_i)
|
||||
|
||||
self.group_key = "#{type_prefix}-#{hour_bucket}"
|
||||
end
|
||||
|
||||
class << self
|
||||
def browserable(types: [], exclude_types: [], from_account_id: nil, include_filtered: false)
|
||||
requested_types = if types.empty?
|
||||
|
@ -171,94 +144,6 @@ class Notification < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def paginate_groups(limit, pagination_order, grouped_types: nil)
|
||||
raise ArgumentError unless %i(asc desc).include?(pagination_order)
|
||||
|
||||
query = reorder(id: pagination_order)
|
||||
|
||||
# Ideally `:types` would be a bind rather than part of the SQL itself, but that does not
|
||||
# seem to be possible to do with Rails, considering that the expression would occur in
|
||||
# multiple places, including in a `select`
|
||||
group_key_sql = begin
|
||||
if grouped_types.present?
|
||||
# Normalize `grouped_types` so the number of different SQL query shapes remains small, and
|
||||
# the queries can be analyzed in monitoring/telemetry tools
|
||||
grouped_types = (grouped_types.map(&:to_sym) & GROUPABLE_NOTIFICATION_TYPES).sort
|
||||
|
||||
sanitize_sql_array([<<~SQL.squish, { types: grouped_types }])
|
||||
COALESCE(
|
||||
CASE
|
||||
WHEN notifications.type IN (:types) THEN notifications.group_key
|
||||
ELSE NULL
|
||||
END,
|
||||
'ungrouped-' || notifications.id
|
||||
)
|
||||
SQL
|
||||
else
|
||||
"COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)"
|
||||
end
|
||||
end
|
||||
|
||||
unscoped
|
||||
.with_recursive(
|
||||
grouped_notifications: [
|
||||
# Base case: fetching one notification and annotating it with visited groups
|
||||
query
|
||||
.select('notifications.*', "ARRAY[#{group_key_sql}] AS groups")
|
||||
.limit(1),
|
||||
# Recursive case, always yielding at most one annotated notification
|
||||
unscoped
|
||||
.from(
|
||||
[
|
||||
# Expose the working table as `wt`, but quit early if we've reached the limit
|
||||
unscoped
|
||||
.select('id', 'groups')
|
||||
.from('grouped_notifications')
|
||||
.where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
|
||||
.arel.as('wt'),
|
||||
# Recursive query, using `LATERAL` so we can refer to `wt`
|
||||
query
|
||||
.where(pagination_order == :desc ? 'notifications.id < wt.id' : 'notifications.id > wt.id')
|
||||
.where.not("#{group_key_sql} = ANY(wt.groups)")
|
||||
.limit(1)
|
||||
.arel.lateral('notifications'),
|
||||
]
|
||||
)
|
||||
.select('notifications.*', "array_append(wt.groups, #{group_key_sql}) AS groups"),
|
||||
]
|
||||
)
|
||||
.from('grouped_notifications AS notifications')
|
||||
.order(id: pagination_order)
|
||||
.limit(limit)
|
||||
end
|
||||
|
||||
# This returns notifications from the request page, but with at most one notification per group.
|
||||
# Notifications that have no `group_key` each count as a separate group.
|
||||
def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil, grouped_types: nil)
|
||||
query = reorder(id: :desc)
|
||||
query = query.where(id: ...(max_id.to_i)) if max_id.present?
|
||||
query = query.where(id: (since_id.to_i + 1)...) if since_id.present?
|
||||
query.paginate_groups(limit, :desc, grouped_types: grouped_types)
|
||||
end
|
||||
|
||||
# Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id,
|
||||
# whereas since_id gives the items with largest id, but with since_id as a cutoff.
|
||||
# Results will be in ascending order by id.
|
||||
def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil, grouped_types: nil)
|
||||
query = reorder(id: :asc)
|
||||
query = query.where(id: (min_id.to_i + 1)...) if min_id.present?
|
||||
query = query.where(id: ...(max_id.to_i)) if max_id.present?
|
||||
query.paginate_groups(limit, :asc, grouped_types: grouped_types)
|
||||
end
|
||||
|
||||
def to_a_grouped_paginated_by_id(limit, options = {})
|
||||
if options[:min_id].present?
|
||||
paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id], grouped_types: options[:grouped_types]).reverse
|
||||
else
|
||||
paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id], grouped_types: options[:grouped_types]).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def preload_cache_collection_target_statuses(notifications, &_block)
|
||||
notifications.group_by(&:type).each do |type, grouped_notifications|
|
||||
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
|
||||
|
|
|
@ -29,8 +29,8 @@ class Poll < ApplicationRecord
|
|||
has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all
|
||||
|
||||
with_options class_name: 'Account', source: :account, through: :votes do
|
||||
has_many :voters, -> { group('accounts.id') }
|
||||
has_many :local_voters, -> { group('accounts.id').merge(Account.local) }
|
||||
has_many :voters, -> { group(accounts: [:id]) }
|
||||
has_many :local_voters, -> { group(accounts: [:id]).merge(Account.local) }
|
||||
end
|
||||
|
||||
has_many :notifications, as: :activity, dependent: :destroy
|
||||
|
|
|
@ -22,6 +22,14 @@ class SoftwareUpdate < ApplicationRecord
|
|||
Gem::Version.new(version)
|
||||
end
|
||||
|
||||
def outdated?
|
||||
runtime_version >= gem_version
|
||||
end
|
||||
|
||||
def pending?
|
||||
gem_version > runtime_version
|
||||
end
|
||||
|
||||
class << self
|
||||
def check_enabled?
|
||||
Rails.configuration.x.mastodon.software_update_url.present?
|
||||
|
@ -30,11 +38,17 @@ class SoftwareUpdate < ApplicationRecord
|
|||
def pending_to_a
|
||||
return [] unless check_enabled?
|
||||
|
||||
all.to_a.filter { |update| update.gem_version > Mastodon::Version.gem_version }
|
||||
all.to_a.filter(&:pending?)
|
||||
end
|
||||
|
||||
def urgent_pending?
|
||||
pending_to_a.any?(&:urgent?)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def runtime_version
|
||||
Mastodon::Version.gem_version
|
||||
end
|
||||
end
|
||||
|
|
|
@ -356,23 +356,23 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def favourites_map(status_ids, account_id)
|
||||
Favourite.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
|
||||
Favourite.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |f, h| h[f.status_id] = true }
|
||||
end
|
||||
|
||||
def bookmarks_map(status_ids, account_id)
|
||||
Bookmark.select('status_id').where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h
|
||||
Bookmark.select(:status_id).where(status_id: status_ids).where(account_id: account_id).map { |f| [f.status_id, true] }.to_h
|
||||
end
|
||||
|
||||
def reblogs_map(status_ids, account_id)
|
||||
unscoped.select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
|
||||
unscoped.select(:reblog_of_id).where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
|
||||
end
|
||||
|
||||
def mutes_map(conversation_ids, account_id)
|
||||
ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
|
||||
ConversationMute.select(:conversation_id).where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
|
||||
end
|
||||
|
||||
def pins_map(status_ids, account_id)
|
||||
StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
|
||||
StatusPin.select(:status_id).where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
|
||||
end
|
||||
|
||||
def from_text(text)
|
||||
|
|
|
@ -61,7 +61,7 @@ class Tag < ApplicationRecord
|
|||
scope :recently_used, lambda { |account|
|
||||
joins(:statuses)
|
||||
.where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) })
|
||||
.group(:id).order(Arel.sql('count(*) desc'))
|
||||
.group(:id).order(Arel.star.count.desc)
|
||||
}
|
||||
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
|
||||
|
||||
|
@ -127,7 +127,7 @@ class Tag < ApplicationRecord
|
|||
query = query.merge(Tag.listable) if options[:exclude_unlistable]
|
||||
query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed]
|
||||
|
||||
query.order(Arel.sql('length(name) ASC, name ASC'))
|
||||
query.order(Arel.sql('LENGTH(name)').asc, name: :asc)
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
end
|
||||
|
|
|
@ -106,7 +106,7 @@ class Trends::Statuses < Trends::Base
|
|||
private
|
||||
|
||||
def eligible?(status)
|
||||
status.public_visibility? && status.account.discoverable? && !status.account.silenced? && !status.account.sensitized? && status.spoiler_text.blank? && !status.sensitive? && !status.reply? && valid_locale?(status.language)
|
||||
status.created_at.past? && status.public_visibility? && status.account.discoverable? && !status.account.silenced? && !status.account.sensitized? && status.spoiler_text.blank? && !status.sensitive? && !status.reply? && valid_locale?(status.language)
|
||||
end
|
||||
|
||||
def calculate_scores(statuses, at_time)
|
||||
|
|
|
@ -125,7 +125,7 @@ class User < ApplicationRecord
|
|||
scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) }
|
||||
scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) }
|
||||
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
|
||||
scope :matches_ip, ->(value) { left_joins(:ips).merge(IpBlock.contained_by(value)).group('users.id') }
|
||||
scope :matches_ip, ->(value) { left_joins(:ips).merge(IpBlock.contained_by(value)).group(users: [:id]) }
|
||||
|
||||
before_validation :sanitize_role
|
||||
before_create :set_approved
|
||||
|
@ -280,6 +280,15 @@ class User < ApplicationRecord
|
|||
save!
|
||||
end
|
||||
|
||||
def applications_last_used
|
||||
Doorkeeper::AccessToken
|
||||
.where(resource_owner_id: id)
|
||||
.where.not(last_used_at: nil)
|
||||
.group(:application_id)
|
||||
.maximum(:last_used_at)
|
||||
.to_h
|
||||
end
|
||||
|
||||
def token_for_app(app)
|
||||
return nil if app.nil? || app.owner != self
|
||||
|
||||
|
@ -457,13 +466,7 @@ class User < ApplicationRecord
|
|||
|
||||
# Doing this conditionally is not very satisfying, but this is consistent
|
||||
# with the MX records validations we do and keeps the specs tractable.
|
||||
unless self.class.skip_mx_check?
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
|
||||
records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }.compact_blank
|
||||
end
|
||||
end
|
||||
records = DomainResource.new(domain).mx unless self.class.skip_mx_check?
|
||||
|
||||
EmailDomainBlock.requires_approval?(records + [domain], attempt_ip: sign_up_ip)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::CredentialApplicationSerializer < REST::ApplicationSerializer
|
||||
attributes :client_id, :client_secret
|
||||
attributes :client_id, :client_secret, :client_secret_expires_at
|
||||
|
||||
def client_id
|
||||
object.uid
|
||||
|
@ -10,4 +10,10 @@ class REST::CredentialApplicationSerializer < REST::ApplicationSerializer
|
|||
def client_secret
|
||||
object.secret
|
||||
end
|
||||
|
||||
# Added for future forwards compatibility when we may decide to expire OAuth
|
||||
# Applications. Set to zero means that the client_secret never expires.
|
||||
def client_secret_expires_at
|
||||
0
|
||||
end
|
||||
end
|
||||
|
|
33
app/services/add_accounts_to_list_service.rb
Normal file
33
app/services/add_accounts_to_list_service.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAccountsToListService < BaseService
|
||||
def call(list, accounts)
|
||||
@list = list
|
||||
@accounts = accounts
|
||||
|
||||
return if @accounts.empty?
|
||||
|
||||
update_list!
|
||||
merge_into_list!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_list!
|
||||
ApplicationRecord.transaction do
|
||||
@accounts.each do |account|
|
||||
@list.accounts << account
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def merge_into_list!
|
||||
MergeWorker.push_bulk(merge_account_ids) do |account_id|
|
||||
[account_id, @list.id, 'list']
|
||||
end
|
||||
end
|
||||
|
||||
def merge_account_ids
|
||||
ListAccount.where(list: @list, account: @accounts).where.not(follow_id: nil).pluck(:account_id)
|
||||
end
|
||||
end
|
|
@ -81,7 +81,10 @@ class FollowService < BaseService
|
|||
follow = @source_account.follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
|
||||
|
||||
LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow')
|
||||
MergeWorker.perform_async(@target_account.id, @source_account.id)
|
||||
MergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
|
||||
MergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:id)) do |list_id|
|
||||
[@target_account.id, list_id, 'list']
|
||||
end
|
||||
|
||||
follow
|
||||
end
|
||||
|
|
29
app/services/remove_accounts_from_list_service.rb
Normal file
29
app/services/remove_accounts_from_list_service.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveAccountsFromListService < BaseService
|
||||
def call(list, accounts)
|
||||
@list = list
|
||||
@accounts = accounts
|
||||
|
||||
return if @accounts.empty?
|
||||
|
||||
unmerge_from_list!
|
||||
update_list!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_list!
|
||||
ListAccount.where(list: @list, account: @accounts).destroy_all
|
||||
end
|
||||
|
||||
def unmerge_from_list!
|
||||
UnmergeWorker.push_bulk(unmerge_account_ids) do |account_id|
|
||||
[account_id, @list.id, 'list']
|
||||
end
|
||||
end
|
||||
|
||||
def unmerge_account_ids
|
||||
ListAccount.where(list: @list, account: @accounts).where.not(follow_id: nil).pluck(:account_id)
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@ class SoftwareUpdateCheckService < BaseService
|
|||
|
||||
def clean_outdated_updates!
|
||||
SoftwareUpdate.find_each do |software_update|
|
||||
software_update.delete if Mastodon::Version.gem_version >= software_update.gem_version
|
||||
software_update.delete if software_update.outdated?
|
||||
rescue ArgumentError
|
||||
software_update.delete
|
||||
end
|
||||
|
|
|
@ -31,7 +31,13 @@ class UnfollowService < BaseService
|
|||
|
||||
create_notification(follow) if !@target_account.local? && @target_account.activitypub?
|
||||
create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
|
||||
UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge]
|
||||
|
||||
unless @options[:skip_unmerge]
|
||||
UnmergeWorker.perform_async(@target_account.id, @source_account.id, 'home')
|
||||
UnmergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:list_id)) do |list_id|
|
||||
[@target_account.id, list_id, 'list']
|
||||
end
|
||||
end
|
||||
|
||||
follow
|
||||
end
|
||||
|
|
|
@ -6,6 +6,12 @@ class UnmuteService < BaseService
|
|||
|
||||
account.unmute!(target_account)
|
||||
|
||||
MergeWorker.perform_async(target_account.id, account.id) if account.following?(target_account)
|
||||
if account.following?(target_account)
|
||||
MergeWorker.perform_async(target_account.id, account.id, 'home')
|
||||
|
||||
MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id|
|
||||
[target_account.id, list_id, 'list']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
- content_for :page_title do
|
||||
= t('about.title')
|
||||
- content_for :page_title, t('about.title')
|
||||
|
||||
- content_for :header_tags do
|
||||
= render partial: 'shared/og'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
- content_for :page_title do
|
||||
#{display_name(@account)} (#{acct(@account)})
|
||||
- content_for :page_title, "#{display_name(@account)} (#{acct(@account)})"
|
||||
|
||||
- content_for :header_tags do
|
||||
- if @account.user_prefers_noindex?
|
||||
|
|
|
@ -30,12 +30,12 @@
|
|||
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
|
||||
= f.input_field :other_domains,
|
||||
as: :boolean,
|
||||
checked_value: record.exchange.to_s,
|
||||
checked_value: record,
|
||||
include_hidden: false,
|
||||
multiple: true
|
||||
.batch-table__row__content.pending-account
|
||||
.pending-account__header
|
||||
%samp= record.exchange.to_s
|
||||
%samp= record
|
||||
%br
|
||||
= t('admin.email_domain_blocks.dns.types.mx')
|
||||
|
||||
|
|
|
@ -2,40 +2,52 @@
|
|||
%label.batch-table__row__select.batch-checkbox
|
||||
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
|
||||
.batch-table__row__content
|
||||
.status__content><
|
||||
- if status.proper.spoiler_text.blank?
|
||||
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
|
||||
- else
|
||||
%details<
|
||||
%summary><
|
||||
%strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
|
||||
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
|
||||
|
||||
- unless status.proper.ordered_media_attachments.empty?
|
||||
= render partial: 'admin/reports/media_attachments', locals: { status: status.proper }
|
||||
|
||||
.detailed-status__meta
|
||||
- if status.application
|
||||
= status.application.name
|
||||
·
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||
- if status.edited?
|
||||
·
|
||||
= link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')),
|
||||
admin_account_status_path(status.account_id, status),
|
||||
class: 'detailed-status__datetime'
|
||||
- if status.discarded?
|
||||
·
|
||||
%span.negative-hint= t('admin.statuses.deleted')
|
||||
·
|
||||
.status__card
|
||||
- if status.reblog?
|
||||
= material_symbol('repeat_active')
|
||||
= t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account))
|
||||
- else
|
||||
.status__prepend
|
||||
= material_symbol('repeat')
|
||||
= t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account, path: admin_account_status_path(status.proper.account.id, status.proper.id)))
|
||||
- elsif status.reply? && status.in_reply_to_id.present?
|
||||
.status__prepend
|
||||
= material_symbol('reply')
|
||||
= t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil))
|
||||
.status__content><
|
||||
- if status.proper.spoiler_text.blank?
|
||||
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
|
||||
- else
|
||||
%details<
|
||||
%summary><
|
||||
%strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
|
||||
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
|
||||
|
||||
- unless status.proper.ordered_media_attachments.empty?
|
||||
= render partial: 'admin/reports/media_attachments', locals: { status: status.proper }
|
||||
|
||||
.detailed-status__meta
|
||||
- if status.application
|
||||
= status.application.name
|
||||
·
|
||||
|
||||
= link_to admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do
|
||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||
- if status.edited?
|
||||
·
|
||||
= link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')),
|
||||
admin_account_status_path(status.account_id, status),
|
||||
class: 'detailed-status__datetime'
|
||||
- if status.discarded?
|
||||
·
|
||||
%span.negative-hint= t('admin.statuses.deleted')
|
||||
·
|
||||
|
||||
= material_symbol visibility_icon(status)
|
||||
= t("statuses.visibilities.#{status.visibility}")
|
||||
- if status.proper.sensitive?
|
||||
·
|
||||
= material_symbol('visibility_off')
|
||||
= t('stream_entries.sensitive_content')
|
||||
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', rel: 'noopener noreferrer' do
|
||||
= t('admin.statuses.view_publicly')
|
||||
|
||||
- if status.proper.sensitive?
|
||||
·
|
||||
= material_symbol('visibility_off')
|
||||
= t('stream_entries.sensitive_content')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue