Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Dalite 2023-11-30 12:12:59 +01:00
commit 91a78e0652
144 changed files with 1634 additions and 815 deletions

View file

@ -24,4 +24,4 @@ RAILS_ENV=development ./bin/rails db:setup
RAILS_ENV=development ./bin/rails assets:precompile RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test # Precompile assets for test
RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile RAILS_ENV=test ./bin/rails assets:precompile

View file

@ -1,4 +1,7 @@
module.exports = { // @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true, root: true,
extends: [ extends: [
@ -193,6 +196,7 @@ module.exports = {
'error', 'error',
{ {
devDependencies: [ devDependencies: [
'.eslintrc.js',
'config/webpack/**', 'config/webpack/**',
'app/javascript/mastodon/performance.js', 'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js', 'app/javascript/mastodon/test_setup.js',
@ -280,7 +284,6 @@ module.exports = {
'formatjs/no-id': 'off', // IDs are used for translation keys 'formatjs/no-id': 'off', // IDs are used for translation keys
'formatjs/no-invalid-icu': 'error', 'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings 'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
'formatjs/no-multiple-whitespaces': 'error', 'formatjs/no-multiple-whitespaces': 'error',
'formatjs/no-offset': 'error', 'formatjs/no-offset': 'error',
'formatjs/no-useless-message': 'error', 'formatjs/no-useless-message': 'error',
@ -299,6 +302,7 @@ module.exports = {
overrides: [ overrides: [
{ {
files: [ files: [
'.eslintrc.js',
'*.config.js', '*.config.js',
'.*rc.js', '.*rc.js',
'ide-helper.js', 'ide-helper.js',
@ -349,7 +353,7 @@ module.exports = {
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/consistent-type-imports': 'error',
"@typescript-eslint/prefer-nullish-coalescing": ['error', {ignorePrimitives: {boolean: true}}], "@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
'jsdoc/require-jsdoc': 'off', 'jsdoc/require-jsdoc': 'off',
@ -372,14 +376,6 @@ module.exports = {
env: { env: {
jest: true, jest: true,
}, },
}, }
{
files: [
'streaming/**/*',
],
rules: {
'import/no-commonjs': 'off',
},
},
], ],
}; });

View file

@ -22,6 +22,7 @@
'react-hotkeys', // Requires code changes 'react-hotkeys', // Requires code changes
// Requires Webpacker upgrade or replacement // Requires Webpacker upgrade or replacement
'@svgr/webpack',
'@types/webpack', '@types/webpack',
'babel-loader', 'babel-loader',
'compression-webpack-plugin', 'compression-webpack-plugin',

View file

@ -21,6 +21,8 @@ on:
type: string type: string
labels: labels:
type: string type: string
file_to_build:
type: string
jobs: jobs:
build-image: build-image:
@ -86,6 +88,7 @@ jobs:
- uses: docker/build-push-action@v5 - uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ${{ inputs.file_to_build }}
build-args: | build-args: |
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }} MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }} MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}

View file

@ -25,6 +25,7 @@ jobs:
needs: compute-suffix needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true use_native_arm64_builder: true
cache: false cache: false
@ -41,3 +42,25 @@ jobs:
type=raw,value=nightly type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }} type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit secrets: inherit
build-image-streaming:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
cache: false
push_to_images: |
tootsuite/mastodon-streaming
ghcr.io/mastodon/mastodon-streaming
version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }}
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: |
latest=auto
tags: |
type=raw,value=edge
type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit

View file

@ -29,6 +29,7 @@ jobs:
needs: compute-suffix needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true use_native_arm64_builder: true
push_to_images: | push_to_images: |
@ -39,3 +40,19 @@ jobs:
tags: | tags: |
type=ref,event=pr type=ref,event=pr
secrets: inherit secrets: inherit
build-image-streaming:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
ghcr.io/mastodon/mastodon-streaming
version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
flavor: |
latest=auto
tags: |
type=ref,event=pr
secrets: inherit

View file

@ -12,6 +12,7 @@ jobs:
build-image: build-image:
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true use_native_arm64_builder: true
push_to_images: | push_to_images: |
@ -27,3 +28,24 @@ jobs:
type=pep440,pattern={{raw}} type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}} type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit secrets: inherit
build-image-streaming:
if: startsWith(github.ref, 'refs/tags/v4.3.')
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
tootsuite/mastodon-streaming
ghcr.io/mastodon/mastodon-streaming
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages
cache: false
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit

View file

@ -7,6 +7,7 @@ on:
- .github/workflows/build-releases.yml - .github/workflows/build-releases.yml
- .github/workflows/test-image-build.yml - .github/workflows/test-image-build.yml
- Dockerfile - Dockerfile
- streaming/Dockerfile
permissions: permissions:
contents: read contents: read
@ -18,4 +19,17 @@ jobs:
uses: ./.github/workflows/build-container-image.yml uses: ./.github/workflows/build-container-image.yml
with: with:
file_to_build: Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true
build-image-streaming:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-streaming
cancel-in-progress: true
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true

View file

@ -307,7 +307,7 @@ Style/FetchEnvVar:
- 'config/initializers/devise.rb' - 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb' - 'config/initializers/vapid.rb'
- 'lib/mastodon/premailer_webpack_strategy.rb' - 'lib/premailer_webpack_strategy.rb'
- 'lib/mastodon/redis_config.rb' - 'lib/mastodon/redis_config.rb'
- 'lib/tasks/repo.rake' - 'lib/tasks/repo.rake'
- 'spec/features/profile_spec.rb' - 'spec/features/profile_spec.rb'
@ -357,8 +357,8 @@ Style/GuardClause:
- 'config/initializers/devise.rb' - 'config/initializers/devise.rb'
- 'db/migrate/20170901141119_truncate_preview_cards.rb' - 'db/migrate/20170901141119_truncate_preview_cards.rb'
- 'db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb' - 'db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb'
- 'lib/devise/two_factor_ldap_authenticatable.rb' - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb'
- 'lib/devise/two_factor_pam_authenticatable.rb' - 'lib/devise/strategies/two_factor_pam_authenticatable.rb'
- 'lib/mastodon/cli/accounts.rb' - 'lib/mastodon/cli/accounts.rb'
- 'lib/mastodon/cli/maintenance.rb' - 'lib/mastodon/cli/maintenance.rb'
- 'lib/mastodon/cli/media.rb' - 'lib/mastodon/cli/media.rb'
@ -493,8 +493,8 @@ Style/SafeNavigation:
# SupportedStyles: only_raise, only_fail, semantic # SupportedStyles: only_raise, only_fail, semantic
Style/SignalException: Style/SignalException:
Exclude: Exclude:
- 'lib/devise/two_factor_ldap_authenticatable.rb' - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb'
- 'lib/devise/two_factor_pam_authenticatable.rb' - 'lib/devise/strategies/two_factor_pam_authenticatable.rb'
# This cop supports unsafe autocorrection (--autocorrect-all). # This cop supports unsafe autocorrection (--autocorrect-all).
Style/SingleArgumentDig: Style/SingleArgumentDig:

View file

@ -1,112 +1,257 @@
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.9-bookworm-slim"
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby # Please see https://docs.docker.com/engine/reference/builder for information about
FROM node:${NODE_VERSION} as build # the extended buildx capabilities used in this file.
# Make sure multiarch TARGETPLATFORM is available for interpolation
# See: https://docs.docker.com/build/building/multi-platform/
ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM}
COPY --link --from=ruby /opt/ruby /opt/ruby # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.2"]
ARG RUBY_VERSION="3.2.2"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
ARG NODE_MAJOR_VERSION="20"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node
# Ruby image to use for base image based on combined variables (ex: 3.2.2-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
ENV DEBIAN_FRONTEND="noninteractive" \ # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
PATH="${PATH}:/opt/ruby/bin" # Example: v4.2.0-nightly.2023.11.09+something
# Overwrite existance of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
ARG MASTODON_VERSION_PRERELEASE="bark"
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="something"]
ARG MASTODON_VERSION_METADATA="dev"
SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Allow Ruby on Rails to serve static files
# See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
ARG RAILS_SERVE_STATIC_FILES="true"
# Allow to use YJIT compiler
# See: https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md
ARG RUBY_YJIT_ENABLE="1"
# Timezone used by the Docker container and runtime, change with [--build-arg TZ=Europe/Berlin]
ARG TZ="Etc/UTC"
# Linux UID (user id) for the mastodon user, change with [--build-arg UID=1234]
ARG UID="991"
# Linux GID (group id) for the mastodon user, change with [--build-arg GID=1234]
ARG GID="991"
# Apply Mastodon build options based on options above
ENV \
# Apply Mastodon version information
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
# Apply Mastodon static files and YJIT options
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
# Apply timezone
TZ=${TZ}
ENV \
# Configure the IP to bind Mastodon to when serving traffic
BIND="0.0.0.0" \
# Use production settings for Yarn, Node and related nodejs based tools
NODE_ENV="production" \
# Use production settings for Ruby on Rails
RAILS_ENV="production" \
# Add Ruby and Mastodon installation to the PATH
DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
# Optimize jemalloc 5.x performance
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0"
# Set default shell used for running commands
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-c"]
ARG TARGETPLATFORM
RUN echo "Target platform is $TARGETPLATFORM"
RUN \
# Remove automatic apt cache Docker cleanup scripts
rm -f /etc/apt/apt.conf.d/docker-clean; \
# Sets timezone
echo "${TZ}" > /etc/localtime; \
# 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
ln -s /opt/mastodon /mastodon;
# Set /opt/mastodon as working directory
WORKDIR /opt/mastodon WORKDIR /opt/mastodon
# hadolint ignore=DL3008 # hadolint ignore=DL3008,DL3005
RUN apt-get update && \ RUN \
apt-get -yq dist-upgrade && \ # Mount Apt cache and lib directories from Docker buildx caches
apt-get install -y --no-install-recommends build-essential \ --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
git \ --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
libicu-dev \ # Apt update & upgrade to check for security updates to Debian image
libidn-dev \ apt-get update; \
libpq-dev \ apt-get dist-upgrade -yq; \
libjemalloc-dev \ # Install jemalloc, curl and other necessary components
zlib1g-dev \ apt-get install -y --no-install-recommends \
libgdbm-dev \ ca-certificates \
libgmp-dev \ curl \
libssl-dev \ ffmpeg \
libyaml-dev \ file \
ca-certificates \ imagemagick \
libreadline8 \ libjemalloc2 \
python3 \ patchelf \
shared-mime-info && \ procps \
bundle config set --local deployment 'true' && \ tini \
bundle config set --local without 'development test' && \ tzdata \
bundle config set silence_root_warning true && \ ; \
corepack enable # Patch Ruby to use jemalloc
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
# Discard patchelf after use
apt-get purge -y \
patchelf \
;
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/ # Create temporary build layer from base image
FROM ruby as build
# Copy Node package configuration files into working directory
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY .yarn /opt/mastodon/.yarn
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib /usr/local/lib
ARG TARGETPLATFORM
# hadolint ignore=DL3008
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
apt-get install -y --no-install-recommends \
g++ \
gcc \
git \
libgdbm-dev \
libgmp-dev \
libicu-dev \
libidn-dev \
libpq-dev \
libssl-dev \
make \
shared-mime-info \
zlib1g-dev \
;
RUN \
# Configure Corepack
rm /usr/local/bin/yarn*; \
corepack enable; \
corepack prepare --activate;
# Create temporary bundler specific build layer from build layer
FROM build as bundler
ARG TARGETPLATFORM
# Copy Gemfile config into working directory
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
bundle config set --global frozen "true"; \
# Configure bundle to not cache downloaded Gems
bundle config set --global cache_all "false"; \
# Configure bundle to only process production Gems
bundle config set --local without "development test"; \
# Configure bundle to not warn about root user
bundle config set silence_root_warning "true"; \
# Download and install required Gems
bundle install -j"$(nproc)";
# Create temporary node specific build layer from build layer
FROM build as yarn
ARG TARGETPLATFORM
# Copy Node package configuration files into working directory
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/ COPY streaming/package.json /opt/mastodon/streaming/
COPY .yarn /opt/mastodon/.yarn COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)" # 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
yarn workspaces focus --production @mastodon/mastodon;
RUN yarn workspaces focus --all --production && \ # Create temporary assets build layer from build layer
yarn cache clean FROM build as precompiler
FROM node:${NODE_VERSION} # Copy Mastodon sources into precompiler layer
COPY . /opt/mastodon/
# Use those args to specify your own version flags & suffixes # Copy bundler and node packages from build layer to container
ARG MASTODON_VERSION_PRERELEASE="bark" COPY --from=yarn /opt/mastodon /opt/mastodon/
ARG MASTODON_VERSION_METADATA="dev" COPY --from=bundler /opt/mastodon /opt/mastodon/
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
ARG UID="991" ARG TARGETPLATFORM
ARG GID="991"
COPY --link --from=ruby /opt/ruby /opt/ruby RUN \
# Use Ruby on Rails to create Mastodon assets
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder bundle exec rails assets:precompile; \
# Cleanup temporary files
rm -fr /opt/mastodon/tmp;
SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Prep final Mastodon Ruby layer
FROM ruby as mastodon
ENV DEBIAN_FRONTEND="noninteractive" \ ARG TARGETPLATFORM
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
# Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use # hadolint ignore=DL3008
# hadolint ignore=DL3008,DL3009 RUN \
RUN apt-get update && \ # Mount Apt cache and lib directories from Docker buildx caches
echo "Etc/UTC" > /etc/localtime && \ --mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
groupadd -g "${GID}" mastodon && \ --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \ # Mount Corepack and Yarn caches from Docker buildx caches
apt-get -y --no-install-recommends install whois \ --mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
wget \ --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
procps \ # Apt update install non-dev versions of necessary components
libssl3 \ apt-get install -y --no-install-recommends \
libpq5 \ libssl3 \
imagemagick \ libpq5 \
ffmpeg \ libicu72 \
libjemalloc2 \ libidn12 \
libicu72 \ libreadline8 \
libidn12 \ libyaml-0-2 \
libyaml-0-2 \ ;
file \
ca-certificates \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon && \
corepack enable
# Note: no, cleaning here since Debian does this automatically # Copy Mastodon sources into final layer
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem COPY . /opt/mastodon/
COPY --chown=mastodon:mastodon . /opt/mastodon # Copy compiled assets to layer
COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon COPY --from=precompiler /opt/mastodon/public/packs /opt/mastodon/public/packs
COPY --from=precompiler /opt/mastodon/public/assets /opt/mastodon/public/assets
# Copy bundler components to layer
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
ENV RAILS_ENV="production" \ RUN \
NODE_ENV="production" \ # Precompile bootsnap code for faster Rails startup
RAILS_SERVE_STATIC_FILES="true" \ bundle exec bootsnap precompile --gemfile app/ lib/;
BIND="0.0.0.0" \
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}"
# Set the run user RUN \
# Pre-create and chown system volume to Mastodon user
mkdir -p /opt/mastodon/public/system; \
chown mastodon:mastodon /opt/mastodon/public/system;
# Set the running user for resulting container
USER mastodon USER mastodon
WORKDIR /opt/mastodon # Expose default Puma ports
EXPOSE 3000
# Precompile assets # Set container tini as default entry point
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile ENTRYPOINT ["/usr/bin/tini", "--"]
# Set the work dir and the container entry point
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 4000

View file

@ -131,7 +131,7 @@ GEM
attr_required (1.0.1) attr_required (1.0.1)
awrence (1.2.1) awrence (1.2.1)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.855.0) aws-partitions (1.857.0)
aws-sdk-core (3.188.0) aws-sdk-core (3.188.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0) aws-partitions (~> 1, >= 1.651.0)
@ -140,7 +140,7 @@ GEM
aws-sdk-kms (1.73.0) aws-sdk-kms (1.73.0)
aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-core (~> 3, >= 3.188.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.139.0) aws-sdk-s3 (1.140.0)
aws-sdk-core (~> 3, >= 3.188.0) aws-sdk-core (~> 3, >= 3.188.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6) aws-sigv4 (~> 1.6)
@ -156,7 +156,7 @@ GEM
nokogiri (~> 1, >= 1.10.8) nokogiri (~> 1, >= 1.10.8)
base64 (0.2.0) base64 (0.2.0)
bcp47_spec (0.2.1) bcp47_spec (0.2.1)
bcrypt (3.1.19) bcrypt (3.1.20)
better_errors (2.10.1) better_errors (2.10.1)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
@ -272,7 +272,7 @@ GEM
et-orbi (1.2.7) et-orbi (1.2.7)
tzinfo tzinfo
excon (0.104.0) excon (0.104.0)
fabrication (2.30.0) fabrication (2.31.0)
faker (3.2.2) faker (3.2.2)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (1.10.3) faraday (1.10.3)
@ -522,7 +522,7 @@ GEM
pastel (0.8.0) pastel (0.8.0)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.5.4) pg (1.5.4)
pghero (3.3.4) pghero (3.4.0)
activerecord (>= 6) activerecord (>= 6)
posix-spawn (0.3.15) posix-spawn (0.3.15)
premailer (1.21.0) premailer (1.21.0)
@ -755,7 +755,7 @@ GEM
attr_required (>= 0.0.5) attr_required (>= 0.0.5)
httpclient (>= 2.4) httpclient (>= 2.4)
sysexits (1.2.0) sysexits (1.2.0)
temple (0.10.2) temple (0.10.3)
terminal-table (3.0.2) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0) terrapin (0.6.0)

6
Vagrantfile vendored
View file

@ -10,7 +10,11 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main' sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS # Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-get update
# Add firewall rule to redirect 80 to PORT and save # Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]} sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class AccountsIndex < Chewy::Index class AccountsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: { settings index: index_preset(refresh_interval: '30s'), analysis: {
filter: { filter: {
english_stop: { english_stop: {
@ -60,7 +62,7 @@ class AccountsIndex < Chewy::Index
field(:following_count, type: 'long') field(:following_count, type: 'long')
field(:followers_count, type: 'long') field(:followers_count, type: 'long')
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties }) field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }) field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' } field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' } field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' } field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module DatetimeClampingConcern
extend ActiveSupport::Concern
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
class_methods do
def clamp_date(datetime)
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
end
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class PublicStatusesIndex < Chewy::Index class PublicStatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: { settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: { filter: {
english_stop: { english_stop: {
@ -62,6 +64,6 @@ class PublicStatusesIndex < Chewy::Index
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) }) field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:language, type: 'keyword') field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties }) field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date') field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end end
end end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class StatusesIndex < Chewy::Index class StatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: { settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: { filter: {
english_stop: { english_stop: {
@ -60,6 +62,6 @@ class StatusesIndex < Chewy::Index
field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by }) field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
field(:language, type: 'keyword') field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties }) field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date') field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end end
end end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class TagsIndex < Chewy::Index class TagsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: { settings index: index_preset(refresh_interval: '30s'), analysis: {
analyzer: { analyzer: {
content: { content: {
@ -42,6 +44,6 @@ class TagsIndex < Chewy::Index
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') } field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }) field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts }) field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
field(:last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }) field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
end end
end end

View file

@ -18,8 +18,6 @@ class AccountsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in? expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
@rss_url = rss_url
end end
format.rss do format.rss do
@ -84,29 +82,21 @@ class AccountsController < ApplicationController
short_account_url(@account, format: 'rss') short_account_url(@account, format: 'rss')
end end
end end
helper_method :rss_url
def media_requested? def media_requested?
request.path.split('.').first.end_with?('/media') && !tag_requested? path_without_format.end_with?('/media') && !tag_requested?
end end
def replies_requested? def replies_requested?
request.path.split('.').first.end_with?('/with_replies') && !tag_requested? path_without_format.end_with?('/with_replies') && !tag_requested?
end end
def tag_requested? def tag_requested?
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) path_without_format.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end end
def cached_filtered_status_page def path_without_format
cache_collection_paginated_by_id( request.path.split('.').first
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end end
end end

View file

@ -16,7 +16,7 @@ module Admin
@moderation_notes = @account.targeted_moderation_notes.latest @moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.strikes.custom.latest @warnings = @account.strikes.custom.latest
render template: 'admin/accounts/show' render 'admin/accounts/show'
end end
end end

View file

@ -6,7 +6,7 @@ module Admin
def index def index
authorize :audit_log, :index? authorize :audit_log, :index?
@auditable_accounts = Account.where(id: Admin::ActionLog.reorder(nil).select('distinct account_id')).select(:id, :username) @auditable_accounts = Account.where(id: Admin::ActionLog.select('distinct account_id')).select(:id, :username)
end end
private private

View file

@ -24,7 +24,7 @@ module Admin
@relay.enable! @relay.enable!
redirect_to admin_relays_path redirect_to admin_relays_path
else else
render action: :new render :new
end end
end end

View file

@ -26,7 +26,7 @@ module Admin
@form = Admin::StatusBatchAction.new @form = Admin::StatusBatchAction.new
@statuses = @report.statuses.with_includes @statuses = @report.statuses.with_includes
render template: 'admin/reports/show' render 'admin/reports/show'
end end
end end

View file

@ -43,7 +43,7 @@ module ChallengableConcern
def render_challenge def render_challenge
@body_classes = 'lighter' @body_classes = 'lighter'
render template: 'auth/challenges/new', layout: 'auth' render 'auth/challenges/new', layout: 'auth'
end end
def challenge_passed? def challenge_passed?

View file

@ -11,7 +11,7 @@ class Disputes::AppealsController < Disputes::BaseController
redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg') redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
@appeal = e.record @appeal = e.record
render template: 'disputes/strikes/show' render 'disputes/strikes/show'
end end
private private

View file

@ -25,7 +25,7 @@ class FiltersController < ApplicationController
if @filter.save if @filter.save
redirect_to filters_path redirect_to filters_path
else else
render action: :new render :new
end end
end end
@ -33,7 +33,7 @@ class FiltersController < ApplicationController
if @filter.update(resource_params) if @filter.update(resource_params)
redirect_to filters_path redirect_to filters_path
else else
render action: :edit render :edit
end end
end end

View file

@ -14,7 +14,7 @@ class StatusesCleanupController < ApplicationController
if @policy.update(resource_params) if @policy.update(resource_params)
redirect_to statuses_cleanup_path, notice: I18n.t('generic.changes_saved_msg') redirect_to statuses_cleanup_path, notice: I18n.t('generic.changes_saved_msg')
else else
render action: :show render :show
end end
rescue ActionController::ParameterMissing rescue ActionController::ParameterMissing
# Do nothing # Do nothing

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Admin::AccountActionsHelper
def account_action_type_label(type)
safe_join(
[
I18n.t("simple_form.labels.admin_account_action.types.#{type}"),
content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint'),
]
)
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Admin::AccountsHelper
def admin_accounts_moderation_options
[
[t('admin.accounts.moderation.active'), 'active'],
[t('admin.accounts.moderation.silenced'), 'silenced'],
[t('admin.accounts.moderation.disabled'), 'disabled'],
[t('admin.accounts.moderation.suspended'), 'suspended'],
[safe_join([t('admin.accounts.moderation.pending'), "(#{pending_user_count_label})"], ' '), 'pending'],
]
end
private
def pending_user_count_label
number_with_delimiter User.pending.count
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Admin::IpBlocksHelper
def ip_blocks_severity_label(severity)
safe_join(
[
I18n.t("simple_form.labels.ip_block.severities.#{severity}"),
content_tag(:span, I18n.t("simple_form.hints.ip_block.severities.#{severity}"), class: 'hint'),
]
)
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Admin
module RolesHelper
def privilege_label(privilege)
safe_join(
[
t("admin.roles.privileges.#{privilege}"),
content_tag(:span, t("admin.roles.privileges.#{privilege}_description"), class: 'hint'),
]
)
end
def disable_permissions?(permissions)
permissions.filter { |privilege| role_flag_value(privilege).zero? }
end
private
def role_flag_value(privilege)
UserRole::FLAGS[privilege] & current_user.role.computed_permissions
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Admin::Settings::DiscoveryHelper
def discovery_warning_hint_text
authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil
end
def discovery_hint_text
t('admin.settings.security.authorized_fetch_hint')
end
def discovery_recommended_value
authorized_fetch_overridden? ? :overridden : nil
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module FiltersHelper
def filter_action_label(action)
safe_join(
[
t("simple_form.labels.filters.actions.#{action}"),
content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint'),
]
)
end
end

View file

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import api from 'mastodon/api'; import api from 'mastodon/api';
import Hashtag from 'mastodon/components/hashtag'; import { Hashtag } from 'mastodon/components/hashtag';
export default class Trends extends PureComponent { export default class Trends extends PureComponent {

View file

@ -1,120 +0,0 @@
// @ts-check
import PropTypes from 'prop-types';
import { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
class SilentErrorBoundary extends Component {
static propTypes = {
children: PropTypes.node,
};
state = {
error: false,
};
componentDidCatch() {
this.setState({ error: true });
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
/**
* Used to render counter of how much people are talking about hashtag
* @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
*/
export const accountsCountRenderer = (displayNumber, pluralReady) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
// @ts-expect-error
export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag
name={hashtag.get('name')}
to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
// @ts-expect-error
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/>
);
ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired,
};
// @ts-expect-error
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Link to={to}>
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
</Link>
{description ? (
<span>{description}</span>
) : (
typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);
Hashtag.propTypes = {
name: PropTypes.string,
to: PropTypes.string,
people: PropTypes.number,
description: PropTypes.node,
uses: PropTypes.number,
history: PropTypes.arrayOf(PropTypes.number),
className: PropTypes.string,
withGraph: PropTypes.bool,
};
Hashtag.defaultProps = {
withGraph: true,
};
export default Hashtag;

View file

@ -0,0 +1,145 @@
import type { JSX } from 'react';
import { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type Immutable from 'immutable';
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
interface SilentErrorBoundaryProps {
children: React.ReactNode;
}
class SilentErrorBoundary extends Component<SilentErrorBoundaryProps> {
state = {
error: false,
};
componentDidCatch() {
this.setState({ error: true });
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
/**
* Used to render counter of how much people are talking about hashtag
* @param displayNumber Counter number to display
* @param pluralReady Whether the count is plural
* @returns Formatted counter of how much people are talking about hashtag
*/
export const accountsCountRenderer = (
displayNumber: JSX.Element,
pluralReady: number,
) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
interface ImmutableHashtagProps {
hashtag: Immutable.Map<string, unknown>;
}
export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
<Hashtag
name={hashtag.get('name') as string}
to={`/tags/${hashtag.get('name') as string}`}
people={
(hashtag.getIn(['history', 0, 'accounts']) as number) * 1 +
(hashtag.getIn(['history', 1, 'accounts']) as number) * 1
}
history={(
hashtag.get('history') as Immutable.Collection.Indexed<
Immutable.Map<string, number>
>
)
.reverse()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map((day) => day.get('uses')!)
.toArray()}
/>
);
export interface HashtagProps {
className?: string;
description?: React.ReactNode;
history?: number[];
name: string;
people: number;
to: string;
uses?: number;
withGraph?: boolean;
}
export const Hashtag: React.FC<HashtagProps> = ({
name,
to,
people,
uses,
history,
className,
description,
withGraph = true,
}) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Link to={to}>
{name ? (
<>
#<span>{name}</span>
</>
) : (
<Skeleton width={50} />
)}
</Link>
{description ? (
<span>{description}</span>
) : typeof people !== 'undefined' ? (
<ShortNumber value={people} renderer={accountsCountRenderer} />
) : (
<Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines
width={50}
height={28}
data={history ? history : Array.from(Array(7)).map(() => 0)}
>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);

View file

@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Hashtag from 'mastodon/components/hashtag'; import { Hashtag } from 'mastodon/components/hashtag';
const messages = defineMessages({ const messages = defineMessages({
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },

View file

@ -13,7 +13,7 @@ import { debounce } from 'lodash';
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import Hashtag from 'mastodon/components/hashtag'; import { Hashtag } from 'mastodon/components/hashtag';
import ScrollableList from 'mastodon/components/scrollable_list'; import ScrollableList from 'mastodon/components/scrollable_list';
import Column from 'mastodon/features/ui/components/column'; import Column from 'mastodon/features/ui/components/column';

View file

@ -250,6 +250,9 @@
"notifications.column_settings.unread_notifications.highlight": "Lig ongelese kennisgewings uit", "notifications.column_settings.unread_notifications.highlight": "Lig ongelese kennisgewings uit",
"notifications.filter.boosts": "Aangestuurde plasings", "notifications.filter.boosts": "Aangestuurde plasings",
"notifications.group": "{count} kennisgewings", "notifications.group": "{count} kennisgewings",
"notifications.permission_denied_alert": "Lessenaarkennisgewings kan nie geaktiveer word nie omdat 'n webblaaier toegewing voorheen geweier was",
"notifications_permission_banner.enable": "Aktiveer lessenaarkennissgewings",
"notifications_permission_banner.how_to_control": "Om kennisgewings te ontvang wanner Mastodon nie oop is nie, aktiveer lessenaarkennisgewings. Jy kan beheer watter spesifieke tipe interaksies lessenaarkennisgewings genereer deur die {icon} knoppie hier bo sodra hulle geaktiveer is.",
"onboarding.actions.go_to_explore": "See what's trending", "onboarding.actions.go_to_explore": "See what's trending",
"onboarding.actions.go_to_home": "Go to your home feed", "onboarding.actions.go_to_home": "Go to your home feed",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Супадзенне паведамленняў {x}", "search.quick_action.status_search": "Супадзенне паведамленняў {x}",
"search.search_or_paste": "Пошук", "search.search_or_paste": "Пошук",
"search_popout.full_text_search_disabled_message": "Недаступна на {domain}.", "search_popout.full_text_search_disabled_message": "Недаступна на {domain}.",
"search_popout.full_text_search_logged_out_message": "Даступна толькі пры ўваходзе ў сістэму.",
"search_popout.language_code": "ISO код мовы", "search_popout.language_code": "ISO код мовы",
"search_popout.options": "Параметры пошуку", "search_popout.options": "Параметры пошуку",
"search_popout.quick_actions": "Хуткія дзеянні", "search_popout.quick_actions": "Хуткія дзеянні",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Съвпадение на публикации {x}", "search.quick_action.status_search": "Съвпадение на публикации {x}",
"search.search_or_paste": "Търсене или поставяне на URL адрес", "search.search_or_paste": "Търсене или поставяне на URL адрес",
"search_popout.full_text_search_disabled_message": "Не е достъпно на {domain}.", "search_popout.full_text_search_disabled_message": "Не е достъпно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Достъпно само при влизане в системата.",
"search_popout.language_code": "Код на езика по ISO", "search_popout.language_code": "Код на езика по ISO",
"search_popout.options": "Възможности при търсене", "search_popout.options": "Възможности при търсене",
"search_popout.quick_actions": "Бързи действия", "search_popout.quick_actions": "Бързи действия",

View file

@ -605,6 +605,7 @@
"search.quick_action.status_search": "Tuts coincidint amb {x}", "search.quick_action.status_search": "Tuts coincidint amb {x}",
"search.search_or_paste": "Cerca o escriu l'URL", "search.search_or_paste": "Cerca o escriu l'URL",
"search_popout.full_text_search_disabled_message": "No disponible a {domain}.", "search_popout.full_text_search_disabled_message": "No disponible a {domain}.",
"search_popout.full_text_search_logged_out_message": "Només disponible en iniciar la sessió.",
"search_popout.language_code": "Codi de llengua ISO", "search_popout.language_code": "Codi de llengua ISO",
"search_popout.options": "Opcions de cerca", "search_popout.options": "Opcions de cerca",
"search_popout.quick_actions": "Accions ràpides", "search_popout.quick_actions": "Accions ràpides",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Indlæg matchende {x}", "search.quick_action.status_search": "Indlæg matchende {x}",
"search.search_or_paste": "Søg efter eller angiv URL", "search.search_or_paste": "Søg efter eller angiv URL",
"search_popout.full_text_search_disabled_message": "Utilgængelig på {domain}.", "search_popout.full_text_search_disabled_message": "Utilgængelig på {domain}.",
"search_popout.full_text_search_logged_out_message": "Kun tilgængelig, når logget ind.",
"search_popout.language_code": "ISO-sprogkode", "search_popout.language_code": "ISO-sprogkode",
"search_popout.options": "Søgevalg", "search_popout.options": "Søgevalg",
"search_popout.quick_actions": "Hurtige handlinger", "search_popout.quick_actions": "Hurtige handlinger",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Beiträge passend zu {x}", "search.quick_action.status_search": "Beiträge passend zu {x}",
"search.search_or_paste": "Suchen oder URL einfügen", "search.search_or_paste": "Suchen oder URL einfügen",
"search_popout.full_text_search_disabled_message": "Auf {domain} nicht verfügbar.", "search_popout.full_text_search_disabled_message": "Auf {domain} nicht verfügbar.",
"search_popout.full_text_search_logged_out_message": "Nur verfügbar, wenn angemeldet.",
"search_popout.language_code": "ISO-Sprachcode", "search_popout.language_code": "ISO-Sprachcode",
"search_popout.options": "Suchoptionen", "search_popout.options": "Suchoptionen",
"search_popout.quick_actions": "Schnellaktionen", "search_popout.quick_actions": "Schnellaktionen",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Mensajes que coinciden con {x}", "search.quick_action.status_search": "Mensajes que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar dirección web", "search.search_or_paste": "Buscar o pegar dirección web",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.", "search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible al iniciar sesión.",
"search_popout.language_code": "Código ISO de idioma", "search_popout.language_code": "Código ISO de idioma",
"search_popout.options": "Opciones de búsqueda", "search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas", "search_popout.quick_actions": "Acciones rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicaciones que coinciden con {x}", "search.quick_action.status_search": "Publicaciones que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar URL", "search.search_or_paste": "Buscar o pegar URL",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.", "search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible si inicias sesión.",
"search_popout.language_code": "Código de idioma ISO", "search_popout.language_code": "Código de idioma ISO",
"search_popout.options": "Opciones de búsqueda", "search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas", "search_popout.quick_actions": "Acciones rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicaciones que coinciden con {x}", "search.quick_action.status_search": "Publicaciones que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar URL", "search.search_or_paste": "Buscar o pegar URL",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.", "search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible si inicias sesión.",
"search_popout.language_code": "Código de idioma ISO", "search_popout.language_code": "Código de idioma ISO",
"search_popout.options": "Opciones de búsqueda", "search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas", "search_popout.quick_actions": "Acciones rápidas",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeeritud", "account.blocked": "Blokeeritud",
"account.browse_more_on_origin_server": "Vaata rohkem algsel profiilil", "account.browse_more_on_origin_server": "Vaata rohkem algsel profiilil",
"account.cancel_follow_request": "Võta jälgimistaotlus tagasi", "account.cancel_follow_request": "Võta jälgimistaotlus tagasi",
"account.copy": "Kopeeri link profiili",
"account.direct": "Maini privaatselt @{name}", "account.direct": "Maini privaatselt @{name}",
"account.disable_notifications": "Peata teavitused @{name} postitustest", "account.disable_notifications": "Peata teavitused @{name} postitustest",
"account.domain_blocked": "Domeen peidetud", "account.domain_blocked": "Domeen peidetud",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Märgi loetuks", "conversation.mark_as_read": "Märgi loetuks",
"conversation.open": "Vaata vestlust", "conversation.open": "Vaata vestlust",
"conversation.with": "Koos {names}", "conversation.with": "Koos {names}",
"copy_icon_button.copied": "Kopeeritud vahemällu",
"copypaste.copied": "Kopeeritud", "copypaste.copied": "Kopeeritud",
"copypaste.copy_to_clipboard": "Kopeeri vahemällu", "copypaste.copy_to_clipboard": "Kopeeri vahemällu",
"directory.federated": "Tuntud födiversumist", "directory.federated": "Tuntud födiversumist",
@ -390,6 +392,7 @@
"lists.search": "Otsi enda jälgitavate inimeste hulgast", "lists.search": "Otsi enda jälgitavate inimeste hulgast",
"lists.subheading": "Sinu nimekirjad", "lists.subheading": "Sinu nimekirjad",
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}", "load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
"loading_indicator.label": "Laadimine…",
"media_gallery.toggle_visible": "{number, plural, one {Varja pilt} other {Varja pildid}}", "media_gallery.toggle_visible": "{number, plural, one {Varja pilt} other {Varja pildid}}",
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.", "moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
"mute_modal.duration": "Kestus", "mute_modal.duration": "Kestus",
@ -478,6 +481,17 @@
"onboarding.follows.empty": "Kahjuks ei saa hetkel tulemusi näidata. Proovi kasutada otsingut või lehitse uurimise lehte, et leida inimesi, keda jälgida, või proovi hiljem uuesti.", "onboarding.follows.empty": "Kahjuks ei saa hetkel tulemusi näidata. Proovi kasutada otsingut või lehitse uurimise lehte, et leida inimesi, keda jälgida, või proovi hiljem uuesti.",
"onboarding.follows.lead": "Haldad ise oma koduvoogu. Mida rohkemaid inimesi jälgid, seda aktiivsem ja huvitavam see on. Need profiilid võiksid olla head alustamiskohad — saad nende jälgimise alati lõpetada!", "onboarding.follows.lead": "Haldad ise oma koduvoogu. Mida rohkemaid inimesi jälgid, seda aktiivsem ja huvitavam see on. Need profiilid võiksid olla head alustamiskohad — saad nende jälgimise alati lõpetada!",
"onboarding.follows.title": "Populaarne Mastodonis", "onboarding.follows.title": "Populaarne Mastodonis",
"onboarding.profile.discoverable": "Muuda mu profiil avastatavaks",
"onboarding.profile.discoverable_hint": "Kui nõustud enda avastamisega Mastodonis, võivad sinu postitused ilmuda otsingutulemustes ja trendides ning sinu profiili võidakse soovitada sinuga sarnaste huvidega inimestele.",
"onboarding.profile.display_name": "Näidatav nimi",
"onboarding.profile.display_name_hint": "Su täisnimi või naljanimi…",
"onboarding.profile.lead": "Saad selle alati hiljem seadetes lõpuni viia, kus on saadaval veel rohkem kohandamisvalikuid.",
"onboarding.profile.note": "Elulugu",
"onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või #sildistada…",
"onboarding.profile.save_and_continue": "Salvesta ja jätka",
"onboarding.profile.title": "Profiili seadistamine",
"onboarding.profile.upload_avatar": "Laadi üles profiilipilt",
"onboarding.profile.upload_header": "Laadi üles profiili päis",
"onboarding.share.lead": "Anna inimestele teada, kuidas sind Mastodonist üles leida!", "onboarding.share.lead": "Anna inimestele teada, kuidas sind Mastodonist üles leida!",
"onboarding.share.message": "Ma olen #Mastodon võrgustikus {username}! tule ja jälgi mind aadressil {url}", "onboarding.share.message": "Ma olen #Mastodon võrgustikus {username}! tule ja jälgi mind aadressil {url}",
"onboarding.share.next_steps": "Võimalikud järgmised sammud:", "onboarding.share.next_steps": "Võimalikud järgmised sammud:",
@ -521,6 +535,7 @@
"privacy.unlisted.short": "Määramata", "privacy.unlisted.short": "Määramata",
"privacy_policy.last_updated": "Viimati uuendatud {date}", "privacy_policy.last_updated": "Viimati uuendatud {date}",
"privacy_policy.title": "Isikuandmete kaitse", "privacy_policy.title": "Isikuandmete kaitse",
"recommended": "Soovitatud",
"refresh": "Värskenda", "refresh": "Värskenda",
"regeneration_indicator.label": "Laeb…", "regeneration_indicator.label": "Laeb…",
"regeneration_indicator.sublabel": "Su koduvoog on ettevalmistamisel!", "regeneration_indicator.sublabel": "Su koduvoog on ettevalmistamisel!",

View file

@ -16,17 +16,17 @@
"account.badges.bot": "Bot-a", "account.badges.bot": "Bot-a",
"account.badges.group": "Taldea", "account.badges.group": "Taldea",
"account.block": "Blokeatu @{name}", "account.block": "Blokeatu @{name}",
"account.block_domain": "Ezkutatu {domain} domeinuko guztia", "account.block_domain": "Blokeatu {domain} domeinua",
"account.block_short": "Blokeatu", "account.block_short": "Blokeatu",
"account.blocked": "Blokeatuta", "account.blocked": "Blokeatuta",
"account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean", "account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean",
"account.cancel_follow_request": "Baztertu jarraitzeko eskaera", "account.cancel_follow_request": "Baztertu jarraitzeko eskaera",
"account.copy": "Kopiatu profilerako esteka", "account.copy": "Kopiatu profilerako esteka",
"account.direct": "Aipatu pribatuki @{name}", "account.direct": "Aipatu pribatuki @{name}",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzailearen bidalketetan", "account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean",
"account.domain_blocked": "Ezkutatutako domeinua", "account.domain_blocked": "Ezkutatutako domeinua",
"account.edit_profile": "Aldatu profila", "account.edit_profile": "Aldatu profila",
"account.enable_notifications": "Jakinarazi @{name} erabiltzaileak bidalketak egitean", "account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean",
"account.endorse": "Nabarmendu profilean", "account.endorse": "Nabarmendu profilean",
"account.featured_tags.last_status_at": "Azken bidalketa {date} datan", "account.featured_tags.last_status_at": "Azken bidalketa {date} datan",
"account.featured_tags.last_status_never": "Bidalketarik ez", "account.featured_tags.last_status_never": "Bidalketarik ez",
@ -40,7 +40,7 @@
"account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.", "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.",
"account.follows_you": "Jarraitzen dizu", "account.follows_you": "Jarraitzen dizu",
"account.go_to_profile": "Joan profilera", "account.go_to_profile": "Joan profilera",
"account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak", "account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak",
"account.in_memoriam": "Oroimenezkoa.", "account.in_memoriam": "Oroimenezkoa.",
"account.joined_short": "Elkartuta", "account.joined_short": "Elkartuta",
"account.languages": "Aldatu harpidetutako hizkuntzak", "account.languages": "Aldatu harpidetutako hizkuntzak",
@ -60,8 +60,8 @@
"account.report": "Salatu @{name}", "account.report": "Salatu @{name}",
"account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko", "account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko",
"account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du",
"account.share": "@{name}(e)ren profila elkarbanatu", "account.share": "Partekatu @{name} erabiltzailearen profila",
"account.show_reblogs": "Erakutsi @{name}(r)en bultzadak", "account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak",
"account.statuses_counter": "{count, plural, one {Bidalketa {counter}} other {{counter} bidalketa}}", "account.statuses_counter": "{count, plural, one {Bidalketa {counter}} other {{counter} bidalketa}}",
"account.unblock": "Desblokeatu @{name}", "account.unblock": "Desblokeatu @{name}",
"account.unblock_domain": "Berriz erakutsi {domain}", "account.unblock_domain": "Berriz erakutsi {domain}",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "{x}-(r)ekin bat datozen argitalpenak", "search.quick_action.status_search": "{x}-(r)ekin bat datozen argitalpenak",
"search.search_or_paste": "Bilatu edo itsatsi URLa", "search.search_or_paste": "Bilatu edo itsatsi URLa",
"search_popout.full_text_search_disabled_message": "{domain}-en ez dago eskuragarri.", "search_popout.full_text_search_disabled_message": "{domain}-en ez dago eskuragarri.",
"search_popout.full_text_search_logged_out_message": "Soilik erabilgarri saioa hastean.",
"search_popout.language_code": "ISO hizkuntza-kodea", "search_popout.language_code": "ISO hizkuntza-kodea",
"search_popout.options": "Bilaketaren aukerak", "search_popout.options": "Bilaketaren aukerak",
"search_popout.quick_actions": "Ekintza azkarrak", "search_popout.quick_actions": "Ekintza azkarrak",

View file

@ -482,7 +482,7 @@
"onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta — voit aina lopettaa niiden seuraamisen myöhemmin!", "onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta — voit aina lopettaa niiden seuraamisen myöhemmin!",
"onboarding.follows.title": "Mukauta kotisyötettäsi", "onboarding.follows.title": "Mukauta kotisyötettäsi",
"onboarding.profile.discoverable": "Aseta profiilini löydettäväksi", "onboarding.profile.discoverable": "Aseta profiilini löydettäväksi",
"onboarding.profile.discoverable_hint": "Kun olet määrittänyt itsesi löydettäväksi Mastodonista, voivat julkaisusi näkyä hakutuloksissa ja suosituissa kohteissa, ja profiiliasi voidaan ehdottaa käyttäjille, jotka ovat kiinnostuneet samoista aiheista kuin sinä.", "onboarding.profile.discoverable_hint": "Kun olet määrittänyt itsesi löydettäväksi Mastodonista, julkaisusi voivat näkyä hakutuloksissa ja suosituissa kohteissa ja profiiliasi voidaan ehdottaa käyttäjille, jotka ovat kiinnostuneet samoista aiheista kuin sinä.",
"onboarding.profile.display_name": "Näyttönimi", "onboarding.profile.display_name": "Näyttönimi",
"onboarding.profile.display_name_hint": "Koko nimesi tai lempinimesi…", "onboarding.profile.display_name_hint": "Koko nimesi tai lempinimesi…",
"onboarding.profile.lead": "Voit viimeistellä tämän milloin tahansa asetuksista, jotka tarjoavat vielä enemmän mukautusvalintoja.", "onboarding.profile.lead": "Voit viimeistellä tämän milloin tahansa asetuksista, jotka tarjoavat vielä enemmän mukautusvalintoja.",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "Julkaisut haulla {x}", "search.quick_action.status_search": "Julkaisut haulla {x}",
"search.search_or_paste": "Hae tai liitä URL-osoite", "search.search_or_paste": "Hae tai liitä URL-osoite",
"search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.", "search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.",
"search_popout.full_text_search_logged_out_message": "Saatavilla vain sisäänkirjautuneena.",
"search_popout.language_code": "ISO-kielikoodi", "search_popout.language_code": "ISO-kielikoodi",
"search_popout.options": "Hakuvalinnat", "search_popout.options": "Hakuvalinnat",
"search_popout.quick_actions": "Pikatoiminnot", "search_popout.quick_actions": "Pikatoiminnot",

View file

@ -596,6 +596,7 @@
"search.quick_action.status_search": "Postar, ið samsvara {x}", "search.quick_action.status_search": "Postar, ið samsvara {x}",
"search.search_or_paste": "Leita ella set URL inn", "search.search_or_paste": "Leita ella set URL inn",
"search_popout.full_text_search_disabled_message": "Ikki tøkt á {domain}.", "search_popout.full_text_search_disabled_message": "Ikki tøkt á {domain}.",
"search_popout.full_text_search_logged_out_message": "Einans tøkt um innritað er.",
"search_popout.language_code": "ISO málkoda", "search_popout.language_code": "ISO málkoda",
"search_popout.options": "Leitimøguleikar", "search_popout.options": "Leitimøguleikar",
"search_popout.quick_actions": "Skjótar atgerðir", "search_popout.quick_actions": "Skjótar atgerðir",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqué·e", "account.blocked": "Bloqué·e",
"account.browse_more_on_origin_server": "Parcourir davantage sur le profil original", "account.browse_more_on_origin_server": "Parcourir davantage sur le profil original",
"account.cancel_follow_request": "Retirer cette demande d'abonnement", "account.cancel_follow_request": "Retirer cette demande d'abonnement",
"account.copy": "Copier le lien vers le profil",
"account.direct": "Mention privée @{name}", "account.direct": "Mention privée @{name}",
"account.disable_notifications": "Ne plus me notifier quand @{name} publie", "account.disable_notifications": "Ne plus me notifier quand @{name} publie",
"account.domain_blocked": "Domaine bloqué", "account.domain_blocked": "Domaine bloqué",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marquer comme lu", "conversation.mark_as_read": "Marquer comme lu",
"conversation.open": "Afficher cette conversation", "conversation.open": "Afficher cette conversation",
"conversation.with": "Avec {names}", "conversation.with": "Avec {names}",
"copy_icon_button.copied": "Copié dans le presse-papier",
"copypaste.copied": "Copié", "copypaste.copied": "Copié",
"copypaste.copy_to_clipboard": "Copier dans le presse-papiers", "copypaste.copy_to_clipboard": "Copier dans le presse-papiers",
"directory.federated": "D'un fediverse connu", "directory.federated": "D'un fediverse connu",
@ -479,12 +481,17 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.", "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon", "onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Rendre mon profil découvrable",
"onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
"onboarding.profile.display_name": "Nom affiché", "onboarding.profile.display_name": "Nom affiché",
"onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
"onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
"onboarding.profile.note": "Biographie", "onboarding.profile.note": "Biographie",
"onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
"onboarding.profile.save_and_continue": "Enregistrer et continuer", "onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.title": "Configuration du profil", "onboarding.profile.title": "Configuration du profil",
"onboarding.profile.upload_avatar": "Importer une photo de profil", "onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.profile.upload_header": "Envoyer une image de profil", "onboarding.profile.upload_header": "Importer un entête de profil",
"onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!", "onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}", "onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles:", "onboarding.share.next_steps": "Étapes suivantes possibles:",
@ -528,6 +535,7 @@
"privacy.unlisted.short": "Non listé", "privacy.unlisted.short": "Non listé",
"privacy_policy.last_updated": "Dernière mise à jour {date}", "privacy_policy.last_updated": "Dernière mise à jour {date}",
"privacy_policy.title": "Politique de confidentialité", "privacy_policy.title": "Politique de confidentialité",
"recommended": "Recommandé",
"refresh": "Actualiser", "refresh": "Actualiser",
"regeneration_indicator.label": "Chargement…", "regeneration_indicator.label": "Chargement…",
"regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!", "regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!",
@ -598,6 +606,7 @@
"search.quick_action.status_search": "Publications correspondant à {x}", "search.quick_action.status_search": "Publications correspondant à {x}",
"search.search_or_paste": "Rechercher ou saisir un URL", "search.search_or_paste": "Rechercher ou saisir un URL",
"search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.", "search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponible uniquement lorsque vous êtes connecté.",
"search_popout.language_code": "code de langue ISO", "search_popout.language_code": "code de langue ISO",
"search_popout.options": "Options de recherche", "search_popout.options": "Options de recherche",
"search_popout.quick_actions": "Actions rapides", "search_popout.quick_actions": "Actions rapides",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqué·e", "account.blocked": "Bloqué·e",
"account.browse_more_on_origin_server": "Parcourir davantage sur le profil original", "account.browse_more_on_origin_server": "Parcourir davantage sur le profil original",
"account.cancel_follow_request": "Annuler le suivi", "account.cancel_follow_request": "Annuler le suivi",
"account.copy": "Copier le lien vers le profil",
"account.direct": "Mention privée @{name}", "account.direct": "Mention privée @{name}",
"account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose", "account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose",
"account.domain_blocked": "Domaine bloqué", "account.domain_blocked": "Domaine bloqué",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marquer comme lu", "conversation.mark_as_read": "Marquer comme lu",
"conversation.open": "Afficher la conversation", "conversation.open": "Afficher la conversation",
"conversation.with": "Avec {names}", "conversation.with": "Avec {names}",
"copy_icon_button.copied": "Copié dans le presse-papier",
"copypaste.copied": "Copié", "copypaste.copied": "Copié",
"copypaste.copy_to_clipboard": "Copier dans le presse-papiers", "copypaste.copy_to_clipboard": "Copier dans le presse-papiers",
"directory.federated": "Du fédiverse connu", "directory.federated": "Du fédiverse connu",
@ -479,12 +481,17 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.", "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personnaliser votre flux principal", "onboarding.follows.title": "Personnaliser votre flux principal",
"onboarding.profile.discoverable": "Rendre mon profil découvrable",
"onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
"onboarding.profile.display_name": "Nom affiché", "onboarding.profile.display_name": "Nom affiché",
"onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
"onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
"onboarding.profile.note": "Biographie", "onboarding.profile.note": "Biographie",
"onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
"onboarding.profile.save_and_continue": "Enregistrer et continuer", "onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.title": "Configuration du profil", "onboarding.profile.title": "Configuration du profil",
"onboarding.profile.upload_avatar": "Importer une photo de profil", "onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.profile.upload_header": "Envoyer une image de profil", "onboarding.profile.upload_header": "Importer un entête de profil",
"onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!", "onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}", "onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles :", "onboarding.share.next_steps": "Étapes suivantes possibles :",
@ -528,6 +535,7 @@
"privacy.unlisted.short": "Non listé", "privacy.unlisted.short": "Non listé",
"privacy_policy.last_updated": "Dernière mise à jour {date}", "privacy_policy.last_updated": "Dernière mise à jour {date}",
"privacy_policy.title": "Politique de confidentialité", "privacy_policy.title": "Politique de confidentialité",
"recommended": "Recommandé",
"refresh": "Actualiser", "refresh": "Actualiser",
"regeneration_indicator.label": "Chargement…", "regeneration_indicator.label": "Chargement…",
"regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation!", "regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation!",
@ -598,6 +606,7 @@
"search.quick_action.status_search": "Publications correspondant à {x}", "search.quick_action.status_search": "Publications correspondant à {x}",
"search.search_or_paste": "Rechercher ou saisir une URL", "search.search_or_paste": "Rechercher ou saisir une URL",
"search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.", "search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponible uniquement lorsque vous êtes connecté.",
"search_popout.language_code": "code de langue ISO", "search_popout.language_code": "code de langue ISO",
"search_popout.options": "Options de recherche", "search_popout.options": "Options de recherche",
"search_popout.quick_actions": "Actions rapides", "search_popout.quick_actions": "Actions rapides",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkearre", "account.blocked": "Blokkearre",
"account.browse_more_on_origin_server": "Mear op it orizjinele profyl besjen", "account.browse_more_on_origin_server": "Mear op it orizjinele profyl besjen",
"account.cancel_follow_request": "Folchfersyk annulearje", "account.cancel_follow_request": "Folchfersyk annulearje",
"account.copy": "Keppeling nei profyl kopiearje",
"account.direct": "Privee fermelde @{name}", "account.direct": "Privee fermelde @{name}",
"account.disable_notifications": "Jou gjin melding mear wannear @{name} in berjocht pleatst", "account.disable_notifications": "Jou gjin melding mear wannear @{name} in berjocht pleatst",
"account.domain_blocked": "Domein blokkearre", "account.domain_blocked": "Domein blokkearre",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "As lêzen markearje", "conversation.mark_as_read": "As lêzen markearje",
"conversation.open": "Petear toane", "conversation.open": "Petear toane",
"conversation.with": "Mei {names}", "conversation.with": "Mei {names}",
"copy_icon_button.copied": "Nei klamboerd kopiearre",
"copypaste.copied": "Kopiearre", "copypaste.copied": "Kopiearre",
"copypaste.copy_to_clipboard": "Nei klamboerd kopiearje", "copypaste.copy_to_clipboard": "Nei klamboerd kopiearje",
"directory.federated": "Fediverse (wat bekend is)", "directory.federated": "Fediverse (wat bekend is)",
@ -390,6 +392,7 @@
"lists.search": "Sykje nei minsken dyt jo folgje", "lists.search": "Sykje nei minsken dyt jo folgje",
"lists.subheading": "Jo listen", "lists.subheading": "Jo listen",
"load_pending": "{count, plural, one {# nij item} other {# nije items}}", "load_pending": "{count, plural, one {# nij item} other {# nije items}}",
"loading_indicator.label": "Lade…",
"media_gallery.toggle_visible": "{number, plural, one {ôfbylding ferstopje} other {ôfbyldingen ferstopje}}", "media_gallery.toggle_visible": "{number, plural, one {ôfbylding ferstopje} other {ôfbyldingen ferstopje}}",
"moved_to_account_banner.text": "Omdat jo nei {movedToAccount} ferhuze binne is jo account {disabledAccount} op dit stuit útskeakele.", "moved_to_account_banner.text": "Omdat jo nei {movedToAccount} ferhuze binne is jo account {disabledAccount} op dit stuit útskeakele.",
"mute_modal.duration": "Doer", "mute_modal.duration": "Doer",
@ -478,6 +481,17 @@
"onboarding.follows.empty": "Spitigernôch kinne op dit stuit gjin resultaten toand wurde. Jo kinne probearje te sykjen of te blêdzjen troch de ferkenningsside om minsken te finen dyt jo folgje kinne, of probearje it letter opnij.", "onboarding.follows.empty": "Spitigernôch kinne op dit stuit gjin resultaten toand wurde. Jo kinne probearje te sykjen of te blêdzjen troch de ferkenningsside om minsken te finen dyt jo folgje kinne, of probearje it letter opnij.",
"onboarding.follows.lead": "Jo beheare jo eigen startside. Hoe mear minsken jo folgje, hoe aktiver en ynteressanter it wêze sil. Dizze profilen kinne in goed startpunt wêze, jo kinne se letter altyd ûntfolgje!", "onboarding.follows.lead": "Jo beheare jo eigen startside. Hoe mear minsken jo folgje, hoe aktiver en ynteressanter it wêze sil. Dizze profilen kinne in goed startpunt wêze, jo kinne se letter altyd ûntfolgje!",
"onboarding.follows.title": "Populêr op Mastodon", "onboarding.follows.title": "Populêr op Mastodon",
"onboarding.profile.discoverable": "Meitsje myn profyl te finen",
"onboarding.profile.discoverable_hint": "Wanneart jo akkoard gean mei it te finen wêzen op Mastodon, ferskine jo berjochten yn sykresultaten en kinne se trending wurde, en jo profyl kin oan oare minsken oanrekommandearre wurde wanneart se fergelykbere ynteressen hawwe.",
"onboarding.profile.display_name": "Werjeftenamme",
"onboarding.profile.display_name_hint": "Jo folsleine namme of in aardige bynamme…",
"onboarding.profile.lead": "Jo kinne dit letter altyd oanfolje yn de ynstellingen, wêrt noch mear oanpassingsopsjes beskikber binne.",
"onboarding.profile.note": "Biografy",
"onboarding.profile.note_hint": "Jo kinne oare minsken @fermelde of #hashtags brûke…",
"onboarding.profile.save_and_continue": "Bewarje en trochgean",
"onboarding.profile.title": "Profyl ynstelle",
"onboarding.profile.upload_avatar": "Profylfoto oplade",
"onboarding.profile.upload_header": "Omslachfoto foar profyl oplade",
"onboarding.share.lead": "Lit minsken witte hoet se jo fine kinne op Mastodon!", "onboarding.share.lead": "Lit minsken witte hoet se jo fine kinne op Mastodon!",
"onboarding.share.message": "Ik bin {username} op #Mastodon! Folgje my op {url}", "onboarding.share.message": "Ik bin {username} op #Mastodon! Folgje my op {url}",
"onboarding.share.next_steps": "Mooglike folgjende stappen:", "onboarding.share.next_steps": "Mooglike folgjende stappen:",
@ -521,6 +535,7 @@
"privacy.unlisted.short": "Minder iepenbier", "privacy.unlisted.short": "Minder iepenbier",
"privacy_policy.last_updated": "Lêst bywurke op {date}", "privacy_policy.last_updated": "Lêst bywurke op {date}",
"privacy_policy.title": "Privacybelied", "privacy_policy.title": "Privacybelied",
"recommended": "Oanrekommandearre",
"refresh": "Ferfarskje", "refresh": "Ferfarskje",
"regeneration_indicator.label": "Lade…", "regeneration_indicator.label": "Lade…",
"regeneration_indicator.sublabel": "Jo starttiidline wurdt oanmakke!", "regeneration_indicator.sublabel": "Jo starttiidline wurdt oanmakke!",
@ -591,6 +606,7 @@
"search.quick_action.status_search": "Berjochten dyt oerienkomme mei {x}", "search.quick_action.status_search": "Berjochten dyt oerienkomme mei {x}",
"search.search_or_paste": "Sykje of fier URL yn", "search.search_or_paste": "Sykje of fier URL yn",
"search_popout.full_text_search_disabled_message": "Net beskikber op {domain}.", "search_popout.full_text_search_disabled_message": "Net beskikber op {domain}.",
"search_popout.full_text_search_logged_out_message": "Allinnich beskikber as jo oanmeld binne.",
"search_popout.language_code": "ISO-taalkoade", "search_popout.language_code": "ISO-taalkoade",
"search_popout.options": "Sykopsjes", "search_popout.options": "Sykopsjes",
"search_popout.quick_actions": "Flugge aksjes", "search_popout.quick_actions": "Flugge aksjes",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicacións coincidentes {x}", "search.quick_action.status_search": "Publicacións coincidentes {x}",
"search.search_or_paste": "Busca ou insire URL", "search.search_or_paste": "Busca ou insire URL",
"search_popout.full_text_search_disabled_message": "Non está dispoñible en {domain}.", "search_popout.full_text_search_disabled_message": "Non está dispoñible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Só dispoñible ao iniciar sesión.",
"search_popout.language_code": "Código ISO do idioma", "search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opcións de busca", "search_popout.options": "Opcións de busca",
"search_popout.quick_actions": "Accións rápidas", "search_popout.quick_actions": "Accións rápidas",
@ -686,7 +687,7 @@
"status.translated_from_with": "Traducido do {lang} usando {provider}", "status.translated_from_with": "Traducido do {lang} usando {provider}",
"status.uncached_media_warning": "A vista previa non está dispoñíble", "status.uncached_media_warning": "A vista previa non está dispoñíble",
"status.unmute_conversation": "Deixar de silenciar conversa", "status.unmute_conversation": "Deixar de silenciar conversa",
"status.unpin": "Desafixar do perfil", "status.unpin": "Non fixar no perfil",
"subscribed_languages.lead": "Ao facer cambios só as publicacións nos idiomas seleccionados aparecerán nas túas cronoloxías. Non elixas ningún para poder ver publicacións en tódolos idiomas.", "subscribed_languages.lead": "Ao facer cambios só as publicacións nos idiomas seleccionados aparecerán nas túas cronoloxías. Non elixas ningún para poder ver publicacións en tódolos idiomas.",
"subscribed_languages.save": "Gardar cambios", "subscribed_languages.save": "Gardar cambios",
"subscribed_languages.target": "Cambiar a subscrición a idiomas para {target}", "subscribed_languages.target": "Cambiar a subscrición a idiomas para {target}",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "הודעות המכילות {x}", "search.quick_action.status_search": "הודעות המכילות {x}",
"search.search_or_paste": "חפש או הזן קישור", "search.search_or_paste": "חפש או הזן קישור",
"search_popout.full_text_search_disabled_message": "בלתי זמין על {domain}.", "search_popout.full_text_search_disabled_message": "בלתי זמין על {domain}.",
"search_popout.full_text_search_logged_out_message": "זמין רק לאחר כניסה לאתר.",
"search_popout.language_code": "קוד ISO לשפה", "search_popout.language_code": "קוד ISO לשפה",
"search_popout.options": "אפשרויות חיפוש", "search_popout.options": "אפשרויות חיפוש",
"search_popout.quick_actions": "פעולות זריזות", "search_popout.quick_actions": "פעולות זריזות",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Bejegyzések a következő keresésre: {x}", "search.quick_action.status_search": "Bejegyzések a következő keresésre: {x}",
"search.search_or_paste": "Keresés vagy URL beillesztése", "search.search_or_paste": "Keresés vagy URL beillesztése",
"search_popout.full_text_search_disabled_message": "Nem érhető el ezen: {domain}.", "search_popout.full_text_search_disabled_message": "Nem érhető el ezen: {domain}.",
"search_popout.full_text_search_logged_out_message": "Csak bejelentkezve érhető el.",
"search_popout.language_code": "ISO nyelvkód", "search_popout.language_code": "ISO nyelvkód",
"search_popout.options": "Keresési beállítások", "search_popout.options": "Keresési beállítások",
"search_popout.quick_actions": "Gyors műveletek", "search_popout.quick_actions": "Gyors műveletek",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Færslur sem samsvara {x}", "search.quick_action.status_search": "Færslur sem samsvara {x}",
"search.search_or_paste": "Leita eða líma slóð", "search.search_or_paste": "Leita eða líma slóð",
"search_popout.full_text_search_disabled_message": "Ekki tiltækt á {domain}.", "search_popout.full_text_search_disabled_message": "Ekki tiltækt á {domain}.",
"search_popout.full_text_search_logged_out_message": "Aðeins tiltækt eftir innskráningu.",
"search_popout.language_code": "ISO-kóði tungumáls", "search_popout.language_code": "ISO-kóði tungumáls",
"search_popout.options": "Leitarvalkostir", "search_popout.options": "Leitarvalkostir",
"search_popout.quick_actions": "Flýtiaðgerðir", "search_popout.quick_actions": "Flýtiaðgerðir",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Post corrispondenti a {x}", "search.quick_action.status_search": "Post corrispondenti a {x}",
"search.search_or_paste": "Cerca o incolla URL", "search.search_or_paste": "Cerca o incolla URL",
"search_popout.full_text_search_disabled_message": "Non disponibile in {domain}.", "search_popout.full_text_search_disabled_message": "Non disponibile in {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponibile solo dopo aver effettuato l'accesso.",
"search_popout.language_code": "Codice ISO lingua", "search_popout.language_code": "Codice ISO lingua",
"search_popout.options": "Opzioni di ricerca", "search_popout.options": "Opzioni di ricerca",
"search_popout.quick_actions": "Azioni rapide", "search_popout.quick_actions": "Azioni rapide",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "{x}에 맞는 게시물", "search.quick_action.status_search": "{x}에 맞는 게시물",
"search.search_or_paste": "검색하거나 URL 붙여넣기", "search.search_or_paste": "검색하거나 URL 붙여넣기",
"search_popout.full_text_search_disabled_message": "{domain}에서는 이용할 수 없습니다.", "search_popout.full_text_search_disabled_message": "{domain}에서는 이용할 수 없습니다.",
"search_popout.full_text_search_logged_out_message": "로그인되어 있을 때만 할 수 있습니다.",
"search_popout.language_code": "ISO 언어코드", "search_popout.language_code": "ISO 언어코드",
"search_popout.options": "검색 옵션", "search_popout.options": "검색 옵션",
"search_popout.quick_actions": "빠른 작업", "search_popout.quick_actions": "빠른 작업",

View file

@ -445,6 +445,7 @@
"search.placeholder": "Paieška", "search.placeholder": "Paieška",
"search.search_or_paste": "Ieškok arba įklijuok URL", "search.search_or_paste": "Ieškok arba įklijuok URL",
"search_popout.full_text_search_disabled_message": "Nepasiekima {domain}.", "search_popout.full_text_search_disabled_message": "Nepasiekima {domain}.",
"search_popout.full_text_search_logged_out_message": "Pasiekiama tik prisijungus.",
"search_popout.language_code": "ISO kalbos kodas", "search_popout.language_code": "ISO kalbos kodas",
"search_popout.specific_date": "konkreti data", "search_popout.specific_date": "konkreti data",
"search_popout.user": "naudotojas", "search_popout.user": "naudotojas",

View file

@ -143,7 +143,7 @@
"compose_form.encryption_warning": "Berichten op Mastodon worden, net zoals op andere social media, niet end-to-end versleuteld. Deel daarom geen gevoelige informatie via Mastodon.", "compose_form.encryption_warning": "Berichten op Mastodon worden, net zoals op andere social media, niet end-to-end versleuteld. Deel daarom geen gevoelige informatie via Mastodon.",
"compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet openbaar is. Alleen openbare berichten kunnen via hashtags gevonden worden.", "compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet openbaar is. Alleen openbare berichten kunnen via hashtags gevonden worden.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.",
"compose_form.lock_disclaimer.lock": "besloten", "compose_form.lock_disclaimer.lock": "vergrendeld",
"compose_form.placeholder": "Wat wil je kwijt?", "compose_form.placeholder": "Wat wil je kwijt?",
"compose_form.poll.add_option": "Keuze toevoegen", "compose_form.poll.add_option": "Keuze toevoegen",
"compose_form.poll.duration": "Duur van de peiling", "compose_form.poll.duration": "Duur van de peiling",
@ -382,7 +382,7 @@
"lists.delete": "Lijst verwijderen", "lists.delete": "Lijst verwijderen",
"lists.edit": "Lijst bewerken", "lists.edit": "Lijst bewerken",
"lists.edit.submit": "Titel veranderen", "lists.edit.submit": "Titel veranderen",
"lists.exclusive": "Verberg deze berichten op je starttijdlijn", "lists.exclusive": "Verberg lijstleden op je starttijdlijn",
"lists.new.create": "Lijst toevoegen", "lists.new.create": "Lijst toevoegen",
"lists.new.title_placeholder": "Naam nieuwe lijst", "lists.new.title_placeholder": "Naam nieuwe lijst",
"lists.replies_policy.followed": "Elke gevolgde gebruiker", "lists.replies_policy.followed": "Elke gevolgde gebruiker",
@ -395,7 +395,7 @@
"loading_indicator.label": "Laden…", "loading_indicator.label": "Laden…",
"media_gallery.toggle_visible": "{number, plural, one {afbeelding verbergen} other {afbeeldingen verbergen}}", "media_gallery.toggle_visible": "{number, plural, one {afbeelding verbergen} other {afbeeldingen verbergen}}",
"moved_to_account_banner.text": "Omdat je naar {movedToAccount} bent verhuisd is jouw account {disabledAccount} momenteel uitgeschakeld.", "moved_to_account_banner.text": "Omdat je naar {movedToAccount} bent verhuisd is jouw account {disabledAccount} momenteel uitgeschakeld.",
"mute_modal.duration": "Duur", "mute_modal.duration": "Tijdsduur",
"mute_modal.hide_notifications": "Verberg meldingen van deze persoon?", "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?",
"mute_modal.indefinite": "Voor onbepaalde tijd", "mute_modal.indefinite": "Voor onbepaalde tijd",
"navigation_bar.about": "Over", "navigation_bar.about": "Over",
@ -481,8 +481,8 @@
"onboarding.follows.empty": "Helaas kunnen op dit moment geen resultaten worden getoond. Je kunt proberen te zoeken of op de verkenningspagina te bladeren om mensen te vinden die je kunt volgen, of probeer het later opnieuw.", "onboarding.follows.empty": "Helaas kunnen op dit moment geen resultaten worden getoond. Je kunt proberen te zoeken of op de verkenningspagina te bladeren om mensen te vinden die je kunt volgen, of probeer het later opnieuw.",
"onboarding.follows.lead": "Jouw starttijdlijn is de belangrijkste manier om Mastodon te ervaren. Hoe meer mensen je volgt, hoe actiever en interessanter het zal zijn. Om te beginnen, zijn hier enkele suggesties:", "onboarding.follows.lead": "Jouw starttijdlijn is de belangrijkste manier om Mastodon te ervaren. Hoe meer mensen je volgt, hoe actiever en interessanter het zal zijn. Om te beginnen, zijn hier enkele suggesties:",
"onboarding.follows.title": "Je starttijdlijn aan jouw wensen aanpassen", "onboarding.follows.title": "Je starttijdlijn aan jouw wensen aanpassen",
"onboarding.profile.discoverable": "Maak mij profiel ontdekbaar", "onboarding.profile.discoverable": "Maak mijn profiel vindbaar",
"onboarding.profile.discoverable_hint": "Wanneer je kiest voor Mastodon kun je berichten weergeven in zoekresultaten en trending, en je profiel kan worden voorgesteld aan mensen met vergelijkbare interesses.", "onboarding.profile.discoverable_hint": "Wanneer je akkoord gaat met het vindbaar zijn op Mastodon, verschijnen je berichten in zoekresultaten en kunnen ze trending worden, en je profiel kan aan andere mensen worden aanbevolen wanneer ze vergelijkbare interesses hebben.",
"onboarding.profile.display_name": "Weergavenaam", "onboarding.profile.display_name": "Weergavenaam",
"onboarding.profile.display_name_hint": "Jouw volledige naam of een leuke bijnaam…", "onboarding.profile.display_name_hint": "Jouw volledige naam of een leuke bijnaam…",
"onboarding.profile.lead": "Je kunt dit later altijd aanvullen in de instellingen, waar nog meer aanpassingsopties beschikbaar zijn.", "onboarding.profile.lead": "Je kunt dit later altijd aanvullen in de instellingen, waar nog meer aanpassingsopties beschikbaar zijn.",
@ -491,7 +491,7 @@
"onboarding.profile.save_and_continue": "Opslaan en doorgaan", "onboarding.profile.save_and_continue": "Opslaan en doorgaan",
"onboarding.profile.title": "Profiel instellen", "onboarding.profile.title": "Profiel instellen",
"onboarding.profile.upload_avatar": "Profielfoto uploaden", "onboarding.profile.upload_avatar": "Profielfoto uploaden",
"onboarding.profile.upload_header": "Kop voor het profiel uploaden", "onboarding.profile.upload_header": "Omslagfoto voor het profiel uploaden",
"onboarding.share.lead": "Laat mensen weten hoe ze je kunnen vinden op Mastodon!", "onboarding.share.lead": "Laat mensen weten hoe ze je kunnen vinden op Mastodon!",
"onboarding.share.message": "Ik ben {username} op #Mastodon! Volg mij op {url}", "onboarding.share.message": "Ik ben {username} op #Mastodon! Volg mij op {url}",
"onboarding.share.next_steps": "Mogelijke volgende stappen:", "onboarding.share.next_steps": "Mogelijke volgende stappen:",
@ -504,7 +504,7 @@
"onboarding.steps.publish_status.body": "Zeg hallo tegen de wereld met tekst, foto's, video's of peilingen {emoji}", "onboarding.steps.publish_status.body": "Zeg hallo tegen de wereld met tekst, foto's, video's of peilingen {emoji}",
"onboarding.steps.publish_status.title": "Maak je eerste bericht", "onboarding.steps.publish_status.title": "Maak je eerste bericht",
"onboarding.steps.setup_profile.body": "Anderen zullen eerder met je in contact treden als je wat over jezelf vertelt.", "onboarding.steps.setup_profile.body": "Anderen zullen eerder met je in contact treden als je wat over jezelf vertelt.",
"onboarding.steps.setup_profile.title": "Pas je profiel aan", "onboarding.steps.setup_profile.title": "Je profiel aanpassen",
"onboarding.steps.share_profile.body": "Laat je vrienden weten waar je te vinden bent op Mastodon", "onboarding.steps.share_profile.body": "Laat je vrienden weten waar je te vinden bent op Mastodon",
"onboarding.steps.share_profile.title": "Deel je Mastodonprofiel", "onboarding.steps.share_profile.title": "Deel je Mastodonprofiel",
"onboarding.tips.2fa": "<strong>Wist je dat?</strong> Je kunt je account beveiligen door tweestapsverificatie in te stellen in je accountinstellingen. Het werkt met elke TOTP-app naar keuze, geen telefoonnummer nodig!", "onboarding.tips.2fa": "<strong>Wist je dat?</strong> Je kunt je account beveiligen door tweestapsverificatie in te stellen in je accountinstellingen. Het werkt met elke TOTP-app naar keuze, geen telefoonnummer nodig!",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "Berichten die overeenkomen met {x}", "search.quick_action.status_search": "Berichten die overeenkomen met {x}",
"search.search_or_paste": "Zoek of voer een URL in", "search.search_or_paste": "Zoek of voer een URL in",
"search_popout.full_text_search_disabled_message": "Niet beschikbaar op {domain}.", "search_popout.full_text_search_disabled_message": "Niet beschikbaar op {domain}.",
"search_popout.full_text_search_logged_out_message": "Alleen beschikbaar als je bent ingelogd.",
"search_popout.language_code": "ISO-taalcode", "search_popout.language_code": "ISO-taalcode",
"search_popout.options": "Zoekopties", "search_popout.options": "Zoekopties",
"search_popout.quick_actions": "Snelle acties", "search_popout.quick_actions": "Snelle acties",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Wpisy pasujące do {x}", "search.quick_action.status_search": "Wpisy pasujące do {x}",
"search.search_or_paste": "Wyszukaj lub wklej adres", "search.search_or_paste": "Wyszukaj lub wklej adres",
"search_popout.full_text_search_disabled_message": "Niedostępne na {domain}.", "search_popout.full_text_search_disabled_message": "Niedostępne na {domain}.",
"search_popout.full_text_search_logged_out_message": "Dostępne tylko po zalogowaniu.",
"search_popout.language_code": "Kod języka wg ISO", "search_popout.language_code": "Kod języka wg ISO",
"search_popout.options": "Opcje wyszukiwania", "search_popout.options": "Opcje wyszukiwania",
"search_popout.quick_actions": "Szybkie akcje", "search_popout.quick_actions": "Szybkie akcje",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicações correspondentes a {x}", "search.quick_action.status_search": "Publicações correspondentes a {x}",
"search.search_or_paste": "Buscar ou colar URL", "search.search_or_paste": "Buscar ou colar URL",
"search_popout.full_text_search_disabled_message": "Não disponível em {domain}.", "search_popout.full_text_search_disabled_message": "Não disponível em {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponível apenas quando conectado.",
"search_popout.language_code": "Código ISO do idioma", "search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opções de pesquisa", "search_popout.options": "Opções de pesquisa",
"search_popout.quick_actions": "Ações rápidas", "search_popout.quick_actions": "Ações rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicações com correspondência a {x}", "search.quick_action.status_search": "Publicações com correspondência a {x}",
"search.search_or_paste": "Pesquisar ou introduzir URL", "search.search_or_paste": "Pesquisar ou introduzir URL",
"search_popout.full_text_search_disabled_message": "Não disponível em {domain}.", "search_popout.full_text_search_disabled_message": "Não disponível em {domain}.",
"search_popout.full_text_search_logged_out_message": "Apenas disponível quando tem sessão iniciada.",
"search_popout.language_code": "Código ISO do idioma", "search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opções de pesquisa", "search_popout.options": "Opções de pesquisa",
"search_popout.quick_actions": "Ações rápidas", "search_popout.quick_actions": "Ações rápidas",

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблокировано", "account.blocked": "Заблокировано",
"account.browse_more_on_origin_server": "Посмотреть в оригинальном профиле", "account.browse_more_on_origin_server": "Посмотреть в оригинальном профиле",
"account.cancel_follow_request": "Отозвать запрос на подписку", "account.cancel_follow_request": "Отозвать запрос на подписку",
"account.copy": "Скопировать ссылку на профиль",
"account.direct": "Лично упоминать @{name}", "account.direct": "Лично упоминать @{name}",
"account.disable_notifications": "Не уведомлять о постах от @{name}", "account.disable_notifications": "Не уведомлять о постах от @{name}",
"account.domain_blocked": "Домен заблокирован", "account.domain_blocked": "Домен заблокирован",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Отметить как прочитанное", "conversation.mark_as_read": "Отметить как прочитанное",
"conversation.open": "Просмотр беседы", "conversation.open": "Просмотр беседы",
"conversation.with": "С {names}", "conversation.with": "С {names}",
"copy_icon_button.copied": "Скопировано в буфер обмена",
"copypaste.copied": "Скопировано", "copypaste.copied": "Скопировано",
"copypaste.copy_to_clipboard": "Копировать в буфер обмена", "copypaste.copy_to_clipboard": "Копировать в буфер обмена",
"directory.federated": "Со всей федерации", "directory.federated": "Со всей федерации",
@ -222,6 +224,7 @@
"emoji_button.search_results": "Результаты поиска", "emoji_button.search_results": "Результаты поиска",
"emoji_button.symbols": "Символы", "emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия и места", "emoji_button.travel": "Путешествия и места",
"empty_column.account_hides_collections": "Данный пользователь решил не предоставлять эту информацию",
"empty_column.account_suspended": "Учетная запись заблокирована", "empty_column.account_suspended": "Учетная запись заблокирована",
"empty_column.account_timeline": "Здесь нет постов!", "empty_column.account_timeline": "Здесь нет постов!",
"empty_column.account_unavailable": "Профиль недоступен", "empty_column.account_unavailable": "Профиль недоступен",
@ -389,6 +392,7 @@
"lists.search": "Искать среди подписок", "lists.search": "Искать среди подписок",
"lists.subheading": "Ваши списки", "lists.subheading": "Ваши списки",
"load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}", "load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}",
"loading_indicator.label": "Загрузка…",
"media_gallery.toggle_visible": "Показать/скрыть {number, plural, =1 {изображение} other {изображения}}", "media_gallery.toggle_visible": "Показать/скрыть {number, plural, =1 {изображение} other {изображения}}",
"moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.", "moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.",
"mute_modal.duration": "Продолжительность", "mute_modal.duration": "Продолжительность",
@ -477,6 +481,17 @@
"onboarding.follows.empty": "К сожалению, сейчас нет результатов. Вы можете попробовать использовать поиск или просмотреть страницу \"Исследования\", чтобы найти людей, за которыми можно следить, или повторить попытку позже.", "onboarding.follows.empty": "К сожалению, сейчас нет результатов. Вы можете попробовать использовать поиск или просмотреть страницу \"Исследования\", чтобы найти людей, за которыми можно следить, или повторить попытку позже.",
"onboarding.follows.lead": "Вы сами формируете свою домашнюю ленту. Чем больше людей, за которыми вы следите, тем активнее и интереснее она будет. Эти профили могут быть хорошей отправной точкой - вы всегда можете от них отказаться!", "onboarding.follows.lead": "Вы сами формируете свою домашнюю ленту. Чем больше людей, за которыми вы следите, тем активнее и интереснее она будет. Эти профили могут быть хорошей отправной точкой - вы всегда можете от них отказаться!",
"onboarding.follows.title": "Популярно на Mastodon", "onboarding.follows.title": "Популярно на Mastodon",
"onboarding.profile.discoverable": "Сделать мой профиль открытым",
"onboarding.profile.discoverable_hint": "Если вы соглашаетесь на открытость на Mastodon, ваши сообщения могут появляться в результатах поиска и трендах, а ваш профиль может быть предложен людям со схожими с вами интересами.",
"onboarding.profile.display_name": "Отображаемое имя",
"onboarding.profile.display_name_hint": "Ваше полное имя или псевдоним…",
"onboarding.profile.lead": "Вы всегда можете завершить это позже в настройках, где доступны еще более широкие возможности настройки.",
"onboarding.profile.note": "О себе",
"onboarding.profile.note_hint": "Вы можете @упоминать других людей или использовать #хэштеги…",
"onboarding.profile.save_and_continue": "Сохранить и продолжить",
"onboarding.profile.title": "Настройка профиля",
"onboarding.profile.upload_avatar": "Загрузить фотографию профиля",
"onboarding.profile.upload_header": "Загрузить заголовок профиля",
"onboarding.share.lead": "Расскажите людям, как они могут найти вас на Mastodon!", "onboarding.share.lead": "Расскажите людям, как они могут найти вас на Mastodon!",
"onboarding.share.message": "Я {username} на #Mastodon! Следуйте за мной по адресу {url}", "onboarding.share.message": "Я {username} на #Mastodon! Следуйте за мной по адресу {url}",
"onboarding.share.next_steps": "Возможные дальнейшие шаги:", "onboarding.share.next_steps": "Возможные дальнейшие шаги:",
@ -520,6 +535,7 @@
"privacy.unlisted.short": "Скрытый", "privacy.unlisted.short": "Скрытый",
"privacy_policy.last_updated": "Последнее обновление {date}", "privacy_policy.last_updated": "Последнее обновление {date}",
"privacy_policy.title": "Политика конфиденциальности", "privacy_policy.title": "Политика конфиденциальности",
"recommended": "Рекомендуется",
"refresh": "Обновить", "refresh": "Обновить",
"regeneration_indicator.label": "Загрузка…", "regeneration_indicator.label": "Загрузка…",
"regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!", "regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!",
@ -590,6 +606,7 @@
"search.quick_action.status_search": "Посты, соответствующие {x}", "search.quick_action.status_search": "Посты, соответствующие {x}",
"search.search_or_paste": "Поиск (или вставьте URL)", "search.search_or_paste": "Поиск (или вставьте URL)",
"search_popout.full_text_search_disabled_message": "Недоступно на {domain}.", "search_popout.full_text_search_disabled_message": "Недоступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно только при авторизации.",
"search_popout.language_code": "Код языка по стандарту ISO", "search_popout.language_code": "Код языка по стандарту ISO",
"search_popout.options": "Параметры поиска", "search_popout.options": "Параметры поиска",
"search_popout.quick_actions": "Быстрые действия", "search_popout.quick_actions": "Быстрые действия",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Objave, ki se ujemajo z {x}", "search.quick_action.status_search": "Objave, ki se ujemajo z {x}",
"search.search_or_paste": "Iščite ali prilepite URL", "search.search_or_paste": "Iščite ali prilepite URL",
"search_popout.full_text_search_disabled_message": "Ni dostopno na {domain}.", "search_popout.full_text_search_disabled_message": "Ni dostopno na {domain}.",
"search_popout.full_text_search_logged_out_message": "Na voljo le, če ste prijavljeni.",
"search_popout.language_code": "Koda ISO jezika", "search_popout.language_code": "Koda ISO jezika",
"search_popout.options": "Možnosti iskanja", "search_popout.options": "Možnosti iskanja",
"search_popout.quick_actions": "Hitra dejanja", "search_popout.quick_actions": "Hitra dejanja",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Podudaranje objava {x}", "search.quick_action.status_search": "Podudaranje objava {x}",
"search.search_or_paste": "Pretražite ili unesite adresu", "search.search_or_paste": "Pretražite ili unesite adresu",
"search_popout.full_text_search_disabled_message": "Nije dostupno na {domain}.", "search_popout.full_text_search_disabled_message": "Nije dostupno na {domain}.",
"search_popout.full_text_search_logged_out_message": "Dostupno samo kada ste prijavljeni.",
"search_popout.language_code": "ISO kod jezika", "search_popout.language_code": "ISO kod jezika",
"search_popout.options": "Opcije pretrage", "search_popout.options": "Opcije pretrage",
"search_popout.quick_actions": "Brze radnje", "search_popout.quick_actions": "Brze radnje",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Подударање објава {x}", "search.quick_action.status_search": "Подударање објава {x}",
"search.search_or_paste": "Претражите или унесите адресу", "search.search_or_paste": "Претражите или унесите адресу",
"search_popout.full_text_search_disabled_message": "Није доступно на {domain}.", "search_popout.full_text_search_disabled_message": "Није доступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно само када сте пријављени.",
"search_popout.language_code": "ISO код језика", "search_popout.language_code": "ISO код језика",
"search_popout.options": "Опције претраге", "search_popout.options": "Опције претраге",
"search_popout.quick_actions": "Брзе радње", "search_popout.quick_actions": "Брзе радње",

View file

@ -604,6 +604,7 @@
"search.quick_action.status_search": "Inlägg som matchar {x}", "search.quick_action.status_search": "Inlägg som matchar {x}",
"search.search_or_paste": "Sök eller klistra in URL", "search.search_or_paste": "Sök eller klistra in URL",
"search_popout.full_text_search_disabled_message": "Inte tillgänglig på {domain}.", "search_popout.full_text_search_disabled_message": "Inte tillgänglig på {domain}.",
"search_popout.full_text_search_logged_out_message": "Endast tillgängligt när du är inloggad.",
"search_popout.language_code": "ISO språkkod", "search_popout.language_code": "ISO språkkod",
"search_popout.options": "Sökalternativ", "search_popout.options": "Sökalternativ",
"search_popout.quick_actions": "Snabbåtgärder", "search_popout.quick_actions": "Snabbåtgärder",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "โพสต์ที่ตรงกับ {x}", "search.quick_action.status_search": "โพสต์ที่ตรงกับ {x}",
"search.search_or_paste": "ค้นหาหรือวาง URL", "search.search_or_paste": "ค้นหาหรือวาง URL",
"search_popout.full_text_search_disabled_message": "ไม่พร้อมใช้งานใน {domain}", "search_popout.full_text_search_disabled_message": "ไม่พร้อมใช้งานใน {domain}",
"search_popout.full_text_search_logged_out_message": "พร้อมใช้งานเฉพาะเมื่อเข้าสู่ระบบแล้วเท่านั้น",
"search_popout.language_code": "รหัสภาษา ISO", "search_popout.language_code": "รหัสภาษา ISO",
"search_popout.options": "ตัวเลือกการค้นหา", "search_popout.options": "ตัวเลือกการค้นหา",
"search_popout.quick_actions": "การกระทำด่วน", "search_popout.quick_actions": "การกระทำด่วน",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Eşleşen gönderiler {x}", "search.quick_action.status_search": "Eşleşen gönderiler {x}",
"search.search_or_paste": "Ara veya bağlantıyı yapıştır", "search.search_or_paste": "Ara veya bağlantıyı yapıştır",
"search_popout.full_text_search_disabled_message": "{domain} sunucusunda mevcut değil.", "search_popout.full_text_search_disabled_message": "{domain} sunucusunda mevcut değil.",
"search_popout.full_text_search_logged_out_message": "Sadece oturum açıldığında mevcuttur.",
"search_popout.language_code": "ISO dil kodu", "search_popout.language_code": "ISO dil kodu",
"search_popout.options": "Arama seçenekleri", "search_popout.options": "Arama seçenekleri",
"search_popout.quick_actions": "Hızlı eylemler", "search_popout.quick_actions": "Hızlı eylemler",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Збіг дописів {x}", "search.quick_action.status_search": "Збіг дописів {x}",
"search.search_or_paste": "Введіть адресу або пошуковий запит", "search.search_or_paste": "Введіть адресу або пошуковий запит",
"search_popout.full_text_search_disabled_message": "Недоступно на {domain}.", "search_popout.full_text_search_disabled_message": "Недоступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно лише після входу.",
"search_popout.language_code": "Код мови ISO", "search_popout.language_code": "Код мови ISO",
"search_popout.options": "Опції пошуку", "search_popout.options": "Опції пошуку",
"search_popout.quick_actions": "Швидкі дії", "search_popout.quick_actions": "Швидкі дії",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Tút nhắc đến {x}", "search.quick_action.status_search": "Tút nhắc đến {x}",
"search.search_or_paste": "Tìm kiếm hoặc nhập URL", "search.search_or_paste": "Tìm kiếm hoặc nhập URL",
"search_popout.full_text_search_disabled_message": "Không khả dụng trên {domain}.", "search_popout.full_text_search_disabled_message": "Không khả dụng trên {domain}.",
"search_popout.full_text_search_logged_out_message": "Cần đăng nhập trước.",
"search_popout.language_code": "Mã ngôn ngữ ISO", "search_popout.language_code": "Mã ngôn ngữ ISO",
"search_popout.options": "Tùy chọn tìm kiếm", "search_popout.options": "Tùy chọn tìm kiếm",
"search_popout.quick_actions": "Thao tác nhanh", "search_popout.quick_actions": "Thao tác nhanh",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "匹配 {x} 的嘟文", "search.quick_action.status_search": "匹配 {x} 的嘟文",
"search.search_or_paste": "搜索或输入网址", "search.search_or_paste": "搜索或输入网址",
"search_popout.full_text_search_disabled_message": "在 {domain} 不可用", "search_popout.full_text_search_disabled_message": "在 {domain} 不可用",
"search_popout.full_text_search_logged_out_message": "只有登录后才可用。",
"search_popout.language_code": "ISO语言代码", "search_popout.language_code": "ISO语言代码",
"search_popout.options": "搜索选项", "search_popout.options": "搜索选项",
"search_popout.quick_actions": "快捷操作", "search_popout.quick_actions": "快捷操作",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "符合的帖文 {x}", "search.quick_action.status_search": "符合的帖文 {x}",
"search.search_or_paste": "搜尋或貼上網址", "search.search_or_paste": "搜尋或貼上網址",
"search_popout.full_text_search_disabled_message": "在 {domain} 上無法使用。", "search_popout.full_text_search_disabled_message": "在 {domain} 上無法使用。",
"search_popout.full_text_search_logged_out_message": "登入後才可使用。",
"search_popout.language_code": "ISO 語言代碼", "search_popout.language_code": "ISO 語言代碼",
"search_popout.options": "搜尋選項", "search_popout.options": "搜尋選項",
"search_popout.quick_actions": "快速動作", "search_popout.quick_actions": "快速動作",

View file

@ -176,7 +176,7 @@
"confirmations.domain_block.confirm": "封鎖整個網域", "confirmations.domain_block.confirm": "封鎖整個網域",
"confirmations.domain_block.message": "您真的非常確定要封鎖整個 {domain} 網域嗎?大部分情況下,封鎖或靜音少數特定的帳號就能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。", "confirmations.domain_block.message": "您真的非常確定要封鎖整個 {domain} 網域嗎?大部分情況下,封鎖或靜音少數特定的帳號就能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。",
"confirmations.edit.confirm": "編輯", "confirmations.edit.confirm": "編輯",
"confirmations.edit.message": "編輯嘟文將覆蓋掉您目前正在撰寫的訊息。是否仍要繼續?", "confirmations.edit.message": "編輯嘟文將覆蓋掉您目前正在撰寫之嘟文內容。您是否仍要繼續?",
"confirmations.logout.confirm": "登出", "confirmations.logout.confirm": "登出",
"confirmations.logout.message": "您確定要登出嗎?", "confirmations.logout.message": "您確定要登出嗎?",
"confirmations.mute.confirm": "靜音", "confirmations.mute.confirm": "靜音",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "符合的嘟文 {x}", "search.quick_action.status_search": "符合的嘟文 {x}",
"search.search_or_paste": "搜尋或輸入網址", "search.search_or_paste": "搜尋或輸入網址",
"search_popout.full_text_search_disabled_message": "{domain} 上無法使用。", "search_popout.full_text_search_disabled_message": "{domain} 上無法使用。",
"search_popout.full_text_search_logged_out_message": "僅於登入時能使用。",
"search_popout.language_code": "ISO 語言代碼 (ISO language code)", "search_popout.language_code": "ISO 語言代碼 (ISO language code)",
"search_popout.options": "搜尋選項", "search_popout.options": "搜尋選項",
"search_popout.quick_actions": "快捷操作", "search_popout.quick_actions": "快捷操作",

View file

@ -1,10 +0,0 @@
import ready from '../ready';
export let assetHost = '';
ready(() => {
const cdnHost = document.querySelector('meta[name=cdn-host]');
if (cdnHost) {
assetHost = cdnHost.content || '';
}
});

View file

@ -0,0 +1,13 @@
import ready from '../ready';
export let assetHost = '';
// eslint-disable-next-line @typescript-eslint/no-floating-promises
ready(() => {
const cdnHost = document.querySelector<HTMLMetaElement>(
'meta[name=cdn-host]',
);
if (cdnHost) {
assetHost = cdnHost.content || '';
}
});

View file

@ -1,6 +0,0 @@
// NB: This function can still return unsafe HTML
export const unescapeHTML = (html) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n').replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View file

@ -0,0 +1,9 @@
// NB: This function can still return unsafe HTML
export const unescapeHTML = (html: string) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html
.replace(/<br\s*\/?>/g, '\n')
.replace(/<\/p><p>/g, '\n\n')
.replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View file

@ -1,13 +1,23 @@
// Copied from emoji-mart for consistency with emoji picker and since // Copied from emoji-mart for consistency with emoji picker and since
// they don't export the icons in the package // they don't export the icons in the package
export const loupeIcon = ( export const loupeIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'> <svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' /> <path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' />
</svg> </svg>
); );
export const deleteIcon = ( export const deleteIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'> <svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' /> <path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' />
</svg> </svg>
); );

View file

@ -1,30 +0,0 @@
// Handles browser quirks, based on
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
const checkNotificationPromise = () => {
try {
// eslint-disable-next-line promise/valid-params, promise/catch-or-return
Notification.requestPermission().then();
} catch(e) {
return false;
}
return true;
};
const handlePermission = (permission, callback) => {
// Whatever the user answers, we make sure Chrome stores the information
if(!('permission' in Notification)) {
Notification.permission = permission;
}
callback(Notification.permission);
};
export const requestNotificationPermission = (callback) => {
if (checkNotificationPromise()) {
Notification.requestPermission().then((permission) => handlePermission(permission, callback)).catch(console.warn);
} else {
Notification.requestPermission((permission) => handlePermission(permission, callback));
}
};

View file

@ -0,0 +1,13 @@
/**
* Tries Notification.requestPermission, console warning instead of rejecting on error.
* @param callback Runs with the permission result on completion.
*/
export const requestNotificationPermission = async (
callback: NotificationPermissionCallback,
) => {
try {
callback(await Notification.requestPermission());
} catch (error) {
console.warn(error);
}
};

View file

@ -1,8 +1,8 @@
import PropTypes from "prop-types"; import PropTypes from 'prop-types';
import { __RouterContext } from "react-router"; import { __RouterContext } from 'react-router';
import hoistStatics from "hoist-non-react-statics"; import hoistStatics from 'hoist-non-react-statics';
export const WithRouterPropTypes = { export const WithRouterPropTypes = {
match: PropTypes.object.isRequired, match: PropTypes.object.isRequired,
@ -16,31 +16,37 @@ export const WithOptionalRouterPropTypes = {
history: PropTypes.object, history: PropTypes.object,
}; };
export interface OptionalRouterProps {
ref: unknown;
wrappedComponentRef: unknown;
}
// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js // This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js
// but does not fail if called outside of a React Router context // but does not fail if called outside of a React Router context
export function withOptionalRouter(Component) { export function withOptionalRouter<
const displayName = `withRouter(${Component.displayName || Component.name})`; ComponentType extends React.ComponentType<OptionalRouterProps>,
const C = props => { >(Component: ComponentType) {
const displayName = `withRouter(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props; const { wrappedComponentRef, ...remainingProps } = props;
return ( return (
<__RouterContext.Consumer> <__RouterContext.Consumer>
{context => { {(context) => {
if(context) // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (context) {
return ( return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component <Component
{...remainingProps} {...remainingProps}
{...context} {...context}
ref={wrappedComponentRef} ref={wrappedComponentRef}
/> />
); );
else } else {
return ( // @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component return <Component {...remainingProps} ref={wrappedComponentRef} />;
{...remainingProps} }
ref={wrappedComponentRef}
/>
);
}} }}
</__RouterContext.Consumer> </__RouterContext.Consumer>
); );
@ -53,8 +59,8 @@ export function withOptionalRouter(Component) {
wrappedComponentRef: PropTypes.oneOfType([ wrappedComponentRef: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.func, PropTypes.func,
PropTypes.object PropTypes.object,
]) ]),
}; };
return hoistStatics(C, Component); return hoistStatics(C, Component);

View file

@ -1,11 +1,7 @@
import { isMobile } from '../is_mobile'; import { isMobile } from '../is_mobile';
/** @type {number | null} */ let cachedScrollbarWidth: number | null = null;
let cachedScrollbarWidth = null;
/**
* @returns {number}
*/
const getActualScrollbarWidth = () => { const getActualScrollbarWidth = () => {
const outer = document.createElement('div'); const outer = document.createElement('div');
outer.style.visibility = 'hidden'; outer.style.visibility = 'hidden';
@ -16,20 +12,19 @@ const getActualScrollbarWidth = () => {
outer.appendChild(inner); outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth; const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
outer.parentNode.removeChild(outer); outer.remove();
return scrollbarWidth; return scrollbarWidth;
}; };
/**
* @returns {number}
*/
export const getScrollbarWidth = () => { export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) { if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth; return cachedScrollbarWidth;
} }
const scrollbarWidth = isMobile(window.innerWidth) ? 0 : getActualScrollbarWidth(); const scrollbarWidth = isMobile(window.innerWidth)
? 0
: getActualScrollbarWidth();
cachedScrollbarWidth = scrollbarWidth; cachedScrollbarWidth = scrollbarWidth;
return scrollbarWidth; return scrollbarWidth;

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AdminMailer < ApplicationMailer class AdminMailer < ApplicationMailer
layout 'plain_mailer' layout 'admin_mailer'
helper :accounts helper :accounts
helper :languages helper :languages

View file

@ -24,12 +24,12 @@ class Admin::ActionLog < ApplicationRecord
belongs_to :account belongs_to :account
belongs_to :target, polymorphic: true, optional: true belongs_to :target, polymorphic: true, optional: true
default_scope -> { order('id desc') }
before_validation :set_human_identifier before_validation :set_human_identifier
before_validation :set_route_param before_validation :set_route_param
before_validation :set_permalink before_validation :set_permalink
scope :latest, -> { order(id: :desc) }
def action def action
super.to_sym super.to_sym
end end

View file

@ -72,7 +72,7 @@ class Admin::ActionLogFilter
end end
def results def results
scope = Admin::ActionLog.includes(:target) scope = latest_action_logs.includes(:target)
params.each do |key, value| params.each do |key, value|
next if key.to_s == 'page' next if key.to_s == 'page'
@ -88,14 +88,18 @@ class Admin::ActionLogFilter
def scope_for(key, value) def scope_for(key, value)
case key case key
when 'action_type' when 'action_type'
Admin::ActionLog.where(ACTION_TYPE_MAP[value.to_sym]) latest_action_logs.where(ACTION_TYPE_MAP[value.to_sym])
when 'account_id' when 'account_id'
Admin::ActionLog.where(account_id: value) latest_action_logs.where(account_id: value)
when 'target_account_id' when 'target_account_id'
account = Account.find_or_initialize_by(id: value) account = Account.find_or_initialize_by(id: value)
Admin::ActionLog.where(target: [account, account.user].compact) latest_action_logs.where(target: [account, account.user].compact)
else else
raise Mastodon::InvalidParameterError, "Unknown filter: #{key}" raise Mastodon::InvalidParameterError, "Unknown filter: #{key}"
end end
end end
def latest_action_logs
Admin::ActionLog.latest
end
end end

View file

@ -131,25 +131,25 @@ class Report < ApplicationRecord
Admin::ActionLog.where( Admin::ActionLog.where(
target_type: 'Report', target_type: 'Report',
target_id: id target_id: id
).unscope(:order).arel, ).arel,
Admin::ActionLog.where( Admin::ActionLog.where(
target_type: 'Account', target_type: 'Account',
target_id: target_account_id target_id: target_account_id
).unscope(:order).arel, ).arel,
Admin::ActionLog.where( Admin::ActionLog.where(
target_type: 'Status', target_type: 'Status',
target_id: status_ids target_id: status_ids
).unscope(:order).arel, ).arel,
Admin::ActionLog.where( Admin::ActionLog.where(
target_type: 'AccountWarning', target_type: 'AccountWarning',
target_id: AccountWarning.where(report_id: id).select(:id) target_id: AccountWarning.where(report_id: id).select(:id)
).unscope(:order).arel, ).arel,
].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) } ].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) }
Admin::ActionLog.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table)) Admin::ActionLog.latest.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table))
end end
private private

View file

@ -49,7 +49,7 @@ class UserRole < ApplicationRecord
invite_users invite_users
).freeze, ).freeze,
moderation: %w( moderation: %i(
view_dashboard view_dashboard
view_audit_log view_audit_log
manage_users manage_users
@ -63,7 +63,7 @@ class UserRole < ApplicationRecord
manage_invites manage_invites
).freeze, ).freeze,
administration: %w( administration: %i(
manage_settings manage_settings
manage_rules manage_rules
manage_roles manage_roles
@ -72,7 +72,7 @@ class UserRole < ApplicationRecord
manage_announcements manage_announcements
).freeze, ).freeze,
devops: %w( devops: %i(
view_devops view_devops
).freeze, ).freeze,

View file

@ -38,7 +38,10 @@ class NodeInfo::Serializer < ActiveModel::Serializer
end end
def metadata def metadata
{} {
nodeName: Setting.site_title,
nodeDescription: Setting.site_short_description,
}
end end
private private

View file

@ -2,7 +2,10 @@
class REST::ApplicationSerializer < ActiveModel::Serializer class REST::ApplicationSerializer < ActiveModel::Serializer
attributes :id, :name, :website, :scopes, :redirect_uri, attributes :id, :name, :website, :scopes, :redirect_uri,
:client_id, :client_secret, :vapid_key :client_id, :client_secret
# NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0
attribute :vapid_key
def id def id
object.id.to_s object.id.to_s

View file

@ -48,6 +48,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
status: object.status_page_url, status: object.status_page_url,
}, },
vapid: {
public_key: Rails.configuration.x.vapid_public_key,
},
accounts: { accounts: {
max_featured_tags: FeaturedTag::LIMIT, max_featured_tags: FeaturedTag::LIMIT,
}, },

View file

@ -100,7 +100,9 @@ class ResolveAccountService < BaseService
end end
def split_acct(acct) def split_acct(acct)
acct.delete_prefix('acct:').split('@') acct.delete_prefix('acct:').split('@').tap do |parts|
raise Webfinger::Error, 'Webfinger response is missing user or host value' unless parts.size == 2
end
end end
def fetch_account! def fetch_account!

View file

@ -5,7 +5,7 @@
- if @account.user_prefers_noindex? - if @account.user_prefers_noindex?
%meta{ name: 'robots', content: 'noindex, noarchive' }/ %meta{ name: 'robots', content: 'noindex, noarchive' }/
%link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/ %link{ rel: 'alternate', type: 'application/rss+xml', href: rss_url }/
%link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/ %link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
- @account.fields.select(&:verifiable?).each do |field| - @account.fields.select(&:verifiable?).each do |field|

View file

@ -5,7 +5,7 @@
= f.input :report_id, as: :hidden = f.input :report_id, as: :hidden
.fields-group .fields-group
= f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { safe_join([I18n.t("simple_form.labels.admin_account_action.types.#{type}"), content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint')]) }, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct) = f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { account_action_type_label(type) }, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct)
- if @account.local? - if @account.local?
%hr.spacer/ %hr.spacer/

View file

@ -10,7 +10,7 @@
.filter-subset.filter-subset--with-select .filter-subset.filter-subset--with-select
%strong= t('admin.accounts.moderation.title') %strong= t('admin.accounts.moderation.title')
.input.select.optional .input.select.optional
= select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.disabled'), 'disabled'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all') = select_tag :status, options_for_select(admin_accounts_moderation_options, params[:status]), prompt: I18n.t('generic.all')
.filter-subset.filter-subset--with-select .filter-subset.filter-subset--with-select
%strong= t('admin.accounts.role') %strong= t('admin.accounts.role')
.input.select.optional .input.select.optional

Some files were not shown because too many files have changed in this diff Show more