mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-23 09:58:07 +01:00
Merge branch 'upstream-main' into develop
Some checks failed
Bundler Audit / security (push) Has been cancelled
Check i18n / check-i18n (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Check formatting / lint (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
Ruby Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Historical data migration test / test (14-alpine) (push) Has been cancelled
Historical data migration test / test (15-alpine) (push) Has been cancelled
Historical data migration test / test (16-alpine) (push) Has been cancelled
Historical data migration test / test (17-alpine) (push) Has been cancelled
Ruby Testing / build (production) (push) Has been cancelled
Ruby Testing / build (test) (push) Has been cancelled
Ruby Testing / test (.ruby-version) (push) Has been cancelled
Ruby Testing / test (3.2) (push) Has been cancelled
Ruby Testing / Libvips tests (push) Has been cancelled
Ruby Testing / End to End testing (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (push) Has been cancelled
Some checks failed
Bundler Audit / security (push) Has been cancelled
Check i18n / check-i18n (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Check formatting / lint (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
Ruby Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Historical data migration test / test (14-alpine) (push) Has been cancelled
Historical data migration test / test (15-alpine) (push) Has been cancelled
Historical data migration test / test (16-alpine) (push) Has been cancelled
Historical data migration test / test (17-alpine) (push) Has been cancelled
Ruby Testing / build (production) (push) Has been cancelled
Ruby Testing / build (test) (push) Has been cancelled
Ruby Testing / test (.ruby-version) (push) Has been cancelled
Ruby Testing / test (3.2) (push) Has been cancelled
Ruby Testing / Libvips tests (push) Has been cancelled
Ruby Testing / End to End testing (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (push) Has been cancelled
This commit is contained in:
commit
a38cc7ec83
395 changed files with 5839 additions and 1618 deletions
|
@ -1 +1 @@
|
|||
3.3.5
|
||||
3.3.6
|
||||
|
|
|
@ -2,6 +2,48 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.3.1] - 2024-10-21
|
||||
|
||||
### Added
|
||||
|
||||
- Add more explicit explanations about author attribution and `fediverse:creator` (#32383 by @ClearlyClaire)
|
||||
- Add ability to group follow notifications in WebUI, can be disabled in the column settings (#32520 by @renchap)
|
||||
- Add back a 6 hours mute duration option (#32522 by @renchap)
|
||||
- Add note about not changing ActiveRecord encryption secrets once they are set (#32413, #32476, #32512, and #32537 by @ClearlyClaire and @mjankowski)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change translation feature to translate to selected regional variant (e.g. pt-BR) if available (#32428 by @c960657)
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove ability to get embed code for remote posts (#32578 by @ClearlyClaire)\
|
||||
Getting the embed code is only reliable for local posts.\
|
||||
It never worked for non-Mastodon servers, and stopped working correctly with the changes made in 4.3.0.\
|
||||
We have therefore decided to remove the menu entry while we investigate solutions.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix follow recommendation moderation page default language when using regional variant (#32580 by @ClearlyClaire)
|
||||
- Fix column-settings spacing in local timeline in advanced view (#32567 by @lindwurm)
|
||||
- Fix broken i18n in text welcome mailer tags area (#32571 by @mjankowski)
|
||||
- Fix missing or incorrect cache-control headers for Streaming server (#32551 by @ThisIsMissEm)
|
||||
- Fix only the first paragraph being displayed in some notifications (#32348 by @ClearlyClaire)
|
||||
- Fix reblog icons on account media view (#32506 by @tribela)
|
||||
- Fix Content-Security-Policy not allowing OpenStack SWIFT object storage URI (#32439 by @kenkiku1021)
|
||||
- Fix back arrow pointing to the incorrect direction in RTL languages (#32485 by @renchap)
|
||||
- Fix streaming server using `REDIS_USERNAME` instead of `REDIS_USER` (#32493 by @ThisIsMissEm)
|
||||
- Fix follow recommendation carrousel scrolling on RTL layouts (#32462 and #32505 by @ClearlyClaire)
|
||||
- Fix follow recommendation suppressions not applying immediately (#32392 by @ClearlyClaire)
|
||||
- Fix language of push notifications (#32415 by @ClearlyClaire)
|
||||
- Fix mute duration not being shown in list of muted accounts in web UI (#32388 by @ClearlyClaire)
|
||||
- Fix “Mark every notification as read” not updating the read marker if scrolled down (#32385 by @ClearlyClaire)
|
||||
- Fix “Mention” appearing for otherwise filtered posts (#32356 by @ClearlyClaire)
|
||||
- Fix notification requests from suspended accounts still being listed (#32354 by @ClearlyClaire)
|
||||
- Fix list edition modal styling (#32358 and #32367 by @ClearlyClaire and @vmstan)
|
||||
- Fix 4 columns barely not fitting on 1920px screen (#32361 by @ClearlyClaire)
|
||||
- Fix icon alignment in applications list (#32293 by @mjankowski)
|
||||
|
||||
## [4.3.0] - 2024-10-08
|
||||
|
||||
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by @mjankowski.
|
||||
|
|
|
@ -12,7 +12,7 @@ ARG BUILDPLATFORM=${BUILDPLATFORM}
|
|||
|
||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
ARG RUBY_VERSION="3.3.5"
|
||||
ARG RUBY_VERSION="3.3.6"
|
||||
# # Node 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"
|
||||
|
|
10
Gemfile
10
Gemfile
|
@ -6,7 +6,7 @@ ruby '>= 3.2.0'
|
|||
gem 'propshaft'
|
||||
gem 'puma', '~> 6.3'
|
||||
gem 'rack', '~> 2.2.7'
|
||||
gem 'rails', '~> 7.1.1'
|
||||
gem 'rails', '~> 7.2.0'
|
||||
gem 'thor', '~> 1.2'
|
||||
|
||||
gem 'dotenv'
|
||||
|
@ -25,7 +25,7 @@ gem 'ruby-vips', '~> 2.2', require: false
|
|||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'bootsnap', '~> 1.18.0', require: false
|
||||
gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'chewy', '~> 7.3'
|
||||
gem 'devise', '~> 4.9'
|
||||
|
@ -47,13 +47,14 @@ gem 'color_diff', '~> 0.1'
|
|||
gem 'csv', '~> 3.2'
|
||||
gem 'discard', '~> 1.2'
|
||||
gem 'doorkeeper', '~> 5.6'
|
||||
gem 'faraday-httpclient'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'fastimage'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 5.2.0'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.7.0'
|
||||
gem 'httplog', '~> 1.7.0', require: false
|
||||
gem 'i18n'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'inline_svg'
|
||||
|
@ -62,6 +63,7 @@ gem 'kaminari', '~> 1.2'
|
|||
gem 'link_header', '~> 0.0'
|
||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||
gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
|
||||
gem 'mutex_m'
|
||||
gem 'nokogiri', '~> 1.15'
|
||||
gem 'oj', '~> 3.14'
|
||||
gem 'ox', '~> 2.14'
|
||||
|
@ -220,7 +222,7 @@ gem 'concurrent-ruby', require: false
|
|||
gem 'connection_pool', require: false
|
||||
gem 'xorcist', '~> 1.1'
|
||||
|
||||
gem 'net-http', '~> 0.4.0'
|
||||
gem 'net-http', '~> 0.5.0'
|
||||
gem 'rubyzip', '~> 2.3'
|
||||
|
||||
gem 'hcaptcha', '~> 7.1'
|
||||
|
|
229
Gemfile.lock
229
Gemfile.lock
|
@ -10,51 +10,46 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
actioncable (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
activejob (= 7.1.4.2)
|
||||
activerecord (= 7.1.4.2)
|
||||
activestorage (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
actionview (= 7.1.4.2)
|
||||
activejob (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailbox (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
activejob (= 7.2.2)
|
||||
activerecord (= 7.2.2)
|
||||
activestorage (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
actionview (= 7.2.2)
|
||||
activejob (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.4.2)
|
||||
actionview (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
actionpack (7.2.2)
|
||||
actionview (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
rack (>= 2.2.4, < 3.2)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
activerecord (= 7.1.4.2)
|
||||
activestorage (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
useragent (~> 0.16)
|
||||
actiontext (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
activerecord (= 7.2.2)
|
||||
activestorage (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
actionview (7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
|
@ -64,31 +59,33 @@ GEM
|
|||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
activejob (7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
activerecord (7.1.4.2)
|
||||
activemodel (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
activemodel (7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
activerecord (7.2.2)
|
||||
activemodel (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
activejob (= 7.1.4.2)
|
||||
activerecord (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
activestorage (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
activejob (= 7.2.2)
|
||||
activerecord (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.1.4.2)
|
||||
activesupport (7.2.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
|
@ -100,8 +97,8 @@ GEM
|
|||
attr_required (1.0.2)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.997.0)
|
||||
aws-sdk-core (3.211.0)
|
||||
aws-partitions (1.1004.0)
|
||||
aws-sdk-core (3.212.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
|
@ -109,17 +106,18 @@ GEM
|
|||
aws-sdk-kms (1.95.0)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.169.0)
|
||||
aws-sdk-s3 (1.170.1)
|
||||
aws-sdk-core (~> 3, >= 3.210.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.10.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-blob (0.5.2)
|
||||
azure-blob (0.5.3)
|
||||
rexml
|
||||
base64 (0.2.0)
|
||||
bcp47_spec (0.2.1)
|
||||
bcrypt (3.1.20)
|
||||
benchmark (0.4.0)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
|
@ -133,7 +131,7 @@ GEM
|
|||
msgpack (~> 1.2)
|
||||
brakeman (6.2.2)
|
||||
racc
|
||||
browser (5.3.1)
|
||||
browser (6.1.0)
|
||||
brpoplpush-redis_script (0.1.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
redis (>= 1.0, < 6)
|
||||
|
@ -180,7 +178,7 @@ GEM
|
|||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.4)
|
||||
date (3.4.0)
|
||||
debug (1.9.2)
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
|
@ -191,17 +189,17 @@ GEM
|
|||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (6.0.0)
|
||||
activesupport (~> 7.0)
|
||||
devise-two-factor (6.1.0)
|
||||
activesupport (>= 7.0, < 8.1)
|
||||
devise (~> 4.0)
|
||||
railties (~> 7.0)
|
||||
railties (>= 7.0, < 8.1)
|
||||
rotp (~> 6.0)
|
||||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.5.1)
|
||||
discard (1.3.0)
|
||||
activerecord (>= 4.2, < 8)
|
||||
discard (1.4.0)
|
||||
activerecord (>= 4.2, < 9.0)
|
||||
docile (1.4.1)
|
||||
domain_name (0.6.20240107)
|
||||
doorkeeper (5.7.1)
|
||||
|
@ -229,29 +227,14 @@ GEM
|
|||
fabrication (2.31.0)
|
||||
faker (3.5.1)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday (2.12.0)
|
||||
faraday-net_http (>= 2.0, < 3.4)
|
||||
json
|
||||
logger
|
||||
faraday-httpclient (2.0.1)
|
||||
httpclient (>= 2.2)
|
||||
faraday-net_http (3.3.0)
|
||||
net-http
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.3.1)
|
||||
ffi (1.17.0)
|
||||
|
@ -347,7 +330,7 @@ GEM
|
|||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.7.4)
|
||||
json (2.8.1)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3.1)
|
||||
activesupport (>= 4.2)
|
||||
|
@ -362,7 +345,7 @@ GEM
|
|||
rack (>= 2.2, < 4)
|
||||
rdf (~> 3.3)
|
||||
rexml (~> 3.2)
|
||||
json-ld-preloaded (3.3.0)
|
||||
json-ld-preloaded (3.3.1)
|
||||
json-ld (~> 3.3)
|
||||
rdf (~> 3.3)
|
||||
json-schema (5.0.1)
|
||||
|
@ -424,17 +407,16 @@ GEM
|
|||
mime-types (3.6.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.1001)
|
||||
mime-types-data (3.2024.1105)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.25.1)
|
||||
msgpack (1.7.3)
|
||||
msgpack (1.7.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.4.1)
|
||||
net-http (0.5.0)
|
||||
uri
|
||||
net-imap (0.5.0)
|
||||
net-imap (0.5.1)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.19.0)
|
||||
|
@ -448,7 +430,7 @@ GEM
|
|||
nokogiri (1.16.7)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.6)
|
||||
oj (3.16.7)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
omniauth (2.1.2)
|
||||
|
@ -572,10 +554,10 @@ GEM
|
|||
opentelemetry-semantic_conventions (1.10.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.0)
|
||||
ostruct (0.6.1)
|
||||
ox (2.14.18)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.5.0)
|
||||
parser (3.3.6.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
parslet (2.0.0)
|
||||
|
@ -597,7 +579,7 @@ GEM
|
|||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
railties (>= 7.0.0)
|
||||
psych (5.1.2)
|
||||
psych (5.2.0)
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (6.4.3)
|
||||
|
@ -629,20 +611,20 @@ GEM
|
|||
rackup (1.0.0)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.1.4.2)
|
||||
actioncable (= 7.1.4.2)
|
||||
actionmailbox (= 7.1.4.2)
|
||||
actionmailer (= 7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
actiontext (= 7.1.4.2)
|
||||
actionview (= 7.1.4.2)
|
||||
activejob (= 7.1.4.2)
|
||||
activemodel (= 7.1.4.2)
|
||||
activerecord (= 7.1.4.2)
|
||||
activestorage (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
rails (7.2.2)
|
||||
actioncable (= 7.2.2)
|
||||
actionmailbox (= 7.2.2)
|
||||
actionmailer (= 7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
actiontext (= 7.2.2)
|
||||
actionview (= 7.2.2)
|
||||
activejob (= 7.2.2)
|
||||
activemodel (= 7.2.2)
|
||||
activerecord (= 7.2.2)
|
||||
activestorage (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.1.4.2)
|
||||
railties (= 7.2.2)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
|
@ -657,10 +639,10 @@ GEM
|
|||
rails-i18n (7.0.10)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (7.1.4.2)
|
||||
actionpack (= 7.1.4.2)
|
||||
activesupport (= 7.1.4.2)
|
||||
irb
|
||||
railties (7.2.2)
|
||||
actionpack (= 7.2.2)
|
||||
activesupport (= 7.2.2)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
|
@ -682,7 +664,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.10)
|
||||
reline (0.5.11)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.6.0)
|
||||
rack (>= 1.4)
|
||||
|
@ -691,7 +673,7 @@ GEM
|
|||
railties (>= 5.2)
|
||||
rexml (3.3.9)
|
||||
rotp (6.3.0)
|
||||
rouge (4.4.0)
|
||||
rouge (4.5.1)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
|
@ -711,7 +693,7 @@ GEM
|
|||
rspec-mocks (3.13.2)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (7.0.1)
|
||||
rspec-rails (7.1.0)
|
||||
actionpack (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
|
@ -760,7 +742,6 @@ GEM
|
|||
ruby-vips (2.2.2)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
|
@ -772,6 +753,7 @@ GEM
|
|||
scenic (1.8.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
securerandom (0.3.2)
|
||||
selenium-webdriver (4.26.0)
|
||||
base64 (~> 0.2)
|
||||
logger (~> 1.4)
|
||||
|
@ -812,8 +794,8 @@ GEM
|
|||
stackprof (0.2.26)
|
||||
stoplight (4.1.0)
|
||||
redlock (~> 1.0)
|
||||
stringio (3.1.1)
|
||||
strong_migrations (2.0.2)
|
||||
stringio (3.1.2)
|
||||
strong_migrations (2.1.0)
|
||||
activerecord (>= 6.1)
|
||||
swd (1.3.0)
|
||||
activesupport (>= 3)
|
||||
|
@ -828,7 +810,7 @@ GEM
|
|||
test-prof (1.4.2)
|
||||
thor (1.3.2)
|
||||
tilt (2.4.0)
|
||||
timeout (0.4.1)
|
||||
timeout (0.4.2)
|
||||
tpm-key_attestation (0.12.1)
|
||||
bindata (~> 2.4)
|
||||
openssl (> 2.0)
|
||||
|
@ -855,6 +837,7 @@ GEM
|
|||
unf_ext (0.0.9.1)
|
||||
unicode-display_width (2.6.0)
|
||||
uri (0.13.1)
|
||||
useragent (0.16.10)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
|
@ -884,7 +867,7 @@ GEM
|
|||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.8.2)
|
||||
webrick (1.9.0)
|
||||
websocket (1.2.11)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
|
@ -908,7 +891,7 @@ DEPENDENCIES
|
|||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.18.0)
|
||||
brakeman (~> 6.0)
|
||||
browser (< 6)
|
||||
browser
|
||||
bundler-audit (~> 0.9)
|
||||
capybara (~> 3.39)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
|
@ -930,6 +913,7 @@ DEPENDENCIES
|
|||
email_spec
|
||||
fabrication (~> 2.30)
|
||||
faker (~> 3.2)
|
||||
faraday-httpclient
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
flatware-rspec
|
||||
|
@ -962,7 +946,8 @@ DEPENDENCIES
|
|||
mario-redis-lock (~> 1.2)
|
||||
memory_profiler
|
||||
mime-types (~> 3.6.0)
|
||||
net-http (~> 0.4.0)
|
||||
mutex_m
|
||||
net-http (~> 0.5.0)
|
||||
net-ldap (~> 0.18)
|
||||
nokogiri (~> 1.15)
|
||||
oj (~> 3.14)
|
||||
|
@ -1000,7 +985,7 @@ DEPENDENCIES
|
|||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 2.0)
|
||||
rack-test (~> 2.1)
|
||||
rails (~> 7.1.1)
|
||||
rails (~> 7.2.0)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 7.0)
|
||||
rdf-normalize (~> 0.5)
|
||||
|
|
2
Rakefile
2
Rakefile
|
@ -3,6 +3,6 @@
|
|||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require File.expand_path('config/application', __dir__)
|
||||
require_relative 'config/application'
|
||||
|
||||
Rails.application.load_tasks
|
||||
|
|
|
@ -5,7 +5,7 @@ module Admin
|
|||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
|
||||
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
|
||||
@email_domain_blocks = EmailDomainBlock.parents.includes(:children).order(id: :desc).page(params[:page])
|
||||
@form = Form::EmailDomainBlockBatch.new
|
||||
end
|
||||
|
||||
|
@ -58,10 +58,7 @@ module Admin
|
|||
private
|
||||
|
||||
def set_resolved_records
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
|
||||
end
|
||||
@resolved_records = DomainResource.new(@email_domain_block.domain).mx
|
||||
end
|
||||
|
||||
def resource_params
|
||||
|
|
|
@ -32,7 +32,7 @@ module Admin
|
|||
|
||||
def deactivate_all
|
||||
authorize :invite, :deactivate_all?
|
||||
Invite.available.in_batches.update_all(expires_at: Time.now.utc)
|
||||
Invite.available.in_batches.touch_all(:expires_at)
|
||||
redirect_to admin_invites_path
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ module Admin
|
|||
@relay = Relay.new(resource_params)
|
||||
|
||||
if @relay.save
|
||||
log_action :create, @relay
|
||||
@relay.enable!
|
||||
redirect_to admin_relays_path
|
||||
else
|
||||
|
@ -31,18 +32,21 @@ module Admin
|
|||
def destroy
|
||||
authorize :relay, :update?
|
||||
@relay.destroy
|
||||
log_action :destroy, @relay
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
def enable
|
||||
authorize :relay, :update?
|
||||
@relay.enable!
|
||||
log_action :enable, @relay
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
def disable
|
||||
authorize :relay, :update?
|
||||
@relay.disable!
|
||||
log_action :disable, @relay
|
||||
redirect_to admin_relays_path
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ module Admin
|
|||
|
||||
def show
|
||||
authorize [:admin, @status], :show?
|
||||
|
||||
@status_batch_action = Admin::StatusBatchAction.new
|
||||
end
|
||||
|
||||
def batch
|
||||
|
|
|
@ -17,6 +17,17 @@ class Api::V1::AnnualReportsController < Api::BaseController
|
|||
relationships: @relationships
|
||||
end
|
||||
|
||||
def show
|
||||
with_read_replica do
|
||||
@presenter = AnnualReportsPresenter.new([@annual_report])
|
||||
@relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id)
|
||||
end
|
||||
|
||||
render json: @presenter,
|
||||
serializer: REST::AnnualReportsSerializer,
|
||||
relationships: @relationships
|
||||
end
|
||||
|
||||
def read
|
||||
@annual_report.view!
|
||||
render_empty
|
||||
|
|
|
@ -7,7 +7,6 @@ module WebAppControllerConcern
|
|||
vary_by 'Accept, Accept-Language, Cookie'
|
||||
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
|
||||
content_security_policy do |p|
|
||||
policy = ContentSecurityPolicy.new
|
||||
|
@ -24,10 +23,6 @@ module WebAppControllerConcern
|
|||
!(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
|
||||
end
|
||||
|
||||
def set_app_body_class
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
|
||||
def redirect_unauthenticated_to_permalinks!
|
||||
return if user_signed_in? # NOTE: Different from upstream because we allow moved users to log in
|
||||
|
||||
|
|
|
@ -35,12 +35,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||
end
|
||||
|
||||
def set_last_used_at_by_app
|
||||
@last_used_at_by_app = Doorkeeper::AccessToken
|
||||
.select('DISTINCT ON (application_id) application_id, last_used_at')
|
||||
.where(resource_owner_id: current_resource_owner.id)
|
||||
.where.not(last_used_at: nil)
|
||||
.order(application_id: :desc, last_used_at: :desc)
|
||||
.pluck(:application_id, :last_used_at)
|
||||
.to_h
|
||||
@last_used_at_by_app = current_resource_owner.applications_last_used
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,12 +12,12 @@ module Admin::AccountModerationNotesHelper
|
|||
)
|
||||
end
|
||||
|
||||
def admin_account_inline_link_to(account)
|
||||
def admin_account_inline_link_to(account, path: nil)
|
||||
return if account.nil?
|
||||
|
||||
link_to(
|
||||
account_inline_text(account),
|
||||
admin_account_path(account.id),
|
||||
path || admin_account_path(account.id),
|
||||
class: class_names('inline-name-tag', suspended: suspended_account?(account)),
|
||||
title: account.acct
|
||||
)
|
||||
|
|
|
@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
|
|||
else
|
||||
I18n.t('admin.action_logs.deleted_account')
|
||||
end
|
||||
when 'Relay'
|
||||
link_to log.human_identifier, admin_relays_path
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ module ApplicationHelper
|
|||
|
||||
def html_title
|
||||
safe_join(
|
||||
[content_for(:page_title).to_s.chomp, title]
|
||||
[content_for(:page_title), title]
|
||||
.compact_blank,
|
||||
' - '
|
||||
)
|
||||
|
|
|
@ -16,6 +16,6 @@ module RegistrationHelper
|
|||
end
|
||||
|
||||
def ip_blocked?(remote_ip)
|
||||
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
|
||||
IpBlock.severity_sign_up_block.containing(remote_ip.to_s).exists?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ export const allNotificationTypes = [
|
|||
'admin.report',
|
||||
'moderation_warning',
|
||||
'severed_relationships',
|
||||
'annual_report',
|
||||
];
|
||||
|
||||
export type NotificationWithStatusType =
|
||||
|
@ -39,7 +40,8 @@ export type NotificationType =
|
|||
| 'moderation_warning'
|
||||
| 'severed_relationships'
|
||||
| 'admin.sign_up'
|
||||
| 'admin.report';
|
||||
| 'admin.report'
|
||||
| 'annual_report';
|
||||
|
||||
export interface BaseNotificationJSON {
|
||||
id: string;
|
||||
|
@ -132,6 +134,15 @@ interface AccountRelationshipSeveranceNotificationJSON
|
|||
event: ApiAccountRelationshipSeveranceEventJSON;
|
||||
}
|
||||
|
||||
export interface ApiAnnualReportEventJSON {
|
||||
year: string;
|
||||
}
|
||||
|
||||
interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'annual_report';
|
||||
annual_report: ApiAnnualReportEventJSON;
|
||||
}
|
||||
|
||||
export type ApiNotificationJSON =
|
||||
| SimpleNotificationJSON
|
||||
| ReportNotificationJSON
|
||||
|
@ -144,7 +155,8 @@ export type ApiNotificationGroupJSON =
|
|||
| ReportNotificationGroupJSON
|
||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||
| NotificationGroupWithStatusJSON
|
||||
| ModerationWarningNotificationGroupJSON;
|
||||
| ModerationWarningNotificationGroupJSON
|
||||
| AnnualReportNotificationGroupJSON;
|
||||
|
||||
export interface ApiNotificationGroupsResultJSON {
|
||||
accounts: ApiAccountJSON[];
|
||||
|
|
|
@ -19,6 +19,7 @@ export const CollapseButton = ({ collapsed, setCollapsed }) => {
|
|||
if (e.button === 0) {
|
||||
setCollapsed(!collapsed);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, [collapsed, setCollapsed]);
|
||||
|
||||
|
|
|
@ -98,12 +98,12 @@ class Item extends PureComponent {
|
|||
height = 50;
|
||||
}
|
||||
|
||||
if (attachment.get('description')?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={attachment.get('description')} />);
|
||||
}
|
||||
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
if (description?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={description} />);
|
||||
}
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
|
|
|
@ -13,11 +13,14 @@ class ModalRoot extends PureComponent {
|
|||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
backgroundColor: PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
backgroundColor: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
]),
|
||||
noEsc: PropTypes.bool,
|
||||
ignoreFocus: PropTypes.bool,
|
||||
...WithOptionalRouterPropTypes,
|
||||
|
@ -146,14 +149,17 @@ class ModalRoot extends PureComponent {
|
|||
|
||||
let backgroundColor = null;
|
||||
|
||||
if (this.props.backgroundColor) {
|
||||
backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
if (this.props.backgroundColor && typeof this.props.backgroundColor === 'string') {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
} else if (this.props.backgroundColor) {
|
||||
const darkenedColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
backgroundColor = `rgb(${darkenedColor.r}, ${darkenedColor.g}, ${darkenedColor.b})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-root' ref={this.setRef}>
|
||||
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.9)` : null }} />
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor }} />
|
||||
<div role='dialog' className='modal-root__container'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -377,26 +377,29 @@ class Status extends ImmutablePureComponent {
|
|||
const { isCollapsed } = this.state;
|
||||
if (!history) return;
|
||||
|
||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
|
||||
if (isCollapsed) this.setCollapsed(false);
|
||||
else if (e.shiftKey) {
|
||||
this.setCollapsed(true);
|
||||
document.getSelection().removeAllRanges();
|
||||
} else if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
} else {
|
||||
if (destination === undefined) {
|
||||
destination = `/@${
|
||||
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
|
||||
}/${
|
||||
status.getIn(['reblog', 'id'], status.get('id'))
|
||||
}`;
|
||||
}
|
||||
history.push(destination);
|
||||
}
|
||||
e.preventDefault();
|
||||
if (e.button !== 0 || e.ctrlKey || e.altKey || e.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCollapsed) this.setCollapsed(false);
|
||||
else if (e.shiftKey) {
|
||||
this.setCollapsed(true);
|
||||
document.getSelection().removeAllRanges();
|
||||
} else if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
} else {
|
||||
if (destination === undefined) {
|
||||
destination = `/@${
|
||||
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
|
||||
}/${
|
||||
status.getIn(['reblog', 'id'], status.get('id'))
|
||||
}`;
|
||||
}
|
||||
history.push(destination);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleToggleMediaVisibility = () => {
|
||||
|
@ -589,22 +592,23 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
let prepend, rebloggedByText;
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className='status focusable' tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{isExpanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
|
||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
|
@ -813,7 +817,8 @@ class Status extends ImmutablePureComponent {
|
|||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
{(!muted || !isCollapsed) && (
|
||||
<header className='status__info'>
|
||||
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */
|
||||
<header onClick={this.parseClick} className='status__info'>
|
||||
<StatusHeader
|
||||
status={status}
|
||||
friend={account}
|
||||
|
|
|
@ -18,15 +18,10 @@ export default class StatusHeader extends PureComponent {
|
|||
parseClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
// Handles clicks on account name/image
|
||||
handleClick = (acct, e) => {
|
||||
const { parseClick } = this.props;
|
||||
parseClick(e, `/@${acct}`);
|
||||
};
|
||||
|
||||
handleAccountClick = (e) => {
|
||||
const { status } = this.props;
|
||||
this.handleClick(status.getIn(['account', 'acct']), e);
|
||||
const { status, parseClick } = this.props;
|
||||
parseClick(e, `/@${status.getIn(['account', 'acct'])}`);
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// Rendering.
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import booster from '@/images/archetypes/booster.png';
|
||||
import lurker from '@/images/archetypes/lurker.png';
|
||||
import oracle from '@/images/archetypes/oracle.png';
|
||||
import pollster from '@/images/archetypes/pollster.png';
|
||||
import replier from '@/images/archetypes/replier.png';
|
||||
import type { Archetype as ArchetypeData } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const Archetype: React.FC<{
|
||||
data: ArchetypeData;
|
||||
}> = ({ data }) => {
|
||||
let illustration, label;
|
||||
|
||||
switch (data) {
|
||||
case 'booster':
|
||||
illustration = booster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.booster'
|
||||
defaultMessage='The cool-hunter'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'replier':
|
||||
illustration = replier;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.replier'
|
||||
defaultMessage='The social butterfly'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'pollster':
|
||||
illustration = pollster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.pollster'
|
||||
defaultMessage='The pollster'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'lurker':
|
||||
illustration = lurker;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.lurker'
|
||||
defaultMessage='The lurker'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'oracle':
|
||||
illustration = oracle;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.oracle'
|
||||
defaultMessage='The oracle'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__archetype'>
|
||||
<div className='annual-report__summary__archetype__label'>{label}</div>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
|
||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||
import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const Followers: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
total?: number;
|
||||
}> = ({ data, total }) => {
|
||||
const change = data.reduce((sum, item) => sum + item.followers, 0);
|
||||
|
||||
const cumulativeGraph = data.reduce(
|
||||
(newData, item) => [
|
||||
...newData,
|
||||
item.followers + (newData[newData.length - 1] ?? 0),
|
||||
],
|
||||
[0],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__followers'>
|
||||
<Sparklines data={cumulativeGraph} margin={0}>
|
||||
<svg>
|
||||
<defs>
|
||||
<linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
|
||||
<stop
|
||||
offset='0%'
|
||||
stopColor='var(--sparkline-gradient-top)'
|
||||
stopOpacity='1'
|
||||
/>
|
||||
<stop
|
||||
offset='100%'
|
||||
stopColor='var(--sparkline-gradient-bottom)'
|
||||
stopOpacity='0'
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
|
||||
<div className='annual-report__summary__followers__foreground'>
|
||||
<div className='annual-report__summary__followers__number'>
|
||||
{change > -1 ? '+' : '-'}
|
||||
<FormattedNumber value={change} />
|
||||
</div>
|
||||
|
||||
<div className='annual-report__summary__followers__label'>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.followers'
|
||||
defaultMessage='followers'
|
||||
/>
|
||||
</span>
|
||||
<div className='annual-report__summary__followers__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.total'
|
||||
defaultMessage='{count} total'
|
||||
values={{ count: <ShortNumber value={total ?? 0} /> }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { toggleStatusSpoilers } from 'flavours/glitch/actions/statuses';
|
||||
import { DetailedStatus } from 'flavours/glitch/features/status/components/detailed_status';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import type { TopStatuses } from 'flavours/glitch/models/annual_report';
|
||||
import {
|
||||
makeGetStatus,
|
||||
makeGetPictureInPicture,
|
||||
} from 'flavours/glitch/selectors';
|
||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
||||
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
|
||||
arg0: any,
|
||||
arg1: any,
|
||||
) => any;
|
||||
|
||||
export const HighlightedPost: React.FC<{
|
||||
data: TopStatuses;
|
||||
}> = ({ data }) => {
|
||||
let statusId, label;
|
||||
|
||||
if (data.by_reblogs) {
|
||||
statusId = data.by_reblogs;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_reblogs'
|
||||
defaultMessage='most boosted post'
|
||||
/>
|
||||
);
|
||||
} else if (data.by_favourites) {
|
||||
statusId = data.by_favourites;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_favourites'
|
||||
defaultMessage='most favourited post'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
statusId = data.by_replies;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_replies'
|
||||
defaultMessage='post with the most replies'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const domain = useAppSelector((state) => state.meta.get('domain'));
|
||||
const status = useAppSelector((state) =>
|
||||
statusId ? getStatus(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const pictureInPicture = useAppSelector((state) =>
|
||||
statusId ? getPictureInPicture(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
|
||||
const handleToggleHidden = useCallback(() => {
|
||||
dispatch(toggleStatusSpoilers(statusId));
|
||||
}, [dispatch, statusId]);
|
||||
|
||||
if (!status) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post' />
|
||||
);
|
||||
}
|
||||
|
||||
const displayName = (
|
||||
<span className='display-name'>
|
||||
<strong className='display-name__html'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.possessive'
|
||||
defaultMessage="{name}'s"
|
||||
values={{
|
||||
name: account && (
|
||||
<bdi
|
||||
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
<span className='display-name__account'>{label}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post'>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
pictureInPicture={pictureInPicture}
|
||||
domain={domain}
|
||||
onToggleHidden={handleToggleHidden}
|
||||
overrideDisplayName={displayName}
|
||||
expanded={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,99 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import {
|
||||
importFetchedStatuses,
|
||||
importFetchedAccounts,
|
||||
} from 'flavours/glitch/actions/importer';
|
||||
import { apiRequestGet, apiRequestPost } from 'flavours/glitch/api';
|
||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
import type { AnnualReport as AnnualReportData } from 'flavours/glitch/models/annual_report';
|
||||
import type { Status } from 'flavours/glitch/models/status';
|
||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
import { Archetype } from './archetype';
|
||||
import { Followers } from './followers';
|
||||
import { HighlightedPost } from './highlighted_post';
|
||||
import { MostUsedHashtag } from './most_used_hashtag';
|
||||
import { NewPosts } from './new_posts';
|
||||
import { Percentile } from './percentile';
|
||||
|
||||
interface AnnualReportResponse {
|
||||
annual_reports: AnnualReportData[];
|
||||
accounts: Account[];
|
||||
statuses: Status[];
|
||||
}
|
||||
|
||||
export const AnnualReport: React.FC<{
|
||||
year: string;
|
||||
}> = ({ year }) => {
|
||||
const [response, setResponse] = useState<AnnualReportResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const currentAccount = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
apiRequestGet<AnnualReportResponse>(`v1/annual_reports/${year}`)
|
||||
.then((data) => {
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
|
||||
setResponse(data);
|
||||
setLoading(false);
|
||||
|
||||
return apiRequestPost(`v1/annual_reports/${year}/read`);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [dispatch, year, setResponse, setLoading]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
const report = response?.annual_reports[0];
|
||||
|
||||
if (!report) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report'>
|
||||
<div className='annual-report__header'>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.thanks'
|
||||
defaultMessage='Thanks for being part of Mastodon!'
|
||||
/>
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.here_it_is'
|
||||
defaultMessage='Here is your {year} in review:'
|
||||
values={{ year: report.year }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='annual-report__bento annual-report__summary'>
|
||||
<Archetype data={report.data.archetype} />
|
||||
<HighlightedPost data={report.data.top_statuses} />
|
||||
<Followers
|
||||
data={report.data.time_series}
|
||||
total={currentAccount?.followers_count}
|
||||
/>
|
||||
<MostUsedHashtag data={report.data.top_hashtags} />
|
||||
<Percentile data={report.data.percentiles} />
|
||||
<NewPosts data={report.data.time_series} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const MostUsedApp: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const app = data[0];
|
||||
|
||||
if (!app) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app'>
|
||||
<div className='annual-report__summary__most-used-app__icon'>
|
||||
{app.name}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-app__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_app.most_used_app'
|
||||
defaultMessage='most used app'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const MostUsedHashtag: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const hashtag = data[0];
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-hashtag'>
|
||||
<div className='annual-report__summary__most-used-hashtag__hashtag'>
|
||||
{hashtag ? (
|
||||
<>#{hashtag.name}</>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_hashtag.none'
|
||||
defaultMessage='None'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-hashtag__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_hashtag.most_used_hashtag'
|
||||
defaultMessage='most used hashtag'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
||||
|
||||
import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react';
|
||||
import type { TimeSeriesMonth } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const NewPosts: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
}> = ({ data }) => {
|
||||
const posts = data.reduce((sum, item) => sum + item.statuses, 0);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__new-posts'>
|
||||
<svg width={500} height={500}>
|
||||
<defs>
|
||||
<pattern
|
||||
id='posts'
|
||||
x='0'
|
||||
y='0'
|
||||
width='32'
|
||||
height='35'
|
||||
patternUnits='userSpaceOnUse'
|
||||
>
|
||||
<circle cx='12' cy='12' r='12' fill='var(--lime)' />
|
||||
<ChatBubbleIcon
|
||||
fill='var(--indigo-1)'
|
||||
x='4'
|
||||
y='4'
|
||||
width='16'
|
||||
height='16'
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
width={500}
|
||||
height={500}
|
||||
fill='url(#posts)'
|
||||
style={{ opacity: 0.2 }}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className='annual-report__summary__new-posts__number'>
|
||||
<FormattedNumber value={posts} />
|
||||
</div>
|
||||
<div className='annual-report__summary__new-posts__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.new_posts.new_posts'
|
||||
defaultMessage='new posts'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import type { Percentiles } from 'flavours/glitch/models/annual_report';
|
||||
|
||||
export const Percentile: React.FC<{
|
||||
data: Percentiles;
|
||||
}> = ({ data }) => {
|
||||
const percentile = data.statuses;
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__percentile'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.text'
|
||||
defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>'
|
||||
values={{
|
||||
topLabel: (str) => (
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
),
|
||||
percentage: () => (
|
||||
<div className='annual-report__summary__percentile__number'>
|
||||
<FormattedNumber
|
||||
value={Math.min(percentile, 99) / 100}
|
||||
style='percent'
|
||||
maximumFractionDigits={percentile < 1 ? 1 : 0}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
bottomLabel: (str) => (
|
||||
<div>
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
|
||||
{percentile < 6 && (
|
||||
<div className='annual-report__summary__percentile__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.we_wont_tell_bernie'
|
||||
defaultMessage="We won't tell Bernie."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{(message) => <>{message}</>}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import CelebrationIcon from '@/material-icons/400-24px/celebration.svg?react';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import type { NotificationGroupAnnualReport } from 'flavours/glitch/models/notification_group';
|
||||
import { useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
export const NotificationAnnualReport: React.FC<{
|
||||
notification: NotificationGroupAnnualReport;
|
||||
unread: boolean;
|
||||
}> = ({ notification: { annualReport }, unread }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const year = annualReport.year;
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'ANNUAL_REPORT',
|
||||
modalProps: { year },
|
||||
}),
|
||||
);
|
||||
}, [dispatch, year]);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
'notification-group notification-group--link notification-group--annual-report focusable',
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='celebration' icon={CelebrationIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.message'
|
||||
defaultMessage="Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!"
|
||||
values={{ year }}
|
||||
/>
|
||||
</p>
|
||||
<button onClick={handleClick} className='link-button'>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.view'
|
||||
defaultMessage='View #Wrapstodon'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
|||
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
import { NotificationFollow } from './notification_follow';
|
||||
import { NotificationFollowRequest } from './notification_follow_request';
|
||||
|
@ -152,6 +153,14 @@ export const NotificationGroup: React.FC<{
|
|||
/>
|
||||
);
|
||||
break;
|
||||
case 'annual_report':
|
||||
content = (
|
||||
<NotificationAnnualReport
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain: string;
|
||||
showMedia?: boolean;
|
||||
withLogo?: boolean;
|
||||
overrideDisplayName?: React.ReactNode;
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
|
@ -70,6 +71,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain,
|
||||
showMedia,
|
||||
withLogo,
|
||||
overrideDisplayName,
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
|
@ -386,7 +388,11 @@ export const DetailedStatus: React.FC<{
|
|||
<div className='detailed-status__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={46} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
|
||||
{overrideDisplayName ?? (
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
)}
|
||||
|
||||
{withLogo && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { AnnualReport } from 'flavours/glitch/features/annual_report';
|
||||
|
||||
const AnnualReportModal: React.FC<{
|
||||
year: string;
|
||||
onChangeBackgroundColor: (arg0: string) => void;
|
||||
}> = ({ year, onChangeBackgroundColor }) => {
|
||||
useEffect(() => {
|
||||
onChangeBackgroundColor('var(--indigo-1)');
|
||||
}, [onChangeBackgroundColor]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal annual-report-modal'>
|
||||
<AnnualReport year={year} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AnnualReportModal;
|
|
@ -20,6 +20,7 @@ import {
|
|||
SubscribedLanguagesModal,
|
||||
ClosedRegistrationsModal,
|
||||
IgnoreNotificationsModal,
|
||||
AnnualReportModal,
|
||||
} from 'flavours/glitch/features/ui/util/async-components';
|
||||
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
|
||||
|
||||
|
@ -82,6 +83,7 @@ export const MODAL_COMPONENTS = {
|
|||
'INTERACTION': InteractionModal,
|
||||
'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
|
||||
'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
|
||||
'ANNUAL_REPORT': AnnualReportModal,
|
||||
};
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
|
|
|
@ -532,7 +532,9 @@ class UI extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleHotkeyBack = () => {
|
||||
handleHotkeyBack = e => {
|
||||
e.preventDefault();
|
||||
|
||||
const { history } = this.props;
|
||||
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
|
|
|
@ -229,3 +229,7 @@ export function NotificationRequest () {
|
|||
export function LinkTimeline () {
|
||||
return import(/*webpackChunkName: "features/glitch/link_timeline" */'../../link_timeline');
|
||||
}
|
||||
|
||||
export function AnnualReportModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/annual_report_modal" */'../components/annual_report_modal');
|
||||
}
|
||||
|
|
|
@ -154,7 +154,5 @@
|
|||
"status.is_poll": "Dieser Toot ist eine Umfrage",
|
||||
"status.local_only": "Nur auf deiner Instanz sichtbar",
|
||||
"status.show_filter_reason": "Trotzdem anzeigen",
|
||||
"status.show_less": "Weniger anzeigen",
|
||||
"status.show_more": "Mehr anzeigen",
|
||||
"status.uncollapse": "Ausklappen"
|
||||
}
|
||||
|
|
|
@ -154,7 +154,5 @@
|
|||
"status.is_poll": "Esta publicación es una encuesta",
|
||||
"status.local_only": "Sólo visible para tu instancia",
|
||||
"status.show_filter_reason": "Mostrar de todos modos",
|
||||
"status.show_less": "Mostrar menos",
|
||||
"status.show_more": "Mostrar más",
|
||||
"status.uncollapse": "Descolapsar"
|
||||
}
|
||||
|
|
|
@ -154,7 +154,5 @@
|
|||
"status.is_poll": "이 글은 설문입니다",
|
||||
"status.local_only": "당신의 서버에서만 보입니다",
|
||||
"status.show_filter_reason": "그냥 표시하기",
|
||||
"status.show_less": "접기",
|
||||
"status.show_more": "더보기",
|
||||
"status.uncollapse": "펼치기"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,88 @@
|
|||
{
|
||||
"about.fork_disclaimer": "Glitch-soc - это бесплатное программное обеспечение с открытым исходным кодом, обращенное от Mastodon.",
|
||||
"about.fork_disclaimer": "Glitch-soc — это свободное программное обеспечение с открытым исходным кодом, ответвлённое от Mastodon.",
|
||||
"account.follows": "Подписки",
|
||||
"account.follows_you": "Подписан(а) на вас",
|
||||
"account.suspended_disclaimer_full": "Этот пользователь был заблокирован модератором.",
|
||||
"boost_modal.missing_description": "Этот пост содержит медиафайлы без описания",
|
||||
"column.favourited_by": "Добавили в избранное",
|
||||
"column.reblogged_by": "Продвинули",
|
||||
"column_header.profile": "Профиль",
|
||||
"column_subheading.lists": "Списки",
|
||||
"column_subheading.navigation": "Навигация",
|
||||
"compose.attach.doodle": "Нарисовать что-нибудь",
|
||||
"compose.change_federation": "Изменить настройки федерации",
|
||||
"compose.content-type.html": "HTML",
|
||||
"compose.content-type.markdown": "Markdown",
|
||||
"compose.content-type.plain": "Простой текст",
|
||||
"compose.disable_threaded_mode": "Отключить режим треда",
|
||||
"compose.enable_threaded_mode": "Включить режим треда",
|
||||
"confirmation_modal.do_not_ask_again": "Больше не спрашивать подтверждение",
|
||||
"confirmations.deprecated_settings.confirm": "Использовать настройки Mastodon",
|
||||
"confirmations.missing_media_description.confirm": "Всё равно опубликовать",
|
||||
"direct.group_by_conversations": "Группировать по перепискам",
|
||||
"endorsed_accounts_editor.endorsed_accounts": "Рекомендованные аккаунты",
|
||||
"favourite_modal.favourite": "Добавить пост в избранное?",
|
||||
"federation.federated.long": "Разрешить делиться этим постом с другими серверами",
|
||||
"federation.local_only.long": "Запретить делиться этим постом с другими серверами",
|
||||
"home.column_settings.advanced": "Продвинутые настройки",
|
||||
"home.column_settings.filter_regex": "Фильтр по регулярным выражениям",
|
||||
"keyboard_shortcuts.bookmark": "добавить закладку",
|
||||
"keyboard_shortcuts.toggle_collapse": "свернуть/развернуть пост",
|
||||
"moved_to_warning": "Этот аккаунт переехал на {moved_to_link}, и скорее всего не принимает новых подписчиков.",
|
||||
"navigation_bar.app_settings": "Настройки приложения",
|
||||
"navigation_bar.keyboard_shortcuts": "Сочетания клавиш",
|
||||
"notification.markForDeletion": "Отметить для удаления",
|
||||
"notification_purge.btn_all": "Выбрать все",
|
||||
"notification_purge.btn_apply": "Удалить выбранное",
|
||||
"notification_purge.btn_invert": "Инвертировать выбор",
|
||||
"notification_purge.btn_none": "Отменить выбор",
|
||||
"notification_purge.start": "Войти в режим очистки уведомлений",
|
||||
"notifications.column_settings.filter_bar.show_bar": "Показать панель фильтров",
|
||||
"notifications.marked_clear": "Удалить выбранные уведомления",
|
||||
"notifications.marked_clear_confirmation": "Вы уверены, что хотите безвозвратно удалить все выбранные уведомления?",
|
||||
"settings.auto_collapse": "Сворачивать автоматически",
|
||||
"settings.auto_collapse_all": "Всё",
|
||||
"settings.auto_collapse_height": "Высота (в пикселях) для того, чтобы пост считался длинным",
|
||||
"settings.auto_collapse_lengthy": "Длинные посты",
|
||||
"settings.auto_collapse_media": "Посты с медиафайлами",
|
||||
"settings.auto_collapse_notifications": "Уведомления",
|
||||
"settings.auto_collapse_reblogs": "Продвижения",
|
||||
"settings.auto_collapse_replies": "Ответы",
|
||||
"settings.close": "Закрыть",
|
||||
"settings.collapsed_statuses": "Сворачивание постов",
|
||||
"settings.compose_box_opts": "Форма постинга",
|
||||
"settings.content_warnings": "Content warnings",
|
||||
"settings.preferences": "Preferences"
|
||||
"settings.content_warnings.regexp": "Регулярное выражение",
|
||||
"settings.content_warnings_unfold_opts": "Автоматическое раскрытие",
|
||||
"settings.deprecated_setting": "Эта опция теперь может быть включена в {settings_page_link} Mastodon",
|
||||
"settings.enable_collapsed": "Включить сворачивание постов",
|
||||
"settings.general": "Общие",
|
||||
"settings.hicolor_privacy_icons": "Цветные значки публичности поста",
|
||||
"settings.hicolor_privacy_icons.hint": "Отображать значки публичности поста в ярких и различимых цветах",
|
||||
"settings.media": "Медиафайлы",
|
||||
"settings.notifications.favicon_badge": "Индикатор уведомлений на иконке сайта",
|
||||
"settings.notifications_opts": "Опции уведомлений",
|
||||
"settings.pop_in_left": "Слева",
|
||||
"settings.pop_in_player": "Включить плавающий плеер",
|
||||
"settings.pop_in_position": "Расположение плавающего плеера:",
|
||||
"settings.pop_in_right": "Справа",
|
||||
"settings.preferences": "Preferences",
|
||||
"settings.shared_settings_link": "настройках пользователя",
|
||||
"settings.show_reply_counter": "Показывать приблизительное число ответов",
|
||||
"settings.side_arm": "Дополнительная кнопка постинга:",
|
||||
"settings.side_arm.none": "Нет",
|
||||
"settings.side_arm_reply_mode": "При ответе на пост дополнительная кнопка постинга должна:",
|
||||
"settings.status_icons": "Значки постов",
|
||||
"settings.status_icons_language": "Индикатор языка",
|
||||
"settings.status_icons_local_only": "Индикатор нефедерируемого поста",
|
||||
"settings.status_icons_media": "Индикаторы медиафайлов и опросов",
|
||||
"settings.status_icons_reply": "Индикатор ответа",
|
||||
"settings.status_icons_visibility": "Индикатор публичности поста",
|
||||
"settings.tag_misleading_links": "Помечать обманчивые ссылки",
|
||||
"status.collapse": "Свернуть",
|
||||
"status.hide": "Скрыть пост",
|
||||
"status.in_reply_to": "Этот пост является ответом",
|
||||
"status.is_poll": "Этот пост содержит опрос",
|
||||
"status.show_filter_reason": "Всё равно показать",
|
||||
"status.uncollapse": "Развернуть"
|
||||
}
|
||||
|
|
|
@ -153,7 +153,5 @@
|
|||
"status.is_poll": "此嘟文是投票",
|
||||
"status.local_only": "此嘟文仅本站可见",
|
||||
"status.show_filter_reason": "仍然显示",
|
||||
"status.show_less": "部分显示",
|
||||
"status.show_more": "完全显示",
|
||||
"status.uncollapse": "展开"
|
||||
}
|
||||
|
|
44
app/javascript/flavours/glitch/models/annual_report.ts
Normal file
44
app/javascript/flavours/glitch/models/annual_report.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
export interface Percentiles {
|
||||
followers: number;
|
||||
statuses: number;
|
||||
}
|
||||
|
||||
export interface NameAndCount {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface TimeSeriesMonth {
|
||||
month: number;
|
||||
statuses: number;
|
||||
following: number;
|
||||
followers: number;
|
||||
}
|
||||
|
||||
export interface TopStatuses {
|
||||
by_reblogs: number;
|
||||
by_favourites: number;
|
||||
by_replies: number;
|
||||
}
|
||||
|
||||
export type Archetype =
|
||||
| 'lurker'
|
||||
| 'booster'
|
||||
| 'pollster'
|
||||
| 'replier'
|
||||
| 'oracle';
|
||||
|
||||
interface AnnualReportV1 {
|
||||
most_used_apps: NameAndCount[];
|
||||
percentiles: Percentiles;
|
||||
top_hashtags: NameAndCount[];
|
||||
top_statuses: TopStatuses;
|
||||
time_series: TimeSeriesMonth[];
|
||||
archetype: Archetype;
|
||||
}
|
||||
|
||||
export interface AnnualReport {
|
||||
year: number;
|
||||
schema_version: number;
|
||||
data: AnnualReportV1;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
ApiAccountRelationshipSeveranceEventJSON,
|
||||
ApiAccountWarningJSON,
|
||||
ApiAnnualReportEventJSON,
|
||||
BaseNotificationGroupJSON,
|
||||
ApiNotificationGroupJSON,
|
||||
ApiNotificationJSON,
|
||||
|
@ -66,6 +67,12 @@ export interface NotificationGroupSeveredRelationships
|
|||
event: AccountRelationshipSeveranceEvent;
|
||||
}
|
||||
|
||||
type AnnualReportEvent = ApiAnnualReportEventJSON;
|
||||
export interface NotificationGroupAnnualReport
|
||||
extends BaseNotification<'annual_report'> {
|
||||
annualReport: AnnualReportEvent;
|
||||
}
|
||||
|
||||
interface Report extends Omit<ApiReportJSON, 'target_account'> {
|
||||
targetAccountId: string;
|
||||
}
|
||||
|
@ -88,7 +95,8 @@ export type NotificationGroup =
|
|||
| NotificationGroupModerationWarning
|
||||
| NotificationGroupSeveredRelationships
|
||||
| NotificationGroupAdminSignUp
|
||||
| NotificationGroupAdminReport;
|
||||
| NotificationGroupAdminReport
|
||||
| NotificationGroupAnnualReport;
|
||||
|
||||
function createReportFromJSON(reportJSON: ApiReportJSON): Report {
|
||||
const { target_account, ...report } = reportJSON;
|
||||
|
@ -114,6 +122,12 @@ function createAccountRelationshipSeveranceEventFromJSON(
|
|||
return eventJson;
|
||||
}
|
||||
|
||||
function createAnnualReportEventFromJSON(
|
||||
eventJson: ApiAnnualReportEventJSON,
|
||||
): AnnualReportEvent {
|
||||
return eventJson;
|
||||
}
|
||||
|
||||
export function createNotificationGroupFromJSON(
|
||||
groupJson: ApiNotificationGroupJSON,
|
||||
): NotificationGroup {
|
||||
|
@ -148,7 +162,6 @@ export function createNotificationGroupFromJSON(
|
|||
event: createAccountRelationshipSeveranceEventFromJSON(group.event),
|
||||
sampleAccountIds,
|
||||
};
|
||||
|
||||
case 'moderation_warning': {
|
||||
const { moderation_warning, ...groupWithoutModerationWarning } = group;
|
||||
return {
|
||||
|
@ -157,6 +170,14 @@ export function createNotificationGroupFromJSON(
|
|||
sampleAccountIds,
|
||||
};
|
||||
}
|
||||
case 'annual_report': {
|
||||
const { annual_report, ...groupWithoutAnnualReport } = group;
|
||||
return {
|
||||
...groupWithoutAnnualReport,
|
||||
annualReport: createAnnualReportEventFromJSON(annual_report),
|
||||
sampleAccountIds,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return {
|
||||
sampleAccountIds,
|
||||
|
|
|
@ -1943,3 +1943,31 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status__card {
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: $ui-base-color;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
font-weight: 400;
|
||||
border: 1px solid lighten($ui-base-color, 4%);
|
||||
color: $primary-text-color;
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
|
||||
.status__prepend {
|
||||
padding: 0 0 15px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status__content {
|
||||
padding-top: 0;
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
340
app/javascript/flavours/glitch/styles/annual_reports.scss
Normal file
340
app/javascript/flavours/glitch/styles/annual_reports.scss
Normal file
|
@ -0,0 +1,340 @@
|
|||
:root {
|
||||
--indigo-1: #17063b;
|
||||
--indigo-2: #2f0c7a;
|
||||
--indigo-3: #562cfc;
|
||||
--indigo-5: #858afa;
|
||||
--indigo-6: #cccfff;
|
||||
--lime: #baff3b;
|
||||
--goldenrod-2: #ffc954;
|
||||
}
|
||||
|
||||
.annual-report {
|
||||
flex: 0 0 auto;
|
||||
background: var(--indigo-1);
|
||||
padding: 24px;
|
||||
|
||||
&__header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
color: var(--lime);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
}
|
||||
|
||||
&__bento {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
|
||||
0,
|
||||
auto
|
||||
);
|
||||
|
||||
&__box {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--indigo-2);
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
&__summary {
|
||||
&__most-boosted-post {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
padding: 0;
|
||||
|
||||
.status__content,
|
||||
.content-warning {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.detailed-status {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.content-warning {
|
||||
border: 0;
|
||||
background: var(--indigo-1);
|
||||
|
||||
.link-button {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status__meta__line {
|
||||
border-bottom-color: var(--indigo-3);
|
||||
}
|
||||
|
||||
.detailed-status__meta {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detailed-status__meta,
|
||||
.poll__footer,
|
||||
.poll__link,
|
||||
.detailed-status .logo,
|
||||
.detailed-status__display-name {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
|
||||
.detailed-status__meta .animated-number,
|
||||
.detailed-status__display-name strong {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.poll__chart {
|
||||
background-color: var(--indigo-3);
|
||||
|
||||
&.leading {
|
||||
background-color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
|
||||
.status-card,
|
||||
.hashtag-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__followers {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-block-start: 24px;
|
||||
padding-block-end: 24px;
|
||||
|
||||
--sparkline-gradient-top: rgba(86, 44, 252, 50%);
|
||||
--sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
|
||||
|
||||
&__foreground {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 31px;
|
||||
font-weight: 600;
|
||||
line-height: 37px;
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-end: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
height: 70%;
|
||||
width: auto;
|
||||
|
||||
path:first-child {
|
||||
fill: url('#gradient') !important;
|
||||
fill-opacity: 1 !important;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
stroke: var(--indigo-3) !important;
|
||||
fill: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__archetype {
|
||||
grid-column: span 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
padding: 16px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-app {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
|
||||
&__percentile {
|
||||
grid-row: span 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
padding: 16px 8px;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 54px;
|
||||
font-weight: 600;
|
||||
line-height: 73px;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&__new-posts {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__label {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--indigo-6);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 76px;
|
||||
font-weight: 600;
|
||||
line-height: 91px;
|
||||
color: var(--goldenrod-2);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
inset-inline-start: -7px;
|
||||
top: -4px;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-hashtag {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__hashtag {
|
||||
font-size: 42px;
|
||||
font-weight: 600;
|
||||
line-height: 58px;
|
||||
color: var(--indigo-6);
|
||||
margin-inline-start: -100%;
|
||||
margin-inline-end: -100%;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.annual-report-modal {
|
||||
max-width: 600px;
|
||||
background: var(--indigo-1);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.loading-indicator .circular-progress {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
border-bottom: 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
.notification-group__main .link-button {
|
||||
font-weight: 500;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
@import 'polls';
|
||||
@import 'modal';
|
||||
@import 'emoji_picker';
|
||||
@import 'annual_reports';
|
||||
@import 'about';
|
||||
@import 'tables';
|
||||
@import 'admin';
|
||||
|
|
|
@ -1861,7 +1861,8 @@ body > [data-popper-placement] {
|
|||
|
||||
.status__wrapper-direct,
|
||||
.notification-ungrouped--direct,
|
||||
.notification-group--direct {
|
||||
.notification-group--direct,
|
||||
.notification-group--annual-report {
|
||||
background: rgba($ui-highlight-color, 0.05);
|
||||
|
||||
&:focus {
|
||||
|
@ -6258,7 +6259,8 @@ a.status-card {
|
|||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
bottom: 0;
|
||||
background: rgba($base-overlay-background, 0.7);
|
||||
opacity: 0.9;
|
||||
background: $base-overlay-background;
|
||||
transition: background 0.5s;
|
||||
}
|
||||
|
||||
|
|
|
@ -598,3 +598,10 @@ a.sparkline {
|
|||
::-webkit-scrollbar-thumb {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon,
|
||||
.notification-group__main .link-button {
|
||||
color: var(--indigo-3);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,12 +339,12 @@ a.table-action-link {
|
|||
}
|
||||
}
|
||||
|
||||
.status__content {
|
||||
padding-top: 0;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
// Reset the status card to not have borders, background or padding when
|
||||
// inline in the table of statuses
|
||||
.status__card {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nothing-here {
|
||||
|
|
BIN
app/javascript/images/archetypes/booster.png
Executable file
BIN
app/javascript/images/archetypes/booster.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 620 KiB |
BIN
app/javascript/images/archetypes/lurker.png
Executable file
BIN
app/javascript/images/archetypes/lurker.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1 MiB |
BIN
app/javascript/images/archetypes/oracle.png
Executable file
BIN
app/javascript/images/archetypes/oracle.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
app/javascript/images/archetypes/pollster.png
Executable file
BIN
app/javascript/images/archetypes/pollster.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 710 KiB |
BIN
app/javascript/images/archetypes/replier.png
Executable file
BIN
app/javascript/images/archetypes/replier.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 786 KiB |
|
@ -20,6 +20,7 @@ export const allNotificationTypes = [
|
|||
'admin.report',
|
||||
'moderation_warning',
|
||||
'severed_relationships',
|
||||
'annual_report',
|
||||
];
|
||||
|
||||
export type NotificationWithStatusType =
|
||||
|
@ -37,7 +38,8 @@ export type NotificationType =
|
|||
| 'moderation_warning'
|
||||
| 'severed_relationships'
|
||||
| 'admin.sign_up'
|
||||
| 'admin.report';
|
||||
| 'admin.report'
|
||||
| 'annual_report';
|
||||
|
||||
export interface BaseNotificationJSON {
|
||||
id: string;
|
||||
|
@ -130,6 +132,15 @@ interface AccountRelationshipSeveranceNotificationJSON
|
|||
event: ApiAccountRelationshipSeveranceEventJSON;
|
||||
}
|
||||
|
||||
export interface ApiAnnualReportEventJSON {
|
||||
year: string;
|
||||
}
|
||||
|
||||
interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||
type: 'annual_report';
|
||||
annual_report: ApiAnnualReportEventJSON;
|
||||
}
|
||||
|
||||
export type ApiNotificationJSON =
|
||||
| SimpleNotificationJSON
|
||||
| ReportNotificationJSON
|
||||
|
@ -142,7 +153,8 @@ export type ApiNotificationGroupJSON =
|
|||
| ReportNotificationGroupJSON
|
||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||
| NotificationGroupWithStatusJSON
|
||||
| ModerationWarningNotificationGroupJSON;
|
||||
| ModerationWarningNotificationGroupJSON
|
||||
| AnnualReportNotificationGroupJSON;
|
||||
|
||||
export interface ApiNotificationGroupsResultJSON {
|
||||
accounts: ApiAccountJSON[];
|
||||
|
|
|
@ -97,12 +97,12 @@ class Item extends PureComponent {
|
|||
height = 50;
|
||||
}
|
||||
|
||||
if (attachment.get('description')?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={attachment.get('description')} />);
|
||||
}
|
||||
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
if (description?.length > 0) {
|
||||
badges.push(<AltTextBadge key='alt' description={description} />);
|
||||
}
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
|
|
|
@ -13,11 +13,14 @@ class ModalRoot extends PureComponent {
|
|||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
backgroundColor: PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
backgroundColor: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
}),
|
||||
]),
|
||||
ignoreFocus: PropTypes.bool,
|
||||
...WithOptionalRouterPropTypes,
|
||||
};
|
||||
|
@ -141,14 +144,17 @@ class ModalRoot extends PureComponent {
|
|||
|
||||
let backgroundColor = null;
|
||||
|
||||
if (this.props.backgroundColor) {
|
||||
backgroundColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
if (this.props.backgroundColor && typeof this.props.backgroundColor === 'string') {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
} else if (this.props.backgroundColor) {
|
||||
const darkenedColor = multiply({ ...this.props.backgroundColor, a: 1 }, { r: 0, g: 0, b: 0, a: 0.7 });
|
||||
backgroundColor = `rgb(${darkenedColor.r}, ${darkenedColor.g}, ${darkenedColor.b})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-root' ref={this.setRef}>
|
||||
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.9)` : null }} />
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor }} />
|
||||
<div role='dialog' className='modal-root__container'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -394,17 +394,6 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
let media, statusAvatar, prepend, rebloggedByText;
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
|
@ -444,6 +433,20 @@ class Status extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||
{expanded && <span>{status.get('content')}</span>}
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
|
@ -538,7 +541,6 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = (!matchedFilters || this.state.showDespiteFilter) && (!status.get('hidden') || status.get('spoiler_text').length === 0);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
|
|
69
app/javascript/mastodon/features/annual_report/archetype.tsx
Normal file
69
app/javascript/mastodon/features/annual_report/archetype.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import booster from '@/images/archetypes/booster.png';
|
||||
import lurker from '@/images/archetypes/lurker.png';
|
||||
import oracle from '@/images/archetypes/oracle.png';
|
||||
import pollster from '@/images/archetypes/pollster.png';
|
||||
import replier from '@/images/archetypes/replier.png';
|
||||
import type { Archetype as ArchetypeData } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Archetype: React.FC<{
|
||||
data: ArchetypeData;
|
||||
}> = ({ data }) => {
|
||||
let illustration, label;
|
||||
|
||||
switch (data) {
|
||||
case 'booster':
|
||||
illustration = booster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.booster'
|
||||
defaultMessage='The cool-hunter'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'replier':
|
||||
illustration = replier;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.replier'
|
||||
defaultMessage='The social butterfly'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'pollster':
|
||||
illustration = pollster;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.pollster'
|
||||
defaultMessage='The pollster'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'lurker':
|
||||
illustration = lurker;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.lurker'
|
||||
defaultMessage='The lurker'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'oracle':
|
||||
illustration = oracle;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.archetype.oracle'
|
||||
defaultMessage='The oracle'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__archetype'>
|
||||
<div className='annual-report__summary__archetype__label'>{label}</div>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
);
|
||||
};
|
69
app/javascript/mastodon/features/annual_report/followers.tsx
Normal file
69
app/javascript/mastodon/features/annual_report/followers.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Followers: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
total?: number;
|
||||
}> = ({ data, total }) => {
|
||||
const change = data.reduce((sum, item) => sum + item.followers, 0);
|
||||
|
||||
const cumulativeGraph = data.reduce(
|
||||
(newData, item) => [
|
||||
...newData,
|
||||
item.followers + (newData[newData.length - 1] ?? 0),
|
||||
],
|
||||
[0],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__followers'>
|
||||
<Sparklines data={cumulativeGraph} margin={0}>
|
||||
<svg>
|
||||
<defs>
|
||||
<linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
|
||||
<stop
|
||||
offset='0%'
|
||||
stopColor='var(--sparkline-gradient-top)'
|
||||
stopOpacity='1'
|
||||
/>
|
||||
<stop
|
||||
offset='100%'
|
||||
stopColor='var(--sparkline-gradient-bottom)'
|
||||
stopOpacity='0'
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
|
||||
<div className='annual-report__summary__followers__foreground'>
|
||||
<div className='annual-report__summary__followers__number'>
|
||||
{change > -1 ? '+' : '-'}
|
||||
<FormattedNumber value={change} />
|
||||
</div>
|
||||
|
||||
<div className='annual-report__summary__followers__label'>
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.followers'
|
||||
defaultMessage='followers'
|
||||
/>
|
||||
</span>
|
||||
<div className='annual-report__summary__followers__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.followers.total'
|
||||
defaultMessage='{count} total'
|
||||
values={{ count: <ShortNumber value={total ?? 0} /> }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-return,
|
||||
@typescript-eslint/no-explicit-any,
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { toggleStatusSpoilers } from 'mastodon/actions/statuses';
|
||||
import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { TopStatuses } from 'mastodon/models/annual_report';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
|
||||
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
|
||||
arg0: any,
|
||||
arg1: any,
|
||||
) => any;
|
||||
|
||||
export const HighlightedPost: React.FC<{
|
||||
data: TopStatuses;
|
||||
}> = ({ data }) => {
|
||||
let statusId, label;
|
||||
|
||||
if (data.by_reblogs) {
|
||||
statusId = data.by_reblogs;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_reblogs'
|
||||
defaultMessage='most boosted post'
|
||||
/>
|
||||
);
|
||||
} else if (data.by_favourites) {
|
||||
statusId = data.by_favourites;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_favourites'
|
||||
defaultMessage='most favourited post'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
statusId = data.by_replies;
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.by_replies'
|
||||
defaultMessage='post with the most replies'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const domain = useAppSelector((state) => state.meta.get('domain'));
|
||||
const status = useAppSelector((state) =>
|
||||
statusId ? getStatus(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const pictureInPicture = useAppSelector((state) =>
|
||||
statusId ? getPictureInPicture(state, { id: statusId }) : undefined,
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
|
||||
const handleToggleHidden = useCallback(() => {
|
||||
dispatch(toggleStatusSpoilers(statusId));
|
||||
}, [dispatch, statusId]);
|
||||
|
||||
if (!status) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post' />
|
||||
);
|
||||
}
|
||||
|
||||
const displayName = (
|
||||
<span className='display-name'>
|
||||
<strong className='display-name__html'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.highlighted_post.possessive'
|
||||
defaultMessage="{name}'s"
|
||||
values={{
|
||||
name: account && (
|
||||
<bdi
|
||||
dangerouslySetInnerHTML={{ __html: account.display_name_html }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
<span className='display-name__account'>{label}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-boosted-post'>
|
||||
<DetailedStatus
|
||||
status={status}
|
||||
pictureInPicture={pictureInPicture}
|
||||
domain={domain}
|
||||
onToggleHidden={handleToggleHidden}
|
||||
overrideDisplayName={displayName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
99
app/javascript/mastodon/features/annual_report/index.tsx
Normal file
99
app/javascript/mastodon/features/annual_report/index.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import {
|
||||
importFetchedStatuses,
|
||||
importFetchedAccounts,
|
||||
} from 'mastodon/actions/importer';
|
||||
import { apiRequestGet, apiRequestPost } from 'mastodon/api';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { Account } from 'mastodon/models/account';
|
||||
import type { AnnualReport as AnnualReportData } from 'mastodon/models/annual_report';
|
||||
import type { Status } from 'mastodon/models/status';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { Archetype } from './archetype';
|
||||
import { Followers } from './followers';
|
||||
import { HighlightedPost } from './highlighted_post';
|
||||
import { MostUsedHashtag } from './most_used_hashtag';
|
||||
import { NewPosts } from './new_posts';
|
||||
import { Percentile } from './percentile';
|
||||
|
||||
interface AnnualReportResponse {
|
||||
annual_reports: AnnualReportData[];
|
||||
accounts: Account[];
|
||||
statuses: Status[];
|
||||
}
|
||||
|
||||
export const AnnualReport: React.FC<{
|
||||
year: string;
|
||||
}> = ({ year }) => {
|
||||
const [response, setResponse] = useState<AnnualReportResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const currentAccount = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
apiRequestGet<AnnualReportResponse>(`v1/annual_reports/${year}`)
|
||||
.then((data) => {
|
||||
dispatch(importFetchedStatuses(data.statuses));
|
||||
dispatch(importFetchedAccounts(data.accounts));
|
||||
|
||||
setResponse(data);
|
||||
setLoading(false);
|
||||
|
||||
return apiRequestPost(`v1/annual_reports/${year}/read`);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [dispatch, year, setResponse, setLoading]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
const report = response?.annual_reports[0];
|
||||
|
||||
if (!report) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report'>
|
||||
<div className='annual-report__header'>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.thanks'
|
||||
defaultMessage='Thanks for being part of Mastodon!'
|
||||
/>
|
||||
</h1>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.here_it_is'
|
||||
defaultMessage='Here is your {year} in review:'
|
||||
values={{ year: report.year }}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='annual-report__bento annual-report__summary'>
|
||||
<Archetype data={report.data.archetype} />
|
||||
<HighlightedPost data={report.data.top_statuses} />
|
||||
<Followers
|
||||
data={report.data.time_series}
|
||||
total={currentAccount?.followers_count}
|
||||
/>
|
||||
<MostUsedHashtag data={report.data.top_hashtags} />
|
||||
<Percentile data={report.data.percentiles} />
|
||||
<NewPosts data={report.data.time_series} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'mastodon/models/annual_report';
|
||||
|
||||
export const MostUsedApp: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const app = data[0];
|
||||
|
||||
if (!app) {
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app' />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-app'>
|
||||
<div className='annual-report__summary__most-used-app__icon'>
|
||||
{app.name}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-app__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_app.most_used_app'
|
||||
defaultMessage='most used app'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import type { NameAndCount } from 'mastodon/models/annual_report';
|
||||
|
||||
export const MostUsedHashtag: React.FC<{
|
||||
data: NameAndCount[];
|
||||
}> = ({ data }) => {
|
||||
const hashtag = data[0];
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__most-used-hashtag'>
|
||||
<div className='annual-report__summary__most-used-hashtag__hashtag'>
|
||||
{hashtag ? (
|
||||
<>#{hashtag.name}</>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_hashtag.none'
|
||||
defaultMessage='None'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='annual-report__summary__most-used-hashtag__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.most_used_hashtag.most_used_hashtag'
|
||||
defaultMessage='most used hashtag'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
53
app/javascript/mastodon/features/annual_report/new_posts.tsx
Normal file
53
app/javascript/mastodon/features/annual_report/new_posts.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
||||
|
||||
import ChatBubbleIcon from '@/material-icons/400-24px/chat_bubble.svg?react';
|
||||
import type { TimeSeriesMonth } from 'mastodon/models/annual_report';
|
||||
|
||||
export const NewPosts: React.FC<{
|
||||
data: TimeSeriesMonth[];
|
||||
}> = ({ data }) => {
|
||||
const posts = data.reduce((sum, item) => sum + item.statuses, 0);
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__new-posts'>
|
||||
<svg width={500} height={500}>
|
||||
<defs>
|
||||
<pattern
|
||||
id='posts'
|
||||
x='0'
|
||||
y='0'
|
||||
width='32'
|
||||
height='35'
|
||||
patternUnits='userSpaceOnUse'
|
||||
>
|
||||
<circle cx='12' cy='12' r='12' fill='var(--lime)' />
|
||||
<ChatBubbleIcon
|
||||
fill='var(--indigo-1)'
|
||||
x='4'
|
||||
y='4'
|
||||
width='16'
|
||||
height='16'
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect
|
||||
width={500}
|
||||
height={500}
|
||||
fill='url(#posts)'
|
||||
style={{ opacity: 0.2 }}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className='annual-report__summary__new-posts__number'>
|
||||
<FormattedNumber value={posts} />
|
||||
</div>
|
||||
<div className='annual-report__summary__new-posts__label'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.new_posts.new_posts'
|
||||
defaultMessage='new posts'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import type { Percentiles } from 'mastodon/models/annual_report';
|
||||
|
||||
export const Percentile: React.FC<{
|
||||
data: Percentiles;
|
||||
}> = ({ data }) => {
|
||||
const percentile = data.statuses;
|
||||
|
||||
return (
|
||||
<div className='annual-report__bento__box annual-report__summary__percentile'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.text'
|
||||
defaultMessage='<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>'
|
||||
values={{
|
||||
topLabel: (str) => (
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
),
|
||||
percentage: () => (
|
||||
<div className='annual-report__summary__percentile__number'>
|
||||
<FormattedNumber
|
||||
value={Math.min(percentile, 99) / 100}
|
||||
style='percent'
|
||||
maximumFractionDigits={percentile < 1 ? 1 : 0}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
bottomLabel: (str) => (
|
||||
<div>
|
||||
<div className='annual-report__summary__percentile__label'>
|
||||
{str}
|
||||
</div>
|
||||
|
||||
{percentile < 6 && (
|
||||
<div className='annual-report__summary__percentile__footnote'>
|
||||
<FormattedMessage
|
||||
id='annual_report.summary.percentile.we_wont_tell_bernie'
|
||||
defaultMessage="We won't tell Bernie."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{(message) => <>{message}</>}
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' iconComponent={PersonAddIcon} heading={intl.formatMessage(messages.heading)} alwaysShowBackButton>
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import CelebrationIcon from '@/material-icons/400-24px/celebration.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import type { NotificationGroupAnnualReport } from 'mastodon/models/notification_group';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
export const NotificationAnnualReport: React.FC<{
|
||||
notification: NotificationGroupAnnualReport;
|
||||
unread: boolean;
|
||||
}> = ({ notification: { annualReport }, unread }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const year = annualReport.year;
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'ANNUAL_REPORT',
|
||||
modalProps: { year },
|
||||
}),
|
||||
);
|
||||
}, [dispatch, year]);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='button'
|
||||
className={classNames(
|
||||
'notification-group notification-group--link notification-group--annual-report focusable',
|
||||
{ 'notification-group--unread': unread },
|
||||
)}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className='notification-group__icon'>
|
||||
<Icon id='celebration' icon={CelebrationIcon} />
|
||||
</div>
|
||||
|
||||
<div className='notification-group__main'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.message'
|
||||
defaultMessage="Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!"
|
||||
values={{ year }}
|
||||
/>
|
||||
</p>
|
||||
<button onClick={handleClick} className='link-button'>
|
||||
<FormattedMessage
|
||||
id='notification.annual_report.view'
|
||||
defaultMessage='View #Wrapstodon'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
|||
|
||||
import { NotificationAdminReport } from './notification_admin_report';
|
||||
import { NotificationAdminSignUp } from './notification_admin_sign_up';
|
||||
import { NotificationAnnualReport } from './notification_annual_report';
|
||||
import { NotificationFavourite } from './notification_favourite';
|
||||
import { NotificationFollow } from './notification_follow';
|
||||
import { NotificationFollowRequest } from './notification_follow_request';
|
||||
|
@ -143,6 +144,14 @@ export const NotificationGroup: React.FC<{
|
|||
/>
|
||||
);
|
||||
break;
|
||||
case 'annual_report':
|
||||
content = (
|
||||
<NotificationAnnualReport
|
||||
unread={unread}
|
||||
notification={notificationGroup}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain: string;
|
||||
showMedia?: boolean;
|
||||
withLogo?: boolean;
|
||||
overrideDisplayName?: React.ReactNode;
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
|
@ -62,6 +63,7 @@ export const DetailedStatus: React.FC<{
|
|||
domain,
|
||||
showMedia,
|
||||
withLogo,
|
||||
overrideDisplayName,
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
|
@ -319,7 +321,11 @@ export const DetailedStatus: React.FC<{
|
|||
<div className='detailed-status__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={46} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
|
||||
{overrideDisplayName ?? (
|
||||
<DisplayName account={status.get('account')} localDomain={domain} />
|
||||
)}
|
||||
|
||||
{withLogo && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { AnnualReport } from 'mastodon/features/annual_report';
|
||||
|
||||
const AnnualReportModal: React.FC<{
|
||||
year: string;
|
||||
onChangeBackgroundColor: (arg0: string) => void;
|
||||
}> = ({ year, onChangeBackgroundColor }) => {
|
||||
useEffect(() => {
|
||||
onChangeBackgroundColor('var(--indigo-1)');
|
||||
}, [onChangeBackgroundColor]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal annual-report-modal'>
|
||||
<AnnualReport year={year} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AnnualReportModal;
|
|
@ -18,6 +18,7 @@ import {
|
|||
SubscribedLanguagesModal,
|
||||
ClosedRegistrationsModal,
|
||||
IgnoreNotificationsModal,
|
||||
AnnualReportModal,
|
||||
} from 'mastodon/features/ui/util/async-components';
|
||||
import { getScrollbarWidth } from 'mastodon/utils/scrollbar';
|
||||
|
||||
|
@ -72,6 +73,7 @@ export const MODAL_COMPONENTS = {
|
|||
'INTERACTION': InteractionModal,
|
||||
'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
|
||||
'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
|
||||
'ANNUAL_REPORT': AnnualReportModal,
|
||||
};
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
|
|
|
@ -482,7 +482,9 @@ class UI extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleHotkeyBack = () => {
|
||||
handleHotkeyBack = e => {
|
||||
e.preventDefault();
|
||||
|
||||
const { history } = this.props;
|
||||
|
||||
if (history.location?.state?.fromMastodon) {
|
||||
|
|
|
@ -217,3 +217,7 @@ export function NotificationRequest () {
|
|||
export function LinkTimeline () {
|
||||
return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline');
|
||||
}
|
||||
|
||||
export function AnnualReportModal () {
|
||||
return import(/*webpackChunkName: "modals/annual_report_modal" */'../components/annual_report_modal');
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"alert.unexpected.title": "المعذرة!",
|
||||
"alt_text_badge.title": "نص بديل",
|
||||
"announcement.announcement": "إعلان",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"attachments_list.unprocessed": "(غير معالَج)",
|
||||
"audio.hide": "إخفاء المقطع الصوتي",
|
||||
"block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
|
||||
|
|
|
@ -154,7 +154,7 @@
|
|||
"compose_form.hashtag_warning": "Гэты допіс не будзе паказаны пад аніякім хэштэгам, бо ён не публічны. Толькі публічныя допісы можна знайсці па хэштэгу.",
|
||||
"compose_form.lock_disclaimer": "Ваш уліковы запіс не {locked}. Усе могуць падпісацца на вас, каб бачыць допісы толькі для падпісчыкаў.",
|
||||
"compose_form.lock_disclaimer.lock": "закрыты",
|
||||
"compose_form.placeholder": "Што здарылася?",
|
||||
"compose_form.placeholder": "Што ў вас новага?",
|
||||
"compose_form.poll.duration": "Працягласць апытання",
|
||||
"compose_form.poll.multiple": "Множны выбар",
|
||||
"compose_form.poll.option_placeholder": "Варыянт {number}",
|
||||
|
|
|
@ -87,6 +87,17 @@
|
|||
"alert.unexpected.title": "Опаа!",
|
||||
"alt_text_badge.title": "Алтернативен текст",
|
||||
"announcement.announcement": "Оповестяване",
|
||||
"annual_report.summary.archetype.lurker": "Дебнещото",
|
||||
"annual_report.summary.archetype.oracle": "Оракул",
|
||||
"annual_report.summary.archetype.pollster": "Анкетьорче",
|
||||
"annual_report.summary.archetype.replier": "Социална пеперуда",
|
||||
"annual_report.summary.followers.followers": "последователи",
|
||||
"annual_report.summary.followers.total": "{count} общо",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "най-правено като любима публикация",
|
||||
"annual_report.summary.most_used_app.most_used_app": "най-употребявано приложение",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "най-употребяван хаштаг",
|
||||
"annual_report.summary.new_posts.new_posts": "нови публикации",
|
||||
"annual_report.summary.thanks": "Благодарим, че сте част от Mastodon!",
|
||||
"attachments_list.unprocessed": "(необработено)",
|
||||
"audio.hide": "Скриване на звука",
|
||||
"block_modal.remote_users_caveat": "Ще поискаме сървърът {domain} да почита решението ви. Съгласието обаче не се гарантира откак някои сървъри могат да боравят с блоковете по различен начин. Обществените публикации още може да се виждат от невлезли в системата потребители.",
|
||||
|
@ -158,6 +169,7 @@
|
|||
"compose_form.poll.duration": "Времетраене на анкетата",
|
||||
"compose_form.poll.multiple": "Множествен избор",
|
||||
"compose_form.poll.option_placeholder": "Избор {number}",
|
||||
"compose_form.poll.single": "Единичен избор",
|
||||
"compose_form.poll.switch_to_multiple": "Промяна на анкетата, за да се позволят множество възможни избора",
|
||||
"compose_form.poll.switch_to_single": "Промяна на анкетата, за да се позволи един възможен избор",
|
||||
"compose_form.poll.type": "Стил",
|
||||
|
@ -195,6 +207,8 @@
|
|||
"confirmations.unfollow.message": "Наистина ли искате да не следвате {name}?",
|
||||
"confirmations.unfollow.title": "Спирате ли да следвате потребителя?",
|
||||
"content_warning.hide": "Скриване на публ.",
|
||||
"content_warning.show": "Нека се покаже",
|
||||
"content_warning.show_more": "Показване на още",
|
||||
"conversation.delete": "Изтриване на разговора",
|
||||
"conversation.mark_as_read": "Маркиране като прочетено",
|
||||
"conversation.open": "Преглед на разговора",
|
||||
|
@ -365,6 +379,9 @@
|
|||
"home.pending_critical_update.link": "Преглед на обновяванията",
|
||||
"home.pending_critical_update.title": "Налично критично обновяване на сигурността!",
|
||||
"home.show_announcements": "Показване на оповестяванията",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon не може да осведоми потребители, че сте пренебрегнали известията им. Пренебрегването на известията няма да спре самите съобщения да не бъдат изпращани.",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Вие все още ще може да приемате, отхвърляте или докладвате потребители",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Прецеждането помага за избягване на възможно объркване",
|
||||
"interaction_modal.description.favourite": "Имайки акаунт в Mastodon, може да сложите тази публикации в любими, за да позволите на автора да узнае, че я цените и да я запазите за по-късно.",
|
||||
"interaction_modal.description.follow": "С акаунт в Mastodon може да последвате {name}, за да получавате публикациите от този акаунт в началния си инфоканал.",
|
||||
"interaction_modal.description.reblog": "С акаунт в Mastodon може да подсилите тази публикация, за да я споделите с последователите си.",
|
||||
|
@ -380,6 +397,7 @@
|
|||
"interaction_modal.title.follow": "Последване на {name}",
|
||||
"interaction_modal.title.reblog": "Подсилване на публикацията на {name}",
|
||||
"interaction_modal.title.reply": "Отговаряне на публикацията на {name}",
|
||||
"interaction_modal.title.vote": "Гласувайте в анкетата на {name}",
|
||||
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
|
||||
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# минута} other {# минути}}",
|
||||
|
@ -420,6 +438,8 @@
|
|||
"lightbox.close": "Затваряне",
|
||||
"lightbox.next": "Напред",
|
||||
"lightbox.previous": "Назад",
|
||||
"lightbox.zoom_in": "Увеличение до действителната големина",
|
||||
"lightbox.zoom_out": "Увеличение до побиране",
|
||||
"limited_account_hint.action": "Показване на профила въпреки това",
|
||||
"limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.",
|
||||
"link_preview.author": "От {name}",
|
||||
|
@ -441,6 +461,7 @@
|
|||
"lists.subheading": "Вашите списъци",
|
||||
"load_pending": "{count, plural, one {# нов елемент} other {# нови елемента}}",
|
||||
"loading_indicator.label": "Зареждане…",
|
||||
"media_gallery.hide": "Скриване",
|
||||
"moved_to_account_banner.text": "Вашият акаунт {disabledAccount} сега е изключен, защото се преместихте в {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Скриване от известията",
|
||||
"mute_modal.hide_options": "Скриване на възможностите",
|
||||
|
@ -489,6 +510,7 @@
|
|||
"notification.favourite": "{name} направи любима публикацията ви",
|
||||
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
||||
"notification.follow": "{name} ви последва",
|
||||
"notification.follow.name_and_others": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> ви последваха",
|
||||
"notification.follow_request": "{name} поиска да ви последва",
|
||||
"notification.follow_request.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} поискаха да ви последват",
|
||||
"notification.label.mention": "Споменаване",
|
||||
|
@ -496,6 +518,7 @@
|
|||
"notification.label.private_reply": "Личен отговор",
|
||||
"notification.label.reply": "Отговор",
|
||||
"notification.mention": "Споменаване",
|
||||
"notification.mentioned_you": "{name} ви спомена",
|
||||
"notification.moderation-warning.learn_more": "Научете повече",
|
||||
"notification.moderation_warning": "Получихте предупреждение за модериране",
|
||||
"notification.moderation_warning.action_delete_statuses": "Някои от публикациите ви са премахнати.",
|
||||
|
@ -752,6 +775,7 @@
|
|||
"status.bookmark": "Отмятане",
|
||||
"status.cancel_reblog_private": "Край на подсилването",
|
||||
"status.cannot_reblog": "Публикацията не може да се подсилва",
|
||||
"status.continued_thread": "Продължена нишка",
|
||||
"status.copy": "Копиране на връзката към публикация",
|
||||
"status.delete": "Изтриване",
|
||||
"status.detailed_status": "Подробен изглед на разговора",
|
||||
|
@ -760,6 +784,7 @@
|
|||
"status.edit": "Редактиране",
|
||||
"status.edited": "Последно редактирано на {date}",
|
||||
"status.edited_x_times": "Редактирано {count, plural,one {{count} път} other {{count} пъти}}",
|
||||
"status.embed": "Вземане на кода за вграждане",
|
||||
"status.favourite": "Любимо",
|
||||
"status.favourites": "{count, plural, one {любимо} other {любими}}",
|
||||
"status.filter": "Филтриране на публ.",
|
||||
|
@ -784,6 +809,7 @@
|
|||
"status.reblogs.empty": "Още никого не е подсилвал публикацията. Подсилващият ще се покаже тук.",
|
||||
"status.redraft": "Изтриване и преработване",
|
||||
"status.remove_bookmark": "Премахване на отметката",
|
||||
"status.replied_in_thread": "Отговорено в нишката",
|
||||
"status.replied_to": "В отговор до {name}",
|
||||
"status.reply": "Отговор",
|
||||
"status.replyAll": "Отговор на нишка",
|
||||
|
@ -821,6 +847,7 @@
|
|||
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
|
||||
"upload_form.audio_description": "Опишете за хора, които са глухи или трудно чуват",
|
||||
"upload_form.description": "Опишете за хора, които са слепи или имат слабо зрение",
|
||||
"upload_form.drag_and_drop.instructions": "Натиснете интервал или enter, за да подберете мултимедийно прикачване. Провлачвайки, ползвайте клавишите със стрелки, за да премествате мултимедията във всяка дадена посока. Натиснете пак интервал или enter, за да се стовари мултимедийното прикачване в новото си положение или натиснете Esc за отмяна.",
|
||||
"upload_form.edit": "Редактиране",
|
||||
"upload_form.thumbnail": "Промяна на миниобраза",
|
||||
"upload_form.video_description": "Опишете за хора, които са глухи или трудно чуват, слепи или имат слабо зрение",
|
||||
|
|
|
@ -82,6 +82,8 @@
|
|||
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
|
||||
"alert.unexpected.title": "Hopala !",
|
||||
"announcement.announcement": "Kemennad",
|
||||
"annual_report.summary.followers.followers": "heulier",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"attachments_list.unprocessed": "(ket meret)",
|
||||
"audio.hide": "Kuzhat ar c'hleved",
|
||||
"block_modal.show_less": "Diskouez nebeutoc'h",
|
||||
|
|
|
@ -87,6 +87,19 @@
|
|||
"alert.unexpected.title": "Vaja!",
|
||||
"alt_text_badge.title": "Text alternatiu",
|
||||
"announcement.announcement": "Anunci",
|
||||
"annual_report.summary.archetype.oracle": "L'Oracle",
|
||||
"annual_report.summary.followers.followers": "seguidors",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "El repàs del vostre {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "la publicació més afavorida",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "la publicació més impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "la publicació amb més respostes",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "l'aplicació més utilitzada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "l'etiqueta més utilitzada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Cap",
|
||||
"annual_report.summary.new_posts.new_posts": "publicacions noves",
|
||||
"annual_report.summary.thanks": "Gràcies per formar part de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sense processar)",
|
||||
"audio.hide": "Amaga l'àudio",
|
||||
"block_modal.remote_users_caveat": "Li demanarem al servidor {domain} que respecti la vostra decisió, tot i que no podem garantir-ho, ja que alguns servidors gestionen de forma diferent els blocatges. És possible que els usuaris no autenticats puguin veure les publicacions públiques.",
|
||||
|
|
|
@ -87,11 +87,30 @@
|
|||
"alert.unexpected.title": "Wps!",
|
||||
"alt_text_badge.title": "Testun Amgen",
|
||||
"announcement.announcement": "Cyhoeddiad",
|
||||
"annual_report.summary.archetype.booster": "Y hyrwyddwr",
|
||||
"annual_report.summary.archetype.lurker": "Yr arsylwr",
|
||||
"annual_report.summary.archetype.oracle": "Yr oracl",
|
||||
"annual_report.summary.archetype.pollster": "Yr arholwr",
|
||||
"annual_report.summary.archetype.replier": "Y sbardunwr",
|
||||
"annual_report.summary.followers.followers": "dilynwyr",
|
||||
"annual_report.summary.followers.total": "{count} cyfanswm",
|
||||
"annual_report.summary.here_it_is": "Dyma eich {year} yn gryno:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "postiad wedi'i ffefrynu fwyaf",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "postiad wedi'i hybu fwyaf",
|
||||
"annual_report.summary.highlighted_post.by_replies": "postiad gyda'r ymatebion mwyaf",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "ap a ddefnyddiwyd fwyaf",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashnod a ddefnyddiwyd fwyaf",
|
||||
"annual_report.summary.most_used_hashtag.none": "Dim",
|
||||
"annual_report.summary.new_posts.new_posts": "postiadau newydd",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Rydych chi yn y </topLabel><percentage></percentage><bottomLabel>mwyaf o ddefnyddwyr Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ni fyddwn yn dweud wrth Bernie.",
|
||||
"annual_report.summary.thanks": "Diolch am fod yn rhan o Mastodon!",
|
||||
"attachments_list.unprocessed": "(heb eu prosesu)",
|
||||
"audio.hide": "Cuddio sain",
|
||||
"block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystro mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.",
|
||||
"block_modal.show_less": "Dangos llai",
|
||||
"block_modal.show_more": "Dangos mwy",
|
||||
"block_modal.show_more": "Dangos rhagor",
|
||||
"block_modal.they_cant_mention": "Nid ydynt yn gallu eich crybwyll na'ch dilyn.",
|
||||
"block_modal.they_cant_see_posts": "Nid ydynt yn gallu gweld eich postiadau ac ni fyddwch yn gweld eu rhai hwy.",
|
||||
"block_modal.they_will_know": "Gallant weld eu bod wedi'u rhwystro.",
|
||||
|
@ -163,9 +182,9 @@
|
|||
"compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
|
||||
"compose_form.poll.type": "Arddull",
|
||||
"compose_form.publish": "Postiad",
|
||||
"compose_form.publish_form": "Cyhoeddi",
|
||||
"compose_form.publish_form": "Postiad newydd",
|
||||
"compose_form.reply": "Ateb",
|
||||
"compose_form.save_changes": "Diweddariad",
|
||||
"compose_form.save_changes": "Diweddaru",
|
||||
"compose_form.spoiler.marked": "Dileu rhybudd cynnwys",
|
||||
"compose_form.spoiler.unmarked": "Ychwanegu rhybudd cynnwys",
|
||||
"compose_form.spoiler_placeholder": "Rhybudd cynnwys (dewisol)",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "Adroddodd {name} {target}",
|
||||
"notification.admin.sign_up": "Cofrestrodd {name}",
|
||||
"notification.admin.sign_up.name_and_others": "Cofrestrodd {name} {count, plural, one {ac # arall} other {a # arall}}",
|
||||
"notification.annual_report.message": "Mae eich #Wrapstodon {year} yn aros i chi! Gwelwch eich uchafbwyntiau ac amseroedd i'w cofio o'r flwyddyn hon ar Mastodon!",
|
||||
"notification.annual_report.view": "Gweld #Wrapstodon",
|
||||
"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.follow": "Dilynodd {name} chi",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Ups!",
|
||||
"alt_text_badge.title": "Alt text",
|
||||
"announcement.announcement": "Bekendtgørelse",
|
||||
"annual_report.summary.archetype.booster": "Cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "Lurker",
|
||||
"annual_report.summary.archetype.oracle": "Oracle",
|
||||
"annual_report.summary.archetype.pollster": "Pollster",
|
||||
"annual_report.summary.archetype.replier": "Social butterfly",
|
||||
"annual_report.summary.followers.followers": "følgere",
|
||||
"annual_report.summary.followers.total": "{count} i alt",
|
||||
"annual_report.summary.here_it_is": "Her er {year} i sammendrag:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "mest favoritmarkerede indlæg",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "mest boostede indlæg",
|
||||
"annual_report.summary.highlighted_post.by_replies": "indlæg med flest svar",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest benyttede app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest benyttede hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Intet",
|
||||
"annual_report.summary.new_posts.new_posts": "nye indlæg",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Det betyder, at man er i top</topLabel><percentage></percentage><bottomLabel>af Mastodon-brugere.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vi fortæller det ikke til Bernie.",
|
||||
"annual_report.summary.thanks": "Tak for at være en del af Mastodon!",
|
||||
"attachments_list.unprocessed": "(ubehandlet)",
|
||||
"audio.hide": "Skjul lyd",
|
||||
"block_modal.remote_users_caveat": "Serveren {domain} vil blive bedt om at respektere din beslutning. Overholdelse er dog ikke garanteret, da nogle servere kan håndtere blokke forskelligt. Offentlige indlæg kan stadig være synlige for ikke-indloggede brugere.",
|
||||
|
@ -158,6 +177,7 @@
|
|||
"compose_form.poll.duration": "Afstemningens varighed",
|
||||
"compose_form.poll.multiple": "Multivalg",
|
||||
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
||||
"compose_form.poll.single": "Enkeltvalg",
|
||||
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
|
||||
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
|
||||
"compose_form.poll.type": "Stil",
|
||||
|
@ -507,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} anmeldte {target}",
|
||||
"notification.admin.sign_up": "{name} tilmeldte sig",
|
||||
"notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# anden} other {# andre}} tilmeldte sig",
|
||||
"notification.annual_report.message": "{year} #Wrapstodon venter! Afslør årets højdepunkter og mindeværdige øjeblikke på Mastodon!",
|
||||
"notification.annual_report.view": "Vis #Wrapstodon",
|
||||
"notification.favourite": "{name} favoritmarkerede dit indlæg",
|
||||
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# anden} other {# andre}}</a> gjorde dit indlæg til favorit",
|
||||
"notification.follow": "{name} begyndte at følge dig",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Oha!",
|
||||
"alt_text_badge.title": "Bildbeschreibung",
|
||||
"announcement.announcement": "Ankündigung",
|
||||
"annual_report.summary.archetype.booster": "Trendjäger*in",
|
||||
"annual_report.summary.archetype.lurker": "Beobachter*in",
|
||||
"annual_report.summary.archetype.oracle": "Orakel",
|
||||
"annual_report.summary.archetype.pollster": "Meinungsforscher*in",
|
||||
"annual_report.summary.archetype.replier": "Geselliger Schmetterling",
|
||||
"annual_report.summary.followers.followers": "Follower",
|
||||
"annual_report.summary.followers.total": "{count} insgesamt",
|
||||
"annual_report.summary.here_it_is": "Dein Jahresrückblick für {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "am häufigsten favorisierter Beitrag",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "am häufigsten geteilter Beitrag",
|
||||
"annual_report.summary.highlighted_post.by_replies": "Beitrag mit den meisten Antworten",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "am häufigsten verwendete App",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "am häufigsten verwendeter Hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Kein",
|
||||
"annual_report.summary.new_posts.new_posts": "neue Beiträge",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Damit gehörst du zu den obersten</topLabel><percentage></percentage><bottomLabel>der Mastodon-Nutzer*innen.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Wir werden Bernie nichts verraten.",
|
||||
"annual_report.summary.thanks": "Danke, dass du Teil von Mastodon bist!",
|
||||
"attachments_list.unprocessed": "(ausstehend)",
|
||||
"audio.hide": "Audio ausblenden",
|
||||
"block_modal.remote_users_caveat": "Wir werden den Server {domain} bitten, deine Entscheidung zu respektieren. Allerdings kann nicht garantiert werden, dass sie eingehalten wird, weil einige Server Blockierungen unterschiedlich handhaben können. Öffentliche Beiträge können für nicht angemeldete Nutzer*innen weiterhin sichtbar sein.",
|
||||
|
@ -507,13 +526,15 @@
|
|||
"notification.admin.report_statuses": "{name} meldete {target} wegen {category}",
|
||||
"notification.admin.report_statuses_other": "{name} meldete {target}",
|
||||
"notification.admin.sign_up": "{name} registrierte sich",
|
||||
"notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} registrierten sich",
|
||||
"notification.admin.sign_up.name_and_others": "{name} und {count, plural, one {# weiteres Profil} other {# weitere Profile}} registrierten sich",
|
||||
"notification.annual_report.message": "Dein {year} #Wrapstodon erwartet dich! Lass deine Highlights und unvergesslichen Momente auf Mastodon erneut aufleben!",
|
||||
"notification.annual_report.view": "#Wrapstodon ansehen",
|
||||
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
||||
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> favorisierten deinen Beitrag",
|
||||
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> favorisierten deinen Beitrag",
|
||||
"notification.follow": "{name} folgt dir",
|
||||
"notification.follow.name_and_others": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> folgen dir",
|
||||
"notification.follow.name_and_others": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> folgen dir",
|
||||
"notification.follow_request": "{name} möchte dir folgen",
|
||||
"notification.follow_request.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} möchten dir folgen",
|
||||
"notification.follow_request.name_and_others": "{name} und {count, plural, one {# weiteres Profil} other {# weitere Profile}} möchten dir folgen",
|
||||
"notification.label.mention": "Erwähnung",
|
||||
"notification.label.private_mention": "Private Erwähnung",
|
||||
"notification.label.private_reply": "Private Antwort",
|
||||
|
@ -532,7 +553,7 @@
|
|||
"notification.own_poll": "Deine Umfrage ist beendet",
|
||||
"notification.poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet",
|
||||
"notification.reblog": "{name} teilte deinen Beitrag",
|
||||
"notification.reblog.name_and_others_with_link": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> teilten deinen Beitrag",
|
||||
"notification.reblog.name_and_others_with_link": "{name} und <a>{count, plural, one {# weiteres Profil} other {# weitere Profile}}</a> teilten deinen Beitrag",
|
||||
"notification.relationships_severance_event": "Verbindungen mit {name} verloren",
|
||||
"notification.relationships_severance_event.account_suspension": "Ein Admin von {from} hat {target} gesperrt. Du wirst von diesem Profil keine Updates mehr erhalten und auch nicht mit ihm interagieren können.",
|
||||
"notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} blockiert – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Ουπς!",
|
||||
"alt_text_badge.title": "Εναλλακτικό κείμενο",
|
||||
"announcement.announcement": "Ανακοίνωση",
|
||||
"annual_report.summary.archetype.booster": "Ο κυνηγός των φοβερών",
|
||||
"annual_report.summary.archetype.lurker": "Ο διακριτικός",
|
||||
"annual_report.summary.archetype.oracle": "Η Πυθία",
|
||||
"annual_report.summary.archetype.pollster": "Ο δημοσκόπος",
|
||||
"annual_report.summary.archetype.replier": "Η κοινωνική πεταλούδα",
|
||||
"annual_report.summary.followers.followers": "ακόλουθοι",
|
||||
"annual_report.summary.followers.total": "{count} συνολικά",
|
||||
"annual_report.summary.here_it_is": "Εδώ είναι το {year} σου σε ανασκόπηση:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "πιο αγαπημένη ανάρτηση",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "πιο ενισχυμένη ανάρτηση",
|
||||
"annual_report.summary.highlighted_post.by_replies": "ανάρτηση με τις περισσότερες απαντήσεις",
|
||||
"annual_report.summary.highlighted_post.possessive": "του χρήστη {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "πιο χρησιμοποιημένη εφαρμογή",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "πιο χρησιμοποιημένη ετικέτα",
|
||||
"annual_report.summary.new_posts.new_posts": "νέες αναρτήσεις",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Αυτό σε βάζει στην κορυφή του </topLabel><percentage></percentage><bottomLabel>των χρηστών του Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Δεν θα το πούμε στον Bernie.",
|
||||
"annual_report.summary.thanks": "Ευχαριστούμε που συμμετέχεις στο Mastodon!",
|
||||
"attachments_list.unprocessed": "(μη επεξεργασμένο)",
|
||||
"audio.hide": "Απόκρυψη αρχείου ήχου",
|
||||
"block_modal.remote_users_caveat": "Θα ζητήσουμε από τον διακομιστή {domain} να σεβαστεί την απόφασή σου. Ωστόσο, η συμμόρφωση δεν είναι εγγυημένη δεδομένου ότι ορισμένοι διακομιστές ενδέχεται να χειρίζονται τους αποκλεισμούς διαφορετικά. Οι δημόσιες αναρτήσεις ενδέχεται να είναι ορατές σε μη συνδεδεμένους χρήστες.",
|
||||
|
@ -158,6 +176,7 @@
|
|||
"compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
|
||||
"compose_form.poll.multiple": "Πολλαπλή επιλογή",
|
||||
"compose_form.poll.option_placeholder": "Επιλογή {number}",
|
||||
"compose_form.poll.single": "Μονή επιλογή",
|
||||
"compose_form.poll.switch_to_multiple": "Ενημέρωση δημοσκόπησης με πολλαπλές επιλογές",
|
||||
"compose_form.poll.switch_to_single": "Ενημέρωση δημοσκόπησης με μοναδική επιλογή",
|
||||
"compose_form.poll.type": "Στυλ",
|
||||
|
@ -196,6 +215,7 @@
|
|||
"confirmations.unfollow.title": "Άρση ακολούθησης;",
|
||||
"content_warning.hide": "Απόκρυψη ανάρτησης",
|
||||
"content_warning.show": "Εμφάνιση ούτως ή άλλως",
|
||||
"content_warning.show_more": "Εμφάνιση περισσότερων",
|
||||
"conversation.delete": "Διαγραφή συζήτησης",
|
||||
"conversation.mark_as_read": "Σήμανση ως αναγνωσμένο",
|
||||
"conversation.open": "Προβολή συνομιλίας",
|
||||
|
@ -304,6 +324,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Χρησιμοποιήστε μια υπάρχουσα κατηγορία ή δημιουργήστε μια νέα",
|
||||
"filter_modal.select_filter.title": "Φιλτράρισμα αυτής της ανάρτησης",
|
||||
"filter_modal.title.status": "Φιλτράρισμα μιας ανάρτησης",
|
||||
"filter_warning.matches_filter": "Ταιριάζει με το φίλτρο “<span>{title}</span>”",
|
||||
"filtered_notifications_banner.pending_requests": "Από {count, plural, =0 {κανένα} one {ένα άτομο} other {# άτομα}} που μπορεί να ξέρεις",
|
||||
"filtered_notifications_banner.title": "Φιλτραρισμένες ειδοποιήσεις",
|
||||
"firehose.all": "Όλα",
|
||||
|
@ -383,9 +404,10 @@
|
|||
"interaction_modal.description.follow": "Με έναν λογαριασμό Mastodon, μπορείς να ακολουθήσεις τον/την {name} ώστε να λαμβάνεις τις αναρτήσεις του/της στη δική σου ροή.",
|
||||
"interaction_modal.description.reblog": "Με ένα λογαριασμό Mastodon, μπορείς να ενισχύσεις αυτή την ανάρτηση για να τη μοιραστείς με τους δικούς σου ακολούθους.",
|
||||
"interaction_modal.description.reply": "Με ένα λογαριασμό Mastodon, μπορείς να απαντήσεις σε αυτή την ανάρτηση.",
|
||||
"interaction_modal.login.action": "Take me home\nΠήγαινέ με στην αρχική σελίδα",
|
||||
"interaction_modal.description.vote": "Με ένα λογαριασμό Mastodon, μπορείς να απαντήσεις σ' αυτή την ανάρτηση.",
|
||||
"interaction_modal.login.action": "Πήγαινέ με στην αρχική σελίδα",
|
||||
"interaction_modal.login.prompt": "Τομέας του οικιακού σου διακομιστή, πχ. mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Not on Mastodon?\nΔεν είστε στο Mastodon;",
|
||||
"interaction_modal.no_account_yet": "Δεν είστε στο Mastodon;",
|
||||
"interaction_modal.on_another_server": "Σε διαφορετικό διακομιστή",
|
||||
"interaction_modal.on_this_server": "Σε αυτόν τον διακομιστή",
|
||||
"interaction_modal.sign_in": "Δεν είσαι συνδεδεμένος σε αυτόν το διακομιστή. Πού φιλοξενείται ο λογαριασμός σου;",
|
||||
|
@ -394,6 +416,7 @@
|
|||
"interaction_modal.title.follow": "Ακολούθησε {name}",
|
||||
"interaction_modal.title.reblog": "Ενίσχυσε την ανάρτηση του {name}",
|
||||
"interaction_modal.title.reply": "Απάντηση στην ανάρτηση του {name}",
|
||||
"interaction_modal.title.vote": "Ψήφισε στη δημοσκόπηση του χρήστη {name}",
|
||||
"intervals.full.days": "{number, plural, one {# μέρα} other {# μέρες}}",
|
||||
"intervals.full.hours": "{number, plural, one {# ώρα} other {# ώρες}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# λεπτό} other {# λεπτά}}",
|
||||
|
@ -503,9 +526,12 @@
|
|||
"notification.admin.report_statuses_other": "Ο χρήστης {name} ανέφερε τον χρήστη {target}",
|
||||
"notification.admin.sign_up": "{name} έχει εγγραφεί",
|
||||
"notification.admin.sign_up.name_and_others": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} έχουν εγγραφεί",
|
||||
"notification.annual_report.message": "Το #Wrapstodon {year} σε περιμένει! Αποκάλυψε τα στιγμιότυπα της χρονιάς και αξέχαστες στιγμές σου στο Mastodon!",
|
||||
"notification.annual_report.view": "Προβολή #Wrapstodon",
|
||||
"notification.favourite": "{name} favorited your post\n{name} προτίμησε την ανάρτηση σου",
|
||||
"notification.favourite.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ανάρτησή σου",
|
||||
"notification.follow": "Ο/Η {name} σε ακολούθησε",
|
||||
"notification.follow.name_and_others": "Ο χρήστης {name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> σε ακολούθησαν",
|
||||
"notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει",
|
||||
"notification.follow_request.name_and_others": "{name} και {count, plural, one {# άλλος} other {# άλλοι}} ζήτησαν να σε ακολουθήσουν",
|
||||
"notification.label.mention": "Επισήμανση",
|
||||
|
@ -513,6 +539,7 @@
|
|||
"notification.label.private_reply": "Ιδιωτική απάντηση",
|
||||
"notification.label.reply": "Απάντηση",
|
||||
"notification.mention": "Επισήμανση",
|
||||
"notification.mentioned_you": "Ο χρήστης {name} σε επισήμανε",
|
||||
"notification.moderation-warning.learn_more": "Μάθε περισσότερα",
|
||||
"notification.moderation_warning": "Έχετε λάβει μία προειδοποίηση συντονισμού",
|
||||
"notification.moderation_warning.action_delete_statuses": "Ορισμένες από τις αναρτήσεις σου έχουν αφαιρεθεί.",
|
||||
|
@ -563,6 +590,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
||||
"notifications.column_settings.follow": "Νέοι ακόλουθοι:",
|
||||
"notifications.column_settings.follow_request": "Νέο αίτημα ακολούθησης:",
|
||||
"notifications.column_settings.group": "Ομάδα",
|
||||
"notifications.column_settings.mention": "Επισημάνσεις:",
|
||||
"notifications.column_settings.poll": "Αποτελέσματα δημοσκόπησης:",
|
||||
"notifications.column_settings.push": "Ειδοποιήσεις Push",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Oops!",
|
||||
"alt_text_badge.title": "Alt text",
|
||||
"announcement.announcement": "Announcement",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "The lurker",
|
||||
"annual_report.summary.archetype.oracle": "The oracle",
|
||||
"annual_report.summary.archetype.pollster": "The pollster",
|
||||
"annual_report.summary.archetype.replier": "The social butterfly",
|
||||
"annual_report.summary.followers.followers": "followers",
|
||||
"annual_report.summary.followers.total": "{count} total",
|
||||
"annual_report.summary.here_it_is": "Here is your {year} in review:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "most favourited post",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "most boosted post",
|
||||
"annual_report.summary.highlighted_post.by_replies": "post with the most replies",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "most used app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "most used hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "None",
|
||||
"annual_report.summary.new_posts.new_posts": "new posts",
|
||||
"annual_report.summary.percentile.text": "<topLabel>That puts you in the top</topLabel><percentage></percentage><bottomLabel>of Mastodon users.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "We won't tell Bernie.",
|
||||
"annual_report.summary.thanks": "Thanks for being part of Mastodon!",
|
||||
"attachments_list.unprocessed": "(unprocessed)",
|
||||
"audio.hide": "Hide audio",
|
||||
"block_modal.remote_users_caveat": "We will ask the server {domain} to respect your decision. However, compliance is not guaranteed since some servers may handle blocks differently. Public posts may still be visible to non-logged-in users.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} reported {target}",
|
||||
"notification.admin.sign_up": "{name} signed up",
|
||||
"notification.admin.sign_up.name_and_others": "{name} and {count, plural, one {# other} other {# others}} signed up",
|
||||
"notification.annual_report.message": "Your {year} #Wrapstodon awaits! Unveil your year's highlights and memorable moments on Mastodon!",
|
||||
"notification.annual_report.view": "View #Wrapstodon",
|
||||
"notification.favourite": "{name} favorited your post",
|
||||
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your post",
|
||||
"notification.follow": "{name} followed you",
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"account.languages": "Ŝanĝi la abonitajn lingvojn",
|
||||
"account.link_verified_on": "Propreco de tiu ligilo estis konfirmita je {date}",
|
||||
"account.locked_info": "Tiu konto estas privatigita. La posedanto mane akceptas tiun, kiu povas sekvi rin.",
|
||||
"account.media": "Plurmedioj",
|
||||
"account.media": "Plurmedio",
|
||||
"account.mention": "Mencii @{name}",
|
||||
"account.moved_to": "{name} indikis, ke ria nova konto estas nun:",
|
||||
"account.mute": "Silentigi @{name}",
|
||||
|
@ -87,6 +87,13 @@
|
|||
"alert.unexpected.title": "Aj!",
|
||||
"alt_text_badge.title": "Alt-teksto",
|
||||
"announcement.announcement": "Anonco",
|
||||
"annual_report.summary.archetype.replier": "La plej societema",
|
||||
"annual_report.summary.followers.followers": "sekvantoj",
|
||||
"annual_report.summary.highlighted_post.by_replies": "afiŝo kun la plej multaj respondoj",
|
||||
"annual_report.summary.most_used_app.most_used_app": "plej uzita apo",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nenio",
|
||||
"annual_report.summary.new_posts.new_posts": "novaj afiŝoj",
|
||||
"annual_report.summary.thanks": "Dankon pro esti parto de Mastodon!",
|
||||
"attachments_list.unprocessed": "(neprilaborita)",
|
||||
"audio.hide": "Kaŝi aŭdion",
|
||||
"block_modal.remote_users_caveat": "Ni petos al la servilo {domain} respekti vian elekton. Tamen, plenumo ne estas garantiita ĉar iuj serviloj eble manipulas blokojn malsame. Publikaj afiŝoj eble ankoraŭ estas videbla por ne-ensalutintaj uzantoj.",
|
||||
|
@ -142,7 +149,7 @@
|
|||
"column_header.unpin": "Malfiksi",
|
||||
"column_subheading.settings": "Agordoj",
|
||||
"community.column_settings.local_only": "Nur loka",
|
||||
"community.column_settings.media_only": "Nur plurmedioj",
|
||||
"community.column_settings.media_only": "Nur plurmedio",
|
||||
"community.column_settings.remote_only": "Nur fora",
|
||||
"compose.language.change": "Ŝanĝi lingvon",
|
||||
"compose.language.search": "Serĉi lingvojn...",
|
||||
|
@ -214,7 +221,7 @@
|
|||
"dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
|
||||
"dismissable_banner.dismiss": "Eksigi",
|
||||
"dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
|
||||
"dismissable_banner.explore_statuses": "Ĉi tiuj estas afiŝoj de la tuta socia reto, kiuj populariĝas hodiaŭ. Pli novaj afiŝoj kun pli da diskonigoj kaj plej ŝatataj estas rangigitaj pli alte.",
|
||||
"dismissable_banner.explore_statuses": "Jen afiŝoj en la socia reto kiuj populariĝis hodiaŭ. Novaj afiŝoj kun pli da diskonigoj kaj stelumoj aperas pli alte.",
|
||||
"dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
|
||||
"dismissable_banner.public_timeline": "Ĉi tiuj estas la plej lastatempaj publikaj afiŝoj de homoj en la socia reto, kiujn homoj sur {domain} sekvas.",
|
||||
"domain_block_modal.block": "Bloki servilon",
|
||||
|
@ -333,7 +340,7 @@
|
|||
"followed_tags": "Sekvataj kradvortoj",
|
||||
"footer.about": "Pri",
|
||||
"footer.directory": "Profilujo",
|
||||
"footer.get_app": "Akiru la Programon",
|
||||
"footer.get_app": "Akiri la apon",
|
||||
"footer.invite": "Inviti homojn",
|
||||
"footer.keyboard_shortcuts": "Fulmoklavoj",
|
||||
"footer.privacy_policy": "Politiko de privateco",
|
||||
|
@ -382,7 +389,7 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ĉu ignori sciigojn de homoj, kiuj ne sekvas vin?",
|
||||
"ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
|
||||
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
|
||||
"interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumi ĉi tiun afiŝon por sciigi la afiŝanton ke vi sâtas kaj konservas ĝin por poste.",
|
||||
"interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povas sekvi {name} por ricevi iliajn afiŝojn en via hejma fluo.",
|
||||
"interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
|
||||
"interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu afiŝo.",
|
||||
|
@ -508,6 +515,7 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportis {target}",
|
||||
"notification.admin.sign_up": "{name} kreis konton",
|
||||
"notification.admin.sign_up.name_and_others": "{name} kaj {count, plural, one {# alia} other {# aliaj}} kreis konton",
|
||||
"notification.annual_report.view": "Vidu #Wrapstodon",
|
||||
"notification.favourite": "{name} stelumis vian afiŝon",
|
||||
"notification.favourite.name_and_others_with_link": "{name} kaj <a>{count, plural, one {# alia} other {# aliaj}}</a> ŝatis vian afiŝon",
|
||||
"notification.follow": "{name} eksekvis vin",
|
||||
|
@ -640,7 +648,7 @@
|
|||
"onboarding.start.lead": "Vi nun estas parto de Mastodon, unika, malcentralizita socia amaskomunikilara platformo, kie vi—ne algoritmo—zorgas vian propran sperton. Ni komencu vin sur ĉi tiu nova socia limo:",
|
||||
"onboarding.start.skip": "Ĉu vi ne bezonas helpon por komenci?",
|
||||
"onboarding.start.title": "Vi atingas ĝin!",
|
||||
"onboarding.steps.follow_people.body": "Sekvi interesajn homojn estas pri kio Mastodonto temas.",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Agordu vian hejman fluon",
|
||||
"onboarding.steps.publish_status.body": "Salutu la mondon per teksto, fotoj, filmetoj aŭ balotenketoj {emoji}",
|
||||
"onboarding.steps.publish_status.title": "Fari vian unuan afiŝon",
|
||||
|
@ -774,9 +782,9 @@
|
|||
"server_banner.is_one_of_many": "{domain} estas unu el la multaj sendependaj Mastodon-serviloj, kiujn vi povas uzi por partopreni en la fediverso.",
|
||||
"server_banner.server_stats": "Statistikoj de la servilo:",
|
||||
"sign_in_banner.create_account": "Krei konton",
|
||||
"sign_in_banner.follow_anyone": "Sekvi iun ajn tra la fediverso kaj vidi ĉion en kronologia ordo. Neniuj algoritmoj, reklamoj aŭ klakbetoj videblas.",
|
||||
"sign_in_banner.mastodon_is": "Mastodonto estas la plej bona maniero por resti flank-al-flanke kun kio okazas.",
|
||||
"sign_in_banner.sign_in": "Saluti",
|
||||
"sign_in_banner.follow_anyone": "Sekvu iun ajn tra la fediverso kaj vidu ĉion laŭ templinio. Nul algoritmo, reklamo aŭ kliklogilo ĉeestas.",
|
||||
"sign_in_banner.mastodon_is": "Mastodon estas la plej bona maniero resti ĝisdata pri aktualaĵoj.",
|
||||
"sign_in_banner.sign_in": "Ensaluti",
|
||||
"sign_in_banner.sso_redirect": "Ensalutu aŭ Registriĝi",
|
||||
"status.admin_account": "Malfermi fasadon de moderigado por @{name}",
|
||||
"status.admin_domain": "Malfermu moderigan interfacon por {domain}",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "¡Epa!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "Corrió la voz",
|
||||
"annual_report.summary.archetype.lurker": "El acechador",
|
||||
"annual_report.summary.archetype.oracle": "El oráculo",
|
||||
"annual_report.summary.archetype.pollster": "Estuvo consultando",
|
||||
"annual_report.summary.archetype.replier": "Respondió un montón",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Acá está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "el mensaje más veces marcado como favorito",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "el mensaje que más adhesiones recibió",
|
||||
"annual_report.summary.highlighted_post.by_replies": "el mensaje que más respuestas recibió",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "la aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "la etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevos mensajes",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en la cima</topLabel><percentage></percentage><bottomLabel>de los usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "[sin procesar]",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar los bloqueos de forma diferente. Los mensajes públicos todavía podrían estar visibles para los usuarios no conectados.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} denunció a {target}",
|
||||
"notification.admin.sign_up": "Se registró {name}",
|
||||
"notification.admin.sign_up.name_and_others": "Se registraron {name} y {count, plural, one {# cuenta más} other {# cuentas más}}",
|
||||
"notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!",
|
||||
"notification.annual_report.view": "Ver #Wrapstodon",
|
||||
"notification.favourite": "{name} marcó tu mensaje como favorito",
|
||||
"notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# cuenta más} other {# cuentas más}}</a> marcaron tu mensaje como favorito",
|
||||
"notification.follow": "{name} te empezó a seguir",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "¡Ups!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "El cazador de tendencias",
|
||||
"annual_report.summary.archetype.lurker": "El acechador",
|
||||
"annual_report.summary.archetype.oracle": "El oraculo",
|
||||
"annual_report.summary.archetype.pollster": "El encuestador",
|
||||
"annual_report.summary.archetype.replier": "La mariposa sociable",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sin procesar)",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado ya que algunos servidores pueden manejar bloques de forma diferente. Las publicaciones públicas pueden ser todavía visibles para los usuarios no conectados.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} reportó {target}",
|
||||
"notification.admin.sign_up": "{name} se unio",
|
||||
"notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} se registraron",
|
||||
"notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!",
|
||||
"notification.annual_report.view": "Ver #Wrapstodon",
|
||||
"notification.favourite": "{name} marcó como favorita tu publicación",
|
||||
"notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# otro} other {# otros}}</a> marcaron tu publicación como favorita",
|
||||
"notification.follow": "{name} te empezó a seguir",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "¡Ups!",
|
||||
"alt_text_badge.title": "Texto alternativo",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "El cazador de tendencias",
|
||||
"annual_report.summary.archetype.lurker": "El acechador",
|
||||
"annual_report.summary.archetype.oracle": "El oráculo",
|
||||
"annual_report.summary.archetype.pollster": "El encuestador",
|
||||
"annual_report.summary.archetype.replier": "El más sociable",
|
||||
"annual_report.summary.followers.followers": "seguidores",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Aquí está tu resumen de {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "publicación con más favoritos",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "publicación más impulsada",
|
||||
"annual_report.summary.highlighted_post.by_replies": "publicación con más respuestas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aplicación más usada",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "etiqueta más usada",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ninguna",
|
||||
"annual_report.summary.new_posts.new_posts": "nuevas publicaciones",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Eso te pone en el top</topLabel><percentage></percentage><bottomLabel>de usuarios de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "No se lo diremos a Bernie.",
|
||||
"annual_report.summary.thanks": "¡Gracias por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sin procesar)",
|
||||
"audio.hide": "Ocultar audio",
|
||||
"block_modal.remote_users_caveat": "Le pediremos al servidor {domain} que respete tu decisión. Sin embargo, el cumplimiento no está garantizado, ya que algunos servidores pueden manejar bloqueos de forma distinta. Los mensajes públicos pueden ser todavía visibles para los usuarios que no hayan iniciado sesión.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} informó de {target}",
|
||||
"notification.admin.sign_up": "{name} se registró",
|
||||
"notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# más} other {# más}} se registraron",
|
||||
"notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!",
|
||||
"notification.annual_report.view": "Ver #Wrapstodon",
|
||||
"notification.favourite": "{name} marcó como favorita tu publicación",
|
||||
"notification.favourite.name_and_others_with_link": "{name} y <a>{count, plural, one {# más} other {# más}}</a> marcaron tu publicación como favorita",
|
||||
"notification.follow": "{name} te empezó a seguir",
|
||||
|
|
|
@ -87,6 +87,23 @@
|
|||
"alert.unexpected.title": "Hups!",
|
||||
"alt_text_badge.title": "Vaihtoehtoinen teksti",
|
||||
"announcement.announcement": "Tiedote",
|
||||
"annual_report.summary.archetype.booster": "Tehostaja",
|
||||
"annual_report.summary.archetype.lurker": "Lymyilijä",
|
||||
"annual_report.summary.archetype.oracle": "Oraakkeli",
|
||||
"annual_report.summary.archetype.pollster": "Mielipidetutkija",
|
||||
"annual_report.summary.archetype.replier": "Sosiaalinen perhonen",
|
||||
"annual_report.summary.followers.followers": "seuraajaa",
|
||||
"annual_report.summary.followers.total": "{count} yhteensä",
|
||||
"annual_report.summary.here_it_is": "Tässä on katsaus vuoteesi {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "suosikkeihin lisätyin julkaisu",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "tehostetuin julkaisu",
|
||||
"annual_report.summary.highlighted_post.by_replies": "julkaisu, jolla on eniten vastauksia",
|
||||
"annual_report.summary.highlighted_post.possessive": "Käyttäjän {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "käytetyin sovellus",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "käytetyin aihetunniste",
|
||||
"annual_report.summary.new_posts.new_posts": "uutta julkaisua",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Olet osa huippujoukkoa, johon kuuluu</topLabel><percentage></percentage><bottomLabel>Mastodon-käyttäjistä.</bottomLabel>",
|
||||
"annual_report.summary.thanks": "Kiitos, että olet osa Mastodonia!",
|
||||
"attachments_list.unprocessed": "(käsittelemätön)",
|
||||
"audio.hide": "Piilota ääni",
|
||||
"block_modal.remote_users_caveat": "Pyydämme palvelinta {domain} kunnioittamaan päätöstäsi. Myötämielisyyttä ei kuitenkaan taata, koska jotkin palvelimet voivat käsitellä estoja eri tavalla. Julkiset julkaisut voivat silti näkyä kirjautumattomille käyttäjille.",
|
||||
|
@ -158,7 +175,7 @@
|
|||
"compose_form.poll.duration": "Äänestyksen kesto",
|
||||
"compose_form.poll.multiple": "Monivalinta",
|
||||
"compose_form.poll.option_placeholder": "Vaihtoehto {number}",
|
||||
"compose_form.poll.single": "Yksi vaihtoehto",
|
||||
"compose_form.poll.single": "Yksittäisvalinta",
|
||||
"compose_form.poll.switch_to_multiple": "Muuta äänestys monivalinnaksi",
|
||||
"compose_form.poll.switch_to_single": "Muuta äänestys yksittäisvalinnaksi",
|
||||
"compose_form.poll.type": "Tyyli",
|
||||
|
@ -386,7 +403,7 @@
|
|||
"interaction_modal.description.follow": "Mastodon-tilillä voit seurata käyttäjää {name} saadaksesi hänen julkaisunsa kotisyötteeseesi.",
|
||||
"interaction_modal.description.reblog": "Mastodon-tilillä voit tehostaa tätä julkaisua jakaaksesi sen seuraajiesi kanssa.",
|
||||
"interaction_modal.description.reply": "Mastodon-tilillä voit vastata tähän julkaisuun.",
|
||||
"interaction_modal.description.vote": "Osallistuminen äänestykseen onnistuu Mastodon-tilillä.",
|
||||
"interaction_modal.description.vote": "Mastodon-tilillä voit osallistua tähän äänestykseen.",
|
||||
"interaction_modal.login.action": "Siirry kotiin",
|
||||
"interaction_modal.login.prompt": "Kotipalvelimesi verkkotunnus, kuten mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Etkö ole vielä Mastodonissa?",
|
||||
|
@ -508,6 +525,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} raportoi käyttäjän {target}",
|
||||
"notification.admin.sign_up": "{name} rekisteröityi",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ja {count, plural, one {# muu} other {# muuta}} rekisteröityivät",
|
||||
"notification.annual_report.message": "Vuoden {year} #Wrapstodon odottaa! Paljasta vuotesi kohokohdat ikimuistoiset hetket Mastodonissa!",
|
||||
"notification.annual_report.view": "Näytä #Wrapstodon",
|
||||
"notification.favourite": "{name} lisäsi julkaisusi suosikkeihinsa",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ja <a>{count, plural, one {# muu} other {# muuta}}</a> lisäsivät julkaisusi suosikkeihinsa",
|
||||
"notification.follow": "{name} seurasi sinua",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Ups!",
|
||||
"alt_text_badge.title": "Annar tekstur",
|
||||
"announcement.announcement": "Kunngerð",
|
||||
"annual_report.summary.archetype.booster": "Kuli jagarin",
|
||||
"annual_report.summary.archetype.lurker": "Lúrarin",
|
||||
"annual_report.summary.archetype.oracle": "Oraklið",
|
||||
"annual_report.summary.archetype.pollster": "Spyrjarin",
|
||||
"annual_report.summary.archetype.replier": "Sosiali firvaldurin",
|
||||
"annual_report.summary.followers.followers": "fylgjarar",
|
||||
"annual_report.summary.followers.total": "{count} íalt",
|
||||
"annual_report.summary.here_it_is": "Her er ein samandráttur av {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "mest dámdi postur",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "oftast lyfti postur",
|
||||
"annual_report.summary.highlighted_post.by_replies": "postur við flestum svarum",
|
||||
"annual_report.summary.highlighted_post.possessive": "hjá {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "mest brúkta app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "mest brúkta frámerki",
|
||||
"annual_report.summary.most_used_hashtag.none": "Einki",
|
||||
"annual_report.summary.new_posts.new_posts": "nýggir postar",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Tað fær teg í topp</topLabel><percentage></percentage><bottomLabel>av Mastodon brúkarum.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Vit fara ikki at fortelja Bernie tað.",
|
||||
"annual_report.summary.thanks": "Takk fyri at tú er partur av Mastodon!",
|
||||
"attachments_list.unprocessed": "(óviðgjørt)",
|
||||
"audio.hide": "Fjal ljóð",
|
||||
"block_modal.remote_users_caveat": "Vit biðja ambætaran {domain} virða tína avgerð. Kortini er eingin vissa um samsvar, av tí at fleiri ambætarar handfara blokkar ymiskt. Almennir postar kunnu framvegis vera sjónligir fyri brúkarar, sum ikki eru innritaðir.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} meldaði {target}",
|
||||
"notification.admin.sign_up": "{name} meldaði seg til",
|
||||
"notification.admin.sign_up.name_and_others": "{name} og {count, plural, one {# annar/onnur} other {# onnur}} teknaðu seg",
|
||||
"notification.annual_report.message": "Títt {year} #Wrapstodon bíðar! Avdúka hæddarpunktini og minniligu løturnar á Mastodon!",
|
||||
"notification.annual_report.view": "Sí #Wrapstodon",
|
||||
"notification.favourite": "{name} dámdi postin hjá tær",
|
||||
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# annar/onnur} other {# onnur}}</a> yndisfrámerktu postin hjá tær",
|
||||
"notification.follow": "{name} fylgdi tær",
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
"alert.unexpected.title": "Oups!",
|
||||
"alt_text_badge.title": "Texte Alt",
|
||||
"announcement.announcement": "Annonce",
|
||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
"alert.unexpected.title": "Oups !",
|
||||
"alt_text_badge.title": "Texte Alt",
|
||||
"announcement.announcement": "Annonce",
|
||||
"annual_report.summary.archetype.oracle": "L’oracle",
|
||||
"annual_report.summary.here_it_is": "Voici votre récap de {year} :",
|
||||
"annual_report.summary.most_used_app.most_used_app": "appli la plus utilisée",
|
||||
"attachments_list.unprocessed": "(non traité)",
|
||||
"audio.hide": "Masquer l'audio",
|
||||
"block_modal.remote_users_caveat": "Nous allons demander au serveur {domain} de respecter votre décision. Cependant, ce respect n'est pas garanti, car certains serveurs peuvent gérer différemment les blocages. Les messages publics peuvent rester visibles par les utilisateur·rice·s non connecté·e·s.",
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
"compose_form.poll.duration": "Doer fan de enkête",
|
||||
"compose_form.poll.multiple": "Mearkar",
|
||||
"compose_form.poll.option_placeholder": "Opsje {number}",
|
||||
"compose_form.poll.single": "Inkelde kar",
|
||||
"compose_form.poll.switch_to_multiple": "Enkête wizigje om meardere karren ta te stean",
|
||||
"compose_form.poll.switch_to_single": "Enkête wizigje om in inkelde kar ta te stean",
|
||||
"compose_form.poll.type": "Styl",
|
||||
|
@ -196,6 +197,7 @@
|
|||
"confirmations.unfollow.title": "Brûker net mear folgje?",
|
||||
"content_warning.hide": "Berjocht ferstopje",
|
||||
"content_warning.show": "Dochs toane",
|
||||
"content_warning.show_more": "Mear toane",
|
||||
"conversation.delete": "Petear fuortsmite",
|
||||
"conversation.mark_as_read": "As lêzen markearje",
|
||||
"conversation.open": "Petear toane",
|
||||
|
@ -304,6 +306,7 @@
|
|||
"filter_modal.select_filter.subtitle": "In besteande kategory brûke of in nije oanmeitsje",
|
||||
"filter_modal.select_filter.title": "Dit berjocht filterje",
|
||||
"filter_modal.title.status": "In berjocht filterje",
|
||||
"filter_warning.matches_filter": "Komt oerien mei filter ‘<span>{title}</span>’",
|
||||
"filtered_notifications_banner.pending_requests": "Fan {count, plural, =0 {net ien} one {ien persoan} other {# persoanen}} dy’t jo mooglik kinne",
|
||||
"filtered_notifications_banner.title": "Filtere meldingen",
|
||||
"firehose.all": "Alles",
|
||||
|
@ -383,6 +386,7 @@
|
|||
"interaction_modal.description.follow": "Jo kinne mei in Mastodon-account {name} folgje, om sa harren berjochten op jo starttiidline te ûntfangen.",
|
||||
"interaction_modal.description.reblog": "Jo kinne mei in Mastodon-account dit berjocht booste, om it sa mei jo folgers te dielen.",
|
||||
"interaction_modal.description.reply": "Jo kinne mei in Mastodon-account op dit berjocht reagearje.",
|
||||
"interaction_modal.description.vote": "Mei in Mastodon-account kinne jo yn dizze enkête stimme.",
|
||||
"interaction_modal.login.action": "Gean nei start",
|
||||
"interaction_modal.login.prompt": "Domein fan jo server, byg. mastodon.social",
|
||||
"interaction_modal.no_account_yet": "Net op Mastodon?",
|
||||
|
@ -394,6 +398,7 @@
|
|||
"interaction_modal.title.follow": "{name} folgje",
|
||||
"interaction_modal.title.reblog": "Berjocht fan {name} booste",
|
||||
"interaction_modal.title.reply": "Op it berjocht fan {name} reagearje",
|
||||
"interaction_modal.title.vote": "Stimme yn {name}’s peiling",
|
||||
"intervals.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
|
||||
"intervals.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
|
||||
"intervals.full.minutes": "{number, plural, one {# minút} other {# minuten}} lyn",
|
||||
|
@ -506,6 +511,7 @@
|
|||
"notification.favourite": "{name} hat jo berjocht as favoryt markearre",
|
||||
"notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo berjocht as favoryt markearre",
|
||||
"notification.follow": "{name} folget dy",
|
||||
"notification.follow.name_and_others": "{name} en <a>{count, plural, one {# oar persoan} other {# oare persoanen}}</a> folgje jo no",
|
||||
"notification.follow_request": "{name} hat dy in folchfersyk stjoerd",
|
||||
"notification.follow_request.name_and_others": "{name} en {count, plural, one {# oar} other {# oaren}} hawwe frege om jo te folgjen",
|
||||
"notification.label.mention": "Fermelding",
|
||||
|
@ -564,6 +570,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Flugge filterbalke",
|
||||
"notifications.column_settings.follow": "Nije folgers:",
|
||||
"notifications.column_settings.follow_request": "Nij folchfersyk:",
|
||||
"notifications.column_settings.group": "Groepearje",
|
||||
"notifications.column_settings.mention": "Fermeldingen:",
|
||||
"notifications.column_settings.poll": "Enkêteresultaten:",
|
||||
"notifications.column_settings.push": "Pushmeldingen",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Hiúps!",
|
||||
"alt_text_badge.title": "Téacs alt",
|
||||
"announcement.announcement": "Fógra",
|
||||
"annual_report.summary.archetype.booster": "An sealgair fionnuar",
|
||||
"annual_report.summary.archetype.lurker": "An lurker",
|
||||
"annual_report.summary.archetype.oracle": "An oracal",
|
||||
"annual_report.summary.archetype.pollster": "An pollaire",
|
||||
"annual_report.summary.archetype.replier": "An féileacán sóisialta",
|
||||
"annual_report.summary.followers.followers": "leanúna",
|
||||
"annual_report.summary.followers.total": "{count} san iomlán",
|
||||
"annual_report.summary.here_it_is": "Seo do {year} faoi athbhreithniú:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "post is fearr leat",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "post is treisithe",
|
||||
"annual_report.summary.highlighted_post.by_replies": "post leis an líon is mó freagraí",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}'s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "aip is mó a úsáidtear",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "hashtag is mó a úsáidtear",
|
||||
"annual_report.summary.new_posts.new_posts": "postanna nua",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Cuireann sé sin i mbarr</topLabel><percentage></percentage><bottomLabel> úsáideoirí Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Ní inseoidh muid do Bernie.",
|
||||
"annual_report.summary.thanks": "Go raibh maith agat as a bheith mar chuid de Mastodon!",
|
||||
"attachments_list.unprocessed": "(neamhphróiseáilte)",
|
||||
"audio.hide": "Cuir fuaim i bhfolach",
|
||||
"block_modal.remote_users_caveat": "Iarrfaimid ar an bhfreastalaí {domain} meas a bheith agat ar do chinneadh. Mar sin féin, ní ráthaítear comhlíonadh toisc go bhféadfadh roinnt freastalaithe bloic a láimhseáil ar bhealach difriúil. Seans go mbeidh postálacha poiblí fós le feiceáil ag úsáideoirí nach bhfuil logáilte isteach.",
|
||||
|
@ -386,6 +404,7 @@
|
|||
"interaction_modal.description.follow": "Le cuntas ar Mastodon, is féidir leat {name} a leanúint chun a gcuid postálacha a fháil i do fhotha baile.",
|
||||
"interaction_modal.description.reblog": "Le cuntas ar Mastodon, is féidir leat an postáil seo a threisiú chun é a roinnt le do leantóirí féin.",
|
||||
"interaction_modal.description.reply": "Le cuntas ar Mastodon, is féidir leat freagra a thabhairt ar an bpostáil seo.",
|
||||
"interaction_modal.description.vote": "Le cuntas ar Mastodon, is féidir leat vótáil sa vótaíocht seo.",
|
||||
"interaction_modal.login.action": "Thabhairt dom abhaile",
|
||||
"interaction_modal.login.prompt": "Fearann do fhreastalaí baile, e.g. mastodon.sóisialta",
|
||||
"interaction_modal.no_account_yet": "Ní ar Mastodon?",
|
||||
|
@ -397,6 +416,7 @@
|
|||
"interaction_modal.title.follow": "Lean {name}",
|
||||
"interaction_modal.title.reblog": "Mol postáil de chuid {name}",
|
||||
"interaction_modal.title.reply": "Freagair postáil {name}",
|
||||
"interaction_modal.title.vote": "Vótáil i vótaíocht {name}",
|
||||
"intervals.full.days": "{number, plural, one {# lá} other {# lá}}",
|
||||
"intervals.full.hours": "{number, plural, one {# uair} other {# uair}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# nóiméad} other {# nóiméad}}",
|
||||
|
@ -506,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} tuairiscithe {target}",
|
||||
"notification.admin.sign_up": "Chláraigh {name}",
|
||||
"notification.admin.sign_up.name_and_others": "{name} agus {count, plural, one {# duine eile} two {# daoine eile} few {# daoine eile} many {# daoine eile} other {# daoine eile}} a chláraigh",
|
||||
"notification.annual_report.message": "Tá do {year} #Wrapstodon ag fanacht! Nocht buaicphointí na bliana agus chuimhneacháin i gcuimhne ar Mastodon!",
|
||||
"notification.annual_report.view": "Amharc #Wrapstodon",
|
||||
"notification.favourite": "Is fearr le {name} do phostáil",
|
||||
"notification.favourite.name_and_others_with_link": "{name} agus <a>{count, plural, one {# duine eile} other {# daoine eile}}</a> thaitin le do phost",
|
||||
"notification.follow": "Lean {name} thú",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "Vaites!",
|
||||
"alt_text_badge.title": "Texto Alt",
|
||||
"announcement.announcement": "Anuncio",
|
||||
"annual_report.summary.archetype.booster": "A axencia de noticias",
|
||||
"annual_report.summary.archetype.lurker": "Volleur",
|
||||
"annual_report.summary.archetype.oracle": "Sabichón/e",
|
||||
"annual_report.summary.archetype.pollster": "O INE",
|
||||
"annual_report.summary.archetype.replier": "Lareteire",
|
||||
"annual_report.summary.followers.followers": "seguidoras",
|
||||
"annual_report.summary.followers.total": "{count} en total",
|
||||
"annual_report.summary.here_it_is": "Este é o resumo do teu {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "a publicación mais favorecida",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "a publicación con mais promocións",
|
||||
"annual_report.summary.highlighted_post.by_replies": "a publicación con mais respostas",
|
||||
"annual_report.summary.highlighted_post.possessive": "de {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "app que mais usaches",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "o cancelo mais utilizado",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nada",
|
||||
"annual_report.summary.new_posts.new_posts": "novas publicacións",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Sitúante no top</topLabel><percentage></percentage><bottomLabel> das usuarias de Mastodon.</bottomLabel>",
|
||||
"annual_report.summary.thanks": "Grazas por ser parte de Mastodon!",
|
||||
"attachments_list.unprocessed": "(sen procesar)",
|
||||
"audio.hide": "Agochar audio",
|
||||
"block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} denunciou a {target}",
|
||||
"notification.admin.sign_up": "{name} rexistrouse",
|
||||
"notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# máis} other {# máis}} crearon unha conta",
|
||||
"notification.annual_report.message": "A #VidaEnMastodon de {year} agarda por ti! Desvela os momentos máis destacados e historias reseñables en Mastodon!",
|
||||
"notification.annual_report.view": "Ver #VidaEnMastodon",
|
||||
"notification.favourite": "{name} marcou como favorita a túa publicación",
|
||||
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# máis} other {# máis}}</a> favoreceron a túa publicación",
|
||||
"notification.follow": "{name} comezou a seguirte",
|
||||
|
|
|
@ -87,6 +87,24 @@
|
|||
"alert.unexpected.title": "אופס!",
|
||||
"alt_text_badge.title": "כיתוב חלופי",
|
||||
"announcement.announcement": "הכרזה",
|
||||
"annual_report.summary.archetype.booster": "ההד-וניסט(ית)",
|
||||
"annual_report.summary.archetype.lurker": "השורץ.ת השקט.ה",
|
||||
"annual_report.summary.archetype.oracle": "כבוד הרב.ה",
|
||||
"annual_report.summary.archetype.pollster": "הסקרן.ית",
|
||||
"annual_report.summary.archetype.replier": "הפרפר.ית החברתי.ת",
|
||||
"annual_report.summary.followers.followers": "עוקבים",
|
||||
"annual_report.summary.followers.total": "{count} בסך הכל",
|
||||
"annual_report.summary.here_it_is": "והנה סיכום {year} שלך:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "התות הכי מחובב",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "התות הכי מהודהד",
|
||||
"annual_report.summary.highlighted_post.by_replies": "התות עם מספר התשובות הגבוה ביותר",
|
||||
"annual_report.summary.highlighted_post.possessive": "של {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "היישומון שהכי בשימוש",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "התג בשימוש הרב ביותר",
|
||||
"annual_report.summary.new_posts.new_posts": "הודעות חדשות",
|
||||
"annual_report.summary.percentile.text": "<topLabel>ממקם אותך באחוזון </topLabel><percentage></percentage><bottomLabel>של משמשי מסטודון.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "לא נגלה לברני.",
|
||||
"annual_report.summary.thanks": "תודה על היותך חלק ממסטודון!",
|
||||
"attachments_list.unprocessed": "(לא מעובד)",
|
||||
"audio.hide": "השתק",
|
||||
"block_modal.remote_users_caveat": "אנו נבקש מהשרת {domain} לכבד את החלטתך. עם זאת, ציות למוסכמות איננו מובטח כיוון ששרתים מסויימים עשויים לטפל בחסימות בצורה אחרת. הודעות פומביות עדיין יהיו גלויות לעיני משתמשים שאינם מחוברים.",
|
||||
|
@ -508,6 +526,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} דיווח.ה על {target}",
|
||||
"notification.admin.sign_up": "{name} נרשמו",
|
||||
"notification.admin.sign_up.name_and_others": "{name} ועוד {count, plural,one {אחד אחר}other {# אחרים}} נרשמו",
|
||||
"notification.annual_report.message": "ה- #סיכומודון שלך לשנת {year} מחכה! גלו את רגעי השיא והזכרונות ממסטודון!",
|
||||
"notification.annual_report.view": "לצפייה ב- #סיכומודון",
|
||||
"notification.favourite": "הודעתך חובבה על ידי {name}",
|
||||
"notification.favourite.name_and_others_with_link": "{name} ועוד <a>{count, plural,one {אחד נוסף}other {# נוספים}}</a> חיבבו את הודעתך",
|
||||
"notification.follow": "{name} במעקב אחרייך",
|
||||
|
|
|
@ -87,6 +87,25 @@
|
|||
"alert.unexpected.title": "Hoppá!",
|
||||
"alt_text_badge.title": "Helyettesítő szöveg",
|
||||
"announcement.announcement": "Közlemény",
|
||||
"annual_report.summary.archetype.booster": "A cool-vadász",
|
||||
"annual_report.summary.archetype.lurker": "A settenkedő",
|
||||
"annual_report.summary.archetype.oracle": "Az orákulum",
|
||||
"annual_report.summary.archetype.pollster": "A közvélemény-kutató",
|
||||
"annual_report.summary.archetype.replier": "A társasági pillangó",
|
||||
"annual_report.summary.followers.followers": "követő",
|
||||
"annual_report.summary.followers.total": "{count} összesen",
|
||||
"annual_report.summary.here_it_is": "Itt a {year}. év értékelése:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "legkedvencebb bejegyzés",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "legtöbbet megtolt bejegyzés",
|
||||
"annual_report.summary.highlighted_post.by_replies": "bejegyzés a legtöbb válasszal",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name} fióktól",
|
||||
"annual_report.summary.most_used_app.most_used_app": "legtöbbet használt app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "legtöbbet használt hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Nincs",
|
||||
"annual_report.summary.new_posts.new_posts": "új bejegyzés",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Ezzel a</topLabel><percentage></percentage><bottomLabel>csúcs Mastodon felhasználó között vagy.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Nem mondjuk el Bernie-nek.",
|
||||
"annual_report.summary.thanks": "Kösz, hogy a Mastodon része vagy!",
|
||||
"attachments_list.unprocessed": "(feldolgozatlan)",
|
||||
"audio.hide": "Hang elrejtése",
|
||||
"block_modal.remote_users_caveat": "Arra kérjük a {domain} kiszolgálót, hogy tartsa tiszteletben a döntésedet. Ugyanakkor az együttműködés nem garantált, mivel néhány kiszolgáló másképp kezelheti a letiltásokat. A nyilvános bejegyzések a be nem jelentkezett felhasználók számára továbbra is látszódhatnak.",
|
||||
|
@ -508,6 +527,8 @@
|
|||
"notification.admin.report_statuses_other": "{name} jelentette: {target}",
|
||||
"notification.admin.sign_up": "{name} regisztrált",
|
||||
"notification.admin.sign_up.name_and_others": "{name} és {count, plural, one {# másik} other {# másik}} regisztrált",
|
||||
"notification.annual_report.message": "Vár a {year}. év #Wrapstodon jelentése! Fedd fel az éved jelentős eseményeit és emlékezetes pillanatait a Mastodonon!",
|
||||
"notification.annual_report.view": "#Wrapstodon Megtekintése",
|
||||
"notification.favourite": "{name} kedvencnek jelölte a bejegyzésedet",
|
||||
"notification.favourite.name_and_others_with_link": "{name} és <a>{count, plural, one {# másik} other {# másik}}</a> kedvencnek jelölte a bejegyzésedet",
|
||||
"notification.follow": "{name} követ téged",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue