Merge commit '7866745a6bf21e5187137c321040ed58d37e4331' into bark-prod
This commit is contained in:
commit
0dc3974f1c
403 changed files with 5878 additions and 2775 deletions
.dockerignore.env.production.sample
.github/workflows
.prettierignore.rubocop.yml.rubocop
.rubocop_todo.ymlCHANGELOG.mdDockerfileGemfileGemfile.lockapp
controllers
admin
api/v1
settings
helpers
javascript
entrypoints
mastodon
actions
api
api_types
components
account_bio.tsxaccount_fields.tsxalt_text_badge.tsxavatar.tsxavatar_overlay.tsxcopy_paste_text.tsxgif.tsxhashtag.tsxhover_card_controller.tsxicon_button.tsxscrollable_list.jsx
features
account_gallery
account_timeline/components
alt_text_modal/components
compose/components
directory
emoji
emoji_compressed.d.tsemoji_compressed.jsemoji_mart_data_light.tsemoji_picker.jsemoji_sheet.jsonemoji_unicode_mapping_light.ts
home_timeline/components
search
standalone/status
status/components
terms_of_service
ui
hooks
locales
|
@ -20,3 +20,9 @@ postgres14
|
|||
redis
|
||||
elasticsearch
|
||||
chart
|
||||
.yarn/
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
|
|
@ -86,3 +86,24 @@ S3_ALIAS_HOST=files.example.com
|
|||
# -----------------------
|
||||
IP_RETENTION_PERIOD=31556952
|
||||
SESSION_RETENTION_PERIOD=31556952
|
||||
|
||||
# Fetch All Replies Behavior
|
||||
# --------------------------
|
||||
# When a user expands a post (DetailedStatus view), fetch all of its replies
|
||||
# (default: false)
|
||||
FETCH_REPLIES_ENABLED=false
|
||||
|
||||
# Period to wait between fetching replies (in minutes)
|
||||
FETCH_REPLIES_COOLDOWN_MINUTES=15
|
||||
|
||||
# Period to wait after a post is first created before fetching its replies (in minutes)
|
||||
FETCH_REPLIES_INITIAL_WAIT_MINUTES=5
|
||||
|
||||
# Max number of replies to fetch - total, recursively through a whole reply tree
|
||||
FETCH_REPLIES_MAX_GLOBAL=1000
|
||||
|
||||
# Max number of replies to fetch - for a single post
|
||||
FETCH_REPLIES_MAX_SINGLE=500
|
||||
|
||||
# Max number of replies Collection pages to fetch - total
|
||||
FETCH_REPLIES_MAX_PAGES=500
|
||||
|
|
2
.github/workflows/lint-haml.yml
vendored
2
.github/workflows/lint-haml.yml
vendored
|
@ -43,4 +43,4 @@ jobs:
|
|||
- name: Run haml-lint
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||
bin/haml-lint --parallel --reporter github
|
||||
bin/haml-lint --reporter github
|
||||
|
|
1
.github/workflows/test-image-build.yml
vendored
1
.github/workflows/test-image-build.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
|||
- .github/workflows/test-image-build.yml
|
||||
- Dockerfile
|
||||
- streaming/Dockerfile
|
||||
- .dockerignore
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
|
12
.github/workflows/test-migrations.yml
vendored
12
.github/workflows/test-migrations.yml
vendored
|
@ -77,6 +77,18 @@ jobs:
|
|||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
|
||||
- name: Ensure no errors with `db:prepare`
|
||||
run: |
|
||||
bin/rails db:drop
|
||||
bin/rails db:prepare
|
||||
bin/rails db:migrate
|
||||
|
||||
- name: Ensure no errors with `db:prepare` and SKIP_POST_DEPLOYMENT_MIGRATIONS
|
||||
run: |
|
||||
bin/rails db:drop
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:prepare
|
||||
bin/rails db:migrate
|
||||
|
||||
- name: Test "one step migration" flow
|
||||
run: |
|
||||
bin/rails db:drop
|
||||
|
|
|
@ -63,6 +63,7 @@ docker-compose.override.yml
|
|||
|
||||
# Ignore emoji map file
|
||||
/app/javascript/mastodon/features/emoji/emoji_map.json
|
||||
/app/javascript/mastodon/features/emoji/emoji_sheet.json
|
||||
|
||||
# Ignore locale files
|
||||
/app/javascript/mastodon/locales/*.json
|
||||
|
|
|
@ -18,6 +18,7 @@ inherit_from:
|
|||
- .rubocop/rspec_rails.yml
|
||||
- .rubocop/rspec.yml
|
||||
- .rubocop/style.yml
|
||||
- .rubocop/i18n.yml
|
||||
- .rubocop/custom.yml
|
||||
- .rubocop_todo.yml
|
||||
- .rubocop/strict.yml
|
||||
|
@ -27,10 +28,9 @@ inherit_mode:
|
|||
- Exclude
|
||||
|
||||
plugins:
|
||||
- rubocop-capybara
|
||||
- rubocop-i18n
|
||||
- rubocop-performance
|
||||
- rubocop-rails
|
||||
- rubocop-rspec
|
||||
- rubocop-performance
|
||||
|
||||
require:
|
||||
- rubocop-rspec_rails
|
||||
- rubocop-capybara
|
||||
|
|
12
.rubocop/i18n.yml
Normal file
12
.rubocop/i18n.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
I18n/RailsI18n:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'config/**/*'
|
||||
- 'db/**/*'
|
||||
- 'lib/**/*'
|
||||
- 'spec/**/*'
|
||||
I18n/GetText:
|
||||
Enabled: false
|
||||
|
||||
I18n/RailsI18n/DecorateStringFormattingUsingInterpolation:
|
||||
Enabled: false
|
|
@ -1,6 +1,6 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||
# using RuboCop version 1.72.2.
|
||||
# using RuboCop version 1.73.2.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -2,6 +2,30 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.3.6] - 2025-03-13
|
||||
|
||||
### Security
|
||||
|
||||
- Update dependency `omniauth-saml`
|
||||
- Update dependency `rack`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix Stoplight errors when using `REDIS_NAMESPACE` (#34126 by @ClearlyClaire)
|
||||
|
||||
## [4.3.5] - 2025-03-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Change hashtag suggestion to prefer personal history capitalization (#34070 by @ClearlyClaire)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix processing errors for some HEIF images from iOS 18 (#34086 by @renchap)
|
||||
- Fix streaming server not filtering unknown-language posts from public timelines (#33774 by @ClearlyClaire)
|
||||
- Fix preview cards under Content Warnings not being shown in detailed statuses (#34068 by @ClearlyClaire)
|
||||
- Fix username and display name being hidden on narrow screens in moderation interface (#33064 by @ClearlyClaire)
|
||||
|
||||
## [4.3.4] - 2025-02-27
|
||||
|
||||
### Security
|
||||
|
|
65
Dockerfile
65
Dockerfile
|
@ -14,12 +14,12 @@ ARG BASE_REGISTRY="docker.io"
|
|||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
ARG RUBY_VERSION="3.4.2"
|
||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# renovate: datasource=node-version depName=node
|
||||
ARG NODE_MAJOR_VERSION="22"
|
||||
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
|
||||
ARG DEBIAN_VERSION="bookworm"
|
||||
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
|
||||
# Node.js image to use for base image based on combined variables (ex: 20-bookworm-slim)
|
||||
FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
|
||||
# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
|
||||
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
||||
|
@ -61,7 +61,7 @@ ENV \
|
|||
ENV \
|
||||
# Configure the IP to bind Mastodon to when serving traffic
|
||||
BIND="0.0.0.0" \
|
||||
# Use production settings for Yarn, Node and related nodejs based tools
|
||||
# Use production settings for Yarn, Node.js and related tools
|
||||
NODE_ENV="production" \
|
||||
# Use production settings for Ruby on Rails
|
||||
RAILS_ENV="production" \
|
||||
|
@ -96,6 +96,9 @@ RUN \
|
|||
# Set /opt/mastodon as working directory
|
||||
WORKDIR /opt/mastodon
|
||||
|
||||
# Add backport repository for some specific packages where we need the latest version
|
||||
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
|
||||
|
||||
# hadolint ignore=DL3008,DL3005
|
||||
RUN \
|
||||
# Mount Apt cache and lib directories from Docker buildx caches
|
||||
|
@ -125,13 +128,6 @@ RUN \
|
|||
# Create temporary build layer from base image
|
||||
FROM ruby AS build
|
||||
|
||||
# Copy Node package configuration files into working directory
|
||||
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||
COPY .yarn /opt/mastodon/.yarn
|
||||
|
||||
COPY --from=node /usr/local/bin /usr/local/bin
|
||||
COPY --from=node /usr/local/lib /usr/local/lib
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
|
@ -165,7 +161,7 @@ RUN \
|
|||
libexif-dev \
|
||||
libexpat1-dev \
|
||||
libgirepository1.0-dev \
|
||||
libheif-dev \
|
||||
libheif-dev/bookworm-backports \
|
||||
libimagequant-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
liblcms2-dev \
|
||||
|
@ -185,12 +181,6 @@ RUN \
|
|||
libx265-dev \
|
||||
;
|
||||
|
||||
RUN \
|
||||
# Configure Corepack
|
||||
rm /usr/local/bin/yarn*; \
|
||||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
||||
# Create temporary libvips specific build layer from build layer
|
||||
FROM build AS libvips
|
||||
|
||||
|
@ -281,38 +271,37 @@ RUN \
|
|||
# Download and install required Gems
|
||||
bundle install -j"$(nproc)";
|
||||
|
||||
# Create temporary node specific build layer from build layer
|
||||
FROM build AS yarn
|
||||
# Create temporary assets build layer from build layer
|
||||
FROM build AS precompiler
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Copy Node package configuration files into working directory
|
||||
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||
COPY streaming/package.json /opt/mastodon/streaming/
|
||||
COPY .yarn /opt/mastodon/.yarn
|
||||
# Copy Mastodon sources into layer
|
||||
COPY . /opt/mastodon/
|
||||
|
||||
# Copy Node.js binaries/libraries into layer
|
||||
COPY --from=node /usr/local/bin /usr/local/bin
|
||||
COPY --from=node /usr/local/lib /usr/local/lib
|
||||
|
||||
RUN \
|
||||
# Configure Corepack
|
||||
rm /usr/local/bin/yarn*; \
|
||||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN \
|
||||
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
||||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||
# Install Node packages
|
||||
# Install Node.js packages
|
||||
yarn workspaces focus --production @mastodon/mastodon;
|
||||
|
||||
# Create temporary assets build layer from build layer
|
||||
FROM build AS precompiler
|
||||
|
||||
# Copy Mastodon sources into precompiler layer
|
||||
COPY . /opt/mastodon/
|
||||
|
||||
# Copy bundler and node packages from build layer to container
|
||||
COPY --from=yarn /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
# Copy libvips components to layer for precompiler
|
||||
# Copy libvips components into layer for precompiler
|
||||
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
|
||||
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# Copy bundler packages into layer for precompiler
|
||||
COPY --from=bundler /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
|
||||
RUN \
|
||||
ldconfig; \
|
||||
|
@ -348,7 +337,7 @@ RUN \
|
|||
# libvips components
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libheif1/bookworm-backports \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -39,7 +39,7 @@ gem 'net-ldap', '~> 0.18'
|
|||
|
||||
gem 'omniauth', '~> 2.0'
|
||||
gem 'omniauth-cas', '~> 3.0.0.beta.1'
|
||||
gem 'omniauth_openid_connect', '~> 0.6.1'
|
||||
gem 'omniauth_openid_connect', '~> 0.8.0'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
gem 'omniauth-saml', '~> 2.0'
|
||||
|
||||
|
@ -102,10 +102,10 @@ gem 'rdf-normalize', '~> 0.5'
|
|||
|
||||
gem 'prometheus_exporter', '~> 2.2', require: false
|
||||
|
||||
gem 'opentelemetry-api', '~> 1.4.0'
|
||||
gem 'opentelemetry-api', '~> 1.5.0'
|
||||
|
||||
group :opentelemetry do
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.30.0', require: false
|
||||
gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false
|
||||
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false
|
||||
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false
|
||||
|
@ -165,6 +165,7 @@ group :development do
|
|||
# Code linting CLI and plugins
|
||||
gem 'rubocop', require: false
|
||||
gem 'rubocop-capybara', require: false
|
||||
gem 'rubocop-i18n', require: false
|
||||
gem 'rubocop-performance', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
gem 'rubocop-rspec', require: false
|
||||
|
|
233
Gemfile.lock
233
Gemfile.lock
|
@ -10,29 +10,29 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actioncable (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionmailbox (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionmailer (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionpack (8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
|
@ -40,15 +40,15 @@ GEM
|
|||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actiontext (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
actionview (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
|
@ -58,22 +58,22 @@ GEM
|
|||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activejob (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activerecord (8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activemodel (8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
activerecord (8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
activestorage (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.0.1)
|
||||
activesupport (8.0.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
|
@ -217,6 +217,8 @@ GEM
|
|||
htmlentities (~> 4.3.3)
|
||||
launchy (>= 2.1, < 4.0)
|
||||
mail (~> 2.7)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.2.11)
|
||||
tzinfo
|
||||
|
@ -228,6 +230,8 @@ GEM
|
|||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-httpclient (2.0.1)
|
||||
httpclient (>= 2.2)
|
||||
faraday-net_http (3.4.0)
|
||||
|
@ -261,8 +265,8 @@ GEM
|
|||
raabro (~> 1.4)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
google-protobuf (3.25.5)
|
||||
googleapis-common-protos-types (1.15.0)
|
||||
google-protobuf (3.25.6)
|
||||
googleapis-common-protos-types (1.18.0)
|
||||
google-protobuf (>= 3.18, < 5.a)
|
||||
haml (6.3.0)
|
||||
temple (>= 0.8.2)
|
||||
|
@ -304,7 +308,7 @@ GEM
|
|||
rainbow (>= 2.0.0)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-tasks (1.0.14)
|
||||
i18n-tasks (1.0.15)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
erubi
|
||||
|
@ -313,6 +317,7 @@ GEM
|
|||
parser (>= 3.2.2.1)
|
||||
rails-i18n
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.8, >= 1.8.1)
|
||||
terminal-table (>= 1.5.1)
|
||||
idn-ruby (0.1.5)
|
||||
inline_svg (1.10.0)
|
||||
|
@ -328,13 +333,15 @@ GEM
|
|||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.10.1)
|
||||
json (2.10.2)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3.1)
|
||||
json-jwt (1.16.7)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
bindata
|
||||
httpclient
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-ld (3.3.2)
|
||||
htmlentities (~> 4.3)
|
||||
json-canonicalization (~> 1.0)
|
||||
|
@ -435,41 +442,43 @@ GEM
|
|||
oj (3.16.10)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
omniauth (2.1.2)
|
||||
omniauth (2.1.3)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-cas (3.0.0)
|
||||
omniauth-cas (3.0.1)
|
||||
addressable (~> 2.8)
|
||||
nokogiri (~> 1.12)
|
||||
omniauth (~> 2.1)
|
||||
omniauth-rails_csrf_protection (1.0.2)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-saml (2.2.1)
|
||||
omniauth-saml (2.2.3)
|
||||
omniauth (~> 2.1)
|
||||
ruby-saml (~> 1.17)
|
||||
omniauth_openid_connect (0.6.1)
|
||||
ruby-saml (~> 1.18)
|
||||
omniauth_openid_connect (0.8.0)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 1.1)
|
||||
openid_connect (1.4.2)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.3.1)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
json-jwt (>= 1.15.0)
|
||||
net-smtp
|
||||
rack-oauth2 (~> 1.21)
|
||||
swd (~> 1.3)
|
||||
email_validator
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
mail
|
||||
rack-oauth2 (~> 2.2)
|
||||
swd (~> 2.0)
|
||||
tzinfo
|
||||
validate_email
|
||||
validate_url
|
||||
webfinger (~> 1.2)
|
||||
webfinger (~> 2.0)
|
||||
openssl (3.3.0)
|
||||
openssl-signature_algorithm (1.3.0)
|
||||
openssl (> 2.0)
|
||||
opentelemetry-api (1.4.0)
|
||||
opentelemetry-common (0.21.0)
|
||||
opentelemetry-api (1.5.0)
|
||||
opentelemetry-common (0.22.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-exporter-otlp (0.29.1)
|
||||
opentelemetry-exporter-otlp (0.30.0)
|
||||
google-protobuf (>= 3.18)
|
||||
googleapis-common-protos-types (~> 1.3)
|
||||
opentelemetry-api (~> 1.1)
|
||||
|
@ -500,8 +509,8 @@ GEM
|
|||
opentelemetry-instrumentation-active_record (0.9.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_storage (0.1.0)
|
||||
opentelemetry-api (~> 1.4.0)
|
||||
opentelemetry-instrumentation-active_storage (0.1.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-active_support (0.8.0)
|
||||
|
@ -553,14 +562,14 @@ GEM
|
|||
opentelemetry-instrumentation-sidekiq (0.26.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-registry (0.3.1)
|
||||
opentelemetry-registry (0.4.0)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-sdk (1.7.0)
|
||||
opentelemetry-sdk (1.8.0)
|
||||
opentelemetry-api (~> 1.1)
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-registry (~> 0.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-semantic_conventions (1.10.1)
|
||||
opentelemetry-semantic_conventions (1.11.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.1)
|
||||
|
@ -600,19 +609,20 @@ GEM
|
|||
public_suffix (6.0.1)
|
||||
puma (6.6.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.4.0)
|
||||
pundit (2.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.11)
|
||||
rack (2.2.13)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (1.21.3)
|
||||
rack-oauth2 (2.2.1)
|
||||
activesupport
|
||||
attr_required
|
||||
httpclient
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (3.2.0)
|
||||
|
@ -627,20 +637,20 @@ GEM
|
|||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (8.0.1)
|
||||
actioncable (= 8.0.1)
|
||||
actionmailbox (= 8.0.1)
|
||||
actionmailer (= 8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
actiontext (= 8.0.1)
|
||||
actionview (= 8.0.1)
|
||||
activejob (= 8.0.1)
|
||||
activemodel (= 8.0.1)
|
||||
activerecord (= 8.0.1)
|
||||
activestorage (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
rails (8.0.2)
|
||||
actioncable (= 8.0.2)
|
||||
actionmailbox (= 8.0.2)
|
||||
actionmailer (= 8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
actiontext (= 8.0.2)
|
||||
actionview (= 8.0.2)
|
||||
activejob (= 8.0.2)
|
||||
activemodel (= 8.0.2)
|
||||
activerecord (= 8.0.2)
|
||||
activestorage (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.0.1)
|
||||
railties (= 8.0.2)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
|
@ -651,9 +661,9 @@ GEM
|
|||
rails-i18n (8.0.1)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 8.0.0, < 9)
|
||||
railties (8.0.1)
|
||||
actionpack (= 8.0.1)
|
||||
activesupport (= 8.0.1)
|
||||
railties (8.0.2)
|
||||
actionpack (= 8.0.2)
|
||||
activesupport (= 8.0.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
|
@ -669,7 +679,7 @@ GEM
|
|||
rdf (~> 3.3)
|
||||
rdoc (6.12.0)
|
||||
psych (>= 4.0.0)
|
||||
redcarpet (3.6.0)
|
||||
redcarpet (3.6.1)
|
||||
redis (4.8.1)
|
||||
redis-namespace (1.11.0)
|
||||
redis (>= 4)
|
||||
|
@ -713,13 +723,13 @@ GEM
|
|||
rspec-expectations (~> 3.13)
|
||||
rspec-mocks (~> 3.13)
|
||||
rspec-support (~> 3.13)
|
||||
rspec-sidekiq (5.0.0)
|
||||
rspec-sidekiq (5.1.0)
|
||||
rspec-core (~> 3.0)
|
||||
rspec-expectations (~> 3.0)
|
||||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 8)
|
||||
sidekiq (>= 5, < 9)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.72.2)
|
||||
rubocop (1.73.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
|
@ -730,15 +740,19 @@ GEM
|
|||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.38.0)
|
||||
rubocop-ast (1.38.1)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-capybara (2.22.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-i18n (3.2.3)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1)
|
||||
rubocop-performance (1.24.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.72.1, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-rails (2.30.2)
|
||||
rubocop-rails (2.30.3)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
|
@ -747,12 +761,13 @@ GEM
|
|||
rubocop-rspec (3.5.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec_rails (2.30.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec (~> 3, >= 3.0.1)
|
||||
rubocop-rspec_rails (2.31.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec (~> 3.5)
|
||||
ruby-prof (1.7.1)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.17.0)
|
||||
ruby-saml (1.18.0)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.2.3)
|
||||
|
@ -810,13 +825,14 @@ GEM
|
|||
stackprof (0.2.27)
|
||||
stoplight (4.1.1)
|
||||
redlock (~> 1.0)
|
||||
stringio (3.1.4)
|
||||
stringio (3.1.5)
|
||||
strong_migrations (2.2.0)
|
||||
activerecord (>= 7)
|
||||
swd (1.3.0)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
sysexits (1.2.0)
|
||||
temple (0.10.3)
|
||||
terminal-table (4.0.0)
|
||||
|
@ -854,11 +870,8 @@ GEM
|
|||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.2)
|
||||
uri (1.0.3)
|
||||
useragent (0.16.11)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
|
@ -872,10 +885,11 @@ GEM
|
|||
openssl (>= 2.2)
|
||||
safety_net_attestation (~> 0.4.0)
|
||||
tpm-key_attestation (~> 0.14.0)
|
||||
webfinger (1.2.0)
|
||||
webfinger (2.1.3)
|
||||
activesupport
|
||||
httpclient (>= 2.4)
|
||||
webmock (3.25.0)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webmock (3.25.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
|
@ -973,9 +987,9 @@ DEPENDENCIES
|
|||
omniauth-cas (~> 3.0.0.beta.1)
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml (~> 2.0)
|
||||
omniauth_openid_connect (~> 0.6.1)
|
||||
opentelemetry-api (~> 1.4.0)
|
||||
opentelemetry-exporter-otlp (~> 0.29.0)
|
||||
omniauth_openid_connect (~> 0.8.0)
|
||||
opentelemetry-api (~> 1.5.0)
|
||||
opentelemetry-exporter-otlp (~> 0.30.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.8.0)
|
||||
opentelemetry-instrumentation-active_model_serializers (~> 0.22.0)
|
||||
opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
|
||||
|
@ -1016,6 +1030,7 @@ DEPENDENCIES
|
|||
rspec-sidekiq (~> 5.0)
|
||||
rubocop
|
||||
rubocop-capybara
|
||||
rubocop-i18n
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Announcements::DistributionsController < Admin::BaseController
|
||||
before_action :set_announcement
|
||||
|
||||
def create
|
||||
authorize @announcement, :distribute?
|
||||
@announcement.touch(:notification_sent_at)
|
||||
Admin::DistributeAnnouncementNotificationWorker.perform_async(@announcement.id)
|
||||
redirect_to admin_announcements_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_announcement
|
||||
@announcement = Announcement.find(params[:announcement_id])
|
||||
end
|
||||
end
|
16
app/controllers/admin/announcements/previews_controller.rb
Normal file
16
app/controllers/admin/announcements/previews_controller.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Announcements::PreviewsController < Admin::BaseController
|
||||
before_action :set_announcement
|
||||
|
||||
def show
|
||||
authorize @announcement, :distribute?
|
||||
@user_count = @announcement.scope_for_notification.count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_announcement
|
||||
@announcement = Announcement.find(params[:announcement_id])
|
||||
end
|
||||
end
|
17
app/controllers/admin/announcements/tests_controller.rb
Normal file
17
app/controllers/admin/announcements/tests_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Announcements::TestsController < Admin::BaseController
|
||||
before_action :set_announcement
|
||||
|
||||
def create
|
||||
authorize @announcement, :distribute?
|
||||
UserMailer.announcement_published(current_user, @announcement).deliver_later!
|
||||
redirect_to admin_announcements_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_announcement
|
||||
@announcement = Announcement.find(params[:announcement_id])
|
||||
end
|
||||
end
|
|
@ -23,7 +23,7 @@ class Admin::TermsOfService::DraftsController < Admin::BaseController
|
|||
private
|
||||
|
||||
def set_terms_of_service
|
||||
@terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text)
|
||||
@terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text, effective_date: 10.days.from_now)
|
||||
end
|
||||
|
||||
def current_terms_of_service
|
||||
|
@ -32,6 +32,6 @@ class Admin::TermsOfService::DraftsController < Admin::BaseController
|
|||
|
||||
def resource_params
|
||||
params
|
||||
.expect(terms_of_service: [:text, :changelog])
|
||||
.expect(terms_of_service: [:text, :changelog, :effective_date])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
class Admin::TermsOfServiceController < Admin::BaseController
|
||||
def index
|
||||
authorize :terms_of_service, :index?
|
||||
@terms_of_service = TermsOfService.live.first
|
||||
@terms_of_service = TermsOfService.published.first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, account_params, raise_error: true)
|
||||
current_user.update(user_params) if user_params
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: ValidationErrorFormatter.new(e).as_json, status: 422
|
||||
|
|
|
@ -5,12 +5,18 @@ class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseCo
|
|||
|
||||
def show
|
||||
cache_even_if_authenticated!
|
||||
render json: @terms_of_service, serializer: REST::PrivacyPolicySerializer
|
||||
render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_terms_of_service
|
||||
@terms_of_service = TermsOfService.live.first!
|
||||
@terms_of_service = begin
|
||||
if params[:date].present?
|
||||
TermsOfService.published.find_by!(effective_date: params[:date])
|
||||
else
|
||||
TermsOfService.live.first || TermsOfService.published.first! # For the case when none of the published terms have become effective yet
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
class Api::V1::MediaController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
||||
before_action :require_user!
|
||||
before_action :set_media_attachment, except: [:create]
|
||||
before_action :check_processing, except: [:create]
|
||||
before_action :set_media_attachment, except: [:create, :destroy]
|
||||
before_action :check_processing, except: [:create, :destroy]
|
||||
|
||||
def show
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
|
@ -25,6 +25,15 @@ class Api::V1::MediaController < Api::BaseController
|
|||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
|
||||
end
|
||||
|
||||
def destroy
|
||||
@media_attachment = current_account.media_attachments.find(params[:id])
|
||||
|
||||
return render json: in_usage_error, status: 422 unless @media_attachment.status_id.nil?
|
||||
|
||||
@media_attachment.destroy
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_code_for_media_attachment
|
||||
|
@ -54,4 +63,8 @@ class Api::V1::MediaController < Api::BaseController
|
|||
def processing_error
|
||||
{ error: 'Error processing thumbnail for uploaded media' }
|
||||
end
|
||||
|
||||
def in_usage_error
|
||||
{ error: 'Media attachment is currently used by a status' }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class Api::V1::Profile::AvatarsController < Api::BaseController
|
|||
def destroy
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, { avatar: nil }, raise_error: true)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class Api::V1::Profile::HeadersController < Api::BaseController
|
|||
def destroy
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, { header: nil }, raise_error: true)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,6 +58,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
statuses = [@status] + @context.ancestors + @context.descendants
|
||||
|
||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
||||
|
||||
ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies?
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -111,7 +113,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
@status.account.statuses_count = @status.account.statuses_count - 1
|
||||
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||
|
||||
RemovalWorker.perform_async(@status.id, { 'redraft' => true })
|
||||
RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) })
|
||||
|
||||
render json: json
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module Settings
|
|||
def destroy
|
||||
if valid_picture?
|
||||
if UpdateAccountService.new.call(@account, { @picture => nil, "#{@picture}_remote_url" => '' })
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg'), status: 303
|
||||
else
|
||||
redirect_to settings_profile_path
|
||||
|
|
|
@ -8,7 +8,7 @@ class Settings::PrivacyController < Settings::BaseController
|
|||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params.except(:settings))
|
||||
current_user.update!(settings_attributes: account_params[:settings])
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_privacy_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :show
|
||||
|
|
|
@ -9,7 +9,7 @@ class Settings::ProfilesController < Settings::BaseController
|
|||
|
||||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
@account.build_fields
|
||||
|
|
|
@ -8,7 +8,7 @@ class Settings::VerificationsController < Settings::BaseController
|
|||
|
||||
def update
|
||||
if UpdateAccountService.new.call(@account, account_params)
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
ActivityPub::UpdateDistributionWorker.perform_in(ActivityPub::UpdateDistributionWorker::DEBOUNCE_DELAY, @account.id)
|
||||
redirect_to settings_verification_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
else
|
||||
render :show
|
||||
|
|
|
@ -155,24 +155,49 @@ module JsonLdHelper
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
|
||||
# Fetch the resource given by uri.
|
||||
# @param uri [String]
|
||||
# @param id_is_known [Boolean]
|
||||
# @param on_behalf_of [nil, Account]
|
||||
# @param raise_on_error [Symbol<:all, :temporary, :none>] See {#fetch_resource_without_id_validation} for possible values
|
||||
def fetch_resource(uri, id_is_known, on_behalf_of = nil, raise_on_error: :none, request_options: {})
|
||||
unless id_is_known
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error)
|
||||
|
||||
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
||||
|
||||
uri = json['id']
|
||||
end
|
||||
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
|
||||
json = fetch_resource_without_id_validation(uri, on_behalf_of, raise_on_error: raise_on_error, request_options: request_options)
|
||||
json.present? && json['id'] == uri ? json : nil
|
||||
end
|
||||
|
||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
|
||||
# Fetch the resource given by uri
|
||||
#
|
||||
# If an error is raised, it contains the response and can be captured for handling like
|
||||
#
|
||||
# begin
|
||||
# fetch_resource_without_id_validation(uri, nil, true)
|
||||
# rescue Mastodon::UnexpectedResponseError => e
|
||||
# e.response
|
||||
# end
|
||||
#
|
||||
# @param uri [String]
|
||||
# @param on_behalf_of [nil, Account]
|
||||
# @param raise_on_error [Symbol<:all, :temporary, :none>]
|
||||
# - +:all+ - raise if response code is not in the 2xx range
|
||||
# - +:temporary+ - raise if the response code is not an "unsalvageable error" like a 404
|
||||
# (see {#response_error_unsalvageable} )
|
||||
# - +:none+ - do not raise, return +nil+
|
||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_error: :none, request_options: {})
|
||||
on_behalf_of ||= Account.representative
|
||||
|
||||
build_request(uri, on_behalf_of, options: request_options).perform do |response|
|
||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
||||
raise Mastodon::UnexpectedResponseError, response if !response_successful?(response) && (
|
||||
raise_on_error == :all ||
|
||||
(!response_error_unsalvageable?(response) && raise_on_error == :temporary)
|
||||
)
|
||||
|
||||
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import './public-path';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';
|
||||
import { afterInitialRender } from 'mastodon/hooks/useRenderSignal';
|
||||
|
||||
import { start } from '../mastodon/common';
|
||||
import { Status } from '../mastodon/features/standalone/status';
|
||||
|
|
|
@ -29,7 +29,7 @@ const debouncedSave = debounce((dispatch, getState) => {
|
|||
api().put('/api/web/settings', { data })
|
||||
.then(() => dispatch({ type: SETTING_SAVE }))
|
||||
.catch(error => dispatch(showAlertForError(error)));
|
||||
}, 5000, { trailing: true });
|
||||
}, 2000, { leading: true, trailing: true });
|
||||
|
||||
export function saveSettings() {
|
||||
return (dispatch, getState) => debouncedSave(dispatch, getState);
|
||||
|
|
|
@ -138,7 +138,7 @@ export function deleteStatus(id, withRedraft = false) {
|
|||
|
||||
dispatch(deleteStatusRequest(id));
|
||||
|
||||
api().delete(`/api/v1/statuses/${id}`).then(response => {
|
||||
api().delete(`/api/v1/statuses/${id}`, { params: { delete_media: !withRedraft } }).then(response => {
|
||||
dispatch(deleteStatusSuccess(id));
|
||||
dispatch(deleteFromTimelines(id));
|
||||
dispatch(importFetchedAccount(response.data.account));
|
||||
|
|
|
@ -4,8 +4,12 @@ import type {
|
|||
ApiPrivacyPolicyJSON,
|
||||
} from 'mastodon/api_types/instance';
|
||||
|
||||
export const apiGetTermsOfService = () =>
|
||||
apiRequestGet<ApiTermsOfServiceJSON>('v1/instance/terms_of_service');
|
||||
export const apiGetTermsOfService = (version?: string) =>
|
||||
apiRequestGet<ApiTermsOfServiceJSON>(
|
||||
version
|
||||
? `v1/instance/terms_of_service/${version}`
|
||||
: 'v1/instance/terms_of_service',
|
||||
);
|
||||
|
||||
export const apiGetPrivacyPolicy = () =>
|
||||
apiRequestGet<ApiPrivacyPolicyJSON>('v1/instance/privacy_policy');
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export interface ApiTermsOfServiceJSON {
|
||||
updated_at: string;
|
||||
effective_date: string;
|
||||
effective: boolean;
|
||||
succeeded_by: string | null;
|
||||
content: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useLinks } from 'mastodon/../hooks/useLinks';
|
||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
||||
|
||||
export const AccountBio: React.FC<{
|
||||
note: string;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import classNames from 'classnames';
|
||||
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import { useLinks } from 'mastodon/../hooks/useLinks';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
||||
export const AccountFields: React.FC<{
|
||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
|||
UsePopperOptions,
|
||||
} from 'react-overlays/esm/usePopper';
|
||||
|
||||
import { useSelectableClick } from '@/hooks/useSelectableClick';
|
||||
import { useSelectableClick } from 'mastodon/hooks/useSelectableClick';
|
||||
|
||||
const offset = [0, 4] as OffsetValue;
|
||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useState, useCallback } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useHovering } from 'mastodon/../hooks/useHovering';
|
||||
import { useHovering } from 'mastodon/hooks/useHovering';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { useHovering } from 'mastodon/hooks/useHovering';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
|
|
|
@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
|
||||
import { useTimeout } from 'mastodon/../hooks/useTimeout';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useTimeout } from 'mastodon/hooks/useTimeout';
|
||||
|
||||
export const CopyPasteText: React.FC<{ value: string }> = ({ value }) => {
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useHovering } from '@/hooks/useHovering';
|
||||
import { useHovering } from 'mastodon/hooks/useHovering';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
|
||||
export const GIF: React.FC<{
|
||||
|
|
|
@ -151,7 +151,7 @@ export const Hashtag: React.FC<HashtagProps> = ({
|
|||
<Sparklines
|
||||
width={50}
|
||||
height={28}
|
||||
data={history ? history : Array.from(Array(7)).map(() => 0)}
|
||||
data={history ?? Array.from(Array(7)).map(() => 0)}
|
||||
>
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
|
|
|
@ -8,8 +8,8 @@ import type {
|
|||
UsePopperOptions,
|
||||
} from 'react-overlays/esm/usePopper';
|
||||
|
||||
import { useTimeout } from 'mastodon/../hooks/useTimeout';
|
||||
import { HoverCardAccount } from 'mastodon/components/hover_card_account';
|
||||
import { useTimeout } from 'mastodon/hooks/useTimeout';
|
||||
|
||||
const offset = [-12, 4] as OffsetValue;
|
||||
const enterDelay = 750;
|
||||
|
|
|
@ -149,6 +149,7 @@ export class IconButton extends PureComponent<Props, States> {
|
|||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
onKeyPress={this.handleKeyPress}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
|
|
|
@ -81,6 +81,7 @@ class ScrollableList extends PureComponent {
|
|||
bindToDocument: PropTypes.bool,
|
||||
preventScroll: PropTypes.bool,
|
||||
footer: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -325,7 +326,7 @@ class ScrollableList extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { children, scrollKey, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, footer, emptyMessage, onLoadMore } = this.props;
|
||||
const { children, scrollKey, className, trackScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, append, footer, emptyMessage, onLoadMore } = this.props;
|
||||
const { fullscreen } = this.state;
|
||||
const childrenCount = Children.count(children);
|
||||
|
||||
|
@ -336,9 +337,9 @@ class ScrollableList extends PureComponent {
|
|||
if (showLoading) {
|
||||
scrollableArea = (
|
||||
<div className='scrollable scrollable--flex' ref={this.setRef}>
|
||||
<div role='feed' className='item-list'>
|
||||
{prepend}
|
||||
</div>
|
||||
{prepend}
|
||||
|
||||
<div role='feed' className='item-list' />
|
||||
|
||||
<div className='scrollable__append'>
|
||||
<LoadingIndicator />
|
||||
|
@ -350,9 +351,9 @@ class ScrollableList extends PureComponent {
|
|||
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||
<div role='feed' className='item-list'>
|
||||
{prepend}
|
||||
{prepend}
|
||||
|
||||
<div role='feed' className={classNames('item-list', className)}>
|
||||
{loadPending}
|
||||
|
||||
{Children.map(this.props.children, (child, index) => (
|
||||
|
|
|
@ -11,11 +11,15 @@ import { Icon } from 'mastodon/components/icon';
|
|||
import { formatTime } from 'mastodon/features/video';
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from 'mastodon/initial_state';
|
||||
import type { Status, MediaAttachment } from 'mastodon/models/status';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const MediaItem: React.FC<{
|
||||
attachment: MediaAttachment;
|
||||
onOpenMedia: (arg0: MediaAttachment) => void;
|
||||
}> = ({ attachment, onOpenMedia }) => {
|
||||
const account = useAppSelector((state) =>
|
||||
state.accounts.get(attachment.getIn(['status', 'account']) as string),
|
||||
);
|
||||
const [visible, setVisible] = useState(
|
||||
(displayMedia !== 'hide_all' &&
|
||||
!attachment.getIn(['status', 'sensitive'])) ||
|
||||
|
@ -66,11 +70,10 @@ export const MediaItem: React.FC<{
|
|||
attachment.get('description')) as string | undefined;
|
||||
const previewUrl = attachment.get('preview_url') as string;
|
||||
const fullUrl = attachment.get('url') as string;
|
||||
const avatarUrl = status.getIn(['account', 'avatar_static']) as string;
|
||||
const avatarUrl = account?.avatar_static;
|
||||
const lang = status.get('language') as string;
|
||||
const blurhash = attachment.get('blurhash') as string;
|
||||
const statusId = status.get('id') as string;
|
||||
const acct = status.getIn(['account', 'acct']) as string;
|
||||
const type = attachment.get('type') as string;
|
||||
|
||||
let thumbnail;
|
||||
|
@ -181,7 +184,7 @@ export const MediaItem: React.FC<{
|
|||
|
||||
<a
|
||||
className='media-gallery__item-thumbnail'
|
||||
href={`/@${acct}/${statusId}`}
|
||||
href={`/@${account?.acct}/${statusId}`}
|
||||
onClick={handleClick}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import { LoadMore } from 'mastodon/components/load_more';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||
import { getAccountGallery } from 'mastodon/selectors';
|
||||
|
||||
import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||
import { AccountHeader } from '../account_timeline/components/account_header';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
import { MediaItem } from './components/media_item';
|
||||
|
||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||
const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
|
||||
|
||||
if (!accountId) {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
accountId,
|
||||
isAccount: !!state.getIn(['accounts', accountId]),
|
||||
attachments: getAccountGallery(state, accountId),
|
||||
isLoading: state.getIn(['timelines', `account:${accountId}:media`, 'isLoading']),
|
||||
hasMore: state.getIn(['timelines', `account:${accountId}:media`, 'hasMore']),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
};
|
||||
|
||||
class LoadMoreMedia extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
maxId: PropTypes.string,
|
||||
onLoadMore: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleLoadMore = () => {
|
||||
this.props.onLoadMore(this.props.maxId);
|
||||
};
|
||||
|
||||
render () {
|
||||
return (
|
||||
<LoadMore
|
||||
disabled={this.props.disabled}
|
||||
onClick={this.handleLoadMore}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.shape({
|
||||
acct: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
}).isRequired,
|
||||
accountId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
attachments: ImmutablePropTypes.list.isRequired,
|
||||
isLoading: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isAccount: PropTypes.bool,
|
||||
blockedBy: PropTypes.bool,
|
||||
suspended: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
width: 323,
|
||||
};
|
||||
|
||||
_load () {
|
||||
const { accountId, isAccount, dispatch } = this.props;
|
||||
|
||||
if (!isAccount) dispatch(fetchAccount(accountId));
|
||||
dispatch(expandAccountMediaTimeline(accountId));
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (accountId) {
|
||||
this._load();
|
||||
} else {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { params: { acct }, accountId, dispatch } = this.props;
|
||||
|
||||
if (prevProps.accountId !== accountId && accountId) {
|
||||
this._load();
|
||||
} else if (prevProps.params.acct !== acct) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}
|
||||
|
||||
handleScrollToBottom = () => {
|
||||
if (this.props.hasMore) {
|
||||
this.handleLoadMore(this.props.attachments.size > 0 ? this.props.attachments.last().getIn(['status', 'id']) : undefined);
|
||||
}
|
||||
};
|
||||
|
||||
handleScroll = e => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
const offset = scrollHeight - scrollTop - clientHeight;
|
||||
|
||||
if (150 > offset && !this.props.isLoading) {
|
||||
this.handleScrollToBottom();
|
||||
}
|
||||
};
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandAccountMediaTimeline(this.props.accountId, { maxId }));
|
||||
};
|
||||
|
||||
handleLoadOlder = e => {
|
||||
e.preventDefault();
|
||||
this.handleScrollToBottom();
|
||||
};
|
||||
|
||||
handleOpenMedia = attachment => {
|
||||
const { dispatch } = this.props;
|
||||
const statusId = attachment.getIn(['status', 'id']);
|
||||
const lang = attachment.getIn(['status', 'language']);
|
||||
|
||||
if (attachment.get('type') === 'video') {
|
||||
dispatch(openModal({
|
||||
modalType: 'VIDEO',
|
||||
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
|
||||
}));
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
dispatch(openModal({
|
||||
modalType: 'AUDIO',
|
||||
modalProps: { media: attachment, statusId, lang, options: { autoPlay: true } },
|
||||
}));
|
||||
} else {
|
||||
const media = attachment.getIn(['status', 'media_attachments']);
|
||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||
|
||||
dispatch(openModal({
|
||||
modalType: 'MEDIA',
|
||||
modalProps: { media, index, statusId, lang },
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
handleRef = c => {
|
||||
if (c) {
|
||||
this.setState({ width: c.offsetWidth });
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended } = this.props;
|
||||
const { width } = this.state;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
|
||||
);
|
||||
}
|
||||
|
||||
if (!attachments && isLoading) {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
let loadOlder = null;
|
||||
|
||||
if (hasMore && !(isLoading && attachments.size === 0)) {
|
||||
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />;
|
||||
}
|
||||
|
||||
let emptyMessage;
|
||||
|
||||
if (suspended) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollContainer scrollKey='account_gallery'>
|
||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||
<AccountHeader accountId={this.props.accountId} />
|
||||
|
||||
{(suspended || blockedBy) ? (
|
||||
<div className='empty-column-indicator'>
|
||||
{emptyMessage}
|
||||
</div>
|
||||
) : (
|
||||
<div role='feed' className='account-gallery__container' ref={this.handleRef}>
|
||||
{attachments.map((attachment, index) => attachment === null ? (
|
||||
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
|
||||
) : (
|
||||
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
|
||||
))}
|
||||
|
||||
{loadOlder}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && attachments.size === 0 && (
|
||||
<div className='scrollable__append'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollContainer>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountGallery);
|
283
app/javascript/mastodon/features/account_gallery/index.tsx
Normal file
283
app/javascript/mastodon/features/account_gallery/index.tsx
Normal file
|
@ -0,0 +1,283 @@
|
|||
import { useEffect, useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import { TimelineHint } from 'mastodon/components/timeline_hint';
|
||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
||||
import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
import Column from 'mastodon/features/ui/components/column';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { MediaItem } from './components/media_item';
|
||||
|
||||
const getAccountGallery = createSelector(
|
||||
[
|
||||
(state: RootState, accountId: string) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn(
|
||||
[`account:${accountId}:media`, 'items'],
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
(state: RootState) => state.statuses,
|
||||
],
|
||||
(statusIds, statuses) => {
|
||||
let items = ImmutableList<MediaAttachment>();
|
||||
|
||||
statusIds.forEach((statusId) => {
|
||||
const status = statuses.get(statusId) as
|
||||
| ImmutableMap<string, unknown>
|
||||
| undefined;
|
||||
|
||||
if (status) {
|
||||
items = items.concat(
|
||||
(
|
||||
status.get('media_attachments') as ImmutableList<MediaAttachment>
|
||||
).map((media) => media.set('status', status)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
);
|
||||
|
||||
interface Params {
|
||||
acct?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const RemoteHint: React.FC<{
|
||||
accountId: string;
|
||||
}> = ({ accountId }) => {
|
||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const acct = account?.acct;
|
||||
const url = account?.url;
|
||||
const domain = acct ? acct.split('@')[1] : undefined;
|
||||
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TimelineHint
|
||||
url={url}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='hints.profiles.posts_may_be_missing'
|
||||
defaultMessage='Some posts from this profile may be missing.'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='hints.profiles.see_more_posts'
|
||||
defaultMessage='See more posts on {domain}'
|
||||
values={{ domain: <strong>{domain}</strong> }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountGallery: React.FC<{
|
||||
multiColumn: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
const { acct, id } = useParams<Params>();
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useAppSelector(
|
||||
(state) =>
|
||||
id ??
|
||||
(state.accounts_map.get(normalizeForLookup(acct)) as string | undefined),
|
||||
);
|
||||
const attachments = useAppSelector((state) =>
|
||||
accountId
|
||||
? getAccountGallery(state, accountId)
|
||||
: ImmutableList<MediaAttachment>(),
|
||||
);
|
||||
const isLoading = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'isLoading',
|
||||
]),
|
||||
);
|
||||
const hasMore = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'hasMore',
|
||||
]),
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
);
|
||||
const blockedBy = useAppSelector(
|
||||
(state) =>
|
||||
state.relationships.getIn([accountId, 'blocked_by'], false) as boolean,
|
||||
);
|
||||
const suspended = useAppSelector(
|
||||
(state) => state.accounts.getIn([accountId, 'suspended'], false) as boolean,
|
||||
);
|
||||
const isAccount = !!account;
|
||||
const remote = account?.acct !== account?.username;
|
||||
const hidden = useAppSelector((state) =>
|
||||
accountId ? getAccountHidden(state, accountId) : false,
|
||||
);
|
||||
const maxId = attachments.last()?.getIn(['status', 'id']) as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId) {
|
||||
dispatch(lookupAccount(acct));
|
||||
}
|
||||
}, [dispatch, accountId, acct]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && !isAccount) {
|
||||
dispatch(fetchAccount(accountId));
|
||||
}
|
||||
|
||||
if (accountId && isAccount) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId));
|
||||
}
|
||||
}, [dispatch, accountId, isAccount]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (maxId) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId, { maxId }));
|
||||
}
|
||||
}, [dispatch, accountId, maxId]);
|
||||
|
||||
const handleOpenMedia = useCallback(
|
||||
(attachment: MediaAttachment) => {
|
||||
const statusId = attachment.getIn(['status', 'id']);
|
||||
const lang = attachment.getIn(['status', 'language']);
|
||||
|
||||
if (attachment.get('type') === 'video') {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'VIDEO',
|
||||
modalProps: {
|
||||
media: attachment,
|
||||
statusId,
|
||||
lang,
|
||||
options: { autoPlay: true },
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'AUDIO',
|
||||
modalProps: {
|
||||
media: attachment,
|
||||
statusId,
|
||||
lang,
|
||||
options: { autoPlay: true },
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const media = attachment.getIn([
|
||||
'status',
|
||||
'media_attachments',
|
||||
]) as ImmutableList<MediaAttachment>;
|
||||
const index = media.findIndex(
|
||||
(x) => x.get('id') === attachment.get('id'),
|
||||
);
|
||||
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'MEDIA',
|
||||
modalProps: { media, index, statusId, lang },
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
if (accountId && !isAccount) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
}
|
||||
|
||||
let emptyMessage;
|
||||
|
||||
if (accountId) {
|
||||
if (suspended) {
|
||||
emptyMessage = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_suspended'
|
||||
defaultMessage='Account suspended'
|
||||
/>
|
||||
);
|
||||
} else if (hidden) {
|
||||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_unavailable'
|
||||
defaultMessage='Profile unavailable'
|
||||
/>
|
||||
);
|
||||
} else if (remote && attachments.isEmpty()) {
|
||||
emptyMessage = <RemoteHint accountId={accountId} />;
|
||||
} else {
|
||||
emptyMessage = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_timeline'
|
||||
defaultMessage='No posts found'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const forceEmptyState = suspended || blockedBy || hidden;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton />
|
||||
|
||||
<ScrollableList
|
||||
className='account-gallery__container'
|
||||
prepend={
|
||||
accountId && (
|
||||
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
||||
)
|
||||
}
|
||||
alwaysPrepend
|
||||
append={remote && accountId && <RemoteHint accountId={accountId} />}
|
||||
scrollKey='account_gallery'
|
||||
isLoading={isLoading}
|
||||
hasMore={!forceEmptyState && hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
>
|
||||
{attachments.map((attachment) => (
|
||||
<MediaItem
|
||||
key={attachment.get('id') as string}
|
||||
attachment={attachment}
|
||||
onOpenMedia={handleOpenMedia}
|
||||
/>
|
||||
))}
|
||||
</ScrollableList>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AccountGallery;
|
|
@ -6,7 +6,6 @@ import classNames from 'classnames';
|
|||
import { Helmet } from 'react-helmet';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { useLinks } from '@/hooks/useLinks';
|
||||
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
|
||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
|
@ -46,6 +45,7 @@ import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
|
|||
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
|
||||
import AccountNoteContainer from 'mastodon/features/account/containers/account_note_container';
|
||||
import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
|
||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
|
@ -58,8 +58,8 @@ import {
|
|||
import { getAccountHidden } from 'mastodon/selectors/accounts';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import MemorialNote from './memorial_note';
|
||||
import MovedNote from './moved_note';
|
||||
import { MemorialNote } from './memorial_note';
|
||||
import { MovedNote } from './moved_note';
|
||||
|
||||
const messages = defineMessages({
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
|
@ -833,7 +833,7 @@ export const AccountHeader: React.FC<{
|
|||
<div className='account-timeline__header'>
|
||||
{!hidden && account.memorial && <MemorialNote />}
|
||||
{!hidden && account.moved && (
|
||||
<MovedNote from={account} to={account.moved} />
|
||||
<MovedNote accountId={account.id} targetAccountId={account.moved} />
|
||||
)}
|
||||
|
||||
<div
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const MemorialNote = () => (
|
||||
export const MemorialNote: React.FC = () => (
|
||||
<div className='account-memorial-banner'>
|
||||
<div className='account-memorial-banner__message'>
|
||||
<FormattedMessage id='account.in_memoriam' defaultMessage='In Memoriam.' />
|
||||
<FormattedMessage
|
||||
id='account.in_memoriam'
|
||||
defaultMessage='In Memoriam.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MemorialNote;
|
|
@ -1,39 +0,0 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
|
||||
export default class MovedNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
from: ImmutablePropTypes.map.isRequired,
|
||||
to: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { from, to } = this.props;
|
||||
|
||||
return (
|
||||
<div className='moved-account-banner'>
|
||||
<div className='moved-account-banner__message'>
|
||||
<FormattedMessage id='account.moved_to' defaultMessage='{name} has indicated that their new account is now:' values={{ name: <bdi><strong dangerouslySetInnerHTML={{ __html: from.get('display_name_html') }} /></bdi> }} />
|
||||
</div>
|
||||
|
||||
<div className='moved-account-banner__action'>
|
||||
<Link to={`/@${to.get('acct')}`} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><AvatarOverlay account={to} friend={from} /></div>
|
||||
<DisplayName account={to} />
|
||||
</Link>
|
||||
|
||||
<Link to={`/@${to.get('acct')}`} className='button'><FormattedMessage id='account.go_to_profile' defaultMessage='Go to profile' /></Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { AvatarOverlay } from 'mastodon/components/avatar_overlay';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const MovedNote: React.FC<{
|
||||
accountId: string;
|
||||
targetAccountId: string;
|
||||
}> = ({ accountId, targetAccountId }) => {
|
||||
const from = useAppSelector((state) => state.accounts.get(accountId));
|
||||
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
|
||||
|
||||
return (
|
||||
<div className='moved-account-banner'>
|
||||
<div className='moved-account-banner__message'>
|
||||
<FormattedMessage
|
||||
id='account.moved_to'
|
||||
defaultMessage='{name} has indicated that their new account is now:'
|
||||
values={{
|
||||
name: (
|
||||
<bdi>
|
||||
<strong
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: from?.display_name_html ?? '',
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='moved-account-banner__action'>
|
||||
<Link to={`/@${to?.acct}`} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'>
|
||||
<AvatarOverlay account={to} friend={from} />
|
||||
</div>
|
||||
<DisplayName account={to} />
|
||||
</Link>
|
||||
|
||||
<Link to={`/@${to?.acct}`} className='button'>
|
||||
<FormattedMessage
|
||||
id='account.go_to_profile'
|
||||
defaultMessage='Go to profile'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -6,9 +6,9 @@ import classNames from 'classnames';
|
|||
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import { useSelectableClick } from '@/hooks/useSelectableClick';
|
||||
import QuestionMarkIcon from '@/material-icons/400-24px/question_mark.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useSelectableClick } from 'mastodon/hooks/useSelectableClick';
|
||||
|
||||
const messages = defineMessages({
|
||||
help: { id: 'info_button.label', defaultMessage: 'Help' },
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
|
@ -28,28 +28,30 @@ export const ActionBar = () => {
|
|||
const dispatch = useDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleLogoutClick = useCallback(() => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
}, [dispatch]);
|
||||
const menu = useMemo(() => {
|
||||
const handleLogoutClick = () => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
};
|
||||
|
||||
let menu = [];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
|
||||
menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
|
||||
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
|
||||
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
|
||||
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
menu.push({ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
|
||||
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.logout), action: handleLogoutClick });
|
||||
return ([
|
||||
{ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' },
|
||||
{ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' },
|
||||
{ text: intl.formatMessage(messages.pins), to: '/pinned' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' },
|
||||
{ text: intl.formatMessage(messages.favourites), to: '/favourites' },
|
||||
{ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
|
||||
{ text: intl.formatMessage(messages.lists), to: '/lists' },
|
||||
{ text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
||||
{ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' },
|
||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.logout), action: handleLogoutClick },
|
||||
]);
|
||||
}, [intl, dispatch]);
|
||||
|
||||
return (
|
||||
<DropdownMenuContainer
|
||||
|
|
|
@ -120,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText);
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct');
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -12,11 +12,14 @@ import Overlay from 'react-overlays/Overlay';
|
|||
|
||||
import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import emojiCompressed from 'mastodon/features/emoji/emoji_compressed';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
|
||||
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
|
||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||
|
||||
const nimblePickerData = emojiCompressed[5];
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
|
||||
|
@ -37,15 +40,18 @@ let EmojiPicker, Emoji; // load asynchronously
|
|||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||
|
||||
const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`;
|
||||
const backgroundImageFn = () => `${assetHost}/emoji/sheet_15.png`;
|
||||
|
||||
const notFoundFn = () => (
|
||||
<div className='emoji-mart-no-results'>
|
||||
<Emoji
|
||||
data={nimblePickerData}
|
||||
emoji='sleuth_or_spy'
|
||||
set='twitter'
|
||||
size={32}
|
||||
sheetSize={32}
|
||||
sheetColumns={62}
|
||||
sheetRows={62}
|
||||
backgroundImageFn={backgroundImageFn}
|
||||
/>
|
||||
|
||||
|
@ -67,7 +73,7 @@ class ModifierPickerMenu extends PureComponent {
|
|||
this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.active) {
|
||||
this.attachListeners();
|
||||
} else {
|
||||
|
@ -75,7 +81,7 @@ class ModifierPickerMenu extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
|
@ -85,12 +91,12 @@ class ModifierPickerMenu extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
attachListeners () {
|
||||
attachListeners() {
|
||||
document.addEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
removeListeners () {
|
||||
removeListeners() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
@ -99,17 +105,17 @@ class ModifierPickerMenu extends PureComponent {
|
|||
this.node = c;
|
||||
};
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { active } = this.props;
|
||||
|
||||
return (
|
||||
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
|
||||
<button type='button' onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={1}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={2}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={3}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={4}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={5}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
|
||||
<button type='button' onClick={this.handleClick} data-index={6}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -139,12 +145,12 @@ class ModifierPicker extends PureComponent {
|
|||
this.props.onClose();
|
||||
};
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { active, modifier } = this.props;
|
||||
|
||||
return (
|
||||
<div className='emoji-picker-dropdown__modifiers'>
|
||||
<Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
|
||||
<Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
|
||||
<ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
|
||||
</div>
|
||||
);
|
||||
|
@ -184,7 +190,7 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
|
||||
|
@ -199,7 +205,7 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
@ -252,7 +258,7 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||
this.props.onSkinTone(modifier);
|
||||
};
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
|
||||
|
||||
if (loading) {
|
||||
|
@ -280,6 +286,9 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||
return (
|
||||
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
|
||||
<EmojiPicker
|
||||
data={nimblePickerData}
|
||||
sheetColumns={62}
|
||||
sheetRows={62}
|
||||
perLine={8}
|
||||
emojiSize={22}
|
||||
sheetSize={32}
|
||||
|
@ -345,7 +354,7 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
|
||||
EmojiPickerAsync().then(EmojiMart => {
|
||||
EmojiPicker = EmojiMart.Picker;
|
||||
Emoji = EmojiMart.Emoji;
|
||||
Emoji = EmojiMart.Emoji;
|
||||
|
||||
this.setState({ loading: false });
|
||||
}).catch(() => {
|
||||
|
@ -386,7 +395,7 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
this.setState({ placement: state.placement });
|
||||
};
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const { active, loading, placement } = this.state;
|
||||
|
@ -403,7 +412,7 @@ class EmojiPickerDropdown extends PureComponent {
|
|||
/>
|
||||
|
||||
<Overlay show={active} placement={placement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
|
||||
{({ props, placement })=> (
|
||||
{({ props, placement }) => (
|
||||
<div {...props} style={{ ...props.style }}>
|
||||
<div className={`dropdown-animation ${placement}`}>
|
||||
<EmojiPickerMenu
|
||||
|
|
|
@ -22,10 +22,9 @@ import { LoadMore } from 'mastodon/components/load_more';
|
|||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { RadioButton } from 'mastodon/components/radio_button';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useSearchParam } from '../../../hooks/useSearchParam';
|
||||
|
||||
import { AccountCard } from './components/account_card';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -45,6 +45,7 @@ type EmojiCompressed = [
|
|||
Category[],
|
||||
Data['aliases'],
|
||||
EmojisWithoutShortCodes,
|
||||
Data,
|
||||
];
|
||||
|
||||
/*
|
||||
|
|
|
@ -9,18 +9,91 @@
|
|||
|
||||
// This version comment should be bumped each time the emoji data is changed
|
||||
// to ensure that the prevaled file is regenerated by Babel
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
const { emojiIndex } = require('emoji-mart');
|
||||
let data = require('emoji-mart/data/all.json');
|
||||
// This json file contains the names of the categories.
|
||||
const emojiMart5LocalesData = require('@emoji-mart/data/i18n/en.json');
|
||||
const emojiMart5Data = require('@emoji-mart/data/sets/15/all.json');
|
||||
const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
|
||||
const _ = require('lodash');
|
||||
|
||||
|
||||
const emojiMap = require('./emoji_map.json');
|
||||
// This json file is downloaded from https://github.com/iamcal/emoji-data/
|
||||
// and is used to correct the sheet coordinates since we're using that repo's sheet
|
||||
const emojiSheetData = require('./emoji_sheet.json');
|
||||
const { unicodeToFilename } = require('./unicode_to_filename');
|
||||
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
|
||||
|
||||
if(data.compressed) {
|
||||
data = emojiMartUncompress(data);
|
||||
// Grabbed from `emoji_utils` to avoid circular dependency
|
||||
function unifiedToNative(unified) {
|
||||
let unicodes = unified.split('-'),
|
||||
codePoints = unicodes.map((u) => `0x${u}`);
|
||||
|
||||
return String.fromCodePoint(...codePoints);
|
||||
}
|
||||
|
||||
let data = {
|
||||
compressed: true,
|
||||
categories: emojiMart5Data.categories.map(cat => {
|
||||
return {
|
||||
...cat,
|
||||
name: emojiMart5LocalesData.categories[cat.id]
|
||||
};
|
||||
}),
|
||||
aliases: emojiMart5Data.aliases,
|
||||
emojis: _(emojiMart5Data.emojis).values().map(emoji => {
|
||||
let skin_variations = {};
|
||||
const unified = emoji.skins[0].unified.toUpperCase();
|
||||
const emojiFromRawData = emojiSheetData.find(e => e.unified === unified);
|
||||
|
||||
if (!emojiFromRawData) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (emoji.skins.length > 1) {
|
||||
const [, ...nonDefaultSkins] = emoji.skins;
|
||||
nonDefaultSkins.forEach(skin => {
|
||||
const [matchingRawCodePoints,matchingRawEmoji] = Object.entries(emojiFromRawData.skin_variations).find((pair) => {
|
||||
const [, value] = pair;
|
||||
return value.unified.toLowerCase() === skin.unified;
|
||||
});
|
||||
|
||||
if (matchingRawEmoji && matchingRawCodePoints) {
|
||||
// At the time of writing, the json from `@emoji-mart/data` doesn't have data
|
||||
// for emoji like `woman-heart-woman` with two different skin tones.
|
||||
const skinToneCode = matchingRawCodePoints.split('-')[0];
|
||||
skin_variations[skinToneCode] = {
|
||||
unified: matchingRawEmoji.unified.toUpperCase(),
|
||||
non_qualified: null,
|
||||
sheet_x: matchingRawEmoji.sheet_x,
|
||||
sheet_y: matchingRawEmoji.sheet_y,
|
||||
has_img_twitter: true,
|
||||
native: unifiedToNative(matchingRawEmoji.unified.toUpperCase())
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
a: emoji.name,
|
||||
b: unified,
|
||||
c: undefined,
|
||||
f: true,
|
||||
j: [emoji.id, ...emoji.keywords],
|
||||
k: [emojiFromRawData.sheet_x, emojiFromRawData.sheet_y],
|
||||
m: emoji.emoticons?.[0],
|
||||
l: emoji.emoticons,
|
||||
o: emoji.version,
|
||||
id: emoji.id,
|
||||
skin_variations,
|
||||
native: unifiedToNative(unified.toUpperCase())
|
||||
};
|
||||
}).compact().keyBy(e => e.id).mapValues(e => _.omit(e, 'id')).value()
|
||||
};
|
||||
|
||||
if (data.compressed) {
|
||||
emojiMartUncompress(data);
|
||||
}
|
||||
|
||||
const emojiMartData = data;
|
||||
|
@ -32,15 +105,10 @@ const shortcodeMap = {};
|
|||
const shortCodesToEmojiData = {};
|
||||
const emojisWithoutShortCodes = [];
|
||||
|
||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||
let emoji = emojiIndex.emojis[key];
|
||||
Object.keys(emojiMart5Data.emojis).forEach(key => {
|
||||
let emoji = emojiMart5Data.emojis[key];
|
||||
|
||||
// Emojis with skin tone modifiers are stored like this
|
||||
if (Object.hasOwn(emoji, '1')) {
|
||||
emoji = emoji['1'];
|
||||
}
|
||||
|
||||
shortcodeMap[emoji.native] = emoji.id;
|
||||
shortcodeMap[emoji.skins[0].native] = emoji.id;
|
||||
});
|
||||
|
||||
const stripModifiers = unicode => {
|
||||
|
@ -84,13 +152,9 @@ Object.keys(emojiMap).forEach(key => {
|
|||
}
|
||||
});
|
||||
|
||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
||||
let emoji = emojiIndex.emojis[key];
|
||||
Object.keys(emojiMartData.emojis).forEach(key => {
|
||||
let emoji = emojiMartData.emojis[key];
|
||||
|
||||
// Emojis with skin tone modifiers are stored like this
|
||||
if (Object.hasOwn(emoji, '1')) {
|
||||
emoji = emoji['1'];
|
||||
}
|
||||
|
||||
const { native } = emoji;
|
||||
let { short_names, search, unified } = emojiMartData.emojis[key];
|
||||
|
@ -135,4 +199,5 @@ module.exports = JSON.parse(JSON.stringify([
|
|||
emojiMartData.categories,
|
||||
emojiMartData.aliases,
|
||||
emojisWithoutShortCodes,
|
||||
emojiMartData
|
||||
]));
|
||||
|
|
|
@ -8,14 +8,15 @@ import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
|
|||
import emojiCompressed from './emoji_compressed';
|
||||
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
||||
|
||||
type Emojis = {
|
||||
[key in NonNullable<keyof ShortCodesToEmojiData>]: {
|
||||
type Emojis = Record<
|
||||
NonNullable<keyof ShortCodesToEmojiData>,
|
||||
{
|
||||
native: BaseEmoji['native'];
|
||||
search: Search;
|
||||
short_names: Emoji['short_names'];
|
||||
unified: Emoji['unified'];
|
||||
};
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
const [
|
||||
shortCodesToEmojiData,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
|
||||
import Picker from 'emoji-mart/dist-es/components/picker/picker';
|
||||
import Emoji from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
|
||||
import Picker from 'emoji-mart/dist-es/components/picker/nimble-picker';
|
||||
|
||||
export {
|
||||
Picker,
|
||||
|
|
1
app/javascript/mastodon/features/emoji/emoji_sheet.json
Normal file
1
app/javascript/mastodon/features/emoji/emoji_sheet.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -9,12 +9,13 @@ import type {
|
|||
import emojiCompressed from './emoji_compressed';
|
||||
import { unicodeToFilename } from './unicode_to_filename';
|
||||
|
||||
type UnicodeMapping = {
|
||||
[key in FilenameData[number][0]]: {
|
||||
type UnicodeMapping = Record<
|
||||
FilenameData[number][0],
|
||||
{
|
||||
shortCode: ShortCodesToEmojiDataKey;
|
||||
filename: FilenameData[number][number];
|
||||
};
|
||||
};
|
||||
}
|
||||
>;
|
||||
|
||||
const [
|
||||
shortCodesToEmojiData,
|
||||
|
|
|
@ -17,7 +17,7 @@ export const ColumnSettings: React.FC = () => {
|
|||
|
||||
const dispatch = useAppDispatch();
|
||||
const onChange = useCallback(
|
||||
(key: string, checked: boolean) => {
|
||||
(key: string[], checked: boolean) => {
|
||||
dispatch(changeSetting(['home', ...key], checked));
|
||||
},
|
||||
[dispatch],
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { useSearchParam } from '@/hooks/useSearchParam';
|
||||
import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
|
@ -20,6 +19,7 @@ import { Icon } from 'mastodon/components/icon';
|
|||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import Status from 'mastodon/containers/status_container';
|
||||
import { Search } from 'mastodon/features/compose/components/search';
|
||||
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
|
||||
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@ import { useEffect, useCallback } from 'react';
|
|||
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { useRenderSignal } from 'mastodon/../hooks/useRenderSignal';
|
||||
import { fetchStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||
import { hydrateStore } from 'mastodon/actions/store';
|
||||
import { Router } from 'mastodon/components/router';
|
||||
import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
|
||||
import { useRenderSignal } from 'mastodon/hooks/useRenderSignal';
|
||||
import initialState from 'mastodon/initial_state';
|
||||
import { IntlProvider } from 'mastodon/locales';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
|
||||
|
|
|
@ -221,12 +221,12 @@ export const DetailedStatus: React.FC<{
|
|||
/>
|
||||
);
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0) {
|
||||
} else if (status.get('card')) {
|
||||
media = (
|
||||
<Card
|
||||
sensitive={status.get('sensitive')}
|
||||
onOpenMedia={onOpenMedia}
|
||||
card={status.get('card', null)}
|
||||
card={status.get('card')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,26 +8,31 @@ import {
|
|||
} from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
|
||||
import { apiGetTermsOfService } from 'mastodon/api/instance';
|
||||
import type { ApiTermsOfServiceJSON } from 'mastodon/api_types/instance';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'terms_of_service.title', defaultMessage: 'Terms of Service' },
|
||||
});
|
||||
|
||||
interface Params {
|
||||
date?: string;
|
||||
}
|
||||
|
||||
const TermsOfService: React.FC<{
|
||||
multiColumn: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const { date } = useParams<Params>();
|
||||
const [response, setResponse] = useState<ApiTermsOfServiceJSON>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
apiGetTermsOfService()
|
||||
apiGetTermsOfService(date)
|
||||
.then((data) => {
|
||||
setResponse(data);
|
||||
setLoading(false);
|
||||
|
@ -36,7 +41,7 @@ const TermsOfService: React.FC<{
|
|||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
}, [date]);
|
||||
|
||||
if (!loading && !response) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
|
@ -55,23 +60,60 @@ const TermsOfService: React.FC<{
|
|||
defaultMessage='Terms of Service'
|
||||
/>
|
||||
</h3>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='privacy_policy.last_updated'
|
||||
defaultMessage='Last updated {date}'
|
||||
values={{
|
||||
date: loading ? (
|
||||
<Skeleton width='10ch' />
|
||||
) : (
|
||||
<FormattedDate
|
||||
value={response?.updated_at}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
<p className='prose'>
|
||||
{response?.effective ? (
|
||||
<FormattedMessage
|
||||
id='privacy_policy.last_updated'
|
||||
defaultMessage='Last updated {date}'
|
||||
values={{
|
||||
date: (
|
||||
<FormattedDate
|
||||
value={response.effective_date}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='terms_of_service.effective_as_of'
|
||||
defaultMessage='Effective as of {date}'
|
||||
values={{
|
||||
date: (
|
||||
<FormattedDate
|
||||
value={response?.effective_date}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{response?.succeeded_by && (
|
||||
<>
|
||||
{' · '}
|
||||
<Link to={`/terms-of-service/${response.succeeded_by}`}>
|
||||
<FormattedMessage
|
||||
id='terms_of_service.upcoming_changes_on'
|
||||
defaultMessage='Upcoming changes on {date}'
|
||||
values={{
|
||||
date: (
|
||||
<FormattedDate
|
||||
value={response.succeeded_by}
|
||||
year='numeric'
|
||||
month='short'
|
||||
day='2-digit'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -101,6 +101,7 @@ const EmbedModal: React.FC<{
|
|||
/>
|
||||
|
||||
<iframe
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
frameBorder='0'
|
||||
ref={iframeRef}
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
|
|
|
@ -205,7 +205,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||
<WrappedRoute path='/about' component={About} content={children} />
|
||||
<WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} />
|
||||
<WrappedRoute path='/terms-of-service' component={TermsOfService} content={children} />
|
||||
<WrappedRoute path='/terms-of-service/:date?' component={TermsOfService} content={children} />
|
||||
|
||||
<WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} />
|
||||
<Redirect from='/timelines/public' to='/public' exact />
|
||||
|
|
|
@ -697,6 +697,7 @@
|
|||
"poll_button.remove_poll": "Премахване на анкета",
|
||||
"privacy.change": "Промяна на поверителността на публикация",
|
||||
"privacy.direct.long": "Споменатите в публикацията",
|
||||
"privacy.direct.short": "Частно споменаване",
|
||||
"privacy.private.long": "Само последователите ви",
|
||||
"privacy.private.short": "Последователи",
|
||||
"privacy.public.long": "Всеки във и извън Mastodon",
|
||||
|
@ -871,7 +872,9 @@
|
|||
"subscribed_languages.target": "Промяна на абонираните езици за {target}",
|
||||
"tabs_bar.home": "Начало",
|
||||
"tabs_bar.notifications": "Известия",
|
||||
"terms_of_service.effective_as_of": "В сила от {date}",
|
||||
"terms_of_service.title": "Условия на услугата",
|
||||
"terms_of_service.upcoming_changes_on": "Предстоящи промени на {date}",
|
||||
"time_remaining.days": "{number, plural, one {остава # ден} other {остават # дни}}",
|
||||
"time_remaining.hours": "{number, plural, one {остава # час} other {остават # часа}}",
|
||||
"time_remaining.minutes": "{number, plural, one {остава # минута} other {остават # минути}}",
|
||||
|
|
|
@ -871,7 +871,9 @@
|
|||
"subscribed_languages.target": "Canvia les llengües subscrites per a {target}",
|
||||
"tabs_bar.home": "Inici",
|
||||
"tabs_bar.notifications": "Notificacions",
|
||||
"terms_of_service.effective_as_of": "En vigor a partir de {date}",
|
||||
"terms_of_service.title": "Condicions de servei",
|
||||
"terms_of_service.upcoming_changes_on": "Propers canvis el {date}",
|
||||
"time_remaining.days": "{number, plural, one {# dia restant} other {# dies restants}}",
|
||||
"time_remaining.hours": "{number, plural, one {# hora restant} other {# hores restants}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# minut restant} other {# minuts restants}}",
|
||||
|
|
|
@ -86,10 +86,10 @@
|
|||
"alert.unexpected.message": "Objevila se neočekávaná chyba.",
|
||||
"alert.unexpected.title": "Jejda!",
|
||||
"alt_text_badge.title": "Popisek",
|
||||
"alt_text_modal.add_alt_text": "Přidat alt text",
|
||||
"alt_text_modal.add_alt_text": "Přidat popisek",
|
||||
"alt_text_modal.add_text_from_image": "Přidat text z obrázku",
|
||||
"alt_text_modal.cancel": "Zrušit",
|
||||
"alt_text_modal.change_thumbnail": "Změnit miniaturu",
|
||||
"alt_text_modal.change_thumbnail": "Změnit náhled",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Popište to pro osoby se sluchovým postižením…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Popište to pro osoby se zrakovým postižením…",
|
||||
"alt_text_modal.done": "Hotovo",
|
||||
|
@ -111,7 +111,7 @@
|
|||
"annual_report.summary.most_used_hashtag.none": "Žádné",
|
||||
"annual_report.summary.new_posts.new_posts": "nové příspěvky",
|
||||
"annual_report.summary.percentile.text": "<topLabel>To vás umisťuje do vrcholu</topLabel><percentage></percentage><bottomLabel>{domain} uživatelů.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "To, že jste zdejší smetánka zůstane mezi námi ;).",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "To, že jste zdejší smetánka, zůstane mezi námi ;).",
|
||||
"annual_report.summary.thanks": "Děkujeme, že jste součástí Mastodonu!",
|
||||
"attachments_list.unprocessed": "(nezpracováno)",
|
||||
"audio.hide": "Skrýt zvuk",
|
||||
|
@ -218,10 +218,10 @@
|
|||
"confirmations.logout.confirm": "Odhlásit se",
|
||||
"confirmations.logout.message": "Opravdu se chcete odhlásit?",
|
||||
"confirmations.logout.title": "Odhlásit se?",
|
||||
"confirmations.missing_alt_text.confirm": "Přidat alt text",
|
||||
"confirmations.missing_alt_text.message": "Váš příspěvek obsahuje média bez alt textu. Přidání popisů pomáhá zpřístupnit váš obsah většímu počtu lidí.",
|
||||
"confirmations.missing_alt_text.confirm": "Přidat popisek",
|
||||
"confirmations.missing_alt_text.message": "Váš příspěvek obsahuje média bez popisku. Jejich přidání pomáhá zpřístupnit váš obsah většímu počtu lidí.",
|
||||
"confirmations.missing_alt_text.secondary": "Přesto odeslat",
|
||||
"confirmations.missing_alt_text.title": "Přidat alt text?",
|
||||
"confirmations.missing_alt_text.title": "Přidat popisek?",
|
||||
"confirmations.mute.confirm": "Skrýt",
|
||||
"confirmations.redraft.confirm": "Smazat a přepsat",
|
||||
"confirmations.redraft.message": "Jste si jistí, že chcete odstranit tento příspěvek a vytvořit z něj koncept? Oblíbené a boosty budou ztraceny a odpovědi na původní příspěvek ztratí kontext.",
|
||||
|
@ -261,18 +261,18 @@
|
|||
"domain_block_modal.they_wont_know": "Nebude vědět, že je zablokován*a.",
|
||||
"domain_block_modal.title": "Blokovat doménu?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "Ztratíte {followersCount, plural, one {{followersCountDisplay} sledujícího} few {{followersCountDisplay} sledující} many {{followersCountDisplay} sledujících} other {{followersCountDisplay} sledujících}} a {followingCount, plural, one {{followingCountDisplay} sledovaného} few {{followingCountDisplay} sledované} many {{followingCountDisplay} sledovaných} other {{followingCountDisplay} sledovaných}}.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Ztratíte všechny sledující a lidi, které sledujete z tohoto serveru.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Ztratíte všechny sledující a sledované z tohoto serveru.",
|
||||
"domain_block_modal.you_wont_see_posts": "Neuvidíte příspěvky ani upozornění od uživatelů z tohoto serveru.",
|
||||
"domain_pill.activitypub_lets_connect": "Umožňuje vám spojit se a komunikovat s lidmi nejen na Mastodonu, ale i s dalšími sociálními aplikacemi.",
|
||||
"domain_pill.activitypub_lets_connect": "Umožňuje vám spojit se a komunikovat s lidmi nejen na Mastodonu, ale i na jiných sociálních aplikacích.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub je jako jazyk, kterým Mastodon mluví s jinými sociálními sítěmi.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Handle:",
|
||||
"domain_pill.their_server": "Jejich digitální domov, kde žijí jejich všechny příspěvky.",
|
||||
"domain_pill.their_username": "Jejich jedinečný identikátor na jejich serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.",
|
||||
"domain_pill.their_username": "Jejich jedinečný identifikátor na jejich serveru. Je možné, že na jiných serverech jsou uživatelé se stejným uživatelským jménem.",
|
||||
"domain_pill.username": "Uživatelské jméno",
|
||||
"domain_pill.whats_in_a_handle": "Co obsahuje handle?",
|
||||
"domain_pill.who_they_are": "Protože handle říkají kdo je kdo a také kde, je možné interagovat s lidmi napříč sociálními weby <button>platforem postavených na ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Protože handle říká kdo jsi a kde jsi, mohou s tebou lidé komunikovat napříč sociálními weby <button>platforem postavených na ActivityPub</button>.",
|
||||
"domain_pill.who_they_are": "Protože handly říkají kdo je kdo a také kde, je možné interagovat s lidmi napříč sociálním webem, skládajícím se z <button>platforem postavených na ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Protože handle říká kdo jsi a kde jsi, mohou s tebou komunikovat lidé napříč sociálním webem, skládajícím se z <button>platforem postavených na ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Tvůj handle:",
|
||||
"domain_pill.your_server": "Tvůj digitální domov, kde žijí všechny tvé příspěvky. Nelíbí se ti? Kdykoliv se přesuň na jiný server a vezmi si sebou i své sledující.",
|
||||
"domain_pill.your_username": "Tvůj jedinečný identifikátor na tomto serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.",
|
||||
|
@ -293,7 +293,7 @@
|
|||
"emoji_button.search_results": "Výsledky hledání",
|
||||
"emoji_button.symbols": "Symboly",
|
||||
"emoji_button.travel": "Cestování a místa",
|
||||
"empty_column.account_hides_collections": "Tento uživatel se rozhodl nezveřejňovat tuto informaci",
|
||||
"empty_column.account_hides_collections": "Tento uživatel se rozhodl tuto informaci nezveřejňovat",
|
||||
"empty_column.account_suspended": "Účet je pozastaven",
|
||||
"empty_column.account_timeline": "Nejsou tu žádné příspěvky!",
|
||||
"empty_column.account_unavailable": "Profil není dostupný",
|
||||
|
@ -574,7 +574,7 @@
|
|||
"notification.mention": "Zmínka",
|
||||
"notification.mentioned_you": "{name} vás zmínil",
|
||||
"notification.moderation-warning.learn_more": "Zjistit více",
|
||||
"notification.moderation_warning": "Obdrželi jste moderační varování",
|
||||
"notification.moderation_warning": "Obdrželi jste varování od moderátorů",
|
||||
"notification.moderation_warning.action_delete_statuses": "Některé z vašich příspěvků byly odstraněny.",
|
||||
"notification.moderation_warning.action_disable": "Váš účet je zablokován.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Některé z vašich příspěvků byly označeny jako citlivé.",
|
||||
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Změnit odebírané jazyky na {target}",
|
||||
"tabs_bar.home": "Domů",
|
||||
"tabs_bar.notifications": "Oznámení",
|
||||
"terms_of_service.effective_as_of": "Platné od {date}",
|
||||
"terms_of_service.title": "Podmínky služby",
|
||||
"terms_of_service.upcoming_changes_on": "Nadcházející změny v {date}",
|
||||
"time_remaining.days": "{number, plural, one {Zbývá # den} few {Zbývají # dny} many {Zbývá # dní} other {Zbývá # dní}}",
|
||||
"time_remaining.hours": "{number, plural, one {Zbývá # hodina} few {Zbývají # hodiny} many {Zbývá # hodin} other {Zbývá # hodin}}",
|
||||
"time_remaining.minutes": "{number, plural, one {Zbývá # minuta} few {Zbývají # minuty} many {Zbývá # minut} other {Zbývá # minut}}",
|
||||
|
|
|
@ -562,6 +562,7 @@
|
|||
"notification.favourite": "Ffafriodd {name} eich postiad",
|
||||
"notification.favourite.name_and_others_with_link": "Ffafriodd {name} a <a>{count, plural, one {# arall} other {# arall}}</a> eich postiad",
|
||||
"notification.favourite_pm": "Mae {name} wedi ffefrynnu eich cyfeiriad preifat",
|
||||
"notification.favourite_pm.name_and_others_with_link": "Mae {name} a <a>{count, plural, one {# arall} other {# arall}}</a> wedi hoffi'ch crybwylliad preifat",
|
||||
"notification.follow": "Dilynodd {name} chi",
|
||||
"notification.follow.name_and_others": "Mae {name} a <a>{count, plural, zero {}one {# arall} two {# arall} few {# arall} many {# others} other {# arall}}</a> nawr yn eich dilyn chi",
|
||||
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
|
||||
|
@ -696,6 +697,7 @@
|
|||
"poll_button.remove_poll": "Tynnu pleidlais",
|
||||
"privacy.change": "Addasu preifatrwdd y post",
|
||||
"privacy.direct.long": "Pawb sydd â sôn amdanyn nhw yn y postiad",
|
||||
"privacy.direct.short": "Crybwylliad preifat",
|
||||
"privacy.private.long": "Eich dilynwyr yn unig",
|
||||
"privacy.private.short": "Dilynwyr",
|
||||
"privacy.public.long": "Unrhyw ar ac oddi ar Mastodon",
|
||||
|
@ -870,7 +872,9 @@
|
|||
"subscribed_languages.target": "Newid ieithoedd tanysgrifio {target}",
|
||||
"tabs_bar.home": "Cartref",
|
||||
"tabs_bar.notifications": "Hysbysiadau",
|
||||
"terms_of_service.effective_as_of": "Yn effeithiol ers {date}",
|
||||
"terms_of_service.title": "Telerau Gwasanaeth",
|
||||
"terms_of_service.upcoming_changes_on": "Newidiadau i ddod ar {date}",
|
||||
"time_remaining.days": "{number, plural, one {# diwrnod} other {# diwrnod}} ar ôl",
|
||||
"time_remaining.hours": "{number, plural, one {# awr} other {# awr}} ar ôl",
|
||||
"time_remaining.minutes": "{number, plural, one {# munud} other {# munud}} ar ôl",
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
"column.domain_blocks": "Blokerede domæner",
|
||||
"column.edit_list": "Redigér liste",
|
||||
"column.favourites": "Favoritter",
|
||||
"column.firehose": "Realtids-strømme",
|
||||
"column.firehose": "Aktuelt",
|
||||
"column.follow_requests": "Følgeanmodninger",
|
||||
"column.home": "Hjem",
|
||||
"column.list_members": "Håndtér listemedlemmer",
|
||||
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Skift abonnementssprog for {target}",
|
||||
"tabs_bar.home": "Hjem",
|
||||
"tabs_bar.notifications": "Notifikationer",
|
||||
"terms_of_service.effective_as_of": "Gældende pr. {date}",
|
||||
"terms_of_service.title": "Tjenestevilkår",
|
||||
"terms_of_service.upcoming_changes_on": "Kommende ændringer pr. {date}",
|
||||
"time_remaining.days": "{number, plural, one {# dag} other {# dage}} tilbage",
|
||||
"time_remaining.hours": "{number, plural, one {# time} other {# timer}} tilbage",
|
||||
"time_remaining.minutes": "{number, plural, one {# minut} other {# minutter}} tilbage",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Abonnierte Sprachen für {target} ändern",
|
||||
"tabs_bar.home": "Startseite",
|
||||
"tabs_bar.notifications": "Benachrichtigungen",
|
||||
"terms_of_service.effective_as_of": "Gültig ab {date}",
|
||||
"terms_of_service.title": "Nutzungsbedingungen",
|
||||
"terms_of_service.upcoming_changes_on": "Anstehende Änderungen am {date}",
|
||||
"time_remaining.days": "noch {number, plural, one {# Tag} other {# Tage}}",
|
||||
"time_remaining.hours": "noch {number, plural, one {# Stunde} other {# Stunden}}",
|
||||
"time_remaining.minutes": "noch {number, plural, one {# Minute} other {# Minuten}}",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Change subscribed languages for {target}",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"terms_of_service.effective_as_of": "Effective as of {date}",
|
||||
"terms_of_service.title": "Terms of Service",
|
||||
"terms_of_service.upcoming_changes_on": "Upcoming changes on {date}",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Ŝanĝu abonitajn lingvojn por {target}",
|
||||
"tabs_bar.home": "Hejmo",
|
||||
"tabs_bar.notifications": "Sciigoj",
|
||||
"terms_of_service.effective_as_of": "Ĝi ekvalidas de {date}",
|
||||
"terms_of_service.title": "Kondiĉoj de uzado",
|
||||
"terms_of_service.upcoming_changes_on": "Venontaj ŝanĝoj el {date}",
|
||||
"time_remaining.days": "{number, plural, one {# tago} other {# tagoj}} restas",
|
||||
"time_remaining.hours": "{number, plural, one {# horo} other {# horoj}} restas",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto} other {# minutoj}} restas",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
|
||||
"tabs_bar.home": "Principal",
|
||||
"tabs_bar.notifications": "Notificaciones",
|
||||
"terms_of_service.effective_as_of": "Efectivo a partir de {date}",
|
||||
"terms_of_service.title": "Términos del servicio",
|
||||
"terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
|
||||
"time_remaining.days": "{number, plural,one {queda # día} other {quedan # días}}",
|
||||
"time_remaining.hours": "{number, plural,one {queda # hora} other {quedan # horas}}",
|
||||
"time_remaining.minutes": "{number, plural,one {queda # minuto} other {quedan # minutos}}",
|
||||
|
|
|
@ -195,7 +195,7 @@
|
|||
"compose_form.publish": "Publicar",
|
||||
"compose_form.publish_form": "Nueva publicación",
|
||||
"compose_form.reply": "Respuesta",
|
||||
"compose_form.save_changes": "Actualización",
|
||||
"compose_form.save_changes": "Actualizar",
|
||||
"compose_form.spoiler.marked": "Quitar advertencia de contenido",
|
||||
"compose_form.spoiler.unmarked": "Añadir advertencia de contenido",
|
||||
"compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)",
|
||||
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.notifications": "Notificaciones",
|
||||
"terms_of_service.effective_as_of": "En vigor a partir del {date}",
|
||||
"terms_of_service.title": "Condiciones del servicio",
|
||||
"terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
|
||||
"time_remaining.days": "{number, plural, one {# día restante} other {# días restantes}}",
|
||||
"time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.notifications": "Notificaciones",
|
||||
"terms_of_service.effective_as_of": "En vigor a partir del {date}",
|
||||
"terms_of_service.title": "Términos del servicio",
|
||||
"terms_of_service.upcoming_changes_on": "Próximos cambios el {date}",
|
||||
"time_remaining.days": "{number, plural, one {# día restante} other {# días restantes}}",
|
||||
"time_remaining.hours": "{number, plural, one {# hora restante} other {# horas restantes}}",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto restante} other {# minutos restantes}}",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "خروج از حساب",
|
||||
"confirmations.logout.message": "مطمئنید میخواهید خارج شوید؟",
|
||||
"confirmations.logout.title": "خروج؟",
|
||||
"confirmations.missing_alt_text.confirm": "متن جایگزین را اضافه کنید",
|
||||
"confirmations.missing_alt_text.message": "پست شما حاوی رسانه بدون متن جایگزین است. افزودن توضیحات کمک می کند تا محتوای شما برای افراد بیشتری قابل دسترسی باشد.",
|
||||
"confirmations.missing_alt_text.secondary": "به هر حال پست کن",
|
||||
"confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟",
|
||||
"confirmations.mute.confirm": "خموش",
|
||||
"confirmations.redraft.confirm": "حذف و بازنویسی",
|
||||
"confirmations.redraft.message": "مطمئنید که میخواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویتها و پسندهایش از دست رفته و پاسخها به آن بیمرجع میشود.",
|
||||
|
@ -693,6 +697,7 @@
|
|||
"poll_button.remove_poll": "برداشتن نظرسنجی",
|
||||
"privacy.change": "تغییر محرمانگی فرسته",
|
||||
"privacy.direct.long": "هرکسی که در فرسته نام برده شده",
|
||||
"privacy.direct.short": "ذکر خصوصی",
|
||||
"privacy.private.long": "تنها پیگیرندگانتان",
|
||||
"privacy.private.short": "پیگیرندگان",
|
||||
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
|
||||
|
@ -867,7 +872,9 @@
|
|||
"subscribed_languages.target": "تغییر زبانهای مشترک شده برای {target}",
|
||||
"tabs_bar.home": "خانه",
|
||||
"tabs_bar.notifications": "آگاهیها",
|
||||
"terms_of_service.effective_as_of": "اعمال شده از {date}",
|
||||
"terms_of_service.title": "شرایط خدمات",
|
||||
"terms_of_service.upcoming_changes_on": "تغییرات پیش رو در {date}",
|
||||
"time_remaining.days": "{number, plural, one {# روز} other {# روز}} باقی مانده",
|
||||
"time_remaining.hours": "{number, plural, one {# ساعت} other {# ساعت}} باقی مانده",
|
||||
"time_remaining.minutes": "{number, plural, one {# دقیقه} other {# دقیقه}} باقی مانده",
|
||||
|
|
|
@ -625,7 +625,7 @@
|
|||
"notifications.column_settings.follow_request": "Uudet seurantapyynnöt:",
|
||||
"notifications.column_settings.group": "Ryhmitä",
|
||||
"notifications.column_settings.mention": "Maininnat:",
|
||||
"notifications.column_settings.poll": "Äänestyksen tulokset:",
|
||||
"notifications.column_settings.poll": "Äänestystulokset:",
|
||||
"notifications.column_settings.push": "Puskuilmoitukset",
|
||||
"notifications.column_settings.reblog": "Tehostukset:",
|
||||
"notifications.column_settings.show": "Näytä sarakkeessa",
|
||||
|
@ -639,7 +639,7 @@
|
|||
"notifications.filter.favourites": "Suosikit",
|
||||
"notifications.filter.follows": "Seuraamiset",
|
||||
"notifications.filter.mentions": "Maininnat",
|
||||
"notifications.filter.polls": "Äänestyksen tulokset",
|
||||
"notifications.filter.polls": "Äänestystulokset",
|
||||
"notifications.filter.statuses": "Päivitykset seuraamiltasi käyttäjiltä",
|
||||
"notifications.grant_permission": "Myönnä käyttöoikeus.",
|
||||
"notifications.group": "{count} ilmoitusta",
|
||||
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Vaihda tilattuja kieliä käyttäjältä {target}",
|
||||
"tabs_bar.home": "Koti",
|
||||
"tabs_bar.notifications": "Ilmoitukset",
|
||||
"terms_of_service.effective_as_of": "Tulee voimaan {date}",
|
||||
"terms_of_service.title": "Käyttöehdot",
|
||||
"terms_of_service.upcoming_changes_on": "Tulevia muutoksia {date}",
|
||||
"time_remaining.days": "{number, plural, one {# päivä} other {# päivää}} jäljellä",
|
||||
"time_remaining.hours": "{number, plural, one {# tunti} other {# tuntia}} jäljellä",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuutti} other {# minuuttia}} jäljellä",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Broyt haldaramál fyri {target}",
|
||||
"tabs_bar.home": "Heim",
|
||||
"tabs_bar.notifications": "Fráboðanir",
|
||||
"terms_of_service.effective_as_of": "Galdandi frá {date}",
|
||||
"terms_of_service.title": "Tænastutreytir",
|
||||
"terms_of_service.upcoming_changes_on": "Komandi broytingar {date}",
|
||||
"time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
|
||||
"time_remaining.hours": "{number, plural, one {# tími} other {# tímar}} eftir",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuttur} other {# minuttir}} eftir",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Athraigh teangacha suibscríofa le haghaidh {target}",
|
||||
"tabs_bar.home": "Baile",
|
||||
"tabs_bar.notifications": "Fógraí",
|
||||
"terms_of_service.effective_as_of": "I bhfeidhm ó {date}",
|
||||
"terms_of_service.title": "Téarmaí Seirbhíse",
|
||||
"terms_of_service.upcoming_changes_on": "Athruithe atá le teacht ar {date}",
|
||||
"time_remaining.days": "{number, plural, one {# lá} other {# lá}} fágtha",
|
||||
"time_remaining.hours": "{number, plural, one {# uair} other {# uair}} fágtha",
|
||||
"time_remaining.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}} fágtha",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Cambiar a subscrición a idiomas para {target}",
|
||||
"tabs_bar.home": "Inicio",
|
||||
"tabs_bar.notifications": "Notificacións",
|
||||
"terms_of_service.effective_as_of": "Con efecto desde o {date}",
|
||||
"terms_of_service.title": "Termos do Servizo",
|
||||
"terms_of_service.upcoming_changes_on": "Cambios por vir o {date}",
|
||||
"time_remaining.days": "Remata en {number, plural, one {# día} other {# días}}",
|
||||
"time_remaining.hours": "Remata en {number, plural, one {# hora} other {# horas}}",
|
||||
"time_remaining.minutes": "Remata en {number, plural, one {# minuto} other {# minutos}}",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "שינוי רישום שפה עבור {target}",
|
||||
"tabs_bar.home": "פיד הבית",
|
||||
"tabs_bar.notifications": "התראות",
|
||||
"terms_of_service.effective_as_of": "בתוקף החל מתאריך {date}",
|
||||
"terms_of_service.title": "תנאי השירות",
|
||||
"terms_of_service.upcoming_changes_on": "שינויים עתידיים שיחולו ביום {date}",
|
||||
"time_remaining.days": "נותרו {number, plural, one {# יום} other {# ימים}}",
|
||||
"time_remaining.hours": "נותרו {number, plural, one {# שעה} other {# שעות}}",
|
||||
"time_remaining.minutes": "נותרו {number, plural, one {# דקה} other {# דקות}}",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Feliratkozott nyelvek módosítása {target} esetében",
|
||||
"tabs_bar.home": "Kezdőlap",
|
||||
"tabs_bar.notifications": "Értesítések",
|
||||
"terms_of_service.effective_as_of": "Hatálybalépés dátuma: {date}",
|
||||
"terms_of_service.title": "Felhasználási feltételek",
|
||||
"terms_of_service.upcoming_changes_on": "Érkező változások: {date}",
|
||||
"time_remaining.days": "{number, plural, one {# nap} other {# nap}} van hátra",
|
||||
"time_remaining.hours": "{number, plural, one {# óra} other {# óra}} van hátra",
|
||||
"time_remaining.minutes": "{number, plural, one {# perc} other {# perc}} van hátra",
|
||||
|
|
|
@ -306,7 +306,7 @@
|
|||
"empty_column.favourited_statuses": "Tu non ha alcun message favorite ancora. Quando tu marca un message como favorite, illo apparera hic.",
|
||||
"empty_column.favourites": "Necuno ha ancora marcate iste message como favorite. Quando alcuno lo face, ille apparera hic.",
|
||||
"empty_column.follow_requests": "Tu non ha ancora requestas de sequimento. Quando tu recipe un, illo apparera hic.",
|
||||
"empty_column.followed_tags": "Tu non ha ancora sequite alcun hashtags. Quando tu lo face, illos apparera hic.",
|
||||
"empty_column.followed_tags": "Tu non seque ancora alcun hashtags. Quando tu lo face, illos apparera hic.",
|
||||
"empty_column.hashtag": "Il non ha ancora alcun cosa in iste hashtag.",
|
||||
"empty_column.home": "Tu chronologia de initio es vacue! Seque plus personas pro plenar lo.",
|
||||
"empty_column.list": "Iste lista es ancora vacue. Quando le membros de iste lista publica nove messages, illos apparera hic.",
|
||||
|
@ -358,11 +358,11 @@
|
|||
"follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.",
|
||||
"follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.",
|
||||
"follow_suggestions.hints.most_interactions": "Iste profilo ha recentemente recipite multe attention sur {domain}.",
|
||||
"follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.",
|
||||
"follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente comenciate a sequer.",
|
||||
"follow_suggestions.personalized_suggestion": "Suggestion personalisate",
|
||||
"follow_suggestions.popular_suggestion": "Suggestion personalisate",
|
||||
"follow_suggestions.popular_suggestion_longer": "Popular sur {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha recentemente comenciate a sequer",
|
||||
"follow_suggestions.view_all": "Vider toto",
|
||||
"follow_suggestions.who_to_follow": "Qui sequer",
|
||||
"followed_tags": "Hashtags sequite",
|
||||
|
@ -563,8 +563,8 @@
|
|||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# altere} other {# alteres}}</a> ha marcate tu message como favorite",
|
||||
"notification.favourite_pm": "{name} ha marcate tu mention privte como favorite",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural, one {# altere} other {# alteres}}</a> ha marcate tu mention private como favorite",
|
||||
"notification.follow": "{name} te ha sequite",
|
||||
"notification.follow.name_and_others": "{name} e <a>{count, plural, one {# other} other {# alteres}}</a> te ha sequite",
|
||||
"notification.follow": "{name} te seque",
|
||||
"notification.follow.name_and_others": "{name} e <a>{count, plural, one {# other} other {# alteres}}</a> te seque",
|
||||
"notification.follow_request": "{name} ha requestate de sequer te",
|
||||
"notification.follow_request.name_and_others": "{name} e {count, plural, one {# altere} other {# alteres}} ha demandate de sequer te",
|
||||
"notification.label.mention": "Mention",
|
||||
|
@ -657,7 +657,7 @@
|
|||
"notifications.policy.filter_limited_accounts_title": "Contos moderate",
|
||||
"notifications.policy.filter_new_accounts.hint": "Create in le ultime {days, plural, one {die} other {# dies}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Nove contos",
|
||||
"notifications.policy.filter_not_followers_hint": "Includente le personas que te ha sequite durante minus de {days, plural, one {un die} other {# dies}}",
|
||||
"notifications.policy.filter_not_followers_hint": "Includente le personas que te seque desde minus de {days, plural, one {un die} other {# dies}}",
|
||||
"notifications.policy.filter_not_followers_title": "Personas qui non te seque",
|
||||
"notifications.policy.filter_not_following_hint": "Usque tu les approba manualmente",
|
||||
"notifications.policy.filter_not_following_title": "Personas que tu non seque",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"about.disclaimer": "Mastodon esas libera, publikfonta e komercmarko di Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Expliko nedisponebla",
|
||||
"about.domain_blocks.preamble": "Mastodon generale permisas on vidar kontenajo e interagar kun uzanti de irga altra servilo en fediverso. Existas eceptioni quo facesis che ca partikulara servilo.",
|
||||
"about.domain_blocks.silenced.explanation": "On generale ne vidar profili e kontenajo de ca servilo, se on ne reale trovar o voluntale juntar per sequar.",
|
||||
"about.domain_blocks.silenced.explanation": "On generale ne vidar profili e enhavajo de ca servilo, se on ne intence serchar o voleskar per sequar.",
|
||||
"about.domain_blocks.silenced.title": "Limitizita",
|
||||
"about.domain_blocks.suspended.explanation": "Nula informi de ca servili procedagesos o retenesos o interchanjesos, do irga interago o komuniko kun uzanti de ca servili esas neposibla.",
|
||||
"about.domain_blocks.suspended.title": "Restriktita",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"about.powered_by": "Necentraligita sociala ret quo povigesas da {mastodon}",
|
||||
"about.rules": "Servilreguli",
|
||||
"account.account_note_header": "Personala noto",
|
||||
"account.add_or_remove_from_list": "Insertez o removez de listi",
|
||||
"account.add_or_remove_from_list": "Adjuntar o forigar de listi",
|
||||
"account.badges.bot": "Boto",
|
||||
"account.badges.group": "Grupo",
|
||||
"account.block": "Blokusar @{name}",
|
||||
|
@ -24,12 +24,12 @@
|
|||
"account.direct": "Private mencionez @{name}",
|
||||
"account.disable_notifications": "Cesez avizar me kande @{name} postas",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "Modifikar profilo",
|
||||
"account.edit_profile": "Redaktar profilo",
|
||||
"account.enable_notifications": "Avizez me kande @{name} postas",
|
||||
"account.endorse": "Traito di profilo",
|
||||
"account.featured_tags.last_status_at": "Antea posto ye {date}",
|
||||
"account.featured_tags.last_status_never": "Nula posti",
|
||||
"account.featured_tags.title": "Estalita hashtagi di {name}",
|
||||
"account.featured_tags.title": "Ekstaca gretvorti di {name}",
|
||||
"account.follow": "Sequar",
|
||||
"account.follow_back": "Anke sequez",
|
||||
"account.followers": "Sequanti",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"account.languages": "Chanjez abonita lingui",
|
||||
"account.link_verified_on": "Proprieteso di ca ligilo kontrolesis ye {date}",
|
||||
"account.locked_info": "La privatesostaco di ca konto fixesas quale lokata. Proprietato manue kontrolas personi qui povas sequar.",
|
||||
"account.media": "Medio",
|
||||
"account.media": "Audvidaji",
|
||||
"account.mention": "Mencionar @{name}",
|
||||
"account.moved_to": "{name} indikis ke lua nova konto es nune:",
|
||||
"account.mute": "Celar @{name}",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"account.no_bio": "Deskriptajo ne provizesis.",
|
||||
"account.open_original_page": "Apertez originala pagino",
|
||||
"account.posts": "Mesaji",
|
||||
"account.posts_with_replies": "Posti e respondi",
|
||||
"account.posts_with_replies": "Afishi e respondi",
|
||||
"account.report": "Denuncar @{name}",
|
||||
"account.requested": "Vartante aprobo",
|
||||
"account.requested_follow": "{name} demandis sequar tu",
|
||||
|
@ -86,12 +86,38 @@
|
|||
"alert.unexpected.message": "Neexpektita eroro eventis.",
|
||||
"alert.unexpected.title": "Problemo!",
|
||||
"alt_text_badge.title": "Alternativa texto",
|
||||
"alt_text_modal.add_alt_text": "Adjuntar alternativa texto",
|
||||
"alt_text_modal.add_text_from_image": "Adjuntar texto de imajo",
|
||||
"alt_text_modal.cancel": "Nuligar",
|
||||
"alt_text_modal.change_thumbnail": "Chanjar imajeto",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Priskribar co por personi kun auddeskapableso…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Priskribar co por personi kun viddeskapableso…",
|
||||
"alt_text_modal.done": "Finis",
|
||||
"announcement.announcement": "Anunco",
|
||||
"annual_report.summary.archetype.booster": "La plurrepetanto",
|
||||
"annual_report.summary.archetype.lurker": "La plurcelanto",
|
||||
"annual_report.summary.archetype.oracle": "La pluraktivo",
|
||||
"annual_report.summary.archetype.pollster": "La votinquestoiganto",
|
||||
"annual_report.summary.archetype.replier": "La plurrespondanto",
|
||||
"annual_report.summary.followers.followers": "sequanti",
|
||||
"annual_report.summary.followers.total": "{count} sumo",
|
||||
"annual_report.summary.here_it_is": "Caibe es vua rivido ye {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "maxim prizita mesajo",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "maxim repetita mesajo",
|
||||
"annual_report.summary.highlighted_post.by_replies": "mesajo kun la maxim multa respondi",
|
||||
"annual_report.summary.highlighted_post.possessive": "di {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "maxim uzita aplikajo",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "maxim uzita gretvorto",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nulo",
|
||||
"annual_report.summary.new_posts.new_posts": "nova afishi",
|
||||
"annual_report.summary.percentile.text": "<topLabel>To pozas vu sur la supro </topLabel><percentage></percentage><bottomLabel>di uzanti di {domain}.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ni ne dicas ad Bernio.",
|
||||
"annual_report.summary.thanks": "Danki por partoprenar sur Mastodon!",
|
||||
"attachments_list.unprocessed": "(neprocedita)",
|
||||
"audio.hide": "Celez audio",
|
||||
"block_modal.remote_users_caveat": "Ni questionos {domain} di la servilo por respektar vua decido. Publika posti forsan ankore estas videbla a neenirinta uzanti.",
|
||||
"block_modal.show_less": "Montrar mine",
|
||||
"block_modal.show_more": "Montrar plue",
|
||||
"block_modal.show_more": "Montrar plu",
|
||||
"block_modal.they_cant_mention": "Oli ne povas mencionar o sequar vu.",
|
||||
"block_modal.they_cant_see_posts": "Oli ne povas vidar vua mesaji e vu ne vidos vidar olia.",
|
||||
"block_modal.they_will_know": "Oli povas vidar ke oli esas blokusita.",
|
||||
|
@ -110,6 +136,7 @@
|
|||
"bundle_column_error.routing.body": "Demandita pagino ne povas trovesar. Ka vu certe ke URL en situobuxo esar korekta?",
|
||||
"bundle_column_error.routing.title": "Eroro di 404",
|
||||
"bundle_modal_error.close": "Klozez",
|
||||
"bundle_modal_error.message": "Ulo nefuncionas dum chargar ca skreno.",
|
||||
"bundle_modal_error.retry": "Probez itere",
|
||||
"closed_registrations.other_server_instructions": "Nam Mastodon es descentraligita, on povas krear konto che altra servilo e senegarde interagar kun ca servilo.",
|
||||
"closed_registrations_modal.description": "Nune on ne povas krear konto che {domain}, ma voluntez savar ke on ne bezonas konto specifike che {domain} por uzar Mastodon.",
|
||||
|
@ -118,19 +145,22 @@
|
|||
"closed_registrations_modal.title": "Krear konto che Mastodon",
|
||||
"column.about": "Pri co",
|
||||
"column.blocks": "Blokusita uzeri",
|
||||
"column.bookmarks": "Libromarki",
|
||||
"column.bookmarks": "Lektosigni",
|
||||
"column.community": "Lokala tempolineo",
|
||||
"column.create_list": "Krear listo",
|
||||
"column.direct": "Privata mencioni",
|
||||
"column.directory": "Videz profili",
|
||||
"column.domain_blocks": "Hidden domains",
|
||||
"column.domain_blocks": "Blokusita domeni",
|
||||
"column.edit_list": "Redaktar listo",
|
||||
"column.favourites": "Favoriziti",
|
||||
"column.firehose": "Nuna flui",
|
||||
"column.follow_requests": "Demandi di sequado",
|
||||
"column.home": "Hemo",
|
||||
"column.list_members": "Administrar listomembri",
|
||||
"column.lists": "Listi",
|
||||
"column.mutes": "Celita uzeri",
|
||||
"column.notifications": "Savigi",
|
||||
"column.pins": "Pinned toot",
|
||||
"column.pins": "Adpinglita afishi",
|
||||
"column.public": "Federata tempolineo",
|
||||
"column_back_button.label": "Retro",
|
||||
"column_header.hide_settings": "Celez ajusti",
|
||||
|
@ -139,26 +169,28 @@
|
|||
"column_header.pin": "Pinglagez",
|
||||
"column_header.show_settings": "Montrez ajusti",
|
||||
"column_header.unpin": "Depinglagez",
|
||||
"column_search.cancel": "Nuligar",
|
||||
"column_subheading.settings": "Ajusti",
|
||||
"community.column_settings.local_only": "Lokala nur",
|
||||
"community.column_settings.media_only": "Media only",
|
||||
"community.column_settings.media_only": "Nur audvidaji",
|
||||
"community.column_settings.remote_only": "Fora nur",
|
||||
"compose.language.change": "Chanjez linguo",
|
||||
"compose.language.search": "Trovez linguo...",
|
||||
"compose.published.body": "Posto publikigita.",
|
||||
"compose.published.open": "Apertez",
|
||||
"compose.saved.body": "Posto konservita.",
|
||||
"compose_form.direct_message_warning_learn_more": "Lernez pluse",
|
||||
"compose_form.direct_message_warning_learn_more": "Lernez plu",
|
||||
"compose_form.encryption_warning": "Posti en Mastodon ne intersequante chifrigesas. Ne partigez irga privata informo che Mastodon.",
|
||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
||||
"compose_form.hashtag_warning": "Ca afisho ne listigesos kun irga gretvorto pro ke ol ne es publika.",
|
||||
"compose_form.lock_disclaimer": "Vua konto ne esas {locked}. Irgu povas sequar vu por vidar vua sequanto-nura posti.",
|
||||
"compose_form.lock_disclaimer.lock": "klefagesas",
|
||||
"compose_form.placeholder": "Quo esas en tua spirito?",
|
||||
"compose_form.poll.duration": "Votpostoduro",
|
||||
"compose_form.placeholder": "Quon vu pensas?",
|
||||
"compose_form.poll.duration": "Votinquestoduro",
|
||||
"compose_form.poll.multiple": "Multopla selekteso",
|
||||
"compose_form.poll.option_placeholder": "Selektato {number}",
|
||||
"compose_form.poll.switch_to_multiple": "Chanjez votposto por permisar multiselektaji",
|
||||
"compose_form.poll.switch_to_single": "Chanjez votposto por permisar una selektajo",
|
||||
"compose_form.poll.single": "Unopla selektato",
|
||||
"compose_form.poll.switch_to_multiple": "Chanjar votinquesto por permisar multopla selektati",
|
||||
"compose_form.poll.switch_to_single": "Chanjez votinquesto por permisar unopla selektato",
|
||||
"compose_form.poll.type": "Stilo",
|
||||
"compose_form.publish": "Posto",
|
||||
"compose_form.publish_form": "Publish",
|
||||
|
@ -175,14 +207,21 @@
|
|||
"confirmations.delete_list.confirm": "Efacez",
|
||||
"confirmations.delete_list.message": "Ka vu certe volas netempale efacar ca listo?",
|
||||
"confirmations.delete_list.title": "Ka efacar listo?",
|
||||
"confirmations.discard_edit_media.confirm": "Efacez",
|
||||
"confirmations.discard_edit_media.message": "Vu havas nesparita chanji di mediodeskript o prevido, vu volas jus efacar?",
|
||||
"confirmations.discard_edit_media.confirm": "Forigar",
|
||||
"confirmations.discard_edit_media.message": "Vu havas nekonservita chanji di audvidajpriskribo o prevido, ka forigas ili irgakaze?",
|
||||
"confirmations.edit.confirm": "Modifikez",
|
||||
"confirmations.edit.message": "Modifikar nun remplasos la mesajo quon vu nune skribas. Ka vu certe volas procedar?",
|
||||
"confirmations.edit.title": "Ka remplasar posto?",
|
||||
"confirmations.follow_to_list.confirm": "Sequar e adjuntar ad listo",
|
||||
"confirmations.follow_to_list.message": "Vu bezonas sequar {name} por adjuntar lu ad listo.",
|
||||
"confirmations.follow_to_list.title": "Ka sequar uzanto?",
|
||||
"confirmations.logout.confirm": "Ekirez",
|
||||
"confirmations.logout.message": "Ka tu certe volas ekirar?",
|
||||
"confirmations.logout.title": "Ka ekirar?",
|
||||
"confirmations.missing_alt_text.confirm": "Adjuntar alternativa texto",
|
||||
"confirmations.missing_alt_text.message": "Vua afisho enhavas audvidaji sen alternativa texto.",
|
||||
"confirmations.missing_alt_text.secondary": "Mesajar irgamaniere",
|
||||
"confirmations.missing_alt_text.title": "Ka adjuntar alternativa texto?",
|
||||
"confirmations.mute.confirm": "Silencigez",
|
||||
"confirmations.redraft.confirm": "Efacez e riskisez",
|
||||
"confirmations.redraft.message": "Ka vu certe volas efacar ca posto e riskisigar ol? Favoriziti e repeti esos perdita, e respondi al posto originala esos orfanigita.",
|
||||
|
@ -195,6 +234,7 @@
|
|||
"confirmations.unfollow.title": "Ka dessequar uzanto?",
|
||||
"content_warning.hide": "Celez posto",
|
||||
"content_warning.show": "Montrez nur",
|
||||
"content_warning.show_more": "Montrar plu",
|
||||
"conversation.delete": "Efacez konverso",
|
||||
"conversation.mark_as_read": "Markizez quale lektita",
|
||||
"conversation.open": "Videz konverso",
|
||||
|
@ -210,6 +250,10 @@
|
|||
"disabled_account_banner.text": "Vua konto {disabledAccount} es nune desaktivigita.",
|
||||
"dismissable_banner.community_timeline": "Co esas maxim recenta publika posti de personi quo havas konto quo hostigesas da {domain}.",
|
||||
"dismissable_banner.dismiss": "Ignorez",
|
||||
"dismissable_banner.explore_links": "Ca novaji es maxim partigita sur fediverso hodie.",
|
||||
"dismissable_banner.explore_statuses": "Ca afishi di tota fediverso populareskis hodie.",
|
||||
"dismissable_banner.explore_tags": "Ca gretvorti populareskas sur fediverso hodie.",
|
||||
"dismissable_banner.public_timeline": "Co es la maxim lastatempa publika afishi di personi sur la fediverso qua personi di {domain} sequas.",
|
||||
"domain_block_modal.block": "Blokusez servilo",
|
||||
"domain_block_modal.block_account_instead": "Blokusez @{name} vice",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Personi de ca servilo povas interagar kun vua desnova posti.",
|
||||
|
@ -239,7 +283,7 @@
|
|||
"emoji_button.custom": "Kustumizato",
|
||||
"emoji_button.flags": "Flagi",
|
||||
"emoji_button.food": "Manjajo & Drinkajo",
|
||||
"emoji_button.label": "Insertar emoji",
|
||||
"emoji_button.label": "Enpozar emocimajo",
|
||||
"emoji_button.nature": "Naturo",
|
||||
"emoji_button.not_found": "Nula tala parigata emojii",
|
||||
"emoji_button.objects": "Kozi",
|
||||
|
@ -257,14 +301,14 @@
|
|||
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
|
||||
"empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
|
||||
"empty_column.direct": "Vu ankore ne havas irga direta mesaji. Kande vu sendos o recevos un, ol montresos hike.",
|
||||
"empty_column.domain_blocks": "There are no hidden domains yet.",
|
||||
"empty_column.explore_statuses": "Nulo esas tendenca nun. Videz itere pose!",
|
||||
"empty_column.domain_blocks": "Ne havas blokusita domeni ankore.",
|
||||
"empty_column.explore_statuses": "Nulo populareskas nun.",
|
||||
"empty_column.favourited_statuses": "Vu ankore ne havas irga posti favorizita. Kande vu favorizos un, ol montresos hike.",
|
||||
"empty_column.favourites": "Nulu favorizis ca posto. Kande ulu favorizis ol, lu montresos hike.",
|
||||
"empty_column.follow_requests": "Vu ne havas irga sequodemandi til nun. Kande vu ganas talo, ol montresos hike.",
|
||||
"empty_column.followed_tags": "Vu ankore ne sequis irga hashtago. Kande vu sequos un, ol montresos hike.",
|
||||
"empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
|
||||
"empty_column.home": "Vua hemtempolineo esas vakua! Sequez plu multa personi por plenigar lu. {suggestions}",
|
||||
"empty_column.home": "Vua hemtempolineo esas desplena!",
|
||||
"empty_column.list": "There is nothing in this list yet.",
|
||||
"empty_column.mutes": "Vu ne silencigis irga uzanti til nun.",
|
||||
"empty_column.notification_requests": "Finis. Kande vu recevas nova savigi, oli aparos hike segun vua preferaji.",
|
||||
|
@ -273,7 +317,7 @@
|
|||
"error.unexpected_crash.explanation": "Pro eroro en nia kodexo o vidilkonciliebloproblemo, ca pagino ne povas korekte montresar.",
|
||||
"error.unexpected_crash.explanation_addons": "Ca pagino ne povas korekte montresar. Ca eroro posible kauzigesas vidilplusajo o automata tradukutensili.",
|
||||
"error.unexpected_crash.next_steps": "Probez rifreshar pagino. Se to ne helpas, vu forsan ankore povas uzar Mastodon per diferenta vidilo o provizita softwaro.",
|
||||
"error.unexpected_crash.next_steps_addons": "Probez desaktivigar e rifreshar pagino. Se to ne helpas, vu forsan ankore povas uzar Mastodon per diferenta vidilo o provizita softwaro.",
|
||||
"error.unexpected_crash.next_steps_addons": "Probez desebligar ili e rifreshar la pagino.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Kopiez amastraso a klipplanko",
|
||||
"errors.unexpected_crash.report_issue": "Reportigez problemo",
|
||||
"explore.suggested_follows": "Personi",
|
||||
|
@ -281,13 +325,13 @@
|
|||
"explore.trending_links": "Novaji",
|
||||
"explore.trending_statuses": "Posti",
|
||||
"explore.trending_tags": "Hashtagi",
|
||||
"filter_modal.added.context_mismatch_explanation": "Ca filtrilgrupo ne relatesas kun informo de ca acesesita posto. Se vu volas posto filtresar kun ca informo anke, vu bezonas modifikar filtrilo.",
|
||||
"filter_modal.added.context_mismatch_explanation": "Ca filtrilgrupo ne uzesis ad informo di ca adirita afisho.",
|
||||
"filter_modal.added.context_mismatch_title": "Kontenajneparigeso!",
|
||||
"filter_modal.added.expired_explanation": "Ca filtrilgrupo expiris, vu bezonas chanjar expirtempo por apliko.",
|
||||
"filter_modal.added.expired_title": "Expirinta filtrilo!",
|
||||
"filter_modal.added.review_and_configure": "Por kontrolar e plue ajustar ca filtrilgrupo, irez a {settings_link}.",
|
||||
"filter_modal.added.review_and_configure_title": "Filtrilopcioni",
|
||||
"filter_modal.added.settings_link": "opcionpagino",
|
||||
"filter_modal.added.review_and_configure": "Por kontrolar e plu ajustar ca filtrilgrupo, irez a {settings_link}.",
|
||||
"filter_modal.added.review_and_configure_title": "Filtrilpreferaji",
|
||||
"filter_modal.added.settings_link": "preferajpagino",
|
||||
"filter_modal.added.short_explanation": "Ca posto adjuntesas a ca filtrilgrupo: {title}.",
|
||||
"filter_modal.added.title": "Filtrilo adjuntesas!",
|
||||
"filter_modal.select_filter.context_mismatch": "ne relatesas kun ca informo",
|
||||
|
@ -297,6 +341,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Usez disponebla grupo o kreez novajo",
|
||||
"filter_modal.select_filter.title": "Filtragez ca posto",
|
||||
"filter_modal.title.status": "Filtragez posto",
|
||||
"filter_warning.matches_filter": "Samas kam filtrilo \"<span>{title}</span>\"",
|
||||
"filtered_notifications_banner.pending_requests": "De {count, plural,=0 {nulu} one {1 persono} other {# personi}} quan vu forsan konocas",
|
||||
"filtered_notifications_banner.title": "Filtrilita savigi",
|
||||
"firehose.all": "Omno",
|
||||
|
@ -306,7 +351,7 @@
|
|||
"follow_request.reject": "Refuzar",
|
||||
"follow_requests.unlocked_explanation": "Quankam vua konto ne klefklozesis, la {domain} laborero pensas ke vu forsan volas kontralar sequodemandi de ca konti manuale.",
|
||||
"follow_suggestions.curated_suggestion": "Selektato de jeranto",
|
||||
"follow_suggestions.dismiss": "Ne montrez pluse",
|
||||
"follow_suggestions.dismiss": "Ne montrez denove",
|
||||
"follow_suggestions.featured_longer": "Selektesis da la grupo di {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "Populara inter personi quan vu sequas",
|
||||
"follow_suggestions.hints.featured": "Ca profilo selektesis da la grupo di {domain}.",
|
||||
|
@ -328,13 +373,15 @@
|
|||
"footer.privacy_policy": "Guidilo pri privateso",
|
||||
"footer.source_code": "Vidar la fontokodexo",
|
||||
"footer.status": "Stando",
|
||||
"footer.terms_of_service": "Serveskondicioni",
|
||||
"generic.saved": "Sparesis",
|
||||
"getting_started.heading": "Debuto",
|
||||
"hashtag.admin_moderation": "Desklozar administrointervizajo por #{name}",
|
||||
"hashtag.column_header.tag_mode.all": "e {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "o {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "sen {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "Nula sugestati",
|
||||
"hashtag.column_settings.select.placeholder": "Insertez hashtagi…",
|
||||
"hashtag.column_settings.select.placeholder": "Insertez gretvorti…",
|
||||
"hashtag.column_settings.tag_mode.all": "Omna co",
|
||||
"hashtag.column_settings.tag_mode.any": "Irga co",
|
||||
"hashtag.column_settings.tag_mode.none": "Nula co",
|
||||
|
@ -342,8 +389,8 @@
|
|||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} partoprenanto} other {{counter} partoprenanti}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} posto} other {{counter} posti}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} posto} other {{counter} posti}} hodie",
|
||||
"hashtag.follow": "Sequez hashtago",
|
||||
"hashtag.unfollow": "Desequez hashtago",
|
||||
"hashtag.follow": "Sequar gretvorto",
|
||||
"hashtag.unfollow": "Dessequar gretvorto",
|
||||
"hashtags.and_other": "…e {count, plural, one {# plusa}other {# plusa}}",
|
||||
"hints.profiles.followers_may_be_missing": "Sequanti di ca profilo forsan ne esas hike.",
|
||||
"hints.profiles.follows_may_be_missing": "Sequati di ca profilo forsan ne esas hike.",
|
||||
|
@ -371,12 +418,23 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ka ignorar savigi de personi qua ne sequas vu?",
|
||||
"ignore_notifications_modal.not_following_title": "Ka ignorar savigi de personi quan vu ne sequas?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ka ignorar savigi de nekonocita privata mencionii?",
|
||||
"info_button.label": "Helpo",
|
||||
"info_button.what_is_alt_text": "<h1>Quo alternativa texto es?</h1> <p>On povas bonigar adireblo e kompreno por omno per skribar klara, deslonga e fakta alternative texto.</p>",
|
||||
"interaction_modal.action.favourite": "Por durar, vu bezonas prizar de vua konto.",
|
||||
"interaction_modal.action.follow": "Por durar, vu bezonas sequar de vua konto.",
|
||||
"interaction_modal.action.reblog": "Por durar, vu bezonas riblogar de vua konto.",
|
||||
"interaction_modal.action.reply": "Por durar, vu bezonas respondar de vua konto.",
|
||||
"interaction_modal.action.vote": "Por durar, vu bezonas vocdonar de vua konto.",
|
||||
"interaction_modal.go": "Irar",
|
||||
"interaction_modal.no_account_yet": "Ka vu ne havas konto ankore?",
|
||||
"interaction_modal.on_another_server": "Che diferanta servilo",
|
||||
"interaction_modal.on_this_server": "Che ca servilo",
|
||||
"interaction_modal.title.favourite": "Favorizez ca posto da {name}",
|
||||
"interaction_modal.title.follow": "Sequez {name}",
|
||||
"interaction_modal.title.reblog": "Repetez posto di {name}",
|
||||
"interaction_modal.title.reply": "Respondez posto di {name}",
|
||||
"interaction_modal.title.vote": "Votar sur votinquesto di {name}",
|
||||
"interaction_modal.username_prompt": "Exemple {example}",
|
||||
"intervals.full.days": "{number, plural, one {# dio} other {# dii}}",
|
||||
"intervals.full.hours": "{number, plural, one {# horo} other {# hori}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minuto} other {# minuti}}",
|
||||
|
@ -401,8 +459,8 @@
|
|||
"keyboard_shortcuts.muted": "to open muted users list",
|
||||
"keyboard_shortcuts.my_profile": "to open your profile",
|
||||
"keyboard_shortcuts.notifications": "to open notifications column",
|
||||
"keyboard_shortcuts.open_media": "to open media",
|
||||
"keyboard_shortcuts.pinned": "to open pinned toots list",
|
||||
"keyboard_shortcuts.open_media": "Desklozar audvidaji",
|
||||
"keyboard_shortcuts.pinned": "Desklozar listo di adpinglita afishi",
|
||||
"keyboard_shortcuts.profile": "to open author's profile",
|
||||
"keyboard_shortcuts.reply": "to reply",
|
||||
"keyboard_shortcuts.requests": "to open follow requests list",
|
||||
|
@ -410,8 +468,9 @@
|
|||
"keyboard_shortcuts.spoilers": "to show/hide CW field",
|
||||
"keyboard_shortcuts.start": "to open \"get started\" column",
|
||||
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Montrar/celar audvidaji",
|
||||
"keyboard_shortcuts.toot": "to start a brand new toot",
|
||||
"keyboard_shortcuts.translate": "por tradukar mesajo",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "to move up in the list",
|
||||
"lightbox.close": "Klozar",
|
||||
|
@ -424,14 +483,35 @@
|
|||
"link_preview.author": "Da {name}",
|
||||
"link_preview.more_from_author": "Plua de {name}",
|
||||
"link_preview.shares": "{count, plural,one {{counter} posto} other {{counter} posti}}",
|
||||
"lists.add_member": "Adjuntar",
|
||||
"lists.add_to_list": "Adjuntar ad listo",
|
||||
"lists.add_to_lists": "Adjuntar {name} ad listi",
|
||||
"lists.create": "Krear",
|
||||
"lists.create_a_list_to_organize": "Krear nova listo por organizar vua hemfluo",
|
||||
"lists.create_list": "Krear listo",
|
||||
"lists.delete": "Efacez listo",
|
||||
"lists.edit": "Modifikez listo",
|
||||
"lists.done": "Finis",
|
||||
"lists.edit": "Redaktar listo",
|
||||
"lists.exclusive": "Celar membri sur Hemo",
|
||||
"lists.exclusive_hint": "Se ulu es en ca listo, celez lu sur vua hemfluo por evitar vidar lua afishi denove.",
|
||||
"lists.find_users_to_add": "Serchi uzanti por adjuntar",
|
||||
"lists.list_members": "Listigar membri",
|
||||
"lists.list_members_count": "{count, plural,one {# membro} other {#membri}}",
|
||||
"lists.list_name": "Listonomo",
|
||||
"lists.new_list_name": "Nova listonomo",
|
||||
"lists.no_lists_yet": "Nula listi ankore.",
|
||||
"lists.no_members_yet": "Nula membri ankore.",
|
||||
"lists.no_results_found": "Nula rezulto trovigesis.",
|
||||
"lists.remove_member": "Forigar",
|
||||
"lists.replies_policy.followed": "Irga sequita uzanto",
|
||||
"lists.replies_policy.list": "Membro di listo",
|
||||
"lists.replies_policy.none": "Nulu",
|
||||
"lists.save": "Konservar",
|
||||
"lists.search": "Serchar",
|
||||
"lists.show_replies_to": "Inkluzar respondi de listomembri",
|
||||
"load_pending": "{count, plural, one {# nova kozo} other {# nova kozi}}",
|
||||
"loading_indicator.label": "Kargante…",
|
||||
"media_gallery.hide": "Celez",
|
||||
"media_gallery.hide": "Celar",
|
||||
"moved_to_account_banner.text": "Vua konto {disabledAccount} es nune desaktiva pro ke vu movis a {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Celez de savigi",
|
||||
"mute_modal.hide_options": "Celez preferaji",
|
||||
|
@ -446,12 +526,12 @@
|
|||
"navigation_bar.administration": "Administro",
|
||||
"navigation_bar.advanced_interface": "Apertez per retintervizajo",
|
||||
"navigation_bar.blocks": "Blokusita uzeri",
|
||||
"navigation_bar.bookmarks": "Libromarki",
|
||||
"navigation_bar.bookmarks": "Lektosigni",
|
||||
"navigation_bar.community_timeline": "Lokala tempolineo",
|
||||
"navigation_bar.compose": "Compose new toot",
|
||||
"navigation_bar.direct": "Privata mencioni",
|
||||
"navigation_bar.discover": "Deskovrez",
|
||||
"navigation_bar.domain_blocks": "Hidden domains",
|
||||
"navigation_bar.domain_blocks": "Blokusita domeni",
|
||||
"navigation_bar.explore": "Explorez",
|
||||
"navigation_bar.favourites": "Favoriziti",
|
||||
"navigation_bar.filters": "Silencigita vorti",
|
||||
|
@ -464,7 +544,7 @@
|
|||
"navigation_bar.mutes": "Celita uzeri",
|
||||
"navigation_bar.opened_in_classic_interface": "Posti, konti e altra pagini specifika apertesas en la retovidilo klasika.",
|
||||
"navigation_bar.personal": "Personala",
|
||||
"navigation_bar.pins": "Pinned toots",
|
||||
"navigation_bar.pins": "Adpinglita afishi",
|
||||
"navigation_bar.preferences": "Preferi",
|
||||
"navigation_bar.public_timeline": "Federata tempolineo",
|
||||
"navigation_bar.search": "Serchez",
|
||||
|
@ -477,9 +557,14 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportis {target}",
|
||||
"notification.admin.sign_up": "{name} registresis",
|
||||
"notification.admin.sign_up.name_and_others": "{name} e {count, plural,one {# altru} other {#altri}} enrejistris",
|
||||
"notification.annual_report.message": "Yen vua ye {year} #Wrapstodon!",
|
||||
"notification.annual_report.view": "Vidar #Wrapstodon",
|
||||
"notification.favourite": "{name} favorizis tua mesajo",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {# altri}}</a> favorizis vua posto",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {# altri}}</a> stelumis vua afisho",
|
||||
"notification.favourite_pm": "{name} prizis vua privata menciono",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} e <a>{count, plural,one {# altro} other {# altri}}</a> prizis vua privata menciono",
|
||||
"notification.follow": "{name} sequeskis tu",
|
||||
"notification.follow.name_and_others": "{name} e <a>{count, plural,one {# altro} other {# altri}}</a> sequis vu",
|
||||
"notification.follow_request": "{name} demandas sequar vu",
|
||||
"notification.follow_request.name_and_others": "{name} e {count, plural,one {# altru} other {# altri}} volas sequar vu",
|
||||
"notification.label.mention": "Mencionez",
|
||||
|
@ -487,37 +572,38 @@
|
|||
"notification.label.private_reply": "Privata respondo",
|
||||
"notification.label.reply": "Respondez",
|
||||
"notification.mention": "Mencionez",
|
||||
"notification.moderation-warning.learn_more": "Lernez pluse",
|
||||
"notification.mentioned_you": "{name} mencionis vu",
|
||||
"notification.moderation-warning.learn_more": "Lernez plu",
|
||||
"notification.moderation_warning": "Vu recevis jeraverto",
|
||||
"notification.moderation_warning.action_delete_statuses": "Kelka vua posti efacesis.",
|
||||
"notification.moderation_warning.action_disable": "Vua konto estas desaktivigita.",
|
||||
"notification.moderation_warning.action_disable": "Vua konto es desaktivigita.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Kelka vua posti markizesis quale sentoza.",
|
||||
"notification.moderation_warning.action_none": "Vua konto recevis jeraverto.",
|
||||
"notification.moderation_warning.action_sensitive": "Vua posti markizesos quale sentoza pos nun.",
|
||||
"notification.moderation_warning.action_silence": "Vua konto limitizesis.",
|
||||
"notification.moderation_warning.action_suspend": "Vua konto restriktesis.",
|
||||
"notification.own_poll": "Vua votposto finigis",
|
||||
"notification.poll": "Votposto quan vu partoprenis finis",
|
||||
"notification.own_poll": "Vua votinquesto fineskis",
|
||||
"notification.poll": "Votinquesto ube vu votis fineskis",
|
||||
"notification.reblog": "{name} repetis tua mesajo",
|
||||
"notification.reblog.name_and_others_with_link": "{name} e <a>{count, plural,one {# altru} other {#altri}}</a> repetis vua posto",
|
||||
"notification.relationships_severance_event": "Desganis konekteso kun {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Administranto de {from} restriktis {target}, do vu ne povas plue recevar novaji de lu o interagar kun lu.",
|
||||
"notification.relationships_severance_event.domain_block": "Administranto de {from} blokusis {target}, e anke {followersCount} de vua sequanti e {followingCount, plural, one {# konto} other {# konti}} quan vu sequas.",
|
||||
"notification.relationships_severance_event.learn_more": "Lernez pluse",
|
||||
"notification.relationships_severance_event.learn_more": "Lernez plu",
|
||||
"notification.relationships_severance_event.user_domain_block": "Vu blokusis {target}, do efacis {followersCount} de vua sequanti e {followingCount, plural, one {# konto} other {#konti}} quan vu sequis.",
|
||||
"notification.status": "{name} nove postigis",
|
||||
"notification.update": "{name} modifikis posto",
|
||||
"notification.update": "{name} redaktis afisho",
|
||||
"notification_requests.accept": "Aceptez",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Aceptar # demando…} other {Aceptar # demandi…}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Aceptar demando} other {Aceptar demandi}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "Vu aceptos {count, plural, one {1 savigdemando} other {# savigdemandi}}. Ka vu certe volas durar?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Ka aceptar savigdemandi?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Ignorez demando} other {Ignorez demandi}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Vu ignoros {count, plural, one {1 savigdemando} other {# savigdemandi}}. Vu ne povas facile ganar {count, plural, one {ol} other {oli}} pluse. Ka vu esas certe ke vu volas durar?",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Vu ignoros {count, plural, one {1 savigdemando} other {# savigdemandi}}. Vu ne povas facile ganar {count, plural, one {ol} other {oli}} denove. Ka vu esas certe ke vu volas durar?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Ka ignorar savigdemandi?",
|
||||
"notification_requests.dismiss": "Ignorez",
|
||||
"notification_requests.dismiss_multiple": "{count, plural,one {Ignorez # demando…} other {Ignorez # demandi…}}",
|
||||
"notification_requests.edit_selection": "Modifikez",
|
||||
"notification_requests.edit_selection": "Redaktar",
|
||||
"notification_requests.exit_selection": "Finas",
|
||||
"notification_requests.explainer_for_limited_account": "Savigi de ca konto filtresis pro ke la konto limitizesis da jeranto.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Savigi de ca konto filtresis pro ke la konto o olua servilo limitizesis da jeranto.",
|
||||
|
@ -537,8 +623,9 @@
|
|||
"notifications.column_settings.filter_bar.category": "Rapidfiltrilbaro",
|
||||
"notifications.column_settings.follow": "Nova sequanti:",
|
||||
"notifications.column_settings.follow_request": "Nova sequodemandi:",
|
||||
"notifications.column_settings.group": "Grupo",
|
||||
"notifications.column_settings.mention": "Mencioni:",
|
||||
"notifications.column_settings.poll": "Votpostorezulti:",
|
||||
"notifications.column_settings.poll": "Votinquestorezulti:",
|
||||
"notifications.column_settings.push": "Pulsavizi",
|
||||
"notifications.column_settings.reblog": "Repeti:",
|
||||
"notifications.column_settings.show": "Montrar en kolumno",
|
||||
|
@ -546,19 +633,19 @@
|
|||
"notifications.column_settings.status": "New toots:",
|
||||
"notifications.column_settings.unread_notifications.category": "Nelektita avizi",
|
||||
"notifications.column_settings.unread_notifications.highlight": "Briligez nelektita avizi",
|
||||
"notifications.column_settings.update": "Modifikati:",
|
||||
"notifications.column_settings.update": "Redaktati:",
|
||||
"notifications.filter.all": "Omna",
|
||||
"notifications.filter.boosts": "Repeti",
|
||||
"notifications.filter.favourites": "Favoriziti",
|
||||
"notifications.filter.follows": "Sequati",
|
||||
"notifications.filter.mentions": "Mencioni",
|
||||
"notifications.filter.polls": "Votpostorezulti",
|
||||
"notifications.filter.polls": "Votinquestorezulti",
|
||||
"notifications.filter.statuses": "Novaji de personi quon vu sequas",
|
||||
"notifications.grant_permission": "Donez permiso.",
|
||||
"notifications.group": "{count} avizi",
|
||||
"notifications.mark_as_read": "Markizez singla avizi quale lektita",
|
||||
"notifications.permission_denied": "Desktopavizi esas nedisplonebla pro antea refuzita vidilpermisdemando",
|
||||
"notifications.permission_denied_alert": "Desktopavizi ne povas aktivigesar pro ke vidilpermiso refuzesis",
|
||||
"notifications.permission_denied_alert": "Komputilsavigi ne povas ebligesar, pro ke retumilpermiso desaceptesis antee",
|
||||
"notifications.permission_required": "Desktopavizi esas nedisplonebla pro ke bezonata permiso ne donesis.",
|
||||
"notifications.policy.accept": "Aceptez",
|
||||
"notifications.policy.accept_hint": "Montrez en savigi",
|
||||
|
@ -577,10 +664,14 @@
|
|||
"notifications.policy.filter_private_mentions_hint": "Filtrita se ol ne esas respondo a vua sua menciono o se vu sequas la sendanto",
|
||||
"notifications.policy.filter_private_mentions_title": "Nekonocita privata mencioni",
|
||||
"notifications.policy.title": "Regular savigi de…",
|
||||
"notifications_permission_banner.enable": "Aktivigez desktopavizi",
|
||||
"notifications_permission_banner.how_to_control": "Por ganar avizi kande Mastodon ne esas apertita, aktivigez dekstopavizi. Vu povas precize regularar quale interakti facas deskstopavizi tra la supera {icon} butono pos oli aktivigesis.",
|
||||
"notifications_permission_banner.enable": "Ebligar komputilsavigi",
|
||||
"notifications_permission_banner.how_to_control": "Por ganar savigi kande Mastodon ne es desklozita, ebligez komputilsavigi.",
|
||||
"notifications_permission_banner.title": "Irga kozo ne pasas vu",
|
||||
"onboarding.follows.back": "Retro",
|
||||
"onboarding.follows.done": "Finis",
|
||||
"onboarding.follows.empty": "Regretinde, nula rezultajo povas montresar nune. Vu povas esforcar serchar, o irar al explorala pagino por trovar personi sequinda, o esforcar itere pose.",
|
||||
"onboarding.follows.search": "Serchar",
|
||||
"onboarding.follows.title": "Sequez personi por komencar",
|
||||
"onboarding.profile.discoverable": "Trovebligez mea profilo",
|
||||
"onboarding.profile.discoverable_hint": "Se vu selektas deskovrebleso che Mastodon, vua posti povas aparar en sercho-rezultaji e populari, e vua profilo forsan sugestesos a personi kun interesi simila a vua.",
|
||||
"onboarding.profile.display_name": "Publika nomo",
|
||||
|
@ -595,28 +686,31 @@
|
|||
"password_confirmation.mismatching": "La konfirmo dil pasvorto ne egalesas",
|
||||
"picture_in_picture.restore": "Retropozez",
|
||||
"poll.closed": "Klozita",
|
||||
"poll.refresh": "Rifreshez",
|
||||
"poll.refresh": "Rifreshar",
|
||||
"poll.reveal": "Vidar rezultaji",
|
||||
"poll.total_people": "{count, plural, one {# persono} other {# personi}}",
|
||||
"poll.total_votes": "{count, plural, one {# voto} other {# voti}}",
|
||||
"poll.vote": "Votez",
|
||||
"poll.vote": "Votar",
|
||||
"poll.voted": "Vu ja votis ca respondo",
|
||||
"poll.votes": "{votes, plural, one {# voto} other {# voti}}",
|
||||
"poll_button.add_poll": "Insertez votposto",
|
||||
"poll_button.remove_poll": "Efacez votposto",
|
||||
"poll_button.add_poll": "Adjuntar votinquesto",
|
||||
"poll_button.remove_poll": "Forigar votinquesto",
|
||||
"privacy.change": "Aranjar privateso di mesaji",
|
||||
"privacy.direct.long": "Omnu quan mencionesis en la posto",
|
||||
"privacy.direct.short": "Privata menciono",
|
||||
"privacy.private.long": "Nur vua sequanti",
|
||||
"privacy.private.short": "Sequanti",
|
||||
"privacy.public.long": "Ulu de e ne de Mastodon",
|
||||
"privacy.public.short": "Publike",
|
||||
"privacy.unlisted.additional": "Co kondutas exakte kam publika, escepte la posto ne aparos en viva novajari o gretiketi, exploro, o sercho di Mastodon, mem se vu esas volunta totkonte.",
|
||||
"privacy.unlisted.additional": "Co kondutas exakte kam publika, ecepte la posto ne aparos en nuna flui o gretvorti, exploro, o sercho di Mastodon, mem se vu esas volunta totkonte.",
|
||||
"privacy.unlisted.long": "Min multa algoritmoridikuli",
|
||||
"privacy.unlisted.short": "Deslauta publiko",
|
||||
"privacy_policy.last_updated": "Antea novajo ye {date}",
|
||||
"privacy_policy.title": "Privatesguidilo",
|
||||
"recommended": "Rekomendata",
|
||||
"refresh": "Rifreshez",
|
||||
"regeneration_indicator.please_stand_by": "Vartez.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Preparas vua hemfluo…",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural, one {# dio} other {# dii}} ante nun",
|
||||
"relative_time.full.hours": "{number, plural, one {# horo} other {# hori}} ante nun",
|
||||
|
@ -628,9 +722,9 @@
|
|||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.today": "hodie",
|
||||
"reply_indicator.attachments": "{count, plural, one {# atachajo} other {# atachaji}}",
|
||||
"reply_indicator.attachments": "{count, plural, one {# addonajo} other {# addonaji}}",
|
||||
"reply_indicator.cancel": "Nihiligar",
|
||||
"reply_indicator.poll": "Votposto",
|
||||
"reply_indicator.poll": "Votinquesto",
|
||||
"report.block": "Restriktez",
|
||||
"report.block_explanation": "Vu ne vidos olia posti. Oli ne povas vidar vua posti o sequar vu. Oli savos ke oli restriktesis.",
|
||||
"report.categories.legal": "Legala",
|
||||
|
@ -665,13 +759,13 @@
|
|||
"report.statuses.title": "Ka existas irga posti quo suportas ca raporto?",
|
||||
"report.submit": "Sendar",
|
||||
"report.target": "Denuncante",
|
||||
"report.thanks.take_action": "Co esas vua opcioni por regularar quo vu vidas che Mastodon:",
|
||||
"report.thanks.take_action": "Yen vua preferaji por regularar quon vu vidas sur Mastodon:",
|
||||
"report.thanks.take_action_actionable": "Dum ke ni kontrolas co, vu povas demarshar kontra @{name}:",
|
||||
"report.thanks.title": "Ka vu ne volas vidar co?",
|
||||
"report.thanks.title_actionable": "Danko por raportizar, ni kontrolos co.",
|
||||
"report.unfollow": "Desequez @{name}",
|
||||
"report.unfollow_explanation": "Vu sequas ca konto. Por ne vidar olia posti en vua hemniuzeto pluse, desequez oli.",
|
||||
"report_notification.attached_statuses": "{count, plural,one {{count} posti} other {{count} posti}} adjuntesas",
|
||||
"report.unfollow_explanation": "Vu sequas ca konto.",
|
||||
"report_notification.attached_statuses": "{count, plural,one {{count} posti} other {{count} posti}} addonita",
|
||||
"report_notification.categories.legal": "Legala",
|
||||
"report_notification.categories.legal_sentence": "deslegala kontenajo",
|
||||
"report_notification.categories.other": "Altra",
|
||||
|
@ -699,9 +793,12 @@
|
|||
"search_popout.user": "uzanto",
|
||||
"search_results.accounts": "Profili",
|
||||
"search_results.all": "Omna",
|
||||
"search_results.hashtags": "Hashtagi",
|
||||
"search_results.hashtags": "Gretvorti",
|
||||
"search_results.no_results": "Nula rezulto.",
|
||||
"search_results.no_search_yet": "Probez serchar afishi, profili o gretvorti.",
|
||||
"search_results.see_all": "Videz omni",
|
||||
"search_results.statuses": "Posti",
|
||||
"search_results.title": "Serchar \"{q}\"",
|
||||
"server_banner.about_active_users": "Personi quo uzas ca servilo dum antea 30 dii (monate aktiva uzanti)",
|
||||
"server_banner.active_users": "aktiva uzanti",
|
||||
"server_banner.administered_by": "Administresis da:",
|
||||
|
@ -716,7 +813,7 @@
|
|||
"status.admin_domain": "Apertez jerintervizajo por {domain}",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
"status.block": "Restriktez @{name}",
|
||||
"status.bookmark": "Libromarko",
|
||||
"status.bookmark": "Lektosigno",
|
||||
"status.cancel_reblog_private": "Desrepetez",
|
||||
"status.cannot_reblog": "Ca posto ne povas repetesar",
|
||||
"status.continued_thread": "Durigita postaro",
|
||||
|
@ -725,34 +822,35 @@
|
|||
"status.detailed_status": "Detala konversvido",
|
||||
"status.direct": "Private mencionez @{name}",
|
||||
"status.direct_indicator": "Privata menciono",
|
||||
"status.edit": "Modifikez",
|
||||
"status.edited": "Recente modifikesis ye {date}",
|
||||
"status.edited_x_times": "Modifikesis {count, plural, one {{count} foyo} other {{count} foyi}}",
|
||||
"status.edit": "Redaktar",
|
||||
"status.edited": "Lastatempe redaktesar ye {date}",
|
||||
"status.edited_x_times": "Redaktesis ye {count, plural, one {{count} foyo} other {{count} foyi}}",
|
||||
"status.embed": "Ganez adherkodexo",
|
||||
"status.favourite": "Favorizar",
|
||||
"status.favourites": "{count, plural, one {favorizo} other {favorizi}}",
|
||||
"status.favourites": "{count, plural, one {stelumo} other {stelumi}}",
|
||||
"status.filter": "Filtragez ca posto",
|
||||
"status.history.created": "{name} kreis ye {date}",
|
||||
"status.history.edited": "{name} modifikis ye {date}",
|
||||
"status.load_more": "Kargar pluse",
|
||||
"status.history.edited": "{name} redaktis ye {date}",
|
||||
"status.load_more": "Kargar plu",
|
||||
"status.media.open": "Klikez por apertar",
|
||||
"status.media.show": "Klikez por montrar",
|
||||
"status.media_hidden": "Kontenajo celita",
|
||||
"status.media_hidden": "Audvidaji es celita",
|
||||
"status.mention": "Mencionar @{name}",
|
||||
"status.more": "Pluse",
|
||||
"status.more": "Plu",
|
||||
"status.mute": "Silencigez @{name}",
|
||||
"status.mute_conversation": "Silencigez konverso",
|
||||
"status.open": "Detaligar ca mesajo",
|
||||
"status.pin": "Pinglagez che profilo",
|
||||
"status.pinned": "Pinned toot",
|
||||
"status.read_more": "Lektez pluse",
|
||||
"status.pinned": "Adpinglita afisho",
|
||||
"status.read_more": "Lektez plu",
|
||||
"status.reblog": "Repetez",
|
||||
"status.reblog_private": "Repetez kun originala videbleso",
|
||||
"status.reblogged_by": "{name} repetis",
|
||||
"status.reblogs": "{count, plural, one {repeto} other {repeti}}",
|
||||
"status.reblogs.empty": "Nulu ja repetis ca posto. Kande ulu facas lo, lu montresos hike.",
|
||||
"status.redraft": "Efacez e riskisigez",
|
||||
"status.remove_bookmark": "Efacez libromarko",
|
||||
"status.remove_bookmark": "Forigar lektosigno",
|
||||
"status.remove_favourite": "Forigar de priziti",
|
||||
"status.replied_in_thread": "Respondesis en postaro",
|
||||
"status.replied_to": "Respondis a {name}",
|
||||
"status.reply": "Respondar",
|
||||
|
@ -761,9 +859,9 @@
|
|||
"status.sensitive_warning": "Trubliva kontenajo",
|
||||
"status.share": "Partigez",
|
||||
"status.show_less_all": "Montrez min por omno",
|
||||
"status.show_more_all": "Montrez pluse por omno",
|
||||
"status.show_more_all": "Montrez plu por omno",
|
||||
"status.show_original": "Montrez originalo",
|
||||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.title.with_attachments": "{user} afishis {attachmentCount, plural, one {addonajo} other {{attachmentCount} addonaji}}",
|
||||
"status.translate": "Tradukez",
|
||||
"status.translated_from_with": "Tradukita de {lang} per {provider}",
|
||||
"status.uncached_media_warning": "Previdajo nedisponebla",
|
||||
|
@ -774,32 +872,33 @@
|
|||
"subscribed_languages.target": "Chanjez abonita lingui por {target}",
|
||||
"tabs_bar.home": "Hemo",
|
||||
"tabs_bar.notifications": "Savigi",
|
||||
"terms_of_service.title": "Servtermini",
|
||||
"time_remaining.days": "{number, plural, one {# dio} other {# dii}} restas",
|
||||
"time_remaining.hours": "{number, plural, one {# horo} other {# hori}} restas",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto} other {# minuti}} restas",
|
||||
"time_remaining.moments": "Poka sekundi restas",
|
||||
"time_remaining.seconds": "{number, plural, one {# sekundo} other {# sekundi}} restas",
|
||||
"trends.counter_by_accounts": "{count, plural,one {{counter} persono} other {{counter} personi}} en antea {days, plural,one {dio} other {{days} dii}}",
|
||||
"trends.trending_now": "Tendencigas nun",
|
||||
"trends.trending_now": "Populareskas nun",
|
||||
"ui.beforeunload": "Vua skisato perdesos se vu ekiras Mastodon.",
|
||||
"units.short.billion": "{count}G",
|
||||
"units.short.million": "{count}M",
|
||||
"units.short.thousand": "{count}K",
|
||||
"upload_area.title": "Tranar faligar por kargar",
|
||||
"upload_button.label": "Adjuntar kontenajo",
|
||||
"upload_error.limit": "Failadcharglimito ecesesis.",
|
||||
"upload_error.poll": "Failadchargo ne permisesas kun votposti.",
|
||||
"upload_form.drag_and_drop.instructions": "Por tenar mediatachajo, presez spaco o eniro. Presez spaco o eniro itere por destenar la mediatachajo en olua nova loko, o presez eskapo por anular.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Tiro anulesis. Mediatachajo {item} destenesis.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Mediatachajo {item} destenesis.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Mediatachajo {item} movigesis.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Tenis mediatachajo {item}.",
|
||||
"upload_form.edit": "Modifikez",
|
||||
"upload_progress.label": "Kargante...",
|
||||
"upload_area.title": "Tenar e destenar por adkargar",
|
||||
"upload_button.label": "Adjuntar imaji, video o sondosiero",
|
||||
"upload_error.limit": "Dosieradkarglimito ecesesis.",
|
||||
"upload_error.poll": "Dosieradkargo ne permisesas kun votinquesti.",
|
||||
"upload_form.drag_and_drop.instructions": "Por tenar audvidajaddonajo, presez spaco o eniro. Presez spaco o eniro denove por destenar la audvidajatachajo en olua nova loko, o presez eskapo por nuligar.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Tiro nuligesis.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Audvidajaddonajo {item} destenesis.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Audvidajaddonajo {item} movigesis.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Tenis audvidajaddonajo {item}.",
|
||||
"upload_form.edit": "Redaktar",
|
||||
"upload_progress.label": "Adkargas...",
|
||||
"upload_progress.processing": "Traktante…",
|
||||
"username.taken": "Ta uzantnomo ja es posedita. Provez altro",
|
||||
"video.close": "Klozez video",
|
||||
"video.download": "Deschargez failo",
|
||||
"video.download": "Deschargar dosiero",
|
||||
"video.exit_fullscreen": "Ekirez plena skreno",
|
||||
"video.expand": "Expansez video",
|
||||
"video.fullscreen": "Plena skreno",
|
||||
|
|
|
@ -598,11 +598,11 @@
|
|||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Samþykkja beiðni} other {Samþykkja beiðnir}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "Þú ert að fara að samþykkja {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Ertu viss um að þú viljir halda áfram?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Samþykkja beiðnir um tilkynningar?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afgreiða beiðni} other {Afgreiða beiðnir}}",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Hafna beiðni} other {Hafna beiðnum}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Þú ert að fara að hunsa {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Þú munt ekki eiga auðvelt með að skoða {count, plural, one {hana} other {þær}} aftur síðar. Ertu viss um að þú viljir halda áfram?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Hunsa beiðnir um tilkynningar?",
|
||||
"notification_requests.dismiss": "Afgreiða",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Afgreiða # beiðni…} other {Afgreiða # beiðnir…}}",
|
||||
"notification_requests.dismiss": "Hafna",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Hafna # beiðni…} other {Hafna # beiðnum…}}",
|
||||
"notification_requests.edit_selection": "Breyta",
|
||||
"notification_requests.exit_selection": "Lokið",
|
||||
"notification_requests.explainer_for_limited_account": "Tilkynningar frá þessum notanda hafa verið síaðar þar sem aðgangur hans hefur verið takmarkaður af umsjónarmanni.",
|
||||
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Breyta tungumálum í áskrift fyrir {target}",
|
||||
"tabs_bar.home": "Heim",
|
||||
"tabs_bar.notifications": "Tilkynningar",
|
||||
"terms_of_service.effective_as_of": "Gildir frá og með {date}",
|
||||
"terms_of_service.title": "Þjónustuskilmálar",
|
||||
"terms_of_service.upcoming_changes_on": "Væntanlegar breytingar þann {date}",
|
||||
"time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
|
||||
"time_remaining.hours": "{number, plural, one {# klukkustund} other {# klukkustundir}} eftir",
|
||||
"time_remaining.minutes": "{number, plural, one {# mínúta} other {# mínútur}} eftir",
|
||||
|
|
|
@ -872,7 +872,9 @@
|
|||
"subscribed_languages.target": "Modifica le lingue in cui sei iscritto per {target}",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.notifications": "Notifiche",
|
||||
"terms_of_service.effective_as_of": "In vigore a partire dal giorno {date}",
|
||||
"terms_of_service.title": "Termini di Servizio",
|
||||
"terms_of_service.upcoming_changes_on": "Prossime modifiche nel giorno {date}",
|
||||
"time_remaining.days": "{number, plural, one {# giorno} other {# giorni}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# ora} other {# ore}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minuto} other {# minuti}} left",
|
||||
|
|
|
@ -219,7 +219,7 @@
|
|||
"confirmations.logout.message": "本当にログアウトしますか?",
|
||||
"confirmations.logout.title": "ログアウトしようとしています",
|
||||
"confirmations.missing_alt_text.confirm": "代替テキストを追加",
|
||||
"confirmations.missing_alt_text.message": "あなたの投稿には大体テキストのないメディアが含まれています。説明文を追加することで、より多くの人がコンテンツにアクセスできるようになります。",
|
||||
"confirmations.missing_alt_text.message": "あなたの投稿には代替テキストのないメディアが含まれています。説明文を追加することで、より多くの人がコンテンツにアクセスできるようになります。",
|
||||
"confirmations.missing_alt_text.secondary": "そのまま投稿する",
|
||||
"confirmations.missing_alt_text.title": "代替テキストを追加しますか?",
|
||||
"confirmations.mute.confirm": "ミュート",
|
||||
|
|
|
@ -872,6 +872,7 @@
|
|||
"subscribed_languages.target": "{target}에 대한 구독 언어 변경",
|
||||
"tabs_bar.home": "홈",
|
||||
"tabs_bar.notifications": "알림",
|
||||
"terms_of_service.effective_as_of": "{date}부터 적용됨",
|
||||
"terms_of_service.title": "이용 약관",
|
||||
"time_remaining.days": "{number} 일 남음",
|
||||
"time_remaining.hours": "{number} 시간 남음",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue