Compare commits

..

101 Commits
v4.4.1 ... main

Author SHA1 Message Date
github-actions[bot]
be3dc5b508
New Crowdin Translations (automated) (#35453)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-22 10:10:37 +00:00
renovate[bot]
ae13063460
chore(deps): update dependency eslint-plugin-jsdoc to v51 (#35026)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Haml Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 16:31:10 +00:00
renovate[bot]
1ed58aaaf2
fix(deps): update dependency axios to v1.10.0 (#35050)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 16:28:49 +00:00
renovate[bot]
bf17895d19
chore(deps): update dependency chromatic to v13 (#35064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 16:28:01 +00:00
Matt Jankowski
20b3c43dde
Update playwright-ruby-client to version 1.54.0 (#35448) 2025-07-21 16:24:55 +00:00
diondiondion
ee21f72211
fix: Don't submit post when pressing Enter in CW field (#35445) 2025-07-21 15:57:20 +00:00
diondiondion
4de5cbd6f5
refactor: Replace react-hotkeys with custom hook (#35425) 2025-07-21 14:43:38 +00:00
Matt Jankowski
fab95b8dae
Add coverage for api/v1/invites scenarios (#35389) 2025-07-21 14:17:53 +00:00
renovate[bot]
4d2655490c
chore(deps): update dependency nokogiri to v1.18.9 (#35433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 12:25:28 +00:00
github-actions[bot]
6bb4113d0a
New Crowdin Translations (automated) (#35428)
Some checks are pending
Bundler Audit / security (push) Waiting to run
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Haml Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-21 09:01:34 +00:00
Matt Jankowski
3e76f01db4
Use bundler version 2.7.0 (#35430) 2025-07-21 08:07:53 +00:00
diondiondion
cf580d8c90
Don't require JSDoc params & return in TS (#35426)
Some checks failed
Check i18n / check-i18n (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (ruby) (push) Has been cancelled
Check formatting / lint (push) Has been cancelled
JavaScript Linting / lint (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 / test (3.3) (push) Has been cancelled
Ruby Testing / ImageMagick tests (.ruby-version) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.2) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.3) (push) Has been cancelled
Ruby Testing / End to End testing (.ruby-version) (push) Has been cancelled
Ruby Testing / End to End testing (3.2) (push) Has been cancelled
Ruby Testing / End to End testing (3.3) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Bundler Audit / security (push) Has been cancelled
2025-07-18 18:34:04 +00:00
github-actions[bot]
dbd0c3cbd9
New Crowdin Translations (automated) (#35420)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-18 07:52:05 +00:00
diondiondion
3771f9e04b
fix: Fix quote posts styling on notifications page (#35411)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Chromatic / Run Chromatic (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
2025-07-17 17:28:30 +00:00
diondiondion
a842b14c84
refactor: Disable useDrag hook when main menu is not openable (#35414) 2025-07-17 12:39:40 +00:00
diondiondion
138746bdcc
fix: Add lang attribute to current composer language in alt text modal (#35412) 2025-07-17 11:12:54 +00:00
Echo
9e6a9efe10
Replace Ruby Vite plugins (#35195)
Co-authored-by: Renaud Chaput <renchap@gmail.com>
2025-07-17 10:04:04 +00:00
renovate[bot]
19626ad89f
chore(deps): update dependency httplog to v1.7.1 (#35406)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Bundler Audit / security (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
Ruby Linting / lint (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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 09:32:57 +00:00
github-actions[bot]
7e2d92284c
New Crowdin Translations (automated) (#35408)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-17 09:31:22 +00:00
renovate[bot]
20fb6bd788
chore(deps): update docker.io/ruby docker tag to v3.4.5 (#35397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 09:03:40 +00:00
diondiondion
faffb73cbd
fix: Improve a11y of custom select menus in notifications settings (#35403) 2025-07-17 08:56:00 +00:00
renovate[bot]
02a4e30594
chore(deps): update dependency propshaft to v1.2.0 (#35398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 08:48:10 +00:00
renovate[bot]
f10b522f0c
chore(deps): update dependency ruby to v3.4.5 (#35395)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-17 08:47:39 +00:00
github-actions[bot]
331599fa2b
New Crowdin Translations (automated) (#35399)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-16 12:24:09 +00:00
diondiondion
558b9c90a6
fix: Fix selected item in poll select menus is unreadable in Firefox (#35402) 2025-07-16 12:03:39 +00:00
Renaud Chaput
7d2dda97b3
Remove the "to triage" label status (#35394)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-07-16 07:17:21 +00:00
diondiondion
74fc4dbacf
refactor: Only remove pointer-events when necessary (#35390)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Crowdin / Upload translations / upload-translations (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
Ruby Linting / lint (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
2025-07-15 15:57:31 +00:00
diondiondion
07912a1cb7
Update age limit wording (#35387) 2025-07-15 15:46:44 +00:00
Claire
d36bf3b6fb
Fix support for quote verification in implicit status updates (#35384) 2025-07-15 15:36:12 +00:00
Claire
594976a538
Refactor ActivityPub::Activity::Accept and ActivityPub::Activity::Reject specs (#35382) 2025-07-15 13:18:37 +00:00
Matt Jankowski
0efb889a9c
Extract constant for attribution domains limit in account (#35350) 2025-07-15 13:08:24 +00:00
Claire
c0eabe289b
Always give local quote of remote posts a quote request URI (#35383) 2025-07-15 13:01:03 +00:00
Claire
5bbc3c5ebb
Fix quoteAuthorization type in JSON-LD context (#35380) 2025-07-15 09:32:02 +00:00
github-actions[bot]
d5e2cf5d3c
New Crowdin Translations (automated) (#35379)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
JavaScript Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-15 08:16:56 +00:00
diondiondion
82a6ff091f
fix: Improve Dropdown component accessibility (#35373) 2025-07-15 07:52:34 +00:00
renovate[bot]
4b8e60682d
fix(deps): update dependency react-select to v5.10.2 (#35352)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 06:32:38 +00:00
renovate[bot]
6c2db9b1cf
fix(deps): update dependency vite-plugin-static-copy to v3.1.1 (#35367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 06:32:00 +00:00
Matt Jankowski
30344d6abf
Confirm User#login_activities in auth/sessions spec (#35372) 2025-07-15 06:31:00 +00:00
Matt Jankowski
1637297085
Add coverage for CustomFilterStatus model (#35374) 2025-07-15 06:28:40 +00:00
Matt Jankowski
dec1fb71f4
Add coverage for FollowRecommendationMute model (#35376) 2025-07-15 06:27:36 +00:00
Matt Jankowski
7273f6c03c
Move shared params to common method in admin/reports/actions (#35353)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-07-14 09:23:18 +00:00
Matt Jankowski
a3ffd2edf8
Use sequence for unique names on webauthn cred fabricator (#35356) 2025-07-14 09:20:50 +00:00
renovate[bot]
a2c5eace88
chore(deps): update dependency annotaterb to v4.17.0 (#35368)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Bundler Audit / security (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 07:07:21 +00:00
github-actions[bot]
a643d9d498
New Crowdin Translations (automated) (#35358)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-14 06:21:56 +00:00
Claire
3b52dca405
Fix quote attributes missing from Mastodon's context (#35354)
Some checks failed
Check i18n / check-i18n (push) Has been cancelled
Chromatic / Run Chromatic (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (ruby) (push) Has been cancelled
Check formatting / 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 / test (3.3) (push) Has been cancelled
Ruby Testing / ImageMagick tests (.ruby-version) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.2) (push) Has been cancelled
Ruby Testing / ImageMagick tests (3.3) (push) Has been cancelled
Ruby Testing / End to End testing (.ruby-version) (push) Has been cancelled
Ruby Testing / End to End testing (3.2) (push) Has been cancelled
Ruby Testing / End to End testing (3.3) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Has been cancelled
Bundler Audit / security (push) Has been cancelled
2025-07-11 16:35:06 +00:00
Echo
853a0c466e
Make bio hashtags open the local page instead of the remote instance (#35349) 2025-07-11 15:18:34 +00:00
Echo
94bceb8683
Expose enabled features to the frontend (#35348)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-07-11 13:15:22 +00:00
Claire
88b0f3a172
Simplify DatabaseViewRecord.refresh (#35252) 2025-07-11 08:36:05 +00:00
github-actions[bot]
b69b5ba775
New Crowdin Translations (automated) (#35344)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-11 08:14:39 +00:00
Matt Jankowski
c442589593
Use ActiveModel::Attributes in FollowLimitable concern (#35327)
Some checks failed
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Bundler Audit / security (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
Haml Linting / lint (push) Has been cancelled
2025-07-10 07:40:56 +00:00
renovate[bot]
28633a504a
chore(deps): update dependency json-schema to v5.2.1 (#35337)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 07:38:07 +00:00
Matt Jankowski
ad78701b6f
Mark private methods in AnnualReport::TopStatuses (#35256) 2025-07-10 07:35:40 +00:00
Matt Jankowski
1496488771
Add Status#not_replying_to_account scope for annual report classes (#35257) 2025-07-10 07:35:04 +00:00
renovate[bot]
dd3d958e75
fix(deps): update dependency core-js to v3.44.0 (#35284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 07:23:54 +00:00
github-actions[bot]
b363a3651d
New Crowdin Translations (automated) (#35335)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-10 07:23:27 +00:00
renovate[bot]
86645fc14c
chore(deps): update dependency rubocop to v1.78.0 (#35289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-10 07:23:23 +00:00
Matt Jankowski
f9beecb343
Improve Accounts CLI prune spec (#35302) 2025-07-10 07:23:09 +00:00
Matt Jankowski
4ecfbd3920
Add Status.only_polls (and without polls) scope (#35330) 2025-07-10 07:13:22 +00:00
Claire
a315934314
Fix styling of external log-in button (#35320) 2025-07-10 06:56:40 +00:00
Claire
e9170e2de1
Bump version to v4.4.1 (#35329)
Some checks failed
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
CSS Linting / lint (push) Waiting to run
Haml Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Crowdin / Upload translations / upload-translations (push) Has been cancelled
2025-07-09 16:22:25 +00:00
Claire
5cfc1fabcf
Fix nearly every sub-directory being crawled as part of Vite build (#35323) 2025-07-09 14:34:16 +00:00
David Roetzel
786b12e379
Relax error restriction in initializer (#35321) 2025-07-09 14:22:47 +00:00
Claire
e7c5c25de8
Fix replying from media modal or pop-in-player tagging user @undefined (#35317) 2025-07-09 12:13:51 +00:00
Echo
a1e8813522
Emoji Indexing and Search (#35253) 2025-07-09 09:55:41 +00:00
github-actions[bot]
76c1446416
New Crowdin Translations (automated) (#35310)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-09 09:10:55 +00:00
Claire
8bd2c87399
Fix support for special characters in various environment variables (#35314)
Co-authored-by: Matt Jankowski <matt@jankowski.online>
2025-07-09 08:58:41 +00:00
Matt Jankowski
1e2d77f2c7
Use if_exists: true when removing duplicate indexes (#35309) 2025-07-09 08:45:29 +00:00
Matt Jankowski
fb6c22f5c2
Use touch to record viewing annual report (#35296) 2025-07-09 08:04:00 +00:00
Matt Jankowski
f7259f625f
Prefer on: :update in Tag validation declaration (#35297) 2025-07-09 08:03:39 +00:00
Claire
b628a98d32
Bump version to v4.4.0 (#35291) 2025-07-08 14:26:43 +00:00
Miguel Landaeta
d8fa807998
Bump linzer to 0.7.7 (#35258) 2025-07-08 13:04:16 +00:00
Echo
ef66d8379c
Add option to set emoji preferences behind feature flag (#35282) 2025-07-08 10:51:11 +00:00
David Roetzel
8ee6cee36e
Better error response to malformed headers (#35278) 2025-07-08 09:31:04 +00:00
github-actions[bot]
71b2120e5c
New Crowdin Translations (automated) (#35286)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-08 09:22:38 +00:00
renovate[bot]
b10078633c
chore(deps): update dependency libvips to v8.17.1 (#35283)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-08 08:35:54 +00:00
diondiondion
b5eebd4d2b
fix: Fix can't skip search field by tabbing (#35281) 2025-07-07 15:10:51 +00:00
Claire
fdefc4d2b4
Add ability to manually trigger i18n uploads (#35279) 2025-07-07 09:22:22 +00:00
github-actions[bot]
f6b2609353
New Crowdin Translations (automated) (#35269)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-07 08:21:51 +00:00
Matt Jankowski
bdffdcb12f
Remove unused scopes in Account model (#35276) 2025-07-07 08:07:01 +00:00
Claire
1ebb87a6a8
Fix incorrect name in scheduler configuration (#35263) 2025-07-04 07:51:01 +00:00
github-actions[bot]
83660ee381
New Crowdin Translations (automated) (#35261)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-04 07:34:54 +00:00
David Roetzel
1fa72d6c44
Raise better exception on FASP error responses (#35262) 2025-07-04 07:25:42 +00:00
Andy Piper
5a7c0d42f7
Add specific language confirming that we test with BrowserStack and Chromatic (#35248)
Signed-off-by: Andy Piper <andypiper@users.noreply.github.com>
2025-07-03 20:51:32 +00:00
Matt Jankowski
e8d2432e6a
Fix intermittent failure of TOS model spec from effective date collision (#35244) 2025-07-03 16:28:47 +00:00
Matt Jankowski
2af17adc34
Use ActiveModel::Attributes in admin/status_batch_action (#35255) 2025-07-03 14:43:36 +00:00
Claire
e97f43399b
Fix error handling for blank actions in account moderation action form (#35246) 2025-07-03 14:42:48 +00:00
github-actions[bot]
c66c5fd73d
New Crowdin Translations (automated) (#35250)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-03 09:47:57 +00:00
diondiondion
3c0767f543
fix: Remove focus highlight when status is clicked in light mode (#35251) 2025-07-03 07:51:12 +00:00
renovate[bot]
70cd1fdc63
fix(deps): update dependency vite-plugin-pwa to v1.0.1 (#35223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 07:32:51 +00:00
renovate[bot]
39028dde40
chore(deps): update dependency scenic to v1.9.0 (#35226)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 07:32:15 +00:00
Matt Jankowski
6e39b5ef04
Use ActiveModel::Attributes for admin/account_action boolean values (#35247) 2025-07-03 07:28:07 +00:00
Matt Jankowski
49db8a9662
Use Account#targeted_reports association where needed (#35249) 2025-07-03 07:28:03 +00:00
Andy Piper
2cfa6cb0e0
Update README with testing tool references. (#35236)
Signed-off-by: Andy Piper <andypiper@users.noreply.github.com>
2025-07-02 12:00:15 +00:00
Matt Jankowski
1ae3510ede
Add coverage for TOS interstitial interruption flow of web app controller concern (#35235) 2025-07-02 09:21:32 +00:00
github-actions[bot]
6f1135d763
New Crowdin Translations (automated) (#35238)
Co-authored-by: GitHub Actions <noreply@github.com>
2025-07-02 09:17:03 +00:00
Echo
52bc2f64f4
Import Emojibase data (#35229) 2025-07-02 08:58:39 +00:00
renovate[bot]
b1375328e1
chore(deps): update dependency faker to v3.5.2 (#35239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 08:02:33 +00:00
renovate[bot]
9443e2cc4b
chore(deps): update dependency opentelemetry-instrumentation-http to v0.25.1 (#35240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 08:02:30 +00:00
Renaud Chaput
3a533c6c8d
Bump version to 4.5.0-alpha.1 (#35231) 2025-07-02 08:00:43 +00:00
Matt Jankowski
c047014214
Add coverage for valid_locale_or_nil languages helper method (#34866) 2025-07-02 07:34:42 +00:00
Claire
68b05e994f
Fix error on log-in from old users requiring ToS interstitial when said ToS has been removed (#35233) 2025-07-01 17:43:59 +00:00
209 changed files with 3992 additions and 1539 deletions

View File

@ -1,6 +1,6 @@
name: Bug Report (Web Interface)
description: There is a problem using Mastodon's web interface.
labels: ['status/to triage', 'area/web interface']
labels: ['area/web interface']
type: Bug
body:
- type: markdown

View File

@ -1,7 +1,6 @@
name: Bug Report (server / API)
description: |
There is a problem with the HTTP server, REST API, ActivityPub interaction, etc.
labels: ['status/to triage']
type: 'Bug'
body:
- type: markdown

View File

@ -23,7 +23,6 @@
matchManagers: ['npm'],
matchPackageNames: [
'tesseract.js', // Requires code changes
'react-hotkeys', // Requires code changes
// react-router: Requires manual upgrade
'history',

View File

@ -21,7 +21,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
@ -39,7 +39,7 @@ jobs:
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.4.') }}
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}

View File

@ -1 +1 @@
3.4.4
3.4.5

View File

@ -1,3 +1,5 @@
import { resolve } from 'node:path';
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
@ -26,6 +28,12 @@ const config: StorybookConfig = {
'oops.png',
].map((path) => ({ from: `../public/${path}`, to: `/${path}` })),
],
viteFinal(config) {
// For an unknown reason, Storybook does not use the root
// from the Vite config so we need to set it manually.
config.root = resolve(__dirname, '../app/javascript');
return config;
},
};
export default config;

View File

@ -13,7 +13,7 @@ ARG BASE_REGISTRY="docker.io"
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
# renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.4.4"
ARG RUBY_VERSION="3.4.5"
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
# renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="22"
@ -186,7 +186,7 @@ FROM build AS libvips
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
ARG VIPS_VERSION=8.17.0
ARG VIPS_VERSION=8.17.1
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download

View File

@ -62,7 +62,7 @@ gem 'inline_svg'
gem 'irb', '~> 1.8'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'linzer', '~> 0.7.2'
gem 'linzer', '~> 0.7.7'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
gem 'mutex_m'

View File

@ -90,13 +90,13 @@ GEM
public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0)
android_key_attestation (0.3.0)
annotaterb (4.16.0)
annotaterb (4.17.0)
activerecord (>= 6.0.0)
activesupport (>= 6.0.0)
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.3.2)
aws-partitions (1.1103.0)
aws-eventstream (1.4.0)
aws-partitions (1.1131.0)
aws-sdk-core (3.215.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
@ -109,9 +109,9 @@ GEM
aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
azure-blob (0.5.8)
azure-blob (0.5.9.1)
rexml
base64 (0.3.0)
bcp47_spec (0.2.1)
@ -224,16 +224,16 @@ GEM
mail (~> 2.7)
email_validator (2.2.4)
activemodel
erb (5.0.1)
erb (5.0.2)
erubi (1.13.1)
et-orbi (1.2.11)
tzinfo
excon (1.2.5)
excon (1.2.8)
logger
fabrication (3.0.0)
faker (3.5.1)
faker (3.5.2)
i18n (>= 1.8.11, < 2)
faraday (2.13.1)
faraday (2.13.2)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@ -241,7 +241,7 @@ GEM
faraday (>= 1, < 3)
faraday-httpclient (2.0.2)
httpclient (>= 2.2)
faraday-net_http (3.4.0)
faraday-net_http (3.4.1)
net-http (>= 0.5.0)
fast_blank (1.0.1)
fastimage (2.4.0)
@ -266,14 +266,14 @@ GEM
fog-openstack (1.1.5)
fog-core (~> 2.1)
fog-json (>= 1.0)
formatador (1.1.0)
formatador (1.1.1)
forwardable (1.3.3)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
google-protobuf (4.31.0)
google-protobuf (4.31.1)
bigdecimal
rake (>= 13)
googleapis-common-protos-types (1.20.0)
@ -287,21 +287,21 @@ GEM
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.64.0)
haml_lint (0.65.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
rubocop (>= 1.0)
sysexits (~> 1.1)
hashdiff (1.1.2)
hashdiff (1.2.0)
hashie (5.0.0)
hcaptcha (7.1.0)
json
highline (3.1.2)
reline
hiredis (0.6.3)
hiredis-client (0.24.0)
redis-client (= 0.24.0)
hiredis-client (0.25.1)
redis-client (= 0.25.1)
hkdf (0.3.0)
htmlentities (4.3.4)
http (5.3.1)
@ -315,7 +315,7 @@ GEM
http_accept_language (2.1.1)
httpclient (2.9.0)
mutex_m
httplog (1.7.0)
httplog (1.7.1)
rack (>= 2.0)
rainbow (>= 2.0.0)
i18n (1.14.7)
@ -335,7 +335,7 @@ GEM
inline_svg (1.10.0)
activesupport (>= 3.0)
nokogiri (>= 1.6)
io-console (0.8.0)
io-console (0.8.1)
irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
@ -345,7 +345,7 @@ GEM
azure-blob (~> 0.5.2)
hashie (~> 5.0)
jmespath (1.6.2)
json (2.12.2)
json (2.13.0)
json-canonicalization (1.0.0)
json-jwt (1.16.7)
activesupport (>= 4.2)
@ -365,11 +365,11 @@ GEM
json-ld-preloaded (3.3.1)
json-ld (~> 3.3)
rdf (~> 3.3)
json-schema (5.1.1)
json-schema (5.2.1)
addressable (~> 2.8)
bigdecimal (~> 3.1)
jsonapi-renderer (0.2.2)
jwt (2.10.1)
jwt (2.10.2)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
@ -403,7 +403,7 @@ GEM
rexml
link_header (0.0.8)
lint_roller (1.1.0)
linzer (0.7.3)
linzer (0.7.7)
cgi (~> 0.4.2)
forwardable (~> 1.3, >= 1.3.3)
logger (~> 1.7, >= 1.7.0)
@ -433,21 +433,21 @@ GEM
marcel (1.0.4)
mario-redis-lock (1.2.1)
redis (>= 3.0.5)
matrix (0.4.2)
matrix (0.4.3)
memory_profiler (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2025.0514)
mime-types-data (3.2025.0715)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (5.25.5)
msgpack (1.8.0)
multi_json (1.15.0)
multi_json (1.17.0)
mutex_m (0.3.0)
net-http (0.6.0)
uri
net-imap (0.5.8)
net-imap (0.5.9)
date
net-protocol
net-ldap (0.19.0)
@ -458,7 +458,7 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.8)
nokogiri (1.18.9)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.11)
@ -515,7 +515,7 @@ GEM
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-active_support (~> 0.7)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-action_pack (0.12.1)
opentelemetry-instrumentation-action_pack (0.12.3)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-rack (~> 0.21)
@ -553,7 +553,7 @@ GEM
opentelemetry-instrumentation-faraday (0.27.0)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-http (0.25.0)
opentelemetry-instrumentation-http (0.25.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-http_client (0.23.0)
@ -597,7 +597,7 @@ GEM
opentelemetry-semantic_conventions (1.11.0)
opentelemetry-api (~> 1.0)
orm_adapter (0.5.0)
ostruct (0.6.1)
ostruct (0.6.3)
ox (2.14.23)
bigdecimal (>= 3.0)
parallel (1.27.0)
@ -610,7 +610,7 @@ GEM
pg (1.5.9)
pghero (3.7.0)
activerecord (>= 7.1)
playwright-ruby-client (1.52.0)
playwright-ruby-client (1.54.0)
concurrent-ruby (>= 1.1.6)
mime-types (>= 3.0)
pp (0.6.2)
@ -627,11 +627,10 @@ GEM
prism (1.4.0)
prometheus_exporter (2.2.0)
webrick
propshaft (1.1.0)
propshaft (1.2.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.2.6)
date
stringio
@ -682,7 +681,7 @@ GEM
activesupport (= 8.0.2)
bundler (>= 1.15.0)
railties (= 8.0.2)
rails-dom-testing (2.2.0)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
@ -702,23 +701,28 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.3.0)
rdf (3.3.2)
rdf (3.3.4)
bcp47_spec (~> 0.2)
bigdecimal (~> 3.1, >= 3.1.5)
link_header (~> 0.0, >= 0.0.8)
logger (~> 1.5)
ostruct (~> 0.6)
readline (~> 0.0)
rdf-normalize (0.7.0)
rdf (~> 3.3)
rdoc (6.14.1)
rdoc (6.14.2)
erb
psych (>= 4.0.0)
readline (0.0.4)
reline
redcarpet (3.6.1)
redis (4.8.1)
redis-client (0.24.0)
redis-client (0.25.1)
connection_pool
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.10.0)
reline (0.6.1)
reline (0.6.2)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
@ -733,11 +737,11 @@ GEM
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.0.0)
rspec (3.13.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.4)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
@ -761,7 +765,7 @@ GEM
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9)
rspec-support (3.13.4)
rubocop (1.77.0)
rubocop (1.78.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@ -772,7 +776,7 @@ GEM
rubocop-ast (>= 1.45.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.45.1)
rubocop-ast (1.46.0)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@ -815,7 +819,7 @@ GEM
sanitize (7.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.16.8)
scenic (1.8.0)
scenic (1.9.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
securerandom (0.4.1)
@ -846,7 +850,7 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.1)
simplecov-html (0.13.2)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
stackprof (0.2.27)
@ -866,11 +870,11 @@ GEM
temple (0.10.3)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
terrapin (1.1.0)
terrapin (1.1.1)
climate_control
test-prof (1.4.4)
thor (1.3.2)
tilt (2.6.0)
thor (1.4.0)
tilt (2.6.1)
timeout (0.4.3)
tpm-key_attestation (0.14.1)
bindata (~> 2.4)
@ -932,7 +936,7 @@ GEM
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.1)
websocket-driver (0.7.7)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@ -1008,7 +1012,7 @@ DEPENDENCIES
letter_opener (~> 1.8)
letter_opener_web (~> 3.0)
link_header (~> 0.0)
linzer (~> 0.7.2)
linzer (~> 0.7.7)
lograge (~> 0.12)
mail (~> 2.8)
mario-redis-lock (~> 1.2)
@ -1102,4 +1106,4 @@ RUBY VERSION
ruby 3.4.1p0
BUNDLED WITH
2.6.9
2.7.0

View File

@ -17,71 +17,71 @@
<img src="https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg" alt="Crowdin" /></a>
</p>
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
Mastodon is a **free, open-source social network server** based on [ActivityPub](https://www.w3.org/TR/activitypub/) where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
## Navigation
- [Project homepage 🐘](https://joinmastodon.org)
- [Support the development via Patreon][patreon]
- [View sponsors](https://joinmastodon.org/sponsors)
- [Blog](https://blog.joinmastodon.org)
- [Documentation](https://docs.joinmastodon.org)
- [Roadmap](https://joinmastodon.org/roadmap)
- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
- [Browse Mastodon servers](https://joinmastodon.org/communities)
- [Browse Mastodon apps](https://joinmastodon.org/apps)
[patreon]: https://www.patreon.com/mastodon
- [Donate to support development 🎁](https://joinmastodon.org/sponsors#donate)
- [View sponsors](https://joinmastodon.org/sponsors)
- [Blog 📰](https://blog.joinmastodon.org)
- [Documentation 📚](https://docs.joinmastodon.org)
- [Official container image 🚢](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
## Features
<img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
<img src="./app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
**No vendor lock-in: Fully interoperable with any conforming platform** - It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
**Part of the Fediverse. Based on open standards, with no vendor lock-in.** - the network goes beyond just Mastodon; anything that implements ActivityPub is part of a broader social network known as [the Fediverse](https://jointhefediverse.net/). You can follow and interact with users on other servers (including those running different software), and they can follow you back.
**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
**Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI.
**Media attachments like images and short videos** - upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
**Media attachments** - upload and view images and videos attached to the updates. Videos with no audio track are treated like animated GIFs; normal videos loop continuously.
**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
**Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and many other features, along with a reporting and moderation system.
**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
**OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, and third party apps can use the REST and Streaming APIs. This results in a [rich app ecosystem](https://joinmastodon.org/apps) with a variety of choices!
## Deployment
### Tech stack
- **Ruby on Rails** powers the REST API and other web pages
- **React.js** and **Redux** are used for the dynamic parts of the interface
- **Node.js** powers the streaming API
- [Ruby on Rails](https://github.com/rails/rails) powers the REST API and other web pages.
- [PostgreSQL](https://www.postgresql.org/) is the main database.
- [Redis](https://redis.io/) and [Sidekiq](https://sidekiq.org/) are used for caching and queueing.
- [Node.js](https://nodejs.org/) powers the streaming API.
- [React.js](https://reactjs.org/) and [Redux](https://redux.js.org/) are used for the dynamic parts of the interface.
- [BrowserStack](https://www.browserstack.com/) supports testing on real devices and browsers. (This project is tested with BrowserStack)
- [Chromatic](https://www.chromatic.com/) provides visual regression testing. (This project is tested with Chromatic)
### Requirements
- **Ruby** 3.2+
- **PostgreSQL** 13+
- **Redis** 6.2+
- **Ruby** 3.2+
- **Node.js** 20+
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
This repository includes deployment configurations for **Docker and docker-compose**, as well as for other environments like Heroku and Scalingo. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). A [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the main documentation.
## Contributing
Mastodon is **free, open-source software** licensed under **AGPLv3**.
Mastodon is **free, open-source software** licensed under **AGPLv3**. We welcome contributions and help from anyone who wants to improve the project.
You can open issues for bugs you've found or features you think are missing. You
can also submit pull requests to this repository or translations via Crowdin. To
get started, look at the [CONTRIBUTING] and [DEVELOPMENT] guides. For changes
accepted into Mastodon, you can request to be paid through our [OpenCollective].
You should read the overall [CONTRIBUTING](https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md) guide, which covers our development processes.
**IRC channel**: #mastodon on [`irc.libera.chat`](https://libera.chat)
You should also read and understand the [CODE OF CONDUCT](https://github.com/mastodon/.github/blob/main/CODE_OF_CONDUCT.md) that enables us to maintain a welcoming and inclusive community. Collaboration begins with mutual respect and understanding.
## License
You can learn about setting up a development environment in the [DEVELOPMENT](docs/DEVELOPMENT.md) documentation.
If you would like to help with translations 🌐 you can do so on [Crowdin](https://crowdin.com/project/mastodon).
## LICENSE
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
```
```text
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
This program is free software: you can redistribute it and/or modify it under
@ -97,7 +97,3 @@ details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see https://www.gnu.org/licenses/
```
[CONTRIBUTING]: CONTRIBUTING.md
[DEVELOPMENT]: docs/DEVELOPMENT.md
[OpenCollective]: https://opencollective.com/mastodon

View File

@ -13,27 +13,9 @@ class Admin::Reports::ActionsController < Admin::BaseController
case action_from_button
when 'delete', 'mark_as_sensitive'
status_batch_action = Admin::StatusBatchAction.new(
type: action_from_button,
status_ids: @report.status_ids,
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?,
text: params[:text]
)
status_batch_action.save!
Admin::StatusBatchAction.new(status_batch_action_params).save!
when 'silence', 'suspend'
account_action = Admin::AccountAction.new(
type: action_from_button,
report_id: @report.id,
target_account: @report.target_account,
current_account: current_account,
send_email_notification: !@report.spam?,
text: params[:text]
)
account_action.save!
Admin::AccountAction.new(account_action_params).save!
else
return redirect_to admin_report_path(@report), alert: I18n.t('admin.reports.unknown_action_msg', action: action_from_button)
end
@ -43,6 +25,26 @@ class Admin::Reports::ActionsController < Admin::BaseController
private
def status_batch_action_params
shared_params
.merge(status_ids: @report.status_ids)
end
def account_action_params
shared_params
.merge(target_account: @report.target_account)
end
def shared_params
{
current_account: current_account,
report_id: @report.id,
send_email_notification: !@report.spam?,
text: params[:text],
type: action_from_button,
}
end
def set_report
@report = Report.find(params[:report_id])
end

View File

@ -7,6 +7,7 @@ class Api::V1::InvitesController < Api::BaseController
skip_around_action :set_locale
before_action :set_invite
before_action :check_valid_usage!
before_action :check_enabled_registrations!
# Override `current_user` to avoid reading session cookies
@ -22,9 +23,11 @@ class Api::V1::InvitesController < Api::BaseController
@invite = Invite.find_by!(code: params[:invite_code])
end
def check_enabled_registrations!
return render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
def check_valid_usage!
render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
end
def check_enabled_registrations!
raise Mastodon::NotPermittedError unless allowed_registration?(request.remote_ip, @invite)
end
end

View File

@ -38,8 +38,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
private
def record_login_activity
LoginActivity.create(
user: @user,
@user.login_activities.create(
success: true,
authentication_method: :omniauth,
provider: @provider,

View File

@ -151,12 +151,11 @@ class Auth::SessionsController < Devise::SessionsController
sign_in(user)
flash.delete(:notice)
LoginActivity.create(
user: user,
success: true,
authentication_method: security_measure,
ip: request.remote_ip,
user_agent: request.user_agent
user.login_activities.create(
request_details.merge(
authentication_method: security_measure,
success: true
)
)
UserMailer.suspicious_sign_in(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! if @login_is_suspicious
@ -167,13 +166,12 @@ class Auth::SessionsController < Devise::SessionsController
end
def on_authentication_failure(user, security_measure, failure_reason)
LoginActivity.create(
user: user,
success: false,
authentication_method: security_measure,
failure_reason: failure_reason,
ip: request.remote_ip,
user_agent: request.user_agent
user.login_activities.create(
request_details.merge(
authentication_method: security_measure,
failure_reason: failure_reason,
success: false
)
)
# Only send a notification email every hour at most
@ -182,6 +180,13 @@ class Auth::SessionsController < Devise::SessionsController
UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later!
end
def request_details
{
ip: request.remote_ip,
user_agent: request.user_agent,
}
end
def second_factor_attempts_key(user)
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
end

View File

@ -5,6 +5,6 @@ class Settings::LoginActivitiesController < Settings::BaseController
skip_before_action :require_functional!
def index
@login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page])
@login_activities = current_user.login_activities.order(id: :desc).page(params[:page])
end
end

View File

@ -66,7 +66,7 @@ module ApplicationHelper
def provider_sign_in_link(provider)
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post
link_to label, omniauth_authorize_path(:user, provider), class: "btn button-#{provider}", method: :post
end
def locale_direction

View File

@ -26,6 +26,12 @@ module ContextHelper
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } },
quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' },
quotes: {
'quote' => 'https://w3id.org/fep/044f#quote',
'quoteUri' => 'http://fedibird.com/ns#quoteUri',
'_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote',
'quoteAuthorization' => { '@id' => 'https://w3id.org/fep/044f#quoteAuthorization', '@type' => '@id' },
},
interaction_policies: {
'gts' => 'https://gotosocial.org/ns#',
'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' },

View File

@ -1,12 +1,30 @@
import { useCallback } from 'react';
import { useLinks } from 'mastodon/hooks/useLinks';
export const AccountBio: React.FC<{
interface AccountBioProps {
note: string;
className: string;
}> = ({ note, className }) => {
const handleClick = useLinks();
dropdownAccountId?: string;
}
if (note.length === 0 || note === '<p></p>') {
export const AccountBio: React.FC<AccountBioProps> = ({
note,
className,
dropdownAccountId,
}) => {
const handleClick = useLinks(!!dropdownAccountId);
const handleNodeChange = useCallback(
(node: HTMLDivElement | null) => {
if (!dropdownAccountId || !node || node.childNodes.length === 0) {
return;
}
addDropdownToHashtags(node, dropdownAccountId);
},
[dropdownAccountId],
);
if (note.length === 0) {
return null;
}
@ -15,6 +33,28 @@ export const AccountBio: React.FC<{
className={`${className} translate`}
dangerouslySetInnerHTML={{ __html: note }}
onClickCapture={handleClick}
ref={handleNodeChange}
/>
);
};
function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
if (!node) {
return;
}
for (const childNode of node.childNodes) {
if (!(childNode instanceof HTMLElement)) {
continue;
}
if (
childNode instanceof HTMLAnchorElement &&
(childNode.classList.contains('hashtag') ||
childNode.innerText.startsWith('#')) &&
!childNode.dataset.menuHashtag
) {
childNode.dataset.menuHashtag = accountId;
} else if (childNode.childNodes.length > 0) {
addDropdownToHashtags(childNode, accountId);
}
}
}

View File

@ -5,6 +5,7 @@ import {
useCallback,
cloneElement,
Children,
useId,
} from 'react';
import classNames from 'classnames';
@ -16,6 +17,7 @@ import Overlay from 'react-overlays/Overlay';
import type {
OffsetValue,
UsePopperOptions,
Placement,
} from 'react-overlays/esm/usePopper';
import { fetchRelationships } from 'mastodon/actions/accounts';
@ -295,6 +297,11 @@ interface DropdownProps<Item = MenuItem> {
title?: string;
disabled?: boolean;
scrollable?: boolean;
placement?: Placement;
/**
* Prevent the `ScrollableList` with this scrollKey
* from being scrolled while the dropdown is open
*/
scrollKey?: string;
status?: ImmutableMap<string, unknown>;
forceDropdown?: boolean;
@ -316,6 +323,7 @@ export const Dropdown = <Item = MenuItem,>({
title = 'Menu',
disabled,
scrollable,
placement = 'bottom',
status,
forceDropdown = false,
renderItem,
@ -331,16 +339,15 @@ export const Dropdown = <Item = MenuItem,>({
);
const [currentId] = useState(id++);
const open = currentId === openDropdownId;
const activeElement = useRef<HTMLElement | null>(null);
const targetRef = useRef<HTMLButtonElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
const menuId = useId();
const prefetchAccountId = status
? status.getIn(['account', 'id'])
: undefined;
const handleClose = useCallback(() => {
if (activeElement.current) {
activeElement.current.focus({ preventScroll: true });
activeElement.current = null;
if (buttonRef.current) {
buttonRef.current.focus({ preventScroll: true });
}
dispatch(
@ -375,7 +382,7 @@ export const Dropdown = <Item = MenuItem,>({
[handleClose, onItemClick, items],
);
const handleClick = useCallback(
const toggleDropdown = useCallback(
(e: React.MouseEvent | React.KeyboardEvent) => {
const { type } = e;
@ -423,38 +430,6 @@ export const Dropdown = <Item = MenuItem,>({
],
);
const handleMouseDown = useCallback(() => {
if (!open && document.activeElement instanceof HTMLElement) {
activeElement.current = document.activeElement;
}
}, [open]);
const handleButtonKeyDown = useCallback(
(e: React.KeyboardEvent) => {
switch (e.key) {
case ' ':
case 'Enter':
handleMouseDown();
break;
}
},
[handleMouseDown],
);
const handleKeyPress = useCallback(
(e: React.KeyboardEvent) => {
switch (e.key) {
case ' ':
case 'Enter':
handleClick(e);
e.stopPropagation();
e.preventDefault();
break;
}
},
[handleClick],
);
useEffect(() => {
return () => {
if (currentId === openDropdownId) {
@ -465,14 +440,16 @@ export const Dropdown = <Item = MenuItem,>({
let button: React.ReactElement;
const buttonProps = {
disabled,
onClick: toggleDropdown,
'aria-expanded': open,
'aria-controls': menuId,
ref: buttonRef,
};
if (children) {
button = cloneElement(Children.only(children), {
onClick: handleClick,
onMouseDown: handleMouseDown,
onKeyDown: handleButtonKeyDown,
onKeyPress: handleKeyPress,
ref: targetRef,
});
button = cloneElement(Children.only(children), buttonProps);
} else if (icon && iconComponent) {
button = (
<IconButton
@ -480,12 +457,7 @@ export const Dropdown = <Item = MenuItem,>({
iconComponent={iconComponent}
title={title}
active={open}
disabled={disabled}
onClick={handleClick}
onMouseDown={handleMouseDown}
onKeyDown={handleButtonKeyDown}
onKeyPress={handleKeyPress}
ref={targetRef}
{...buttonProps}
/>
);
} else {
@ -499,13 +471,13 @@ export const Dropdown = <Item = MenuItem,>({
<Overlay
show={open}
offset={offset}
placement='bottom'
placement={placement}
flip
target={targetRef}
target={buttonRef}
popperConfig={popperConfig}
>
{({ props, arrowProps, placement }) => (
<div {...props}>
<div {...props} id={menuId}>
<div className={`dropdown-animation dropdown-menu ${placement}`}>
<div
className={`dropdown-menu__arrow ${placement}`}

View File

@ -0,0 +1,171 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect } from 'storybook/test';
import type { HandlerMap } from '.';
import { Hotkeys } from '.';
const meta = {
title: 'Components/Hotkeys',
component: Hotkeys,
args: {
global: undefined,
focusable: undefined,
handlers: {},
},
tags: ['test'],
} satisfies Meta<typeof Hotkeys>;
export default meta;
type Story = StoryObj<typeof meta>;
const hotkeyTest: Story['play'] = async ({ canvas, userEvent }) => {
async function confirmHotkey(name: string, shouldFind = true) {
// 'status' is the role of the 'output' element
const output = await canvas.findByRole('status');
if (shouldFind) {
await expect(output).toHaveTextContent(name);
} else {
await expect(output).not.toHaveTextContent(name);
}
}
const button = await canvas.findByRole('button');
await userEvent.click(button);
await userEvent.keyboard('n');
await confirmHotkey('new');
await userEvent.keyboard('/');
await confirmHotkey('search');
await userEvent.keyboard('o');
await confirmHotkey('open');
await userEvent.keyboard('{Alt>}N{/Alt}');
await confirmHotkey('forceNew');
await userEvent.keyboard('gh');
await confirmHotkey('goToHome');
await userEvent.keyboard('gn');
await confirmHotkey('goToNotifications');
await userEvent.keyboard('gf');
await confirmHotkey('goToFavourites');
/**
* Ensure that hotkeys are not triggered when certain
* interactive elements are focused:
*/
await userEvent.keyboard('{enter}');
await confirmHotkey('open', false);
const input = await canvas.findByRole('textbox');
await userEvent.click(input);
await userEvent.keyboard('n');
await confirmHotkey('new', false);
await userEvent.keyboard('{backspace}');
await confirmHotkey('None', false);
/**
* Reset playground:
*/
await userEvent.click(button);
await userEvent.keyboard('{backspace}');
};
export const Default = {
render: function Render() {
const [matchedHotkey, setMatchedHotkey] = useState<keyof HandlerMap | null>(
null,
);
const handlers = {
back: () => {
setMatchedHotkey(null);
},
new: () => {
setMatchedHotkey('new');
},
forceNew: () => {
setMatchedHotkey('forceNew');
},
search: () => {
setMatchedHotkey('search');
},
open: () => {
setMatchedHotkey('open');
},
goToHome: () => {
setMatchedHotkey('goToHome');
},
goToNotifications: () => {
setMatchedHotkey('goToNotifications');
},
goToFavourites: () => {
setMatchedHotkey('goToFavourites');
},
};
return (
<Hotkeys handlers={handlers}>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 8,
padding: '1em',
border: '1px dashed #ccc',
fontSize: 14,
color: '#222',
}}
>
<h1
style={{
fontSize: 22,
marginBottom: '0.3em',
}}
>
Hotkey playground
</h1>
<p>
Last pressed hotkey: <output>{matchedHotkey ?? 'None'}</output>
</p>
<p>
Click within the dashed border and press the &quot;<kbd>n</kbd>
&quot; or &quot;<kbd>/</kbd>&quot; key. Press &quot;
<kbd>Backspace</kbd>&quot; to clear the displayed hotkey.
</p>
<p>
Try typing a sequence, like &quot;<kbd>g</kbd>&quot; shortly
followed by &quot;<kbd>h</kbd>&quot;, &quot;<kbd>n</kbd>&quot;, or
&quot;<kbd>f</kbd>&quot;
</p>
<p>
Note that this playground doesn&apos;t support all hotkeys we use in
the app.
</p>
<p>
When a <button>Button</button> is focused, &quot;
<kbd>Enter</kbd>
&quot; should not trigger &quot;open&quot;, but &quot;<kbd>o</kbd>
&quot; should.
</p>
<p>
When an input element is focused, hotkeys should not interfere with
regular typing:
</p>
<input type='text' />
</div>
</Hotkeys>
);
},
play: hotkeyTest,
};

View File

@ -0,0 +1,282 @@
import { useEffect, useRef } from 'react';
import { normalizeKey, isKeyboardEvent } from './utils';
/**
* In case of multiple hotkeys matching the pressed key(s),
* the hotkey with a higher priority is selected. All others
* are ignored.
*/
const hotkeyPriority = {
singleKey: 0,
combo: 1,
sequence: 2,
} as const;
/**
* This type of function receives a keyboard event and an array of
* previously pressed keys (within the last second), and returns
* `isMatch` (whether the pressed keys match a hotkey) and `priority`
* (a weighting used to resolve conflicts when two hotkeys match the
* pressed keys)
*/
type KeyMatcher = (
event: KeyboardEvent,
bufferedKeys?: string[],
) => {
/**
* Whether the event.key matches the hotkey
*/
isMatch: boolean;
/**
* If there are multiple matching hotkeys, the
* first one with the highest priority will be handled
*/
priority: (typeof hotkeyPriority)[keyof typeof hotkeyPriority];
};
/**
* Matches a single key
*/
function just(keyName: string): KeyMatcher {
return (event) => ({
isMatch: normalizeKey(event.key) === keyName,
priority: hotkeyPriority.singleKey,
});
}
/**
* Matches any single key out of those provided
*/
function any(...keys: string[]): KeyMatcher {
return (event) => ({
isMatch: keys.some((keyName) => just(keyName)(event).isMatch),
priority: hotkeyPriority.singleKey,
});
}
/**
* Matches a single key combined with the option/alt modifier
*/
function optionPlus(key: string): KeyMatcher {
return (event) => ({
// Matching against event.code here as alt combos are often
// mapped to other characters
isMatch: event.altKey && event.code === `Key${key.toUpperCase()}`,
priority: hotkeyPriority.combo,
});
}
/**
* Matches when all provided keys are pressed in sequence.
*/
function sequence(...sequence: string[]): KeyMatcher {
return (event, bufferedKeys) => {
const lastKeyInSequence = sequence.at(-1);
const startOfSequence = sequence.slice(0, -1);
const relevantBufferedKeys = bufferedKeys?.slice(-startOfSequence.length);
const bufferMatchesStartOfSequence =
!!relevantBufferedKeys &&
startOfSequence.join('') === relevantBufferedKeys.join('');
return {
isMatch:
bufferMatchesStartOfSequence &&
normalizeKey(event.key) === lastKeyInSequence,
priority: hotkeyPriority.sequence,
};
};
}
/**
* This is a map of all global hotkeys we support.
* To trigger a hotkey, a handler with a matching name must be
* provided to the `useHotkeys` hook or `Hotkeys` component.
*/
const hotkeyMatcherMap = {
help: just('?'),
search: any('s', '/'),
back: just('backspace'),
new: just('n'),
forceNew: optionPlus('n'),
focusColumn: any('1', '2', '3', '4', '5', '6', '7', '8', '9'),
reply: just('r'),
favourite: just('f'),
boost: just('b'),
mention: just('m'),
open: any('enter', 'o'),
openProfile: just('p'),
moveDown: any('down', 'j'),
moveUp: any('up', 'k'),
toggleHidden: just('x'),
toggleSensitive: just('h'),
toggleComposeSpoilers: optionPlus('x'),
openMedia: just('e'),
onTranslate: just('t'),
goToHome: sequence('g', 'h'),
goToNotifications: sequence('g', 'n'),
goToLocal: sequence('g', 'l'),
goToFederated: sequence('g', 't'),
goToDirect: sequence('g', 'd'),
goToStart: sequence('g', 's'),
goToFavourites: sequence('g', 'f'),
goToPinned: sequence('g', 'p'),
goToProfile: sequence('g', 'u'),
goToBlocked: sequence('g', 'b'),
goToMuted: sequence('g', 'm'),
goToRequests: sequence('g', 'r'),
cheat: sequence(
'up',
'up',
'down',
'down',
'left',
'right',
'left',
'right',
'b',
'a',
'enter',
),
} as const;
type HotkeyName = keyof typeof hotkeyMatcherMap;
export type HandlerMap = Partial<
Record<HotkeyName, (event: KeyboardEvent) => void>
>;
export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
const ref = useRef<T>(null);
const bufferedKeys = useRef<string[]>([]);
const sequenceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
/**
* Store the latest handlers object in a ref so we don't need to
* add it as a dependency to the main event listener effect
*/
const handlersRef = useRef(handlers);
useEffect(() => {
handlersRef.current = handlers;
}, [handlers]);
useEffect(() => {
const element = ref.current ?? document;
function listener(event: Event) {
// Ignore key presses from input, textarea, or select elements
const tagName = (event.target as HTMLElement).tagName.toLowerCase();
const shouldHandleEvent =
isKeyboardEvent(event) &&
!event.defaultPrevented &&
!['input', 'textarea', 'select'].includes(tagName) &&
!(
['a', 'button'].includes(tagName) &&
normalizeKey(event.key) === 'enter'
);
if (shouldHandleEvent) {
const matchCandidates: {
handler: (event: KeyboardEvent) => void;
priority: number;
}[] = [];
(Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach(
(handlerName) => {
const handler = handlersRef.current[handlerName];
if (handler) {
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
const { isMatch, priority } = hotkeyMatcher(
event,
bufferedKeys.current,
);
if (isMatch) {
matchCandidates.push({ handler, priority });
}
}
},
);
// Sort all matches by priority
matchCandidates.sort((a, b) => b.priority - a.priority);
const bestMatchingHandler = matchCandidates.at(0)?.handler;
if (bestMatchingHandler) {
bestMatchingHandler(event);
event.stopPropagation();
event.preventDefault();
}
// Add last keypress to buffer
bufferedKeys.current.push(normalizeKey(event.key));
// Reset the timeout
if (sequenceTimer.current) {
clearTimeout(sequenceTimer.current);
}
sequenceTimer.current = setTimeout(() => {
bufferedKeys.current = [];
}, 1000);
}
}
element.addEventListener('keydown', listener);
return () => {
element.removeEventListener('keydown', listener);
if (sequenceTimer.current) {
clearTimeout(sequenceTimer.current);
}
};
}, []);
return ref;
}
/**
* The Hotkeys component allows us to globally register keyboard combinations
* under a name and assign actions to them, either globally or scoped to a portion
* of the app.
*
* ### How to use
*
* To add a new hotkey, add its key combination to the `hotkeyMatcherMap` object
* and give it a name.
*
* Use the `<Hotkeys>` component or the `useHotkeys` hook in the part of of the app
* where you want to handle the action, and pass in a handlers object.
*
* ```tsx
* <Hotkeys handlers={{open: openStatus}} />
* ```
*
* Now this function will be called when the 'open' hotkey is pressed by the user.
*/
export const Hotkeys: React.FC<{
/**
* An object containing functions to be run when a hotkey is pressed.
* The key must be the name of a registered hotkey, e.g. "help" or "search"
*/
handlers: HandlerMap;
/**
* When enabled, hotkeys will be matched against the document root
* rather than only inside of this component's DOM node.
*/
global?: boolean;
/**
* Allow the rendered `div` to be focused
*/
focusable?: boolean;
children: React.ReactNode;
}> = ({ handlers, global, focusable = true, children }) => {
const ref = useHotkeys<HTMLDivElement>(handlers);
return (
<div ref={global ? undefined : ref} tabIndex={focusable ? -1 : undefined}>
{children}
</div>
);
};

View File

@ -0,0 +1,29 @@
export function isKeyboardEvent(event: Event): event is KeyboardEvent {
return 'key' in event;
}
export function normalizeKey(key: string): string {
const lowerKey = key.toLowerCase();
switch (lowerKey) {
case ' ':
case 'spacebar': // for older browsers
return 'space';
case 'arrowup':
return 'up';
case 'arrowdown':
return 'down';
case 'arrowleft':
return 'left';
case 'arrowright':
return 'right';
case 'esc':
case 'escape':
return 'escape';
default:
return lowerKey;
}
}

View File

@ -14,7 +14,6 @@ interface Props {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
active?: boolean;
expanded?: boolean;
style?: React.CSSProperties;
@ -45,7 +44,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
activeStyle,
onClick,
onKeyDown,
onKeyPress,
onMouseDown,
active = false,
disabled = false,
@ -85,16 +83,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
[disabled, onClick],
);
const handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> =
useCallback(
(e) => {
if (!disabled) {
onKeyPress?.(e);
}
},
[disabled, onKeyPress],
);
const handleMouseDown: React.MouseEventHandler<HTMLButtonElement> =
useCallback(
(e) => {
@ -161,7 +149,6 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
onClick={handleClick}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated
style={buttonStyle}
tabIndex={tabIndex}
disabled={disabled}

View File

@ -8,10 +8,9 @@ import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import { Hotkeys } from 'mastodon/components/hotkeys';
import { ContentWarning } from 'mastodon/components/content_warning';
import { FilterWarning } from 'mastodon/components/filter_warning';
import { Icon } from 'mastodon/components/icon';
@ -35,7 +34,6 @@ import StatusActionBar from './status_action_bar';
import StatusContent from './status_content';
import { StatusThreadLabel } from './status_thread_label';
import { VisibilityIcon } from './visibility_icon';
const domParser = new DOMParser();
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
@ -325,11 +323,11 @@ class Status extends ImmutablePureComponent {
};
handleHotkeyMoveUp = e => {
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
this.props.onMoveUp?.(this.props.status.get('id'), this.node.getAttribute('data-featured'));
};
handleHotkeyMoveDown = e => {
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
this.props.onMoveDown?.(this.props.status.get('id'), this.node.getAttribute('data-featured'));
};
handleHotkeyToggleHidden = () => {
@ -437,13 +435,13 @@ class Status extends ImmutablePureComponent {
if (hidden) {
return (
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
<Hotkeys handlers={handlers} focusable={!unfocusable}>
<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>
</Hotkeys>
);
}
@ -543,7 +541,7 @@ class Status extends ImmutablePureComponent {
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
return (
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
<Hotkeys handlers={handlers} focusable={!unfocusable}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
{!skipPrepend && prepend}
@ -604,7 +602,7 @@ class Status extends ImmutablePureComponent {
}
</div>
</div>
</HotKeys>
</Hotkeys>
);
}

View File

@ -56,7 +56,7 @@ export default class StatusList extends ImmutablePureComponent {
const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
this._selectChild(elementIndex, true);
};
handleMoveDown = (id, featured) => {
const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
this._selectChild(elementIndex, false);
@ -69,6 +69,7 @@ export default class StatusList extends ImmutablePureComponent {
_selectChild (index, align_top) {
const container = this.node.node;
// TODO: This breaks at the inline-follow-suggestions container
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
if (element) {

View File

@ -6,6 +6,7 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { AccountBio } from '@/mastodon/components/account_bio';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
@ -773,7 +774,6 @@ export const AccountHeader: React.FC<{
);
}
const content = { __html: account.note_emojified };
const displayNameHtml = { __html: account.display_name_html };
const fields = account.fields;
const isLocal = !account.acct.includes('@');
@ -897,12 +897,11 @@ export const AccountHeader: React.FC<{
<AccountNote accountId={accountId} />
)}
{account.note.length > 0 && account.note !== '<p></p>' && (
<div
className='account__header__content translate'
dangerouslySetInnerHTML={content}
/>
)}
<AccountBio
note={account.note_emojified}
dropdownAccountId={accountId}
className='account__header__content'
/>
<div className='account__header__fields'>
<dl>

View File

@ -261,7 +261,9 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
);
const lang = useAppSelector(
(state) =>
(state.compose as ImmutableMap<string, unknown>).get('lang') as string,
(state.compose as ImmutableMap<string, unknown>).get(
'language',
) as string,
);
const focusX =
(media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;

View File

@ -92,10 +92,29 @@ class ComposeForm extends ImmutablePureComponent {
this.props.onChange(e.target.value);
};
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
blurOnEscape = (e) => {
if (['esc', 'escape'].includes(e.key.toLowerCase())) {
e.target.blur();
}
}
handleKeyDownPost = (e) => {
if (e.key.toLowerCase() === 'enter' && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
}
this.blurOnEscape(e);
};
handleKeyDownSpoiler = (e) => {
if (e.key.toLowerCase() === 'enter') {
if (e.ctrlKey || e.metaKey) {
this.handleSubmit();
} else {
e.preventDefault();
this.textareaRef.current?.focus();
}
}
this.blurOnEscape(e);
};
getFulltextForCharacterCounting = () => {
@ -248,7 +267,7 @@ class ComposeForm extends ImmutablePureComponent {
value={this.props.spoilerText}
disabled={isSubmitting}
onChange={this.handleChangeSpoilerText}
onKeyDown={this.handleKeyDown}
onKeyDown={this.handleKeyDownSpoiler}
ref={this.setSpoilerText}
suggestions={this.props.suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
@ -273,7 +292,7 @@ class ComposeForm extends ImmutablePureComponent {
onChange={this.handleChange}
suggestions={this.props.suggestions}
onFocus={this.handleFocus}
onKeyDown={this.handleKeyDown}
onKeyDown={this.handleKeyDownPost}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}

View File

@ -10,15 +10,13 @@ import { createSelector } from '@reduxjs/toolkit';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { useDispatch, useSelector } from 'react-redux';
import { HotKeys } from 'react-hotkeys';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
import { replyCompose } from 'mastodon/actions/compose';
import { markConversationRead, deleteConversation } from 'mastodon/actions/conversations';
import { openModal } from 'mastodon/actions/modal';
import { muteStatus, unmuteStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
import { Hotkeys } from 'mastodon/components/hotkeys';
import AttachmentList from 'mastodon/components/attachment_list';
import AvatarComposite from 'mastodon/components/avatar_composite';
import { IconButton } from 'mastodon/components/icon_button';
@ -169,7 +167,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
};
return (
<HotKeys handlers={handlers}>
<Hotkeys handlers={handlers}>
<div className={classNames('conversation focusable muted', { unread })} tabIndex={0}>
<div className='conversation__avatar' onClick={handleClick} role='presentation'>
<AvatarComposite accounts={accounts} size={48} />
@ -219,7 +217,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
</div>
</div>
</div>
</HotKeys>
</Hotkeys>
);
};

View File

@ -0,0 +1,110 @@
// Utility codes
export const VARIATION_SELECTOR_CODE = 0xfe0f;
export const KEYCAP_CODE = 0x20e3;
// Gender codes
export const GENDER_FEMALE_CODE = 0x2640;
export const GENDER_MALE_CODE = 0x2642;
// Skin tone codes
export const SKIN_TONE_CODES = [
0x1f3fb, // Light skin tone
0x1f3fc, // Medium-light skin tone
0x1f3fd, // Medium skin tone
0x1f3fe, // Medium-dark skin tone
0x1f3ff, // Dark skin tone
] as const;
export const EMOJIS_WITH_DARK_BORDER = [
'🎱', // 1F3B1
'🐜', // 1F41C
'⚫', // 26AB
'🖤', // 1F5A4
'⬛', // 2B1B
'◼️', // 25FC-FE0F
'◾', // 25FE
'◼️', // 25FC-FE0F
'✒️', // 2712-FE0F
'▪️', // 25AA-FE0F
'💣', // 1F4A3
'🎳', // 1F3B3
'📷', // 1F4F7
'📸', // 1F4F8
'♣️', // 2663-FE0F
'🕶️', // 1F576-FE0F
'✴️', // 2734-FE0F
'🔌', // 1F50C
'💂‍♀️', // 1F482-200D-2640-FE0F
'📽️', // 1F4FD-FE0F
'🍳', // 1F373
'🦍', // 1F98D
'💂', // 1F482
'🔪', // 1F52A
'🕳️', // 1F573-FE0F
'🕹️', // 1F579-FE0F
'🕋', // 1F54B
'🖊️', // 1F58A-FE0F
'🖋️', // 1F58B-FE0F
'💂‍♂️', // 1F482-200D-2642-FE0F
'🎤', // 1F3A4
'🎓', // 1F393
'🎥', // 1F3A5
'🎼', // 1F3BC
'♠️', // 2660-FE0F
'🎩', // 1F3A9
'🦃', // 1F983
'📼', // 1F4FC
'📹', // 1F4F9
'🎮', // 1F3AE
'🐃', // 1F403
'🏴', // 1F3F4
'🐞', // 1F41E
'🕺', // 1F57A
'📱', // 1F4F1
'📲', // 1F4F2
'🚲', // 1F6B2
'🪮', // 1FAA6
'🐦‍⬛', // 1F426-200D-2B1B
];
export const EMOJIS_WITH_LIGHT_BORDER = [
'👽', // 1F47D
'⚾', // 26BE
'🐔', // 1F414
'☁️', // 2601-FE0F
'💨', // 1F4A8
'🕊️', // 1F54A-FE0F
'👀', // 1F440
'🍥', // 1F365
'👻', // 1F47B
'🐐', // 1F410
'❕', // 2755
'❔', // 2754
'⛸️', // 26F8-FE0F
'🌩️', // 1F329-FE0F
'🔊', // 1F50A
'🔇', // 1F507
'📃', // 1F4C3
'🌧️', // 1F327-FE0F
'🐏', // 1F40F
'🍚', // 1F35A
'🍙', // 1F359
'🐓', // 1F413
'🐑', // 1F411
'💀', // 1F480
'☠️', // 2620-FE0F
'🌨️', // 1F328-FE0F
'🔉', // 1F509
'🔈', // 1F508
'💬', // 1F4AC
'💭', // 1F4AD
'🏐', // 1F3D0
'🏳️', // 1F3F3-FE0F
'⚪', // 26AA
'⬜', // 2B1C
'◽', // 25FD
'◻️', // 25FB-FE0F
'▫️', // 25AB-FE0F
'🪽', // 1FAE8
'🪿', // 1FABF
];

View File

@ -0,0 +1,102 @@
import { SUPPORTED_LOCALES } from 'emojibase';
import type { FlatCompactEmoji, Locale } from 'emojibase';
import type { DBSchema } from 'idb';
import { openDB } from 'idb';
import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji';
import type { LocaleOrCustom } from './locale';
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
interface EmojiDB extends LocaleTables, DBSchema {
custom: {
key: string;
value: ApiCustomEmojiJSON;
indexes: {
category: string;
};
};
etags: {
key: LocaleOrCustom;
value: string;
};
}
interface LocaleTable {
key: string;
value: FlatCompactEmoji;
indexes: {
group: number;
label: string;
order: number;
tags: string[];
};
}
type LocaleTables = Record<Locale, LocaleTable>;
const SCHEMA_VERSION = 1;
const db = await openDB<EmojiDB>('mastodon-emoji', SCHEMA_VERSION, {
upgrade(database) {
const customTable = database.createObjectStore('custom', {
keyPath: 'shortcode',
autoIncrement: false,
});
customTable.createIndex('category', 'category');
database.createObjectStore('etags');
for (const locale of SUPPORTED_LOCALES) {
const localeTable = database.createObjectStore(locale, {
keyPath: 'hexcode',
autoIncrement: false,
});
localeTable.createIndex('group', 'group');
localeTable.createIndex('label', 'label');
localeTable.createIndex('order', 'order');
localeTable.createIndex('tags', 'tags', { multiEntry: true });
}
},
});
export async function putEmojiData(emojis: FlatCompactEmoji[], locale: Locale) {
const trx = db.transaction(locale, 'readwrite');
await Promise.all(emojis.map((emoji) => trx.store.put(emoji)));
await trx.done;
}
export async function putCustomEmojiData(emojis: ApiCustomEmojiJSON[]) {
const trx = db.transaction('custom', 'readwrite');
await Promise.all(emojis.map((emoji) => trx.store.put(emoji)));
await trx.done;
}
export function putLatestEtag(etag: string, localeString: string) {
const locale = toSupportedLocaleOrCustom(localeString);
return db.put('etags', etag, locale);
}
export function searchEmojiByHexcode(hexcode: string, localeString: string) {
const locale = toSupportedLocale(localeString);
return db.get(locale, hexcode);
}
export function searchEmojiByTag(tag: string, localeString: string) {
const locale = toSupportedLocale(localeString);
const range = IDBKeyRange.only(tag.toLowerCase());
return db.getAllFromIndex(locale, 'tags', range);
}
export function searchCustomEmojiByShortcode(shortcode: string) {
return db.get('custom', shortcode);
}
export async function loadLatestEtag(localeString: string) {
const locale = toSupportedLocaleOrCustom(localeString);
const rowCount = await db.count(locale);
if (!rowCount) {
return null; // No data for this locale, return null even if there is an etag.
}
const etag = await db.get('etags', locale);
return etag ?? null;
}

View File

@ -0,0 +1,38 @@
import initialState from '@/mastodon/initial_state';
import { toSupportedLocale } from './locale';
const serverLocale = toSupportedLocale(initialState?.meta.locale ?? 'en');
const worker =
'Worker' in window
? new Worker(new URL('./worker', import.meta.url), {
type: 'module',
})
: null;
export async function initializeEmoji() {
if (worker) {
worker.addEventListener('message', (event: MessageEvent<string>) => {
const { data: message } = event;
if (message === 'ready') {
worker.postMessage(serverLocale);
worker.postMessage('custom');
}
});
} else {
const { importCustomEmojiData, importEmojiData } = await import('./loader');
await Promise.all([importCustomEmojiData(), importEmojiData(serverLocale)]);
}
}
export async function loadEmojiLocale(localeString: string) {
const locale = toSupportedLocale(localeString);
if (worker) {
worker.postMessage(locale);
} else {
const { importEmojiData } = await import('./loader');
await importEmojiData(locale);
}
}

View File

@ -0,0 +1,77 @@
import { flattenEmojiData } from 'emojibase';
import type { CompactEmoji, FlatCompactEmoji } from 'emojibase';
import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji';
import { isDevelopment } from '@/mastodon/utils/environment';
import {
putEmojiData,
putCustomEmojiData,
loadLatestEtag,
putLatestEtag,
} from './database';
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
import type { LocaleOrCustom } from './locale';
export async function importEmojiData(localeString: string) {
const locale = toSupportedLocale(localeString);
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale);
if (!emojis) {
return;
}
const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis);
await putEmojiData(flattenedEmojis, locale);
}
export async function importCustomEmojiData() {
const emojis = await fetchAndCheckEtag<ApiCustomEmojiJSON[]>('custom');
if (!emojis) {
return;
}
await putCustomEmojiData(emojis);
}
async function fetchAndCheckEtag<ResultType extends object[]>(
localeOrCustom: LocaleOrCustom,
): Promise<ResultType | null> {
const locale = toSupportedLocaleOrCustom(localeOrCustom);
let uri: string;
if (locale === 'custom') {
uri = '/api/v1/custom_emojis';
} else {
uri = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`;
}
const oldEtag = await loadLatestEtag(locale);
const response = await fetch(uri, {
headers: {
'Content-Type': 'application/json',
'If-None-Match': oldEtag ?? '', // Send the old ETag to check for modifications
},
});
// If not modified, return null
if (response.status === 304) {
return null;
}
if (!response.ok) {
throw new Error(
`Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`,
);
}
const data = (await response.json()) as ResultType;
if (!Array.isArray(data)) {
throw new Error(
`Unexpected data format for ${localeOrCustom}: expected an array`,
);
}
// Store the ETag for future requests
const etag = response.headers.get('ETag');
if (etag) {
await putLatestEtag(etag, localeOrCustom);
}
return data;
}

View File

@ -0,0 +1,29 @@
import { SUPPORTED_LOCALES } from 'emojibase';
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
describe('toSupportedLocale', () => {
test('returns the same locale if it is supported', () => {
for (const locale of SUPPORTED_LOCALES) {
expect(toSupportedLocale(locale)).toBe(locale);
}
});
test('returns "en" for unsupported locales', () => {
const unsupportedLocales = ['xx', 'fr-CA'];
for (const locale of unsupportedLocales) {
expect(toSupportedLocale(locale)).toBe('en');
}
});
});
describe('toSupportedLocaleOrCustom', () => {
test('returns custom for "custom" locale', () => {
expect(toSupportedLocaleOrCustom('custom')).toBe('custom');
});
test('returns supported locale for valid locales', () => {
for (const locale of SUPPORTED_LOCALES) {
expect(toSupportedLocaleOrCustom(locale)).toBe(locale);
}
});
});

View File

@ -0,0 +1,23 @@
import type { Locale } from 'emojibase';
import { SUPPORTED_LOCALES } from 'emojibase';
export type LocaleOrCustom = Locale | 'custom';
export function toSupportedLocale(localeBase: string): Locale {
const locale = localeBase.toLowerCase();
if (isSupportedLocale(locale)) {
return locale;
}
return 'en'; // Default to English if unsupported
}
export function toSupportedLocaleOrCustom(locale: string): LocaleOrCustom {
if (locale.toLowerCase() === 'custom') {
return 'custom';
}
return toSupportedLocale(locale);
}
function isSupportedLocale(locale: string): locale is Locale {
return SUPPORTED_LOCALES.includes(locale.toLowerCase() as Locale);
}

View File

@ -0,0 +1,101 @@
import { readdir } from 'fs/promises';
import { basename, resolve } from 'path';
import { flattenEmojiData } from 'emojibase';
import unicodeRawEmojis from 'emojibase-data/en/data.json';
import {
twemojiHasBorder,
twemojiToUnicodeInfo,
unicodeToTwemojiHex,
CODES_WITH_DARK_BORDER,
CODES_WITH_LIGHT_BORDER,
emojiToUnicodeHex,
} from './normalize';
const emojiSVGFiles = await readdir(
// This assumes tests are run from project root
resolve(process.cwd(), 'public/emoji'),
{
withFileTypes: true,
},
);
const svgFileNames = emojiSVGFiles
.filter((file) => file.isFile() && file.name.endsWith('.svg'))
.map((file) => basename(file.name, '.svg').toUpperCase());
const svgFileNamesWithoutBorder = svgFileNames.filter(
(fileName) => !fileName.endsWith('_BORDER'),
);
const unicodeEmojis = flattenEmojiData(unicodeRawEmojis);
describe('emojiToUnicodeHex', () => {
test.concurrent.for([
['🎱', '1F3B1'],
['🐜', '1F41C'],
['⚫', '26AB'],
['🖤', '1F5A4'],
['💀', '1F480'],
['💂‍♂️', '1F482-200D-2642-FE0F'],
] as const)(
'emojiToUnicodeHex converts %s to %s',
([emoji, hexcode], { expect }) => {
expect(emojiToUnicodeHex(emoji)).toBe(hexcode);
},
);
});
describe('unicodeToTwemojiHex', () => {
test.concurrent.for(
unicodeEmojis
// Our version of Twemoji only supports up to version 15.1
.filter((emoji) => emoji.version < 16)
.map((emoji) => [emoji.hexcode, emoji.label] as [string, string]),
)('verifying an emoji exists for %s (%s)', ([hexcode], { expect }) => {
const result = unicodeToTwemojiHex(hexcode);
expect(svgFileNamesWithoutBorder).toContain(result);
});
});
describe('twemojiHasBorder', () => {
test.concurrent.for(
svgFileNames
.filter((file) => file.endsWith('_BORDER'))
.map((file) => {
const hexCode = file.replace('_BORDER', '');
return [
hexCode,
CODES_WITH_LIGHT_BORDER.includes(hexCode),
CODES_WITH_DARK_BORDER.includes(hexCode),
] as const;
}),
)('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => {
const result = twemojiHasBorder(hexCode);
expect(result).toHaveProperty('hexCode', hexCode);
expect(result).toHaveProperty('hasLightBorder', isLight);
expect(result).toHaveProperty('hasDarkBorder', isDark);
});
});
describe('twemojiToUnicodeInfo', () => {
const unicodeCodeSet = new Set(unicodeEmojis.map((emoji) => emoji.hexcode));
test.concurrent.for(svgFileNamesWithoutBorder)(
'verifying SVG file %s maps to Unicode emoji',
(svgFileName, { expect }) => {
assert(!!svgFileName);
const result = twemojiToUnicodeInfo(svgFileName);
const hexcode = typeof result === 'string' ? result : result.unqualified;
if (!hexcode) {
// No hexcode means this is a special case like the Shibuya 109 emoji
expect(result).toHaveProperty('label');
return;
}
assert(!!hexcode);
expect(
unicodeCodeSet.has(hexcode),
`${hexcode} (${svgFileName}) not found`,
).toBeTruthy();
},
);
});

View File

@ -0,0 +1,173 @@
import {
VARIATION_SELECTOR_CODE,
KEYCAP_CODE,
GENDER_FEMALE_CODE,
GENDER_MALE_CODE,
SKIN_TONE_CODES,
EMOJIS_WITH_DARK_BORDER,
EMOJIS_WITH_LIGHT_BORDER,
} from './constants';
// Misc codes that have special handling
const SKIER_CODE = 0x26f7;
const CHRISTMAS_TREE_CODE = 0x1f384;
const MR_CLAUS_CODE = 0x1f385;
const EYE_CODE = 0x1f441;
const LEVITATING_PERSON_CODE = 0x1f574;
const SPEECH_BUBBLE_CODE = 0x1f5e8;
const MS_CLAUS_CODE = 0x1f936;
export function emojiToUnicodeHex(emoji: string): string {
const codes: number[] = [];
for (const char of emoji) {
const code = char.codePointAt(0);
if (code !== undefined) {
codes.push(code);
}
}
return hexNumbersToString(codes);
}
export function unicodeToTwemojiHex(unicodeHex: string): string {
const codes = hexStringToNumbers(unicodeHex);
const normalizedCodes: number[] = [];
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
if (!code) {
continue;
}
// Some emoji have their variation selector removed
if (code === VARIATION_SELECTOR_CODE) {
// Key emoji
if (i === 1 && codes.at(-1) === KEYCAP_CODE) {
continue;
}
// Eye in speech bubble
if (codes.at(0) === EYE_CODE && codes.at(-2) === SPEECH_BUBBLE_CODE) {
continue;
}
}
// This removes zero padding to correctly match the SVG filenames
normalizedCodes.push(code);
}
return hexNumbersToString(normalizedCodes, 0);
}
interface TwemojiBorderInfo {
hexCode: string;
hasLightBorder: boolean;
hasDarkBorder: boolean;
}
export const CODES_WITH_DARK_BORDER =
EMOJIS_WITH_DARK_BORDER.map(emojiToUnicodeHex);
export const CODES_WITH_LIGHT_BORDER =
EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex);
export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo {
const normalizedHex = twemojiHex.toUpperCase();
let hasLightBorder = false;
let hasDarkBorder = false;
if (CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) {
hasLightBorder = true;
}
if (CODES_WITH_DARK_BORDER.includes(normalizedHex)) {
hasDarkBorder = true;
}
return {
hexCode: normalizedHex,
hasLightBorder,
hasDarkBorder,
};
}
interface TwemojiSpecificEmoji {
unqualified?: string;
gender?: number;
skin?: number;
label?: string;
}
// Normalize man/woman to male/female
const GENDER_CODES_MAP: Record<number, number> = {
[GENDER_FEMALE_CODE]: GENDER_FEMALE_CODE,
[GENDER_MALE_CODE]: GENDER_MALE_CODE,
// These are man/woman markers, but are used for gender sometimes.
[0x1f468]: GENDER_MALE_CODE,
[0x1f469]: GENDER_FEMALE_CODE,
};
const TWEMOJI_SPECIAL_CASES: Record<string, string | TwemojiSpecificEmoji> = {
'1F441-200D-1F5E8': '1F441-FE0F-200D-1F5E8-FE0F', // Eye in speech bubble
// An emoji that was never ported to the Unicode standard.
// See: https://emojipedia.org/shibuya
E50A: { label: 'Shibuya 109' },
};
export function twemojiToUnicodeInfo(
twemojiHex: string,
): TwemojiSpecificEmoji | string {
const specialCase = TWEMOJI_SPECIAL_CASES[twemojiHex.toUpperCase()];
if (specialCase) {
return specialCase;
}
const codes = hexStringToNumbers(twemojiHex);
let gender: undefined | number;
let skin: undefined | number;
for (const code of codes) {
if (!gender && code in GENDER_CODES_MAP) {
gender = GENDER_CODES_MAP[code];
} else if (!skin && code in SKIN_TONE_CODES) {
skin = code;
}
// Exit if we have both skin and gender
if (skin && gender) {
break;
}
}
let mappedCodes: unknown[] = codes;
if (codes.at(-1) === CHRISTMAS_TREE_CODE && codes.length >= 3 && gender) {
// Twemoji uses the christmas tree with a ZWJ for Mr. and Mrs. Claus,
// but in Unicode that only works for Mx. Claus.
const START_CODE =
gender === GENDER_FEMALE_CODE ? MS_CLAUS_CODE : MR_CLAUS_CODE;
mappedCodes = [START_CODE, skin];
} else if (codes.at(-1) === KEYCAP_CODE && codes.length === 2) {
// For key emoji, insert the variation selector
mappedCodes = [codes[0], VARIATION_SELECTOR_CODE, KEYCAP_CODE];
} else if (
(codes.at(0) === SKIER_CODE || codes.at(0) === LEVITATING_PERSON_CODE) &&
codes.length > 1
) {
// Twemoji offers more gender and skin options for the skier and levitating person emoji.
return {
unqualified: hexNumbersToString([codes.at(0)]),
skin,
gender,
};
}
return hexNumbersToString(mappedCodes);
}
function hexStringToNumbers(hexString: string): number[] {
return hexString
.split('-')
.map((code) => Number.parseInt(code, 16))
.filter((code) => !Number.isNaN(code));
}
function hexNumbersToString(codes: unknown[], padding = 4): string {
return codes
.filter(
(code): code is number =>
typeof code === 'number' && code > 0 && !Number.isNaN(code),
)
.map((code) => code.toString(16).padStart(padding, '0').toUpperCase())
.join('-');
}

View File

@ -0,0 +1,13 @@
import { importEmojiData, importCustomEmojiData } from './loader';
addEventListener('message', handleMessage);
self.postMessage('ready'); // After the worker is ready, notify the main thread
function handleMessage(event: MessageEvent<string>) {
const { data: locale } = event;
if (locale !== 'custom') {
void importEmojiData(locale);
} else {
void importCustomEmojiData();
}
}

View File

@ -50,16 +50,22 @@ export const MoreLink: React.FC = () => {
const menu = useMemo(() => {
const arr: MenuItem[] = [
{ text: intl.formatMessage(messages.filters), href: '/filters' },
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
{
text: intl.formatMessage(messages.domainBlocks),
to: '/domain_blocks',
href: '/filters',
text: intl.formatMessage(messages.filters),
},
{
to: '/mutes',
text: intl.formatMessage(messages.mutes),
},
{
to: '/blocks',
text: intl.formatMessage(messages.blocks),
},
{
to: '/domain_blocks',
text: intl.formatMessage(messages.domainBlocks),
},
];
arr.push(
null,
{
href: '/settings/privacy',
@ -77,7 +83,7 @@ export const MoreLink: React.FC = () => {
href: '/settings/export',
text: intl.formatMessage(messages.importExport),
},
);
];
if (canManageReports(permissions)) {
arr.push(null, {
@ -106,7 +112,7 @@ export const MoreLink: React.FC = () => {
}, [intl, dispatch, permissions]);
return (
<Dropdown items={menu}>
<Dropdown items={menu} placement='bottom-start'>
<button className='column-link column-link--transparent'>
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />

View File

@ -431,6 +431,7 @@ export const CollapsibleNavigationPanel: React.FC = () => {
filterTaps: true,
bounds: isLtrDir ? { left: 0 } : { right: 0 },
rubberband: true,
enabled: openable,
},
);

View File

@ -8,7 +8,6 @@ import { Link, withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
@ -20,6 +19,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import { Account } from 'mastodon/components/account';
import { Icon } from 'mastodon/components/icon';
import { Hotkeys } from 'mastodon/components/hotkeys';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { me } from 'mastodon/initial_state';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -137,7 +137,7 @@ class Notification extends ImmutablePureComponent {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='user-plus' icon={PersonAddIcon} />
@ -149,7 +149,7 @@ class Notification extends ImmutablePureComponent {
<Account id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
</Hotkeys>
);
}
@ -157,7 +157,7 @@ class Notification extends ImmutablePureComponent {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='user' icon={PersonIcon} />
@ -169,7 +169,7 @@ class Notification extends ImmutablePureComponent {
<FollowRequestContainer id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
</Hotkeys>
);
}
@ -195,7 +195,7 @@ class Notification extends ImmutablePureComponent {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-favourite focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='star' icon={StarIcon} className='star-icon' />
@ -217,7 +217,7 @@ class Notification extends ImmutablePureComponent {
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -225,7 +225,7 @@ class Notification extends ImmutablePureComponent {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='retweet' icon={RepeatIcon} />
@ -247,7 +247,7 @@ class Notification extends ImmutablePureComponent {
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -259,7 +259,7 @@ class Notification extends ImmutablePureComponent {
}
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-status focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='home' icon={HomeIcon} />
@ -282,7 +282,7 @@ class Notification extends ImmutablePureComponent {
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -294,7 +294,7 @@ class Notification extends ImmutablePureComponent {
}
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-update focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='pencil' icon={EditIcon} />
@ -317,7 +317,7 @@ class Notification extends ImmutablePureComponent {
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -331,7 +331,7 @@ class Notification extends ImmutablePureComponent {
}
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-poll focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='tasks' icon={InsertChartIcon} />
@ -358,7 +358,7 @@ class Notification extends ImmutablePureComponent {
cacheMediaWidth={this.props.cacheMediaWidth}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -371,7 +371,7 @@ class Notification extends ImmutablePureComponent {
}
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-severed-relationships focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.relationshipsSevered, { name: notification.getIn(['event', 'target_name']) }), notification.get('created_at'))}>
<RelationshipsSeveranceEvent
type={event.get('type')}
@ -381,7 +381,7 @@ class Notification extends ImmutablePureComponent {
hidden={hidden}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -394,7 +394,7 @@ class Notification extends ImmutablePureComponent {
}
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-moderation-warning focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.moderationWarning), notification.get('created_at'))}>
<ModerationWarning
action={warning.get('action')}
@ -402,7 +402,7 @@ class Notification extends ImmutablePureComponent {
hidden={hidden}
/>
</div>
</HotKeys>
</Hotkeys>
);
}
@ -410,7 +410,7 @@ class Notification extends ImmutablePureComponent {
const { intl, unread } = this.props;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='user-plus' icon={PersonAddIcon} />
@ -422,7 +422,7 @@ class Notification extends ImmutablePureComponent {
<Account id={account.get('id')} hidden={this.props.hidden} />
</div>
</HotKeys>
</Hotkeys>
);
}
@ -438,7 +438,7 @@ class Notification extends ImmutablePureComponent {
const targetLink = <bdi><Link className='notification__display-name' title={targetAccount.get('acct')} data-hover-card-account={targetAccount.get('id')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} /></bdi>;
return (
<HotKeys handlers={this.getHandlers()}>
<Hotkeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<Icon id='flag' icon={FlagIcon} />
@ -450,7 +450,7 @@ class Notification extends ImmutablePureComponent {
<Report account={account} report={notification.get('report')} hidden={this.props.hidden} />
</div>
</HotKeys>
</Hotkeys>
);
}

View File

@ -122,98 +122,93 @@ export const PolicyControls: React.FC = () => {
value={notificationPolicy.for_not_following}
onChange={handleFilterNotFollowing}
options={options}
>
<strong>
label={
<FormattedMessage
id='notifications.policy.filter_not_following_title'
defaultMessage="People you don't follow"
/>
</strong>
<span className='hint'>
}
hint={
<FormattedMessage
id='notifications.policy.filter_not_following_hint'
defaultMessage='Until you manually approve them'
/>
</span>
</SelectWithLabel>
}
/>
<SelectWithLabel
value={notificationPolicy.for_not_followers}
onChange={handleFilterNotFollowers}
options={options}
>
<strong>
label={
<FormattedMessage
id='notifications.policy.filter_not_followers_title'
defaultMessage='People not following you'
/>
</strong>
<span className='hint'>
}
hint={
<FormattedMessage
id='notifications.policy.filter_not_followers_hint'
defaultMessage='Including people who have been following you fewer than {days, plural, one {one day} other {# days}}'
values={{ days: 3 }}
/>
</span>
</SelectWithLabel>
}
/>
<SelectWithLabel
value={notificationPolicy.for_new_accounts}
onChange={handleFilterNewAccounts}
options={options}
>
<strong>
label={
<FormattedMessage
id='notifications.policy.filter_new_accounts_title'
defaultMessage='New accounts'
/>
</strong>
<span className='hint'>
}
hint={
<FormattedMessage
id='notifications.policy.filter_new_accounts.hint'
defaultMessage='Created within the past {days, plural, one {one day} other {# days}}'
values={{ days: 30 }}
/>
</span>
</SelectWithLabel>
}
/>
<SelectWithLabel
value={notificationPolicy.for_private_mentions}
onChange={handleFilterPrivateMentions}
options={options}
>
<strong>
label={
<FormattedMessage
id='notifications.policy.filter_private_mentions_title'
defaultMessage='Unsolicited private mentions'
/>
</strong>
<span className='hint'>
}
hint={
<FormattedMessage
id='notifications.policy.filter_private_mentions_hint'
defaultMessage="Filtered unless it's in reply to your own mention or if you follow the sender"
/>
</span>
</SelectWithLabel>
}
/>
<SelectWithLabel
value={notificationPolicy.for_limited_accounts}
onChange={handleFilterLimitedAccounts}
options={options}
>
<strong>
label={
<FormattedMessage
id='notifications.policy.filter_limited_accounts_title'
defaultMessage='Moderated accounts'
/>
</strong>
<span className='hint'>
}
hint={
<FormattedMessage
id='notifications.policy.filter_limited_accounts_hint'
defaultMessage='Limited by server moderators'
/>
</span>
</SelectWithLabel>
}
/>
</div>
</section>
);

View File

@ -1,5 +1,5 @@
import type { PropsWithChildren } from 'react';
import { useCallback, useState, useRef } from 'react';
import { useCallback, useState, useRef, useId } from 'react';
import classNames from 'classnames';
@ -16,6 +16,8 @@ interface DropdownProps {
options: SelectItem[];
disabled?: boolean;
onChange: (value: string) => void;
'aria-labelledby': string;
'aria-describedby'?: string;
placement?: Placement;
}
@ -24,51 +26,33 @@ const Dropdown: React.FC<DropdownProps> = ({
options,
disabled,
onChange,
'aria-labelledby': ariaLabelledBy,
'aria-describedby': ariaDescribedBy,
placement: initialPlacement = 'bottom-end',
}) => {
const activeElementRef = useRef<Element | null>(null);
const containerRef = useRef(null);
const containerRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const [isOpen, setOpen] = useState<boolean>(false);
const [placement, setPlacement] = useState<Placement>(initialPlacement);
const handleToggle = useCallback(() => {
if (
isOpen &&
activeElementRef.current &&
activeElementRef.current instanceof HTMLElement
) {
activeElementRef.current.focus({ preventScroll: true });
}
setOpen(!isOpen);
}, [isOpen, setOpen]);
const handleMouseDown = useCallback(() => {
if (!isOpen) activeElementRef.current = document.activeElement;
}, [isOpen]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
switch (e.key) {
case ' ':
case 'Enter':
if (!isOpen) activeElementRef.current = document.activeElement;
break;
}
},
[isOpen],
);
const uniqueId = useId();
const menuId = `${uniqueId}-menu`;
const buttonLabelId = `${uniqueId}-button`;
const handleClose = useCallback(() => {
if (
isOpen &&
activeElementRef.current &&
activeElementRef.current instanceof HTMLElement
)
activeElementRef.current.focus({ preventScroll: true });
if (isOpen && buttonRef.current) {
buttonRef.current.focus({ preventScroll: true });
}
setOpen(false);
}, [isOpen]);
const handleToggle = useCallback(() => {
if (isOpen) {
handleClose();
} else {
setOpen(true);
}
}, [isOpen, handleClose]);
const handleOverlayEnter = useCallback(
(state: Partial<PopperState>) => {
if (state.placement) setPlacement(state.placement);
@ -82,13 +66,18 @@ const Dropdown: React.FC<DropdownProps> = ({
<div ref={containerRef}>
<button
type='button'
ref={buttonRef}
onClick={handleToggle}
onMouseDown={handleMouseDown}
onKeyDown={handleKeyDown}
disabled={disabled}
aria-expanded={isOpen}
aria-controls={menuId}
aria-labelledby={`${ariaLabelledBy} ${buttonLabelId}`}
aria-describedby={ariaDescribedBy}
className={classNames('dropdown-button', { active: isOpen })}
>
<span className='dropdown-button__label'>{valueOption?.text}</span>
<span id={buttonLabelId} className='dropdown-button__label'>
{valueOption?.text}
</span>
<Icon id='down' icon={ArrowDropDownIcon} />
</button>
@ -101,7 +90,7 @@ const Dropdown: React.FC<DropdownProps> = ({
popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
>
{({ props, placement }) => (
<div {...props}>
<div {...props} id={menuId}>
<div
className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}
>
@ -123,6 +112,8 @@ const Dropdown: React.FC<DropdownProps> = ({
interface Props {
value: string;
options: SelectItem[];
label: string | React.ReactElement;
hint: string | React.ReactElement;
disabled?: boolean;
onChange: (value: string) => void;
}
@ -130,13 +121,26 @@ interface Props {
export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
value,
options,
label,
hint,
disabled,
children,
onChange,
}) => {
const uniqueId = useId();
const labelId = `${uniqueId}-label`;
const descId = `${uniqueId}-desc`;
return (
// This label is only used for its click-forwarding behaviour,
// accessible names are assigned manually
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>{children}</div>
<div className='app-form__toggle__label'>
<strong id={labelId}>{label}</strong>
<span className='hint' id={descId}>
{hint}
</span>
</div>
<div className='app-form__toggle__toggle'>
<div>
@ -144,6 +148,8 @@ export const SelectWithLabel: React.FC<PropsWithChildren<Props>> = ({
value={value}
onChange={onChange}
disabled={disabled}
aria-labelledby={labelId}
aria-describedby={descId}
options={options}
/>
</div>

View File

@ -1,9 +1,8 @@
import { useMemo } from 'react';
import { HotKeys } from 'react-hotkeys';
import { navigateToProfile } from 'mastodon/actions/accounts';
import { mentionComposeById } from 'mastodon/actions/compose';
import { Hotkeys } from 'mastodon/components/hotkeys';
import type { NotificationGroup as NotificationGroupModel } from 'mastodon/models/notification_group';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@ -156,5 +155,5 @@ export const NotificationGroup: React.FC<{
return null;
}
return <HotKeys handlers={handlers}>{content}</HotKeys>;
return <Hotkeys handlers={handlers}>{content}</Hotkeys>;
};

View File

@ -3,12 +3,11 @@ import type { JSX } from 'react';
import classNames from 'classnames';
import { HotKeys } from 'react-hotkeys';
import { replyComposeById } from 'mastodon/actions/compose';
import { navigateToStatus } from 'mastodon/actions/statuses';
import { Avatar } from 'mastodon/components/avatar';
import { AvatarGroup } from 'mastodon/components/avatar_group';
import { Hotkeys } from 'mastodon/components/hotkeys';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
@ -91,7 +90,7 @@ export const NotificationGroupWithStatus: React.FC<{
);
return (
<HotKeys handlers={handlers}>
<Hotkeys handlers={handlers}>
<div
role='button'
className={classNames(
@ -149,6 +148,6 @@ export const NotificationGroupWithStatus: React.FC<{
)}
</div>
</div>
</HotKeys>
</Hotkeys>
);
};

View File

@ -2,14 +2,13 @@ import { useMemo } from 'react';
import classNames from 'classnames';
import { HotKeys } from 'react-hotkeys';
import { replyComposeById } from 'mastodon/actions/compose';
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
import {
navigateToStatus,
toggleStatusSpoilers,
} from 'mastodon/actions/statuses';
import { Hotkeys } from 'mastodon/components/hotkeys';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
@ -83,7 +82,7 @@ export const NotificationWithStatus: React.FC<{
if (!statusId || isFiltered) return null;
return (
<HotKeys handlers={handlers}>
<Hotkeys handlers={handlers}>
<div
role='button'
className={classNames(
@ -111,6 +110,6 @@ export const NotificationWithStatus: React.FC<{
unfocusable
/>
</div>
</HotKeys>
</Hotkeys>
);
};

View File

@ -10,10 +10,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { HotKeys } from 'react-hotkeys';
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
import { Hotkeys } from 'mastodon/components/hotkeys';
import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { TimelineHint } from 'mastodon/components/timeline_hint';
@ -616,7 +615,7 @@ class Status extends ImmutablePureComponent {
<div className={classNames('scrollable', { fullscreen })} ref={this.setContainerRef}>
{ancestors}
<HotKeys handlers={handlers}>
<Hotkeys handlers={handlers}>
<div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)} ref={this.setStatusRef}>
<DetailedStatus
key={`details-${status.get('id')}`}
@ -654,7 +653,7 @@ class Status extends ImmutablePureComponent {
onEmbed={this.handleEmbed}
/>
</div>
</HotKeys>
</Hotkeys>
{descendants}
{remoteHint}

View File

@ -9,13 +9,13 @@ import { Redirect, Route, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { debounce } from 'lodash';
import { HotKeys } from 'react-hotkeys';
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { fetchNotifications } from 'mastodon/actions/notification_groups';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { AlertsController } from 'mastodon/components/alerts_controller';
import { Hotkeys } from 'mastodon/components/hotkeys';
import { HoverCardController } from 'mastodon/components/hover_card_controller';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
@ -98,40 +98,6 @@ const mapStateToProps = state => ({
username: state.getIn(['accounts', me, 'username']),
});
const keyMap = {
help: '?',
new: 'n',
search: ['s', '/'],
forceNew: 'option+n',
toggleComposeSpoilers: 'option+x',
focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
reply: 'r',
favourite: 'f',
boost: 'b',
mention: 'm',
open: ['enter', 'o'],
openProfile: 'p',
moveDown: ['down', 'j'],
moveUp: ['up', 'k'],
back: 'backspace',
goToHome: 'g h',
goToNotifications: 'g n',
goToLocal: 'g l',
goToFederated: 'g t',
goToDirect: 'g d',
goToStart: 'g s',
goToFavourites: 'g f',
goToPinned: 'g p',
goToProfile: 'g u',
goToBlocked: 'g b',
goToMuted: 'g m',
goToRequests: 'g r',
toggleHidden: 'x',
toggleSensitive: 'h',
openMedia: 'e',
onTranslate: 't',
};
class SwitchingColumnsArea extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
@ -400,6 +366,10 @@ class UI extends PureComponent {
}
};
handleDonate = () => {
location.href = 'https://joinmastodon.org/sponsors#donate'
}
componentDidMount () {
const { signedIn } = this.props.identity;
@ -426,10 +396,6 @@ class UI extends PureComponent {
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
}
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
};
}
componentWillUnmount () {
@ -509,10 +475,6 @@ class UI extends PureComponent {
}
};
setHotkeysRef = c => {
this.hotkeys = c;
};
handleHotkeyToggleHelp = () => {
if (this.props.location.pathname === '/keyboard-shortcuts') {
this.props.history.goBack();
@ -593,10 +555,11 @@ class UI extends PureComponent {
goToBlocked: this.handleHotkeyGoToBlocked,
goToMuted: this.handleHotkeyGoToMuted,
goToRequests: this.handleHotkeyGoToRequests,
cheat: this.handleDonate,
};
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<Hotkeys global handlers={handlers}>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'} forceOnboarding={firstLaunch && newAccount}>
{children}
@ -611,7 +574,7 @@ class UI extends PureComponent {
<ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
</div>
</HotKeys>
</Hotkeys>
);
}

View File

@ -8,13 +8,14 @@ import { openURL } from 'mastodon/actions/search';
import { useAppDispatch } from 'mastodon/store';
const isMentionClick = (element: HTMLAnchorElement) =>
element.classList.contains('mention');
element.classList.contains('mention') &&
!element.classList.contains('hashtag');
const isHashtagClick = (element: HTMLAnchorElement) =>
element.textContent?.[0] === '#' ||
element.previousSibling?.textContent?.endsWith('#');
export const useLinks = () => {
export const useLinks = (skipHashtags?: boolean) => {
const history = useHistory();
const dispatch = useAppDispatch();
@ -61,12 +62,12 @@ export const useLinks = () => {
if (isMentionClick(target)) {
e.preventDefault();
void handleMentionClick(target);
} else if (isHashtagClick(target)) {
} else if (isHashtagClick(target) && !skipHashtags) {
e.preventDefault();
handleHashtagClick(target);
}
},
[handleMentionClick, handleHashtagClick],
[skipHashtags, handleMentionClick, handleHashtagClick],
);
return handleClick;

View File

@ -1,6 +1,5 @@
// @ts-check
/**
* @typedef {[code: string, name: string, localName: string]} InitialStateLanguage
*/
@ -64,6 +63,7 @@
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
* @property {string[]} features
*/
const element = document.getElementById('initial-state');
@ -140,4 +140,12 @@ export function getAccessToken() {
return getMeta('access_token');
}
/**
* @param {string} feature
* @returns {boolean}
*/
export function isFeatureEnabled(feature) {
return initialState?.features?.includes(feature) || false;
}
export default initialState;

View File

@ -1,6 +1,7 @@
{
"about.blocks": "Мадэраваныя серверы",
"about.contact": "Кантакт:",
"about.default_locale": "Прадвызначаная",
"about.disclaimer": "Mastodon - свабоднае праграмнае забеспячэнне, з адкрытым зыходным кодам, і гандлёвай маркай Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Прычына недаступная",
"about.domain_blocks.preamble": "Mastodon, у асноўным, дазваляе вам праглядаць кантэнт і ўзаемадзейнічаць з карыстальнікамі з іншых сервераў у федэсвету. Гэтыя выключэнні былі зроблены дакладна на гэтым серверы.",
@ -8,6 +9,7 @@
"about.domain_blocks.silenced.title": "Абмежаваны",
"about.domain_blocks.suspended.explanation": "Ніякая інфармацыя з гэтага сервера не будзе апрацавана, захавана або абменена, узаемадзеянне або камунікацыя з карыстальнікамі гэтага сервера немагчымы.",
"about.domain_blocks.suspended.title": "Прыпынены",
"about.language_label": "Мова",
"about.not_available": "Дадзеная інфармацыя не дасяжная на гэтым серверы.",
"about.powered_by": "Дэцэнтралізаваная сацыяльная сетка, створаная {mastodon}",
"about.rules": "Правілы сервера",
@ -19,13 +21,21 @@
"account.block_domain": "Заблакіраваць дамен {domain}",
"account.block_short": "Заблакіраваць",
"account.blocked": "Заблакіраваны",
"account.blocking": "Блакіраванне",
"account.cancel_follow_request": "Скасаваць запыт на падпіску",
"account.copy": "Скапіраваць спасылку на профіль",
"account.direct": "Згадаць асабіста @{name}",
"account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}",
"account.domain_blocking": "Блакіраванне дамена",
"account.edit_profile": "Рэдагаваць профіль",
"account.enable_notifications": "Апавяшчаць мяне пра допісы @{name}",
"account.endorse": "Паказваць у профілі",
"account.familiar_followers_many": "Мае сярод падпісчыкаў {name1}, {name2}, і {othersCount, plural, one {яшчэ # чалавека, знаёмага вам} few {яшчэ # чалавекі, знаёмыя вам} many {яшчэ # чалавек, знаёмых вам} other {яшчэ # чалавекі, знаёмыя вам}}",
"account.familiar_followers_one": "Мае сярод падпісчыкаў {name1}",
"account.familiar_followers_two": "Мае сярод падпісчыкаў {name1} і {name2}",
"account.featured": "Рэкамендаванае",
"account.featured.accounts": "Профілі",
"account.featured.hashtags": "Хэштэгі",
"account.featured_tags.last_status_at": "Апошні допіс ад {date}",
"account.featured_tags.last_status_never": "Няма допісаў",
"account.follow": "Падпісацца",
@ -33,9 +43,11 @@
"account.followers": "Падпісчыкі",
"account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.",
"account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}",
"account.followers_you_know_counter": "{count, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}",
"account.following": "Падпіскі",
"account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}",
"account.follows.empty": "Карыстальнік ні на каго не падпісаны.",
"account.follows_you": "Падпісаны на вас",
"account.go_to_profile": "Перайсці да профілю",
"account.hide_reblogs": "Схаваць пашырэнні ад @{name}",
"account.in_memoriam": "У памяць.",
@ -50,18 +62,22 @@
"account.mute_notifications_short": "Не апавяшчаць",
"account.mute_short": "Ігнараваць",
"account.muted": "Ігнаруецца",
"account.mutual": "Вы падпісаны адно на аднаго",
"account.no_bio": "Апісанне адсутнічае.",
"account.open_original_page": "Адкрыць арыгінальную старонку",
"account.posts": "Допісы",
"account.posts_with_replies": "Допісы і адказы",
"account.remove_from_followers": "Выдаліць карыстальніка {name} з падпісчыкаў",
"account.report": "Паскардзіцца на @{name}",
"account.requested": "Чакаецца ўхваленне. Націсніце, каб скасаваць запыт на падпіску",
"account.requested_follow": "{name} адправіў запыт на падпіску",
"account.requests_to_follow_you": "Хоча падпісацца на вас",
"account.share": "Абагуліць профіль @{name}",
"account.show_reblogs": "Паказаць падштурхоўванні ад @{name}",
"account.statuses_counter": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
"account.unblock": "Разблакіраваць @{name}",
"account.unblock_domain": "Разблакіраваць дамен {domain}",
"account.unblock_domain_short": "Разблакіраваць",
"account.unblock_short": "Разблакіраваць",
"account.unendorse": "Не паказваць у профілі",
"account.unfollow": "Адпісацца",
@ -83,26 +99,30 @@
"alert.unexpected.message": "Узнікла нечаканая памылка.",
"alert.unexpected.title": "Вой!",
"alt_text_badge.title": "Альтэрнатыўны тэкст",
"alt_text_modal.add_alt_text": "Дадаць альтэрнатыўны тэкст",
"alt_text_modal.add_text_from_image": "Дадаць тэкст з відарыса",
"alt_text_modal.cancel": "Скасаваць",
"alt_text_modal.change_thumbnail": "Змяніць мініяцюру",
"alt_text_modal.done": "Гатова",
"announcement.announcement": "Аб'ява",
"annual_report.summary.archetype.booster": "Трэнда-сьледнік",
"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.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.most_used_app.most_used_app": "праграма, якой карысталіся часцей",
"annual_report.summary.most_used_hashtag.most_used_hashtag": "хэштэг, якім карысталіся часцей",
"annual_report.summary.most_used_hashtag.none": "Няма",
"annual_report.summary.new_posts.new_posts": "новыя допісы",
"annual_report.summary.percentile.text": "<topLabel>Мэта месьціць вас у топ</topLabel><percentage></percentage><bottomLabel> карыстальнікаў {domain}.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "Мы ня скажам аб гэтым Сіняпальцаму.",
"annual_report.summary.percentile.text": "<topLabel>З-за гэтага, вы знаходзіцеся ў топе</topLabel><percentage></percentage><bottomLabel> карыстальнікаў {domain}.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "КДБ пра гэта не даведаецца.",
"annual_report.summary.thanks": "Дзякуй за ўдзел у Mastodon!",
"attachments_list.unprocessed": "(неапрацаваны)",
"audio.hide": "Схаваць аўдыя",
@ -127,7 +147,7 @@
"bundle_column_error.routing.body": "Запытаная старонка не знойдзена. Вы ўпэўнены, што URL у адрасным радку правільны?",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Закрыць",
"bundle_modal_error.message": "Падчас загрузкі гэтага экрана штосьці пайшло ня так.",
"bundle_modal_error.message": "Падчас загрузкі гэтага экрана штосьці пайшло не так.",
"bundle_modal_error.retry": "Паспрабуйце зноў",
"closed_registrations.other_server_instructions": "Паколькі Mastodon дэцэнтралізаваны, вы можаце стварыць уліковы запіс на іншым серверы і працягваць узаемадзейнічаць з ім.",
"closed_registrations_modal.description": "Стварэнне ўліковага запісу на {domain} цяпер немагчыма. Заўважце, што няма неабходнасці мець уліковы запіс менавіта на {domain}, каб выкарыстоўваць Mastodon.",
@ -147,7 +167,7 @@
"column.firehose": "Стужкі",
"column.follow_requests": "Запыты на падпіску",
"column.home": "Галоўная",
"column.list_members": "Кіраванне ўдзельнікамі спісу",
"column.list_members": "Кіраванне ўдзельнікамі спіса",
"column.lists": "Спісы",
"column.mutes": "Ігнараваныя карыстальнікі",
"column.notifications": "Апавяшчэнні",
@ -196,19 +216,24 @@
"confirmations.delete_list.confirm": "Выдаліць",
"confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?",
"confirmations.delete_list.title": "Выдаліць спіс?",
"confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне",
"confirmations.discard_edit_media.confirm": "Адмяніць",
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
"confirmations.follow_to_list.confirm": "Падпісацца й дадаць у сьпіс",
"confirmations.follow_to_list.message": "Вы мусіце быць падпісаныя на {name} каб дадаць яго ў сьпіс.",
"confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс",
"confirmations.follow_to_list.message": "Вам трэба падпісацца на карыстальніка {name}, каб дадаць яго ў спіс.",
"confirmations.follow_to_list.title": "Падпісацца на карыстальніка?",
"confirmations.logout.confirm": "Выйсці",
"confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
"confirmations.logout.title": "Выйсці?",
"confirmations.missing_alt_text.title": "Дадаць апісаньне?",
"confirmations.missing_alt_text.confirm": "Дадаць альтэрнатыўны тэкст",
"confirmations.missing_alt_text.secondary": "Усё адно апублікаваць",
"confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?",
"confirmations.mute.confirm": "Ігнараваць",
"confirmations.redraft.confirm": "Выдаліць і перапісаць",
"confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.",
"confirmations.redraft.title": "Выдаліць і перапісаць допіс?",
"confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка",
"confirmations.remove_from_followers.title": "Выдаліць падпісчыка?",
"confirmations.unfollow.confirm": "Адпісацца",
"confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?",
"confirmations.unfollow.title": "Адпісацца ад карыстальніка?",
@ -221,7 +246,7 @@
"conversation.with": "З {names}",
"copy_icon_button.copied": "Скапіявана ў буфер абмену",
"copypaste.copied": "Скапіравана",
"copypaste.copy_to_clipboard": "Капіраваць у буфер абмену",
"copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену",
"directory.federated": "З вядомага федэсвету",
"directory.local": "Толькі з {domain}",
"directory.new_arrivals": "Новыя карыстальнікі",
@ -230,7 +255,7 @@
"disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.",
"dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.",
"dismissable_banner.dismiss": "Адхіліць",
"dismissable_banner.public_timeline": "Гэта самыя новыя публічныя допісы ад карыстальнікаў фэдывёрсу на якіх падпісаныя карыстальнікі {domain}.",
"dismissable_banner.public_timeline": "Вось апошнія публічныя допісы ад карыстальнікаў fediverse на якіх падпісаны карыстальнікі {domain}.",
"domain_block_modal.block": "Заблакіраваць сервер",
"domain_block_modal.block_account_instead": "Заблакіраваць @{name} замест гэтага",
"domain_block_modal.they_can_interact_with_old_posts": "Людзі з гэтага сервера змогуць узаемадзейнічаць з вашымі старымі допісамі.",
@ -275,15 +300,15 @@
"empty_column.account_timeline": "Тут няма допісаў!",
"empty_column.account_unavailable": "Профіль недаступны",
"empty_column.blocks": "Вы яшчэ нікога не заблакіравалі.",
"empty_column.bookmarked_statuses": "У вас яшчэ няма паведамленняў з закладкамі. Калі вы дадасце закладку, яна з'явіцца тут.",
"empty_column.community": "Мясцовая стужка пустая. Напішыце што-небудзь публічна, каб зрушыць з месца!",
"empty_column.direct": "Пакуль у вас няма асабістых згадак. Калі вы дашляце або атрымаеце штось, яно з'явіцца тут.",
"empty_column.bookmarked_statuses": "У вашых закладках яшчэ няма допісаў. Калі вы дадасце закладку, яна з’явіцца тут.",
"empty_column.community": "Мясцовая стужка пустая. Напішыце нешта публічнае, каб разварушыць справу!",
"empty_column.direct": "Пакуль у вас няма асабістых згадванняў. Калі вы дашляце або атрымаеце штосьці, яно з’явіцца тут.",
"empty_column.domain_blocks": "Заблакіраваных даменаў пакуль няма.",
"empty_column.explore_statuses": "Зараз не ў трэндзе. Праверце пазней",
"empty_column.favourited_statuses": "Вы яшчэ не ўпадабалі ніводны допіс. Калі гэта адбудзецца, вы ўбачыце яго тут.",
"empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, вы ўбачыце гэтых людзей тут.",
"empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіскуі. Калі вы атрымаеце запыт, ён з'явяцца тут.",
"empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны з'явяцца тут.",
"empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіску. Калі вы атрымаеце запыт, ён з’явіцца тут.",
"empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны зявяцца тут.",
"empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.",
"empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}",
"empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.",
@ -294,13 +319,18 @@
"error.unexpected_crash.explanation": "Гэта старонка не можа быць адлюстравана карэктна з-за памылкі ў нашым кодзе, або праблемы з сумяшчальнасцю браўзера.",
"error.unexpected_crash.explanation_addons": "Гэтая старонка не можа быць адлюстравана карэктна. Верагодна, гэтая памылка выклікана дадатковым кампанентам браўзера або інструментамі аўтаматычнага перакладу",
"error.unexpected_crash.next_steps": "Паспрабуйце абнавіць старонку. Калі гэта не дапаможа, вы можаце паспрабаваць іншы браўзер, альбо выкарыстаць усталяваную праграму.",
"error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і аднавіць старонку. Калі гэта не дапаможа, вы можаце карыстацца Мастадонт праз другі браўзер ці аплікацыю.",
"error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і абнавіць старонку. Калі гэта не дапамагае, вы ўсё яшчэ можаце карыстацца Mastodon праз іншы браўзер ці натыўную праграму.",
"errors.unexpected_crash.copy_stacktrace": "Дадаць дыягнастычны стэк у буфер абмену",
"errors.unexpected_crash.report_issue": "Паведаміць аб праблеме",
"explore.suggested_follows": "Людзі",
"explore.title": "Трэндавае",
"explore.trending_links": "Навіны",
"explore.trending_statuses": "Допісы",
"explore.trending_tags": "Хэштэгі",
"featured_carousel.next": "Далей",
"featured_carousel.post": "Допіс",
"featured_carousel.previous": "Назад",
"featured_carousel.slide": "{index} з {total}",
"filter_modal.added.context_mismatch_explanation": "Гэтая катэгорыя фільтра не прымяняецца да кантэксту, у якім вы адкрылі гэты пост. Калі вы хочаце, каб паведамленне таксама было адфільтравана ў гэтым кантэксце, вам трэба будзе адрэдагаваць фільтр",
"filter_modal.added.context_mismatch_title": "Неадпаведны кантэкст!",
"filter_modal.added.expired_explanation": "Тэрмін дзеяння гэтай катэгорыі фільтраў скончыўся, вам трэба будзе змяніць дату заканчэння тэрміну дзеяння, каб яна прымянялася",
@ -349,10 +379,10 @@
"footer.privacy_policy": "Палітыка прыватнасці",
"footer.source_code": "Прагледзець зыходны код",
"footer.status": "Статус",
"footer.terms_of_service": "Умовы абслугоўваньня",
"footer.terms_of_service": "Умовы выкарыстання",
"generic.saved": "Захавана",
"getting_started.heading": "Пачатак працы",
"hashtag.admin_moderation": "Адкрыць інтэрфэйс мадаратара для #{name}",
"hashtag.admin_moderation": "Адкрыць інтэрфейс мадэратара для #{name}",
"hashtag.column_header.tag_mode.all": "і {additional}",
"hashtag.column_header.tag_mode.any": "або {additional}",
"hashtag.column_header.tag_mode.none": "без {additional}",
@ -374,35 +404,39 @@
"hints.profiles.see_more_followers": "Глядзець больш падпісаных на {domain}",
"hints.profiles.see_more_follows": "Глядзець больш падпісак на {domain}",
"hints.profiles.see_more_posts": "Глядзець больш допісаў на {domain}",
"hints.threads.replies_may_be_missing": "Адказы зь іншых сэрвэраў могуць адсутнічаць.",
"hints.threads.replies_may_be_missing": "Адказы з іншых сервераў могуць адсутнічаць.",
"hints.threads.see_more": "Глядзіце больш адказаў на {domain}",
"home.column_settings.show_quotes": "Паказаць цытаты",
"home.column_settings.show_reblogs": "Паказваць пашырэнні",
"home.column_settings.show_replies": "Паказваць адказы",
"home.hide_announcements": "Схаваць аб'явы",
"home.pending_critical_update.body": "Калі ласка, абнавіце свой сервер Mastodon як мага хутчэй!",
"home.pending_critical_update.body": "Абнавіце свой сервер Mastodon як мага хутчэй!",
"home.pending_critical_update.link": "Прагледзець абнаўленні",
"home.pending_critical_update.title": "Даступна крытычнае абнаўленне бяспекі!",
"home.show_announcements": "Паказаць аб'явы",
"ignore_notifications_modal.disclaimer": "Mastodon ня можа йнфармаваць карыстальнікаў аб тым, што вы прайігнаравалі йх паведамленьні. Ігнараваньне паведамленьняў не спыніць іх адпраўку.",
"ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.",
"ignore_notifications_modal.filter_instead": "Замест гэтага адфільтраваць",
"ignore_notifications_modal.filter_to_act_users": "Вы па-ранейшаму зможаце прымаць, адхіляць ці скардзіцца на карыстальнікаў",
"ignore_notifications_modal.filter_to_avoid_confusion": "Фільтраваньне дапамагае пазьбегнуць патэнцыйнай блытаніны",
"ignore_notifications_modal.filter_to_review_separately": "Вы можаце прагледзець адфільтраваныя паведамленьні асобна",
"ignore_notifications_modal.ignore": "Ігнараваць паведамленьні",
"ignore_notifications_modal.limited_accounts_title": "Ігнараваць паведамленьні ад абмежаваных уліковых запісаў?",
"ignore_notifications_modal.new_accounts_title": "Ігнараваць паведамленьні ад новых уліковых запісаў?",
"ignore_notifications_modal.not_followers_title": "Ігнараваць паведамленьні ад людзей, якія ня падпісаныя на вас?",
"ignore_notifications_modal.filter_to_avoid_confusion": "Выкарыстанне фільтраў дапамагае пазбягаць патэнцыйнай блытаніны",
"ignore_notifications_modal.filter_to_review_separately": "Вы можаце прагледзець адфільтраваныя апавяшчэнні асобна",
"ignore_notifications_modal.ignore": "Ігнараваць апавяшчэнні",
"ignore_notifications_modal.limited_accounts_title": "Ігнараваць апавяшчэнні ад уліковых запісаў пад мадэрацыяй?",
"ignore_notifications_modal.new_accounts_title": "Ігнараваць апавяшчэнні ад новых уліковых запісаў?",
"ignore_notifications_modal.not_followers_title": "Ігнараваць апавяшчэнні ад людзей, якія не падпісаныя на вас?",
"ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?",
"ignore_notifications_modal.private_mentions_title": "Ігнараваць паведамленьні аб непажаданых прыватных згадках?",
"ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?",
"info_button.label": "Даведка",
"interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць з вашага ўліковага запісу.",
"interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца з вашага ўліковага запісу.",
"interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.",
"interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.",
"interaction_modal.go": "Перайсці",
"interaction_modal.no_account_yet": "Не маеце ўліковага запісу?",
"interaction_modal.on_another_server": "На іншым серверы",
"interaction_modal.on_this_server": "На гэтым серверы",
"interaction_modal.title.favourite": "Упадабаць допіс {name}",
"interaction_modal.title.follow": "Падпісацца на {name}",
"interaction_modal.title.reblog": "Пашырыць допіс ад {name}",
"interaction_modal.title.reply": "Адказаць на допіс {name}",
"interaction_modal.username_prompt": "Напр., {example}",
"intervals.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}",
"intervals.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}}",
"intervals.full.minutes": "{number, plural, one {# хвіліна} few {# хвіліны} many {# хвілін} other {# хвіліны}}",
@ -438,21 +472,32 @@
"keyboard_shortcuts.toggle_hidden": "Паказаць/схаваць тэкст за папярэджаннем пра кантэнт",
"keyboard_shortcuts.toggle_sensitivity": "Паказаць/схаваць медыя",
"keyboard_shortcuts.toot": "Стварыць новы допіс",
"keyboard_shortcuts.translate": "каб перакласці допіс",
"keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок",
"keyboard_shortcuts.up": "Перамясціцца ўверх па спісе",
"lightbox.close": "Закрыць",
"lightbox.next": "Далей",
"lightbox.previous": "Назад",
"lightbox.zoom_in": "Маштабаваць да фактычнага памеру",
"lightbox.zoom_out": "Дапасаваць усё змесціва пад памеры экрана",
"limited_account_hint.action": "Усе роўна паказваць профіль",
"limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі",
"link_preview.author": "Ад {name}",
"link_preview.more_from_author": "Больш ад {name}",
"link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}",
"lists.add_member": "Дадаць",
"lists.add_to_list": "Дадаць у спіс",
"lists.add_to_lists": "Дадаць {name} у спісы",
"lists.create": "Стварыць",
"lists.create_list": "Стварыць спіс",
"lists.delete": "Выдаліць спіс",
"lists.done": "Гатова",
"lists.edit": "Рэдагаваць спіс",
"lists.list_name": "Назва спіса",
"lists.new_list_name": "Назва новага спіса",
"lists.no_lists_yet": "Пакуль няма спісаў.",
"lists.no_members_yet": "Пакуль няма ўдзельнікаў.",
"lists.no_results_found": "Нічога не знойдзена.",
"lists.remove_member": "Выдаліць",
"lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся",
"lists.replies_policy.list": "Удзельнікі гэтага спісу",
@ -460,7 +505,7 @@
"lists.save": "Захаваць",
"lists.search": "Пошук",
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
"loading_indicator.label": "Загрузка…",
"loading_indicator.label": "Ідзе загрузка…",
"media_gallery.hide": "Схаваць",
"moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны таму што вы перанесены на {movedToAccount}.",
"mute_modal.hide_from_notifications": "Схаваць з апавяшчэнняў",
@ -473,7 +518,10 @@
"mute_modal.you_wont_see_mentions": "Вы не ўбачыце паведамленняў са згадваннем карыстальніка.",
"mute_modal.you_wont_see_posts": "Карыстальнік па-ранейшаму будзе бачыць вашыя паведамленні, але вы не будзеце паведамленні карыстальніка.",
"navigation_bar.about": "Пра нас",
"navigation_bar.account_settings": "Пароль і бяспека",
"navigation_bar.administration": "Адміністрацыя",
"navigation_bar.advanced_interface": "Адкрыць у пашыраным вэб-інтэрфейсе",
"navigation_bar.automated_deletion": "Аўтаматычнае выдаленне допісаў",
"navigation_bar.blocks": "Заблакіраваныя карыстальнікі",
"navigation_bar.bookmarks": "Закладкі",
"navigation_bar.direct": "Асабістыя згадванні",
@ -483,13 +531,21 @@
"navigation_bar.follow_requests": "Запыты на падпіску",
"navigation_bar.followed_tags": "Падпіскі",
"navigation_bar.follows_and_followers": "Падпіскі і падпісчыкі",
"navigation_bar.import_export": "Імпарт і экспарт",
"navigation_bar.lists": "Спісы",
"navigation_bar.live_feed_local": "Жывая стужка (лакальная)",
"navigation_bar.live_feed_public": "Жывая стужка (публічная)",
"navigation_bar.logout": "Выйсці",
"navigation_bar.moderation": "Мадэрацыя",
"navigation_bar.more": "Больш",
"navigation_bar.mutes": "Ігнараваныя карыстальнікі",
"navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
"navigation_bar.preferences": "Налады",
"navigation_bar.privacy_and_reach": "Прыватнасць і пошук",
"navigation_bar.search": "Пошук",
"navigation_bar.search_trends": "Пошук / Трэндавае",
"navigation_panel.collapse_followed_tags": "Згарнуць меню падпісак на хэштэгі",
"navigation_panel.collapse_lists": "Згарнуць меню спісаў",
"not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.",
"notification.admin.report": "{name} паскардзіўся на {target}",
"notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}",
@ -497,7 +553,10 @@
"notification.admin.report_statuses": "{name} паскардзіўся на {target} з прычыны {category}",
"notification.admin.report_statuses_other": "{name} паскардзіўся на {target}",
"notification.admin.sign_up": "{name} зарэгістраваўся",
"notification.annual_report.view": "Перайсці да #Wrapstodon",
"notification.favourite": "Ваш допіс упадабаны {name}",
"notification.favourite_pm": "Ваша асабістае згадванне ўпадабана {name}",
"notification.favourite_pm.name_and_others_with_link": "{name} і <a>{count, plural, one {# іншы} few {# іншыя} many {# іншых} other {# іншых}}</a> ўпадабалі ваша асабістае згадванне",
"notification.follow": "{name} падпісаўся на вас",
"notification.follow_request": "{name} адправіў запыт на падпіску",
"notification.follow_request.name_and_others": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} запыталіся падпісацца на вас",
@ -508,7 +567,7 @@
"notification.mention": "Згадванне",
"notification.mentioned_you": "{name} згадаў вас",
"notification.moderation-warning.learn_more": "Даведацца больш",
"notification.moderation_warning": "Вы атрымалі папярэджанне аб мадэрацыі",
"notification.moderation_warning": "Вы атрымалі папярэджанне ад мадэратараў",
"notification.moderation_warning.action_delete_statuses": "Некаторыя вашыя допісы былі выдаленыя.",
"notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.",
@ -539,7 +598,7 @@
"notification_requests.title": "Адфільтраваныя апавяшчэнні",
"notification_requests.view": "Прагляд апавяшчэнняў",
"notifications.clear": "Ачысціць апавяшчэнні",
"notifications.clear_confirmation": "Вы ўпэўнены, што жадаеце назаўсёды сцерці ўсё паведамленні?",
"notifications.clear_confirmation": "Вы ўпэўнены, што хочаце назаўсёды сцерці ўсе свае паведамленні?",
"notifications.clear_title": "Ачысціць апавяшчэнні?",
"notifications.column_settings.admin.report": "Новыя скаргі:",
"notifications.column_settings.admin.sign_up": "Новыя ўваходы:",
@ -549,7 +608,7 @@
"notifications.column_settings.filter_bar.category": "Панэль хуткай фільтрацыі",
"notifications.column_settings.follow": "Новыя падпісчыкі:",
"notifications.column_settings.follow_request": "Новыя запыты на падпіску:",
"notifications.column_settings.group": "Аб'яднаць апавяшчэнні ад падпісчыкаў",
"notifications.column_settings.group": "Абяднаць апавяшчэнні ад падпісчыкаў",
"notifications.column_settings.mention": "Згадванні:",
"notifications.column_settings.poll": "Вынікі апытання:",
"notifications.column_settings.push": "Push-апавяшчэнні",
@ -571,13 +630,13 @@
"notifications.group": "{count} Апавяшчэнняў",
"notifications.mark_as_read": "Пазначыць усе апавяшчэнні як прачытаныя",
"notifications.permission_denied": "Апавяшчэнні на працоўным стале недаступныя з-за папярэдне адхіленага запыта праў браўзера",
"notifications.permission_denied_alert": "Апавяшчэнні на працоўным стале не могуць быць уключаныя, з-за таго што запыт браўзера быў адхілены",
"notifications.permission_denied_alert": "З-за таго, што запыт браўзера быў раней адхілены, немагчыма ўключыць апавяшчэнні на працоўным стале",
"notifications.permission_required": "Апавяшчэнні на працоўным стале недаступныя, з-за таго што неабходны дазвол не быў дадзены.",
"notifications.policy.accept": "Прыняць",
"notifications.policy.accept_hint": "Паказваць у апавяшчэннях",
"notifications.policy.drop": "Iгнараваць",
"notifications.policy.filter": "Фільтраваць",
"notifications.policy.filter_limited_accounts_title": "Абмежаваныя ўліковыя запісы",
"notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй",
"notifications.policy.filter_new_accounts.hint": "Створаныя на працягу {days, plural, one {апошняга # дня} few {апошніх # дзён} many {апошніх # дзён} other {апошняй # дня}}",
"notifications.policy.filter_new_accounts_title": "Новыя ўліковыя запісы",
"notifications.policy.filter_not_followers_hint": "Уключаючы людзей, якія падпісаны на вас менш, чым {days, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}",
@ -594,13 +653,13 @@
"onboarding.follows.done": "Гатова",
"onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыце спробу пазней.",
"onboarding.follows.search": "Пошук",
"onboarding.follows.title": "Падпішыцеся каб пачаць",
"onboarding.follows.title": "Падпішыцеся на некага, каб пачаць",
"onboarding.profile.discoverable": "Зрабіць мой профіль бачным",
"onboarding.profile.discoverable_hint": "Калі вы звяртаецеся да адкрытасці на Mastodon, вашы паведамленні могуць з'яўляцца ў выніках пошуку і тэндэнцый, а ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.",
"onboarding.profile.display_name": "Бачнае імя",
"onboarding.profile.display_name_hint": "Ваша поўнае імя або ваш псеўданім…",
"onboarding.profile.note": "Біяграфія",
"onboarding.profile.note_hint": "Вы можаце @згадаць іншых людзей або выкарыстоўваць #хэштэгі…",
"onboarding.profile.note_hint": "Вы можаце @згадваць іншых людзей або выкарыстоўваць #хэштэгі…",
"onboarding.profile.save_and_continue": "Захаваць і працягнуць",
"onboarding.profile.title": "Налады профілю",
"onboarding.profile.upload_avatar": "Загрузіць фота профілю",
@ -620,6 +679,7 @@
"poll_button.remove_poll": "Выдаліць апытанне",
"privacy.change": "Змяніць прыватнасць допісу",
"privacy.direct.long": "Усе згаданыя ў допісе",
"privacy.direct.short": "Асабістае згадванне",
"privacy.private.long": "Толькі вашыя падпісчыкі",
"privacy.private.short": "Падпісчыкі",
"privacy.public.long": "Усе, хто ёсць і каго няма ў Mastodon",
@ -629,10 +689,10 @@
"privacy.unlisted.short": "Ціхі публічны",
"privacy_policy.last_updated": "Адноўлена {date}",
"privacy_policy.title": "Палітыка канфідэнцыйнасці",
"recommended": "Рэкамендуем",
"recommended": "Рэкамендаванае",
"refresh": "Абнавiць",
"regeneration_indicator.please_stand_by": "Калі ласка, пачакайце.",
"regeneration_indicator.preparing_your_home_feed": "Рыхтуем вашую стужку…",
"regeneration_indicator.please_stand_by": "Пачакайце.",
"regeneration_indicator.preparing_your_home_feed": "Рыхтуем вашу галоўную стужку…",
"relative_time.days": "{number} д",
"relative_time.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}} таму",
"relative_time.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}} таму",
@ -649,7 +709,7 @@
"reply_indicator.poll": "Апытанне",
"report.block": "Заблакіраваць",
"report.block_explanation": "Вы перастанеце бачыць допісы гэтага карыстальніка. Ён не зможа сачыць за вамі і бачыць вашы допісы. Ён зможа зразумець, што яго заблакіравалі.",
"report.categories.legal": "Права",
"report.categories.legal": "Звязанае з правам",
"report.categories.other": "Іншае",
"report.categories.spam": "Спам",
"report.categories.violation": "Змест парушае адно ці некалькі правілаў сервера",
@ -716,8 +776,8 @@
"search_results.accounts": "Профілі",
"search_results.all": "Усё",
"search_results.hashtags": "Хэштэгі",
"search_results.no_results": "Анічога ня знойдзена.",
"search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або гэштаґі.",
"search_results.no_results": "Няма вынікаў.",
"search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або хэштэгі.",
"search_results.see_all": "Праглядзець усе",
"search_results.statuses": "Допісы",
"search_results.title": "Шукаць \"{q}\"",
@ -738,7 +798,7 @@
"status.bookmark": "Дадаць закладку",
"status.cancel_reblog_private": "Прыбраць",
"status.cannot_reblog": "Гэты пост нельга пашырыць",
"status.continued_thread": "Працяг тэмы",
"status.continued_thread": "Працяг ланцужка",
"status.copy": "Скапіраваць спасылку на допіс",
"status.delete": "Выдаліць",
"status.detailed_status": "Дэтальны агляд размовы",
@ -763,6 +823,7 @@
"status.mute_conversation": "Ігнараваць размову",
"status.open": "Разгарнуць гэты допіс",
"status.pin": "Замацаваць у профілі",
"status.quote_post_author": "Допіс карыстальніка @{name}",
"status.read_more": "Чытаць болей",
"status.reblog": "Пашырыць",
"status.reblog_private": "Пашырыць з першапачатковай бачнасцю",
@ -771,7 +832,7 @@
"status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.",
"status.redraft": "Выдаліць і паправіць",
"status.remove_bookmark": "Выдаліць закладку",
"status.replied_in_thread": "Адказаў у тэме",
"status.replied_in_thread": "Адказаў у ланцужку",
"status.replied_to": "Адказаў {name}",
"status.reply": "Адказаць",
"status.replyAll": "Адказаць у ланцугу",
@ -791,8 +852,11 @@
"subscribed_languages.save": "Захаваць змены",
"subscribed_languages.target": "Змяніць мовы падпіскі для {target}",
"tabs_bar.home": "Галоўная",
"tabs_bar.menu": "Меню",
"tabs_bar.notifications": "Апавяшчэнні",
"terms_of_service.title": "Умовы абслугоўваньня",
"tabs_bar.publish": "Новы допіс",
"tabs_bar.search": "Пошук",
"terms_of_service.title": "Умовы выкарыстання",
"time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}",
"time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}",
"time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}",
@ -818,6 +882,12 @@
"video.expand": "Разгарнуць відэа",
"video.fullscreen": "Увесь экран",
"video.hide": "Схаваць відэа",
"video.mute": "Выключыць гук",
"video.pause": "Паўза",
"video.play": "Прайграць"
"video.play": "Прайграць",
"video.skip_backward": "Праматаць назад",
"video.skip_forward": "Праматаць уперад",
"video.unmute": "Уключыць гук",
"video.volume_down": "Паменшыць гучнасць",
"video.volume_up": "Павялічыць гучнасць"
}

View File

@ -188,7 +188,7 @@
"community.column_settings.remote_only": "Само отдалечено",
"compose.language.change": "Смяна на езика",
"compose.language.search": "Търсене на езици...",
"compose.published.body": "Публикувана публикация.",
"compose.published.body": "Публикувано.",
"compose.published.open": "Отваряне",
"compose.saved.body": "Запазена публикация.",
"compose_form.direct_message_warning_learn_more": "Още информация",

View File

@ -219,6 +219,13 @@
"confirmations.delete_list.confirm": "Elimina",
"confirmations.delete_list.message": "Segur que vols suprimir permanentment aquesta llista?",
"confirmations.delete_list.title": "Eliminar la llista?",
"confirmations.discard_draft.confirm": "Descarta i continua",
"confirmations.discard_draft.edit.cancel": "Continua l'edició",
"confirmations.discard_draft.edit.message": "Continuar descartarà tots els canvis que hàgiu fet a la publicació que editeu.",
"confirmations.discard_draft.edit.title": "Descartar els canvis a la publicació?",
"confirmations.discard_draft.post.cancel": "Reprendre l'esborrany",
"confirmations.discard_draft.post.message": "Continuar descartarà la publicació que escriviu.",
"confirmations.discard_draft.post.title": "Descartar l'esborrany?",
"confirmations.discard_edit_media.confirm": "Descarta",
"confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?",
"confirmations.follow_to_list.confirm": "Seguir i afegir a una llista",
@ -556,6 +563,8 @@
"navigation_bar.follows_and_followers": "Seguint i seguidors",
"navigation_bar.import_export": "Importació i exportació",
"navigation_bar.lists": "Llistes",
"navigation_bar.live_feed_local": "Canal en directe (local)",
"navigation_bar.live_feed_public": "Canal en directe (públic)",
"navigation_bar.logout": "Tanca la sessió",
"navigation_bar.moderation": "Moderació",
"navigation_bar.more": "Més",
@ -564,7 +573,10 @@
"navigation_bar.preferences": "Preferències",
"navigation_bar.privacy_and_reach": "Privacitat i abast",
"navigation_bar.search": "Cerca",
"navigation_bar.search_trends": "Cerca / En tendència",
"navigation_panel.collapse_followed_tags": "Comprimeix el menú d'etiquetes seguides",
"navigation_panel.collapse_lists": "Tanca el menú",
"navigation_panel.expand_followed_tags": "Expandeix el menú d'etiquetes seguides",
"navigation_panel.expand_lists": "Expandeix el menú",
"not_signed_in_indicator.not_signed_in": "Cal que iniciïs la sessió per a accedir a aquest recurs.",
"notification.admin.report": "{name} ha reportat {target}",
@ -792,6 +804,7 @@
"report_notification.categories.violation": "Violació de norma",
"report_notification.categories.violation_sentence": "violació de normes",
"report_notification.open": "Obre l'informe",
"search.clear": "Esborra la cerca",
"search.no_recent_searches": "No hi ha cerques recents",
"search.placeholder": "Cerca",
"search.quick_action.account_search": "Perfils coincidint amb {x}",

View File

@ -62,7 +62,7 @@
"account.mute_notifications_short": "Sluk for notifikationer",
"account.mute_short": "Skjul",
"account.muted": "Skjult",
"account.muting": "Tavsgørelse",
"account.muting": "Skjuler",
"account.mutual": "I følger hinanden",
"account.no_bio": "Ingen beskrivelse til rådighed.",
"account.open_original_page": "Åbn oprindelig side",
@ -350,7 +350,7 @@
"filter_modal.added.context_mismatch_title": "Kontekstmisforhold!",
"filter_modal.added.expired_explanation": "Denne filterkategori er udløbet. Ændr dens udløbsdato, for at anvende den.",
"filter_modal.added.expired_title": "Udløbet filter!",
"filter_modal.added.review_and_configure": "Gå til {settings_link} for at gennemse og yderligere opsætte denne filterkategori.",
"filter_modal.added.review_and_configure": "Gå til {settings_link} for at gennemgå og konfigurere denne filterkategori yderligere.",
"filter_modal.added.review_and_configure_title": "Filterindstillinger",
"filter_modal.added.settings_link": "indstillingsside",
"filter_modal.added.short_explanation": "Dette indlæg er nu føjet til følgende filterkategori: {title}.",
@ -386,7 +386,7 @@
"follow_suggestions.similar_to_recently_followed_longer": "Minder om profiler, du har fulgt for nylig",
"follow_suggestions.view_all": "Vis alle",
"follow_suggestions.who_to_follow": "Hvem, som skal følges",
"followed_tags": "Hashtag, som følges",
"followed_tags": "Hashtags, som følges",
"footer.about": "Om",
"footer.directory": "Profiloversigt",
"footer.get_app": "Hent appen",
@ -560,7 +560,7 @@
"navigation_bar.favourites": "Favoritter",
"navigation_bar.filters": "Skjulte ord",
"navigation_bar.follow_requests": "Følgeanmodninger",
"navigation_bar.followed_tags": "Hashtag, som følges",
"navigation_bar.followed_tags": "Hashtags, som følges",
"navigation_bar.follows_and_followers": "Følges og følgere",
"navigation_bar.import_export": "Import og eksport",
"navigation_bar.lists": "Lister",
@ -572,7 +572,7 @@
"navigation_bar.mutes": "Skjulte brugere",
"navigation_bar.opened_in_classic_interface": "Indlæg, konti og visse andre sider åbnes som standard i den klassiske webgrænseflade.",
"navigation_bar.preferences": "Præferencer",
"navigation_bar.privacy_and_reach": "Fortrolighed og udbredelse",
"navigation_bar.privacy_and_reach": "Fortrolighed og rækkevidde",
"navigation_bar.search": "Søg",
"navigation_bar.search_trends": "Søg/Trender",
"navigation_panel.collapse_followed_tags": "Sammenfold menuen Fulgte hashtags",

View File

@ -4,7 +4,7 @@
"about.default_locale": "Προεπιλογή",
"about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Αιτιολογία μη διαθέσιμη",
"about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σού επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.",
"about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σου επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.",
"about.domain_blocks.silenced.explanation": "Συνήθως δε θα βλέπεις προφίλ και περιεχόμενο απ' αυτόν τον διακομιστή, εκτός αν κάνεις συγκεκριμένη αναζήτηση ή επιλέξεις να τον ακολουθήσεις.",
"about.domain_blocks.silenced.title": "Περιορισμένος",
"about.domain_blocks.suspended.explanation": "Τα δεδομένα αυτού του διακομιστή, δε θα επεξεργάζονται, δε θα αποθηκεύονται και δε θα ανταλλάσσονται, καθιστώντας οποιαδήποτε αλληλεπίδραση ή επικοινωνία με χρήστες από αυτόν το διακομιστή αδύνατη.",
@ -65,7 +65,7 @@
"account.muting": "Σίγαση",
"account.mutual": "Ακολουθείτε ο ένας τον άλλο",
"account.no_bio": "Δεν υπάρχει περιγραφή.",
"account.open_original_page": "Ανοικτό",
"account.open_original_page": "Άνοιγμα αυθεντικής σελίδας",
"account.posts": "Τουτ",
"account.posts_with_replies": "Τουτ και απαντήσεις",
"account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους",
@ -194,9 +194,9 @@
"compose.saved.body": "Η ανάρτηση αποθηκεύτηκε.",
"compose_form.direct_message_warning_learn_more": "Μάθε περισσότερα",
"compose_form.encryption_warning": "Οι δημοσιεύσεις στο Mastodon δεν είναι κρυπτογραφημένες από άκρο σε άκρο. Μη μοιράζεσαι ευαίσθητες πληροφορίες μέσω του Mastodon.",
"compose_form.hashtag_warning": "Αυτή η δημοσίευση δεν θα εμφανίζεται κάτω από οποιαδήποτε ετικέτα καθώς δεν είναι δημόσια. Μόνο οι δημόσιες δημοσιεύσεις μπορούν να αναζητηθούν με ετικέτα.",
"compose_form.hashtag_warning": "Αυτή η ανάρτηση δεν θα εμφανίζεται κάτω από οποιαδήποτε ετικέτα καθώς δεν είναι δημόσια. Μόνο οι δημόσιες αναρτήσεις μπορούν να αναζητηθούν με ετικέτα.",
"compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σου προς τους ακολούθους σου.",
"compose_form.lock_disclaimer.lock": "κλειδωμένο",
"compose_form.lock_disclaimer.lock": "κλειδωμένος",
"compose_form.placeholder": "Τι σκέφτεσαι;",
"compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
"compose_form.poll.multiple": "Πολλαπλή επιλογή",
@ -214,13 +214,18 @@
"confirmation_modal.cancel": "Άκυρο",
"confirmations.block.confirm": "Αποκλεισμός",
"confirmations.delete.confirm": "Διαγραφή",
"confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή τη δημοσίευση;",
"confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή την ανάρτηση;",
"confirmations.delete.title": "Διαγραφή ανάρτησης;",
"confirmations.delete_list.confirm": "Διαγραφή",
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
"confirmations.delete_list.title": "Διαγραφή λίστας;",
"confirmations.discard_draft.confirm": "Απόρριψη και συνέχεια",
"confirmations.discard_draft.edit.cancel": "Συνέχιση επεξεργασίας",
"confirmations.discard_draft.edit.message": "Συνεχίζοντας θα απορρίψει τυχόν αλλαγές που έχετε κάνει στην ανάρτηση που επεξεργάζεστε.",
"confirmations.discard_draft.edit.title": "Απόρριψη αλλαγών στην ανάρτηση σας;",
"confirmations.discard_draft.post.cancel": "Συνέχιση προχείρου",
"confirmations.discard_draft.post.message": "Συνεχίζοντας θα απορρίψει την ανάρτηση που συνθέτετε.",
"confirmations.discard_draft.post.title": "Απόρριψη της πρόχειρης ανάρτησης σας;",
"confirmations.discard_edit_media.confirm": "Απόρριψη",
"confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;",
"confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα",
@ -232,7 +237,7 @@
"confirmations.missing_alt_text.confirm": "Προσθήκη εναλ κειμένου",
"confirmations.missing_alt_text.message": "Η ανάρτησή σου περιέχει πολυμέσα χωρίς εναλλακτικό κείμενο. Η προσθήκη περιγραφών βοηθά να γίνει το περιεχόμενό σου προσβάσιμο σε περισσότερους ανθρώπους.",
"confirmations.missing_alt_text.secondary": "Δημοσίευση όπως και να ΄χει",
"confirmations.missing_alt_text.title": "Προσθήκη alt κειμένου;",
"confirmations.missing_alt_text.title": "Προσθήκη εναλλακτικού κειμένου;",
"confirmations.mute.confirm": "Αποσιώπηση",
"confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
"confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
@ -271,7 +276,7 @@
"domain_block_modal.you_will_lose_num_followers": "Θα χάσετε {followersCount, plural, one {{followersCountDisplay} ακόλουθο} other {{followersCountDisplay} ακόλουθους}} και {followingCount, plural, one {{followingCountDisplay} άτομο που ακολουθείτε} other {{followingCountDisplay} άτομα που ακολουθείτε}}.",
"domain_block_modal.you_will_lose_relationships": "Θα χάσετε όλους τους ακόλουθους και τα άτομα που ακολουθείτε από αυτόν τον διακομιστή.",
"domain_block_modal.you_wont_see_posts": "Δεν θα βλέπεις αναρτήσεις ή ειδοποιήσεις από χρήστες σε αυτόν το διακομιστή.",
"domain_pill.activitypub_lets_connect": "Σού επιτρέπει να συνδεθείς και να αλληλεπιδράσεις με τους ανθρώπους όχι μόνο στο Mastodon, αλλά και σε διαφορετικές κοινωνικές εφαρμογές.",
"domain_pill.activitypub_lets_connect": "Σου επιτρέπει να συνδεθείς και να αλληλεπιδράσεις με τους ανθρώπους όχι μόνο στο Mastodon, αλλά και σε διαφορετικές κοινωνικές εφαρμογές.",
"domain_pill.activitypub_like_language": "Το ActivityPub είναι σαν τη γλώσσα Mastodon μιλάει με άλλα κοινωνικά δίκτυα.",
"domain_pill.server": "Διακομιστής",
"domain_pill.their_handle": "Το πλήρες όνομα χρήστη:",
@ -317,7 +322,7 @@
"empty_column.favourited_statuses": "Δεν έχεις καμία αγαπημένη ανάρτηση ακόμα. Μόλις αγαπήσεις κάποια, θα εμφανιστεί εδώ.",
"empty_column.favourites": "Κανείς δεν έχει αγαπήσει αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.",
"empty_column.follow_requests": "Δεν έχεις κανένα αίτημα παρακολούθησης ακόμα. Μόλις λάβεις κάποιο, θα εμφανιστεί εδώ.",
"empty_column.followed_tags": "Δεν έχετε παρακολουθήσει ακόμα καμία ετικέτα. Όταν το κάνετε, θα εμφανιστούν εδώ.",
"empty_column.followed_tags": "Δεν έχετε ακολουθήσει ακόμα καμία ετικέτα. Όταν το κάνετε, θα εμφανιστούν εδώ.",
"empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.",
"empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.",
"empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.",
@ -381,7 +386,7 @@
"follow_suggestions.similar_to_recently_followed_longer": "Παρόμοια με προφίλ που ακολούθησες πρόσφατα",
"follow_suggestions.view_all": "Εμφάνιση όλων",
"follow_suggestions.who_to_follow": "Ποιον να ακολουθήσεις",
"followed_tags": "Ετικέτες που ακολουθούνται",
"followed_tags": "Ακολουθούμενες ετικέτες",
"footer.about": "Σχετικά με",
"footer.directory": "Κατάλογος προφίλ",
"footer.get_app": "Αποκτήστε την εφαρμογή",
@ -412,7 +417,7 @@
"hashtag.mute": "Σίγαση #{hashtag}",
"hashtag.unfeature": "Να μην αναδεικνύεται στο προφίλ",
"hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας",
"hashtags.and_other": "…και {count, plural, one {}other {# ακόμη}}",
"hashtags.and_other": "…και {count, plural, other {# ακόμη}}",
"hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.",
"hints.profiles.follows_may_be_missing": "Άτομα που ακολουθούνται μπορεί να λείπουν απ' αυτό το προφίλ.",
"hints.profiles.posts_may_be_missing": "Κάποιες αναρτήσεις από αυτό το προφίλ μπορεί να λείπουν.",
@ -420,7 +425,7 @@
"hints.profiles.see_more_follows": "Δες περισσότερα άτομα που ακολουθούνται στο {domain}",
"hints.profiles.see_more_posts": "Δες περισσότερες αναρτήσεις στο {domain}",
"hints.threads.replies_may_be_missing": "Απαντήσεις από άλλους διακομιστές μπορεί να λείπουν.",
"hints.threads.see_more": "Δες περισσότερες αναρτήσεις στο {domain}",
"hints.threads.see_more": "Δες περισσότερες απαντήσεις στο {domain}",
"home.column_settings.show_quotes": "Εμφάνιση παραθεμάτων",
"home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
"home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
@ -451,7 +456,7 @@
"interaction_modal.no_account_yet": "Δεν έχεις ακόμη λογαριασμό;",
"interaction_modal.on_another_server": "Σε διαφορετικό διακομιστή",
"interaction_modal.on_this_server": "Σε αυτόν τον διακομιστή",
"interaction_modal.title.favourite": "Favorite {name}'s post\nΠροτίμησε την ανάρτηση της/του {name}",
"interaction_modal.title.favourite": "Αγάπησε την ανάρτηση του χρήστη {name}",
"interaction_modal.title.follow": "Ακολούθησε {name}",
"interaction_modal.title.reblog": "Ενίσχυσε την ανάρτηση του {name}",
"interaction_modal.title.reply": "Απάντηση στην ανάρτηση του {name}",
@ -463,13 +468,13 @@
"keyboard_shortcuts.back": "Μετάβαση πίσω",
"keyboard_shortcuts.blocked": "Άνοιγμα λίστας αποκλεισμένων χρηστών",
"keyboard_shortcuts.boost": "Ενίσχυση ανάρτησης",
"keyboard_shortcuts.column": "Στήλη εστίασης",
"keyboard_shortcuts.compose": "Περιοχή συγγραφής κειμένου εστίασης",
"keyboard_shortcuts.column": "Εστίαση στη στήλη",
"keyboard_shortcuts.compose": "Εστίαση στην περιοχή συγγραφής κειμένου",
"keyboard_shortcuts.description": "Περιγραφή",
"keyboard_shortcuts.direct": "για το άνοιγμα της στήλης ιδιωτικών επισημάνσεων",
"keyboard_shortcuts.down": "κίνηση προς τα κάτω στη λίστα",
"keyboard_shortcuts.enter": "Εμφάνιση ανάρτησης",
"keyboard_shortcuts.favourite": "Αγαπημένη δημοσίευση",
"keyboard_shortcuts.down": "Μετακίνηση προς τα κάτω στη λίστα",
"keyboard_shortcuts.enter": "Άνοιγμα ανάρτησης",
"keyboard_shortcuts.favourite": "Αγάπησε την ανάρτηση",
"keyboard_shortcuts.favourites": "Άνοιγμα λίστας αγαπημένων",
"keyboard_shortcuts.federated": "Άνοιγμα ροής συναλλαγών",
"keyboard_shortcuts.heading": "Συντομεύσεις πληκτρολογίου",
@ -486,13 +491,13 @@
"keyboard_shortcuts.profile": "Άνοιγμα προφίλ συγγραφέα",
"keyboard_shortcuts.reply": "Απάντηση στην ανάρτηση",
"keyboard_shortcuts.requests": "Άνοιγμα λίστας αιτημάτων ακολούθησης",
"keyboard_shortcuts.search": "Γραμμή αναζήτησης εστίασης",
"keyboard_shortcuts.search": "Εστίαση στη γραμμή αναζήτησης",
"keyboard_shortcuts.spoilers": "Εμφάνιση/απόκρυψη πεδίου CW",
"keyboard_shortcuts.start": "Άνοιγμα της στήλης \"Ας ξεκινήσουμε\"",
"keyboard_shortcuts.toggle_hidden": "Εμφάνιση/απόκρυψη κειμένου πίσω από το CW",
"keyboard_shortcuts.toggle_sensitivity": "Εμφάνιση/απόκρυψη πολυμέσων",
"keyboard_shortcuts.toot": "Δημιουργία νέας ανάρτησης",
"keyboard_shortcuts.translate": "να μεταφράσει μια δημοσίευση",
"keyboard_shortcuts.translate": "για να μεταφραστεί μια ανάρτηση",
"keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης",
"keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα",
"lightbox.close": "Κλείσιμο",
@ -555,10 +560,12 @@
"navigation_bar.favourites": "Αγαπημένα",
"navigation_bar.filters": "Αποσιωπημένες λέξεις",
"navigation_bar.follow_requests": "Αιτήματα ακολούθησης",
"navigation_bar.followed_tags": "Ετικέτες που ακολουθούνται",
"navigation_bar.followed_tags": "Ακολουθούμενες ετικέτες",
"navigation_bar.follows_and_followers": "Ακολουθείς και σε ακολουθούν",
"navigation_bar.import_export": "Εισαγωγή και εξαγωγή",
"navigation_bar.lists": "Λίστες",
"navigation_bar.live_feed_local": "Ζωντανή ροή (τοπική)",
"navigation_bar.live_feed_public": "Ζωντανή ροή (δημόσια)",
"navigation_bar.logout": "Αποσύνδεση",
"navigation_bar.moderation": "Συντονισμός",
"navigation_bar.more": "Περισσότερα",
@ -568,9 +575,9 @@
"navigation_bar.privacy_and_reach": "Ιδιωτικότητα και προσιτότητα",
"navigation_bar.search": "Αναζήτηση",
"navigation_bar.search_trends": "Αναζήτηση / Τάσεις",
"navigation_panel.collapse_followed_tags": "Σύμπτυξη μενού ετικετών που ακολουθούνται",
"navigation_panel.collapse_followed_tags": "Σύμπτυξη μενού ετικετών που ακολουθείτε",
"navigation_panel.collapse_lists": "Σύμπτυξη μενού λίστας",
"navigation_panel.expand_followed_tags": "Επέκταση μενού ετικετών που ακολουθούνται",
"navigation_panel.expand_followed_tags": "Επέκταση μενού ετικετών που ακολουθείτε",
"navigation_panel.expand_lists": "Επέκταση μενού λίστας",
"not_signed_in_indicator.not_signed_in": "Πρέπει να συνδεθείς για να αποκτήσεις πρόσβαση σε αυτόν τον πόρο.",
"notification.admin.report": "Ο/Η {name} ανέφερε τον {target}",
@ -582,11 +589,11 @@
"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} αγάπησε την ανάρτηση σου",
"notification.favourite.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ανάρτησή σου",
"notification.favourite_pm": "Ο χρήστης {name} αγάπησε την ιδιωτική σου επισήμανση",
"notification.favourite_pm.name_and_others_with_link": "Ο χρήστης {name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ιδωτική σου επισήμανση",
"notification.follow": "Ο/Η {name} σε ακολούθησε",
"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 {# άλλοι}} ζήτησαν να σε ακολουθήσουν",
@ -719,7 +726,7 @@
"poll_button.add_poll": "Προσθήκη δημοσκόπησης",
"poll_button.remove_poll": "Αφαίρεση δημοσκόπησης",
"privacy.change": "Προσαρμογή ιδιωτικότητας ανάρτησης",
"privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στη δημοσίευση",
"privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στην ανάρτηση",
"privacy.direct.short": "Ιδιωτική επισήμανση",
"privacy.private.long": "Μόνο οι ακόλουθοί σας",
"privacy.private.short": "Ακόλουθοι",
@ -797,7 +804,7 @@
"report_notification.categories.spam_sentence": "ανεπιθύμητα",
"report_notification.categories.violation": "Παραβίαση κανόνα",
"report_notification.categories.violation_sentence": "παραβίαση κανόνα",
"report_notification.open": "Ανοιχτή αναφορά",
"report_notification.open": "Άνοιγμα αναφοράς",
"search.clear": "Εκκαθάριση αναζήτησης",
"search.no_recent_searches": "Καμία πρόσφατη αναζήτηση",
"search.placeholder": "Αναζήτηση",
@ -851,7 +858,7 @@
"status.edited_x_times": "Επεξεργάστηκε {count, plural, one {{count} φορά} other {{count} φορές}}",
"status.embed": "Απόκτηση κώδικα ενσωμάτωσης",
"status.favourite": "Αγαπημένα",
"status.favourites": "{count, plural, one {# αγαπημένο} other {# αγαπημένα}}",
"status.favourites": "{count, plural, one {αγαπημένο} other {αγαπημένα}}",
"status.filter": "Φιλτράρισμα αυτής της ανάρτησης",
"status.history.created": "{name} δημιούργησε στις {date}",
"status.history.edited": "{name} επεξεργάστηκε στις {date}",
@ -876,7 +883,7 @@
"status.reblog": "Ενίσχυση",
"status.reblog_private": "Ενίσχυση με αρχική ορατότητα",
"status.reblogged_by": "{name} προώθησε",
"status.reblogs": "{count, plural, one {# ενίσχυση} other {# ενισχύσεις}}",
"status.reblogs": "{count, plural, one {ενίσχυση} other {ενισχύσεις}}",
"status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.",
"status.redraft": "Σβήσε & ξαναγράψε",
"status.remove_bookmark": "Αφαίρεση σελιδοδείκτη",
@ -888,8 +895,8 @@
"status.report": "Αναφορά @{name}",
"status.sensitive_warning": "Ευαίσθητο περιεχόμενο",
"status.share": "Κοινοποίηση",
"status.show_less_all": "Δείξε λιγότερα για όλα",
"status.show_more_all": "Δείξε περισσότερα για όλα",
"status.show_less_all": "Δείξε λιγότερο για όλες",
"status.show_more_all": "Δείξε περισσότερο για όλες",
"status.show_original": "Εμφάνιση αρχικού",
"status.title.with_attachments": "{user} δημοσίευσε {attachmentCount, plural, one {ένα συνημμένο} other {{attachmentCount} συνημμένα}}",
"status.translate": "Μετάφραση",

View File

@ -219,6 +219,13 @@
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.title": "Delete list?",
"confirmations.discard_draft.confirm": "Discard and continue",
"confirmations.discard_draft.edit.cancel": "Resume editing",
"confirmations.discard_draft.edit.message": "Continuing will discard any changes you have made to the post you are currently editing.",
"confirmations.discard_draft.edit.title": "Discard changes to your post?",
"confirmations.discard_draft.post.cancel": "Resume draft",
"confirmations.discard_draft.post.message": "Continuing will discard the post you are currently composing.",
"confirmations.discard_draft.post.title": "Discard your draft post?",
"confirmations.discard_edit_media.confirm": "Discard",
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
"confirmations.follow_to_list.confirm": "Follow and add to list",
@ -330,6 +337,7 @@
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.report_issue": "Report issue",
"explore.suggested_follows": "People",
"explore.title": "Trending",
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
@ -541,8 +549,10 @@
"mute_modal.you_wont_see_mentions": "You won't see posts that mention them.",
"mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.",
"navigation_bar.about": "About",
"navigation_bar.account_settings": "Password and security",
"navigation_bar.administration": "Administration",
"navigation_bar.advanced_interface": "Open in advanced web interface",
"navigation_bar.automated_deletion": "Automated post deletion",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.bookmarks": "Bookmarks",
"navigation_bar.direct": "Private mentions",
@ -552,13 +562,23 @@
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.followed_tags": "Followed hashtags",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.import_export": "Import and export",
"navigation_bar.lists": "Lists",
"navigation_bar.live_feed_local": "Live feed (local)",
"navigation_bar.live_feed_public": "Live feed (public)",
"navigation_bar.logout": "Logout",
"navigation_bar.moderation": "Moderation",
"navigation_bar.more": "More",
"navigation_bar.mutes": "Muted users",
"navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.",
"navigation_bar.preferences": "Preferences",
"navigation_bar.privacy_and_reach": "Privacy and reach",
"navigation_bar.search": "Search",
"navigation_bar.search_trends": "Search / Trending",
"navigation_panel.collapse_followed_tags": "Collapse followed hashtags menu",
"navigation_panel.collapse_lists": "Collapse list menu",
"navigation_panel.expand_followed_tags": "Expand followed hashtags menu",
"navigation_panel.expand_lists": "Expand list menu",
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
"notification.admin.report": "{name} reported {target}",
"notification.admin.report_account": "{name} reported {count, plural, one {one post} other {# posts}} from {target} for {category}",
@ -785,6 +805,7 @@
"report_notification.categories.violation": "Rule violation",
"report_notification.categories.violation_sentence": "rule violation",
"report_notification.open": "Open report",
"search.clear": "Clear search",
"search.no_recent_searches": "No recent searches",
"search.placeholder": "Search",
"search.quick_action.account_search": "Profiles matching {x}",
@ -887,7 +908,10 @@
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"tabs_bar.home": "Home",
"tabs_bar.menu": "Menu",
"tabs_bar.notifications": "Notifications",
"tabs_bar.publish": "New Post",
"tabs_bar.search": "Search",
"terms_of_service.effective_as_of": "Effective as of {date}",
"terms_of_service.title": "Terms of Service",
"terms_of_service.upcoming_changes_on": "Upcoming changes on {date}",

View File

@ -564,8 +564,8 @@
"navigation_bar.follows_and_followers": "Cuentas seguidas y seguidores",
"navigation_bar.import_export": "Importación y exportación",
"navigation_bar.lists": "Listas",
"navigation_bar.live_feed_local": "Cronología local",
"navigation_bar.live_feed_public": "Cronología pública",
"navigation_bar.live_feed_local": "Línea temporal (local)",
"navigation_bar.live_feed_public": "Línea temporal (federada)",
"navigation_bar.logout": "Cerrar sesión",
"navigation_bar.moderation": "Moderación",
"navigation_bar.more": "Más",
@ -858,7 +858,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "Obtener código para insertar",
"status.favourite": "Marcar como favorito",
"status.favourites": "{count, plural, one {# vez marcado como favorito} other {# veces marcado como favorito}}",
"status.favourites": "{count, plural, one {vez marcado como favorito} other {veces marcado como favorito}}",
"status.filter": "Filtrar este mensaje",
"status.history.created": "Creado por {name}, {date}",
"status.history.edited": "Editado por {name}, {date}",

View File

@ -795,7 +795,7 @@
"report.thanks.title_actionable": "Gracias por informar, estudiaremos esto.",
"report.unfollow": "Dejar de seguir a @{name}",
"report.unfollow_explanation": "Estás siguiendo esta cuenta. Para dejar de ver sus publicaciones en tu página de inicio, deja de seguirla.",
"report_notification.attached_statuses": "{count, plural, one {{count} publicación} other {{count} publicaciones}} adjunta(s)",
"report_notification.attached_statuses": "{count, plural, one {{count} publicación adjunta} other {{count} publicaciones adjuntas}}",
"report_notification.categories.legal": "Legal",
"report_notification.categories.legal_sentence": "contenido ilegal",
"report_notification.categories.other": "Otros",

View File

@ -569,6 +569,7 @@
"notification.admin.sign_up.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiago} other {# erabiltzaile gehiago}} erregistratu dira",
"notification.favourite": "{name}(e)k zure bidalketa gogoko du",
"notification.favourite.name_and_others_with_link": "{name} eta <a>{count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}}</a> zure bidalketa gogoko dute",
"notification.favourite_pm": "{name}-ek zure aipamen pribatua gogokoetan jarri du",
"notification.follow": "{name}(e)k jarraitzen dizu",
"notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du",
"notification.follow_request.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zu jarraitzeko eskaera egin dute",
@ -902,5 +903,7 @@
"video.hide": "Ezkutatu bideoa",
"video.pause": "Pausatu",
"video.play": "Jo",
"video.unmute": "Soinua ezarri",
"video.volume_down": "Bolumena jaitsi",
"video.volume_up": "Bolumena Igo"
}

View File

@ -62,7 +62,7 @@
"account.mute_notifications_short": "خموشی آگاهی‌ها",
"account.mute_short": "خموشی",
"account.muted": "خموش",
"account.muting": "خموش کرده",
"account.muting": "خموشش کرده‌اید",
"account.mutual": "یکدیگر را پی می‌گیرید",
"account.no_bio": "شرحی فراهم نشده.",
"account.open_original_page": "گشودن صفحهٔ اصلی",
@ -129,14 +129,14 @@
"annual_report.summary.thanks": "سپاس که بخشی از ماستودون هستید!",
"attachments_list.unprocessed": "(پردازش نشده)",
"audio.hide": "نهفتن صدا",
"block_modal.remote_users_caveat": "ما از کارساز {domain} خواهیم خواست که به تصمیم شما احترام بگذارد. با این حال، تضمینی برای رعایت آن وجود ندارد زیرا برخی کارسازها ممکن است بلوک‌ها را به‌طور متفاوتی مدیریت کنند. فرسته‌های عمومی ممکن است همچنان برای کاربران که وارد نشده قابل مشاهده باشند.",
"block_modal.remote_users_caveat": "از کارساز {domain} خواهیم خواست که به تصمیمتان احترام بگذارد. با این حال تضمینی برای رعایتش وجود ندارد؛ زیرا برخی کارسازها ممکن است مسدودی را متفاوت مدیریت کنند. ممکن است فرسته‌های عمومی همچنان برای کاربران وارد نشده نمایان باشند.",
"block_modal.show_less": "نمایش کم‌تر",
"block_modal.show_more": "نمایش بیش‌تر",
"block_modal.they_cant_mention": "نمی‌توانند نامتان را برده یا پیتان بگیرند.",
"block_modal.they_cant_see_posts": "نمی‌توانند فرسته‌هایتان را دیده و فرسته‌هایشان را نمی‌بینید.",
"block_modal.they_will_know": "می‌توانند ببینند که مسدود شده‌اند.",
"block_modal.they_cant_mention": "نمی‌تواند نامتان را برده یا پیتان بگیرد.",
"block_modal.they_cant_see_posts": "نمی‌تواند فرسته‌هایتان را ببیند و فرسته‌هایش را نمی‌بینید.",
"block_modal.they_will_know": "می‌تواند ببینند که مسدود شده.",
"block_modal.title": "انسداد کاربر؟",
"block_modal.you_wont_see_mentions": "فرسته‌هایی که از اون نام برده را نخواهید دید.",
"block_modal.you_wont_see_mentions": "فرسته‌هایی که به او اشاره کرده‌اند را نخواهید دید.",
"boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
"boost_modal.reblog": "تقویت فرسته؟",
"boost_modal.undo_reblog": "ناتقویت فرسته؟",
@ -269,9 +269,9 @@
"dismissable_banner.public_timeline": "این‌ها جدیدترین فرسته‌های عمومی از افرادی روی وب اجتماعیند که اعضای {domain} پی می‌گیرندشان.",
"domain_block_modal.block": "انسداد کارساز",
"domain_block_modal.block_account_instead": "انسداد @{name} به جایش",
"domain_block_modal.they_can_interact_with_old_posts": "افزارد روی این کراساز می‌توانند با فرسته‌های قدیمیتان تعامل داشته باشند.",
"domain_block_modal.they_can_interact_with_old_posts": "افزارد روی این کارساز می‌توانند با فرسته‌های قدیمیتان تعامل داشته باشند.",
"domain_block_modal.they_cant_follow": "هیچ‌کسی از این کارساز نمی‌تواند پیتان بگیرد.",
"domain_block_modal.they_wont_know": "نخواهند دانست که مسدود شده‌اند.",
"domain_block_modal.they_wont_know": "نخواهد دانست که مسدود شده.",
"domain_block_modal.title": "انسداد دامنه؟",
"domain_block_modal.you_will_lose_num_followers": "شما {followersCount, plural, one {{followersCountDisplay} پی‌گیرنده} other {{followersCountDisplay} پی‌گیرنده}} و {followingCount, plural, one {{followingCountDisplay} فرد پی‌گرفته‌شده} other {{followingCountDisplay} فرد پی‌گرفته‌شده}} را از دست خواهید داد.",
"domain_block_modal.you_will_lose_relationships": "شما تمام پیگیرکنندگان و افرادی که از این کارساز پیگیری می‌کنید را از دست خواهید داد.",
@ -543,11 +543,11 @@
"mute_modal.hide_options": "گزینه‌های نهفتن",
"mute_modal.indefinite": "تا وقتی ناخموشش کنم",
"mute_modal.show_options": "نمایش گزینه‌ها",
"mute_modal.they_can_mention_and_follow": "می‌توانند به شما اشاره کرده و پیتان بگیرند، ولی نخواهید دیدشان.",
"mute_modal.they_wont_know": "نخواهند دانست که خموش شده‌اند.",
"mute_modal.they_can_mention_and_follow": "می‌تواند به شما اشاره کرده و پیتان بگیرد؛ ولی نخواهید دیدش.",
"mute_modal.they_wont_know": "نخواهد دانست که خموش شده.",
"mute_modal.title": "خموشی کاربر؟",
"mute_modal.you_wont_see_mentions": "فرسته‌هایی که به او اشاره کرده‌اند را نخواهید دید.",
"mute_modal.you_wont_see_posts": "هنوز می‌توانند فرسته‌هایتان را ببینند، ولی فرسته‌هایشان را نمی‌بینید.",
"mute_modal.you_wont_see_posts": "همچنان می‌تواند فرسته‌هایتان را ببینند؛ ولی فرسته‌هایش را نمی‌بینید.",
"navigation_bar.about": "درباره",
"navigation_bar.account_settings": "گذرواژه و امنیت",
"navigation_bar.administration": "مدیریت",
@ -687,7 +687,7 @@
"notifications.policy.filter_limited_accounts_title": "حساب‌های مدیریت شده",
"notifications.policy.filter_new_accounts.hint": "ساخته شده در {days, plural, one {یک} other {#}} روز اخیر",
"notifications.policy.filter_new_accounts_title": "حساب‌های جدید",
"notifications.policy.filter_not_followers_hint": "از جمله کسانی که کم‌تر از {days, plural, one {یک} other {#}} روز است پیتان می‌گیرند",
"notifications.policy.filter_not_followers_hint": "از جمله کسانی که کم‌تر از {days, plural, one {یک} other {#}} روز است پیتان می‌گیرند",
"notifications.policy.filter_not_followers_title": "کسانی که شما را دنبال میکنند",
"notifications.policy.filter_not_following_hint": "تا به صورت دستی تأییدشان کنید",
"notifications.policy.filter_not_following_title": "کسانی که پی نمی‌گیرید",
@ -756,7 +756,7 @@
"reply_indicator.cancel": "لغو",
"reply_indicator.poll": "نظرسنجی",
"report.block": "انسداد",
"report.block_explanation": "شما فرسته‌هایشان را نخواهید دید. آن‌ها نمی‌توانند فرسته‌هایتان را ببینند یا شما را پی‌بگیرند. آنها می‌توانند بگویند که مسدود شده‌اند.",
"report.block_explanation": "فرسته‌هایش را نخواهید دید. نخواهد توانست فرسته‌هایتان را دیده یا پیتان بگیرد. قادر است تشخیص دهد مسدود شده.",
"report.categories.legal": "حقوقی",
"report.categories.other": "غیره",
"report.categories.spam": "هرزنامه",
@ -770,7 +770,7 @@
"report.forward": "فرستادن به {target}",
"report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
"report.mute": "خموش",
"report.mute_explanation": "شما فرسته‌های آن‌ها را نخواهید دید. آن‌ها همچنان می‌توانند شما را پی‌بگیرند و فرسته‌هایتان را ببینند و نمی‌دانند که خموش شده‌اند.",
"report.mute_explanation": "فرسته‌هایش را نخواهید دید. همچنان خواهد توانست پیتان گرفته و فرسته‌هایتان را ببیند. نخواهد دانست که خموش شده.",
"report.next": "بعدی",
"report.placeholder": "توضیحات اضافه",
"report.reasons.dislike": "من آن را دوست ندارم",

View File

@ -564,6 +564,8 @@
"navigation_bar.follows_and_followers": "Folgers en folgjenden",
"navigation_bar.import_export": "Ymportearje en eksportearje",
"navigation_bar.lists": "Listen",
"navigation_bar.live_feed_local": "Livefeed (lokaal)",
"navigation_bar.live_feed_public": "Livefeed (iepenbier)",
"navigation_bar.logout": "Ofmelde",
"navigation_bar.moderation": "Moderaasje",
"navigation_bar.more": "Mear",
@ -803,6 +805,7 @@
"report_notification.categories.violation": "Skeinde regels",
"report_notification.categories.violation_sentence": "skeinde regels",
"report_notification.open": "Rapport iepenje",
"search.clear": "Sykopdracht wiskje",
"search.no_recent_searches": "Gjin resinte sykopdrachten",
"search.placeholder": "Sykje",
"search.quick_action.account_search": "Accounts dyt oerienkomme mei {x}",

View File

@ -219,6 +219,13 @@
"confirmations.delete_list.confirm": "削除",
"confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
"confirmations.delete_list.title": "リストを削除しようとしています",
"confirmations.discard_draft.confirm": "破棄して続ける",
"confirmations.discard_draft.edit.cancel": "編集を再開",
"confirmations.discard_draft.edit.message": "続行すると、現在編集している投稿に加えた変更は破棄されます。",
"confirmations.discard_draft.edit.title": "投稿の変更を破棄しますか?",
"confirmations.discard_draft.post.cancel": "下書きを再開",
"confirmations.discard_draft.post.message": "続行すると、現在作成中の投稿が破棄されます。",
"confirmations.discard_draft.post.title": "下書きを破棄しますか?",
"confirmations.discard_edit_media.confirm": "破棄",
"confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?",
"confirmations.follow_to_list.confirm": "フォローしてリストに追加",
@ -330,6 +337,7 @@
"errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
"errors.unexpected_crash.report_issue": "問題を報告",
"explore.suggested_follows": "ユーザー",
"explore.title": "トレンド",
"explore.trending_links": "ニュース",
"explore.trending_statuses": "投稿",
"explore.trending_tags": "ハッシュタグ",
@ -417,6 +425,7 @@
"hints.profiles.see_more_posts": "{domain} でその他の投稿を見る",
"hints.threads.replies_may_be_missing": "リモートの返信は表示されない場合があります。",
"hints.threads.see_more": "{domain} でその他の返信を見る",
"home.column_settings.show_quotes": "引用を表示",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
"home.hide_announcements": "お知らせを隠す",
@ -539,8 +548,10 @@
"mute_modal.you_wont_see_mentions": "宛先に相手が入っている投稿も閲覧できなくなります。",
"mute_modal.you_wont_see_posts": "相手はあなたの投稿を今までどおり閲覧できますが、あなたは相手の投稿を閲覧できなくなります。",
"navigation_bar.about": "概要",
"navigation_bar.account_settings": "パスワードとセキュリティ",
"navigation_bar.administration": "管理",
"navigation_bar.advanced_interface": "上級者向けUIに戻る",
"navigation_bar.automated_deletion": "投稿の自動削除",
"navigation_bar.blocks": "ブロックしたユーザー",
"navigation_bar.bookmarks": "ブックマーク",
"navigation_bar.direct": "非公開の返信",
@ -550,13 +561,21 @@
"navigation_bar.follow_requests": "フォローリクエスト",
"navigation_bar.followed_tags": "フォロー中のハッシュタグ",
"navigation_bar.follows_and_followers": "フォロー・フォロワー",
"navigation_bar.import_export": "インポートとエクスポート",
"navigation_bar.lists": "リスト",
"navigation_bar.logout": "ログアウト",
"navigation_bar.moderation": "モデレーション",
"navigation_bar.more": "もっと見る",
"navigation_bar.mutes": "ミュートしたユーザー",
"navigation_bar.opened_in_classic_interface": "投稿やプロフィールを直接開いた場合は一時的に標準UIで表示されます。",
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.privacy_and_reach": "プライバシーとつながりやすさ",
"navigation_bar.search": "検索",
"navigation_bar.search_trends": "検索 / トレンド",
"navigation_panel.collapse_followed_tags": "フォロー中のハッシュタグメニューを閉じる",
"navigation_panel.collapse_lists": "リストメニューを閉じる",
"navigation_panel.expand_followed_tags": "フォロー中のハッシュタグメニューを展開",
"navigation_panel.expand_lists": "リストメニューを展開",
"not_signed_in_indicator.not_signed_in": "この機能を使うにはログインする必要があります。",
"notification.admin.report": "{name}さんが{target}さんを通報しました",
"notification.admin.report_account": "{name}さんが{target}さんの投稿{count, plural, other {#件}}を「{category}」として通報しました",
@ -783,6 +802,7 @@
"report_notification.categories.violation": "ルール違反",
"report_notification.categories.violation_sentence": "ルール違反",
"report_notification.open": "通報を開く",
"search.clear": "検索をクリア",
"search.no_recent_searches": "検索履歴はありません",
"search.placeholder": "検索",
"search.quick_action.account_search": "{x}に該当するプロフィール",
@ -885,7 +905,10 @@
"subscribed_languages.save": "変更を保存",
"subscribed_languages.target": "{target}さんの購読言語を変更します",
"tabs_bar.home": "ホーム",
"tabs_bar.menu": "メニュー",
"tabs_bar.notifications": "通知",
"tabs_bar.publish": "新しい投稿",
"tabs_bar.search": "検索",
"terms_of_service.effective_as_of": "{date}より有効",
"terms_of_service.title": "サービス利用規約",
"terms_of_service.upcoming_changes_on": "{date}から適用される変更",

View File

@ -6,6 +6,7 @@
"about.domain_blocks.preamble": "Maṣṭudun s umata yeḍmen-ak ad teẓreḍ agbur, ad tesdemreḍ akked yimseqdacen-nniḍen seg yal aqeddac deg fedivers. Ha-tent-an ɣur-k tsuraf i yellan deg uqeddac-agi.",
"about.domain_blocks.silenced.title": "Ɣur-s talast",
"about.domain_blocks.suspended.title": "Yettwaḥbes",
"about.language_label": "Tutlayt",
"about.not_available": "Talɣut-a ur tettwabder ara deg uqeddac-a.",
"about.powered_by": "Azeṭṭa inmetti yettwasɣelsen sɣur {mastodon}",
"about.rules": "Ilugan n uqeddac",
@ -115,6 +116,7 @@
"column.domain_blocks": "Taɣulin yeffren",
"column.edit_list": "Ẓreg tabdart",
"column.favourites": "Imenyafen",
"column.firehose": "Isuddam usriden",
"column.follow_requests": "Isuturen n teḍfeṛt",
"column.home": "Agejdan",
"column.lists": "Tibdarin",
@ -223,6 +225,7 @@
"empty_column.bookmarked_statuses": "Ulac kra n tsuffeɣt i terniḍ ɣer yismenyifen-ik·im ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.",
"empty_column.community": "Tasuddemt tazayezt tadigant n yisallen d tilemt. Aru ihi kra akken ad tt-teččareḍ!",
"empty_column.domain_blocks": "Ulac kra n taɣult yettwaffren ar tura.",
"empty_column.explore_statuses": "Ulac ayen yellan d anezzuɣ akka tura. Uɣal-d ticki!",
"empty_column.follow_requests": "Ulac ɣur-k·m ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.",
"empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaɣ ɣer uhacṭag-agi.",
"empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
@ -234,9 +237,14 @@
"errors.unexpected_crash.copy_stacktrace": "Nɣel stacktrace ɣef wafus",
"errors.unexpected_crash.report_issue": "Mmel ugur",
"explore.suggested_follows": "Imdanen",
"explore.title": "Inezzaɣ",
"explore.trending_links": "Isallen",
"explore.trending_statuses": "Tisuffaɣ",
"explore.trending_tags": "Ihacṭagen",
"featured_carousel.next": "Uḍfiṛ",
"featured_carousel.post": "Tasuffeɣt",
"featured_carousel.previous": "Uzwir",
"featured_carousel.slide": "{index} ɣef {total}",
"filter_modal.added.review_and_configure_title": "Iɣewwaṛen n imzizdig",
"filter_modal.added.settings_link": "asebter n yiɣewwaṛen",
"filter_modal.added.short_explanation": "Tasuffeɣt-a tettwarna ɣer taggayt-a n yimsizdegen: {title}.",
@ -357,6 +365,7 @@
"lists.add_to_list": "Rnu ɣer tebdart",
"lists.add_to_lists": "Rnu {name} ɣer tebdarin",
"lists.create": "Snulfu-d",
"lists.create_list": "Snulfu-d tabdart",
"lists.delete": "Kkes tabdart",
"lists.done": "Immed",
"lists.edit": "Ẓreg tabdart",
@ -395,12 +404,16 @@
"navigation_bar.followed_tags": "Ihacṭagen yettwaḍfaren",
"navigation_bar.follows_and_followers": "Imeḍfaṛen akked wid i teṭṭafaṛeḍ",
"navigation_bar.lists": "Tibdarin",
"navigation_bar.live_feed_local": "Asuddem usrid (adigan)",
"navigation_bar.live_feed_public": "Asuddem usrid (azayaz)",
"navigation_bar.logout": "Ffeɣ",
"navigation_bar.moderation": "Aseɣyed",
"navigation_bar.more": "Ugar",
"navigation_bar.mutes": "Iseqdacen yettwasusmen",
"navigation_bar.opened_in_classic_interface": "Tisuffaɣ, imiḍanen akked isebtar-nniḍen igejdanen ldin-d s wudem amezwer deg ugrudem web aklasiki.",
"navigation_bar.preferences": "Imenyafen",
"navigation_bar.search": "Nadi",
"navigation_bar.search_trends": "Anadi / Anezzuɣ",
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
"notification.admin.report": "Yemla-t-id {name} {target}",
"notification.admin.sign_up": "Ijerred {name}",
@ -505,6 +518,7 @@
"recommended": "Yettuwelleh",
"refresh": "Smiren",
"regeneration_indicator.please_stand_by": "Ttxil rǧu.",
"regeneration_indicator.preparing_your_home_feed": "Ha-tt-an tsuddemt-ik·im tagejdant tettwaheggay…",
"relative_time.days": "{number}u",
"relative_time.full.just_now": "tura kan",
"relative_time.hours": "{number}isr",
@ -545,6 +559,7 @@
"report.thanks.title": "Ur tebɣiḍ ara ad twaliḍ aya?",
"report.thanks.title_actionable": "Tanemmirt ɣef uneqqis, ad nwali deg waya.",
"report.unfollow": "Seḥbes aḍfar n @{name}",
"report.unfollow_explanation": "Aql-ik·ikem teṭṭafareḍ amiḍan-a. I wakken ur tettwaliḍ ara akk, akka d asawen, tisuffaɣ-is deg tsuddemt-ik·im tagejdant, ur teṭṭafar ara.",
"report_notification.attached_statuses": "{count, plural, one {{count} n tsuffeɣt} other {{count} n tsuffiɣin}} ttwaqnent",
"report_notification.categories.legal": "Azerfan",
"report_notification.categories.other": "Ayen nniḍen",
@ -610,6 +625,7 @@
"status.mute_conversation": "Sgugem adiwenni",
"status.open": "Semɣeṛ tasuffeɣt-ayi",
"status.pin": "Senteḍ-itt deg umaɣnu",
"status.quote_post_author": "Izen sɣur {name}",
"status.read_more": "Issin ugar",
"status.reblog": "Bḍu",
"status.reblogged_by": "Yebḍa-tt {name}",
@ -635,7 +651,10 @@
"status.unpin": "Kkes asenteḍ seg umaɣnu",
"subscribed_languages.save": "Sekles ibeddilen",
"tabs_bar.home": "Agejdan",
"tabs_bar.menu": "Umuɣ",
"tabs_bar.notifications": "Ilɣa",
"tabs_bar.publish": "Tasuffeɣt tamaynut",
"tabs_bar.search": "Nadi",
"terms_of_service.title": "Tiwtilin n useqdec",
"time_remaining.days": "Mazal {number, plural, one {# wass} other {# wussan}}",
"time_remaining.hours": "Mazal {number, plural, one {# usarag} other {# yisragen}}",
@ -643,7 +662,7 @@
"time_remaining.moments": "Akuden i d-yeqqimen",
"time_remaining.seconds": "Mazal {number, plural, one {# n tasint} other {# n tsinin}} id yugran",
"trends.counter_by_accounts": "{count, plural, one {{counter} wemdan} other {{counter} medden}} deg {days, plural, one {ass} other {{days} wussan}} iɛeddan",
"trends.trending_now": "Ayen mucaɛen tura",
"trends.trending_now": "Anezzuɣ tura",
"ui.beforeunload": "Arewway-ik·im ad iruḥ ma yella tefeɣ-d deg Maṣṭudun.",
"units.short.billion": "{count}B",
"units.short.million": "{count}M",

View File

@ -356,6 +356,7 @@
"hashtag.counter_by_accounts": "{count, plural, one {{counter} partisipante} other {{counter} partisipantes}}",
"hashtag.counter_by_uses": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}",
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}} oy",
"hashtag.feature": "Avalia en profil",
"hashtag.follow": "Sige etiketa",
"hashtag.mute": "Silensia #{hashtag}",
"hashtag.unfeature": "No avalia en profil",
@ -390,6 +391,7 @@
"interaction_modal.title.reblog": "Repartaja publikasyon de {name}",
"interaction_modal.title.reply": "Arisponde a publikasyon de {name}",
"interaction_modal.title.vote": "Vota en la anketa de {name}",
"interaction_modal.username_prompt": "Por enshemplo {example}",
"intervals.full.days": "{number, plural, one {# diya} other {# diyas}}",
"intervals.full.hours": "{number, plural, one {# ora} other {# oras}}",
"intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}",
@ -442,6 +444,7 @@
"lists.delete": "Efasa lista",
"lists.done": "Fecho",
"lists.edit": "Edita lista",
"lists.list_name": "Nombre de lista",
"lists.new_list_name": "Nombre de mueva lista",
"lists.replies_policy.followed": "Kualseker utilizador segido",
"lists.replies_policy.list": "Miembros de la lista",
@ -738,6 +741,7 @@
"status.reblogs.empty": "Ainda nadie tiene repartajado esta publikasyon. Kuando algien lo aga, se amostrara aki.",
"status.redraft": "Efasa i eskrive de muevo",
"status.remove_bookmark": "Kita markador",
"status.remove_favourite": "Kita de los favoritos",
"status.replied_in_thread": "Arispondo en filo",
"status.replied_to": "Arispondio a {name}",
"status.reply": "Arisponde",
@ -758,6 +762,7 @@
"subscribed_languages.save": "Guadra trokamientos",
"subscribed_languages.target": "Troka linguas abonadas para {target}",
"tabs_bar.home": "Linya prinsipala",
"tabs_bar.menu": "Menu",
"tabs_bar.notifications": "Avizos",
"tabs_bar.publish": "Mueva publikasyon",
"tabs_bar.search": "Bushkeda",

View File

@ -1,8 +1,12 @@
{
"about.contact": "ਸੰਪਰਕ:",
"about.default_locale": "ਮੂਲ",
"about.disclaimer": "ਮਸਟੋਡੋਨ ਇੱਕ ਆਜ਼ਾਦ, ਖੁੱਲ੍ਹੇ ਸਰੋਤ ਵਾਲਾ ਸਾਫਟਵੇਅਰ ਹੈ ਅਤੇ Mastodon gGmbH ਦਾ ਮਾਰਕਾ ਹੈ।",
"about.domain_blocks.no_reason_available": "ਕਾਰਨ ਮੌਜੂਦ ਨਹੀਂ ਹੈ",
"about.domain_blocks.silenced.title": "ਸੀਮਿਤ",
"about.domain_blocks.suspended.title": "ਮੁਅੱਤਲ ਕੀਤੀ",
"about.domain_blocks.suspended.title": "ਸਸਪੈਂਡ ਕੀਤਾ",
"about.language_label": "ਭਾਸ਼ਾ",
"about.not_available": "ਇਹ ਜਾਣਕਾਰੀ ਨੂੰ ਇਸ ਸਰਵਰ ਉੱਤੇ ਉਪਲੱਬਧ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।",
"about.rules": "ਸਰਵਰ ਨਿਯਮ",
"account.account_note_header": "ਨਿੱਜੀ ਨੋਟ",
"account.add_or_remove_from_list": "ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ ਜਾਂ ਹਟਾਓ",
@ -12,21 +16,33 @@
"account.block_domain": "{domain} ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ",
"account.block_short": "ਪਾਬੰਦੀ",
"account.blocked": "ਪਾਬੰਦੀਸ਼ੁਦਾ",
"account.blocking": "ਪਾਬੰਦੀ ਲਾਉਣੀ",
"account.cancel_follow_request": "ਫ਼ਾਲੋ ਕਰਨ ਨੂੰ ਰੱਦ ਕਰੋ",
"account.copy": "ਪਰੋਫਾਇਲ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
"account.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ @{name}",
"account.disable_notifications": "ਜਦੋਂ {name} ਕੋਈ ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਨਾ ਦਿਓ",
"account.domain_blocking": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ",
"account.edit_profile": "ਪਰੋਫਾਈਲ ਨੂੰ ਸੋਧੋ",
"account.enable_notifications": "ਜਦੋਂ {name} ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਦਿਓ",
"account.endorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ",
"account.familiar_followers_one": "{name1} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ",
"account.familiar_followers_two": "{name1} ਅਤੇ {name2} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ",
"account.featured": "ਫ਼ੀਚਰ",
"account.featured.accounts": "ਪਰੋਫਾਈਲ",
"account.featured.hashtags": "ਹੈਸ਼ਟੈਗ",
"account.featured_tags.last_status_at": "{date} ਨੂੰ ਆਖਰੀ ਪੋਸਟ",
"account.featured_tags.last_status_never": "ਕੋਈ ਪੋਸਟ ਨਹੀਂ",
"account.follow": "ਫ਼ਾਲੋ",
"account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ",
"account.followers": "ਫ਼ਾਲੋਅਰ",
"account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
"account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}",
"account.followers_you_know_counter": "{counter} ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ",
"account.following": "ਫ਼ਾਲੋ ਕੀਤਾ",
"account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।",
"account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹਨ",
"account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ",
"account.hide_reblogs": "{name} ਵਲੋਂ ਬੂਸਟ ਨੂੰ ਲੁਕਾਓ",
"account.joined_short": "ਜੁਆਇਨ ਕੀਤਾ",
"account.media": "ਮੀਡੀਆ",
"account.mention": "@{name} ਦਾ ਜ਼ਿਕਰ",
@ -34,6 +50,7 @@
"account.mute_notifications_short": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਮੌਨ ਕਰੋ",
"account.mute_short": "ਮੌਨ ਕਰੋ",
"account.muted": "ਮੌਨ ਕੀਤੀਆਂ",
"account.mutual": "ਤੁਸੀਂ ਇੱਕ ਦੂਜੇ ਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹੋ",
"account.no_bio": "ਕੋਈ ਵਰਣਨ ਨਹੀਂ ਦਿੱਤਾ।",
"account.open_original_page": "ਅਸਲ ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ",
"account.posts": "ਪੋਸਟਾਂ",
@ -42,8 +59,10 @@
"account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ",
"account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ",
"account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ",
"account.statuses_counter": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}",
"account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ",
"account.unblock_domain": "{domain} ਡੋਮੇਨ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ",
"account.unblock_domain_short": "ਪਾਬੰਦੀ ਹਟਾਓ",
"account.unblock_short": "ਪਾਬੰਦੀ ਹਟਾਓ",
"account.unendorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ ਨਾ ਕਰੋ",
"account.unfollow": "ਅਣ-ਫ਼ਾਲੋ",
@ -148,6 +167,8 @@
"confirmations.missing_alt_text.secondary": "ਕਿਵੇਂ ਵੀ ਪੋਸਟ ਕਰੋ",
"confirmations.mute.confirm": "ਮੌਨ ਕਰੋ",
"confirmations.redraft.confirm": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ",
"confirmations.remove_from_followers.confirm": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਓ",
"confirmations.remove_from_followers.title": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?",
"confirmations.unfollow.confirm": "ਅਣ-ਫ਼ਾਲੋ",
"confirmations.unfollow.message": "ਕੀ ਤੁਸੀਂ {name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?",
"confirmations.unfollow.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?",
@ -182,7 +203,9 @@
"emoji_button.custom": "ਕਸਟਮ",
"emoji_button.flags": "ਝੰਡੀਆਂ",
"emoji_button.food": "ਖਾਣਾ-ਪੀਣਾ",
"emoji_button.label": "ਇਮੋਜੀ ਪਾਓ",
"emoji_button.nature": "ਕੁਦਰਤ",
"emoji_button.not_found": "ਕੋਈ ਮਿਲਦਾ ਇਮੋਜ਼ੀ ਨਹੀਂ ਲੱਭਿਆ",
"emoji_button.objects": "ਇਕਾਈ",
"emoji_button.people": "ਲੋਕ",
"emoji_button.recent": "ਅਕਸਰ ਵਰਤੇ",
@ -199,9 +222,15 @@
"empty_column.list": "ਇਸ ਸੂਚੀ ਵਿੱਚ ਹਾਲੇ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ। ਜਦੋਂ ਇਸ ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਨਵੀਆਂ ਪੋਸਟਾਂ ਪਾਉਂਦੇ ਹਨ ਤਾਂ ਉਹ ਇੱਥੇ ਦਿਖਾਈ ਦੇਣਗੀਆਂ।",
"errors.unexpected_crash.report_issue": "ਮੁੱਦੇ ਦੀ ਰਿਪੋਰਟ ਕਰੋ",
"explore.suggested_follows": "ਲੋਕ",
"explore.title": "ਰੁਝਾਨ",
"explore.trending_links": "ਖ਼ਬਰਾਂ",
"explore.trending_statuses": "ਪੋਸਟਾਂ",
"explore.trending_tags": "ਹੈਸ਼ਟੈਗ",
"featured_carousel.header": "{count, plural, one {ਟੰਗੀ ਹੋਈ ਪੋਸਟ} other {ਟੰਗੀਆਂ ਹੋਈਆਂ ਪੋਸਟਾਂ}}",
"featured_carousel.next": "ਅੱਗੇ",
"featured_carousel.post": "ਪੋਸਟ",
"featured_carousel.previous": "ਪਿੱਛੇ",
"featured_carousel.slide": "{total} ਵਿੱਚੋਂ {index}",
"filter_modal.added.expired_title": "ਫਿਲਟਰ ਦੀ ਮਿਆਦ ਪੁੱਗੀ!",
"filter_modal.added.review_and_configure_title": "ਫਿਲਟਰ ਸੈਟਿੰਗਾਂ",
"filter_modal.added.settings_link": "ਸੈਟਿੰਗਾਂ ਸਫ਼ਾ",
@ -252,6 +281,8 @@
"home.column_settings.show_replies": "ਜਵਾਬਾਂ ਨੂੰ ਵੇਖੋ",
"home.hide_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ",
"home.pending_critical_update.link": "ਅੱਪਡੇਟ ਵੇਖੋ",
"home.pending_critical_update.title": "ਗੰਭੀਰ ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ!",
"home.show_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਵੇਖਾਓ",
"ignore_notifications_modal.ignore": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰੋ",
"info_button.label": "ਮਦਦ",
"interaction_modal.go": "ਜਾਓ",
@ -332,9 +363,12 @@
"media_gallery.hide": "ਲੁਕਾਓ",
"mute_modal.hide_from_notifications": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚੋਂ ਲੁਕਾਓ",
"mute_modal.show_options": "ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ",
"mute_modal.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਮੌਨ ਕਰਨਾ ਹੈ?",
"navigation_bar.about": "ਇਸ ਬਾਰੇ",
"navigation_bar.account_settings": "ਪਾਸਵਰਡ ਅਤੇ ਸੁਰੱਖਿਆ",
"navigation_bar.administration": "ਪਰਸ਼ਾਸ਼ਨ",
"navigation_bar.advanced_interface": "ਤਕਨੀਕੀ ਵੈੱਬ ਇੰਟਰਫੇਸ ਵਿੱਚ ਖੋਲ੍ਹੋ",
"navigation_bar.automated_deletion": "ਆਪਣੇ-ਆਪ ਹਟਾਈ ਪੋਸਟ",
"navigation_bar.blocks": "ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ",
"navigation_bar.bookmarks": "ਬੁੱਕਮਾਰਕ",
"navigation_bar.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ",
@ -346,11 +380,16 @@
"navigation_bar.follows_and_followers": "ਫ਼ਾਲੋ ਅਤੇ ਫ਼ਾਲੋ ਕਰਨ ਵਾਲੇ",
"navigation_bar.lists": "ਸੂਚੀਆਂ",
"navigation_bar.logout": "ਲਾਗ ਆਉਟ",
"navigation_bar.more": "ਹੋਰ",
"navigation_bar.mutes": "ਮੌਨ ਕੀਤੇ ਵਰਤੋਂਕਾਰ",
"navigation_bar.preferences": "ਪਸੰਦਾਂ",
"navigation_bar.privacy_and_reach": "ਪਰਦੇਦਾਰੀ ਅਤੇ ਪਹੁੰਚ",
"navigation_bar.search": "ਖੋਜੋ",
"navigation_bar.search_trends": "ਖੋਜ / ਰੁਝਾਨ",
"not_signed_in_indicator.not_signed_in": "ਇਹ ਸਰੋਤ ਵਰਤਣ ਲਈ ਤੁਹਾਨੂੰ ਲਾਗਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।",
"notification.admin.sign_up": "{name} ਨੇ ਸਾਈਨ ਅੱਪ ਕੀਤਾ",
"notification.favourite": "{name} ਨੇ ਤੁਹਾਡੀ ਪੋਸਟ ਨੂੰ ਪਸੰਦ ਕੀਤਾ",
"notification.favourite_pm": "{name} ਨੇ ਤੁਹਾਡੇ ਨਿੱਜੀ ਜ਼ਿਕਰ ਨੂੰ ਪਸੰਦ ਕੀਤਾ",
"notification.follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ",
"notification.follow.name_and_others": "{name} ਅਤੇ <a>{count, plural, one {# ਹੋਰ} other {# ਹੋਰਾਂ}}</a> ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ",
"notification.follow_request": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ",
@ -365,6 +404,7 @@
"notification.moderation_warning.action_silence": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਸੀਮਿਤ ਕੀਤਾ ਗਿਆ ਹੈ।",
"notification.moderation_warning.action_suspend": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਮੁਅੱਤਲ ਕੀਤਾ ਗਿਆ ਹੈ।",
"notification.reblog": "{name} boosted your status",
"notification.relationships_severance_event": "{name} ਨਾਲ ਕਨੈਕਸ਼ਨ ਗੁਆਚੇ",
"notification.relationships_severance_event.learn_more": "ਹੋਰ ਜਾਣੋ",
"notification.status": "{name} ਨੇ ਹੁਣੇ ਪੋਸਟ ਕੀਤਾ",
"notification.update": "{name} ਨੋ ਪੋਸਟ ਨੂੰ ਸੋਧਿਆ",
@ -540,7 +580,10 @@
"status.unpin": "ਪਰੋਫਾਈਲ ਤੋਂ ਲਾਹੋ",
"subscribed_languages.save": "ਤਬਦੀਲੀਆਂ ਸੰਭਾਲੋ",
"tabs_bar.home": "ਘਰ",
"tabs_bar.menu": "ਮੇਨੂ",
"tabs_bar.notifications": "ਸੂਚਨਾਵਾਂ",
"tabs_bar.publish": "ਨਵੀਂ ਪੋਸਟ",
"tabs_bar.search": "ਖੋਜੋ",
"terms_of_service.title": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ",
"time_remaining.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}} ਬਾਕੀ",
"time_remaining.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}} ਬਾਕੀ",
@ -561,6 +604,9 @@
"video.expand": "ਵੀਡੀਓ ਨੂੰ ਫੈਲਾਓ",
"video.fullscreen": "ਪੂਰੀ ਸਕਰੀਨ",
"video.hide": "ਵੀਡੀਓ ਨੂੰ ਲੁਕਾਓ",
"video.mute": "ਮੌਨ",
"video.pause": "ਠਹਿਰੋ",
"video.play": "ਚਲਾਓ"
"video.play": "ਚਲਾਓ",
"video.volume_down": "ਅਵਾਜ਼ ਘਟਾਓ",
"video.volume_up": "ਅਵਾਜ਼ ਵਧਾਓ"
}

View File

@ -205,8 +205,8 @@
"compose_form.poll.switch_to_multiple": "Permitir múltiplas escolhas",
"compose_form.poll.switch_to_single": "Opção única",
"compose_form.poll.type": "Estilo",
"compose_form.publish": "Publicação",
"compose_form.reply": "Resposta",
"compose_form.publish": "Publicar",
"compose_form.reply": "Responder",
"compose_form.save_changes": "Atualização",
"compose_form.spoiler.marked": "Com Aviso de Conteúdo",
"compose_form.spoiler.unmarked": "Sem Aviso de Conteúdo",

View File

@ -4,17 +4,17 @@
"about.default_locale": "по умолчанию",
"about.disclaimer": "Mastodon — свободное программное обеспечение с открытым исходным кодом и торговая марка Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Причина не указана",
"about.domain_blocks.preamble": "Mastodon обычно позволяет просматривать содержимое и взаимодействовать с пользователями любых других серверов в федивёрсе. Вот исключения, сделанные конкретно для этого сервера.",
"about.domain_blocks.silenced.explanation": "Как правило, вы не увидите профили и контент с этого сервера, если специально не будете их искать или не подпишетесь на них.",
"about.domain_blocks.preamble": "Обычно Mastodon даёт вам возможность просматривать содержимое с любых других серверов в федивёрсе и взаимодействовать с их пользователями. Вот исключения, сделанные конкретно для этого сервера:",
"about.domain_blocks.silenced.explanation": "Как правило, вы не увидите профили и содержимое с этого сервера, если только вы не запросите их с помощью поиска или не подпишетесь на пользователя с этого сервера.",
"about.domain_blocks.silenced.title": "Ограничивается",
"about.domain_blocks.suspended.explanation": "Никакие данные с этого сервера не будут обрабатываться, храниться или обмениваться, что делает невозможным любое взаимодействие или связь с пользователями с этого сервера.",
"about.domain_blocks.suspended.explanation": "Никакие данные с этого сервера не будут обрабатываться, храниться и обмениваться, что делает невозможным любое взаимодействие и связь с пользователями на этом сервере.",
"about.domain_blocks.suspended.title": "Заблокирован",
"about.language_label": "Язык",
"about.not_available": "Эта информация не указана на данном сервере.",
"about.not_available": "Администраторы сервера предпочли не раскрывать эту информацию.",
"about.powered_by": "Децентрализованная социальная сеть на базе {mastodon}",
"about.rules": "Правила сервера",
"account.account_note_header": "Личная заметка",
"account.add_or_remove_from_list": "Управление списками",
"account.add_or_remove_from_list": "Добавить в списки",
"account.badges.bot": "Бот",
"account.badges.group": "Группа",
"account.block": "Заблокировать @{name}",
@ -25,10 +25,10 @@
"account.cancel_follow_request": "Отозвать запрос на подписку",
"account.copy": "Копировать ссылку на профиль",
"account.direct": "Упомянуть @{name} лично",
"account.disable_notifications": "Не уведомлять о постах от @{name}",
"account.disable_notifications": "Не уведомлять о постах пользователя @{name}",
"account.domain_blocking": "Домен заблокирован",
"account.edit_profile": "Редактировать",
"account.enable_notifications": "Уведомлять о постах от @{name}",
"account.enable_notifications": "Уведомлять о постах пользователя @{name}",
"account.endorse": "Рекомендовать в профиле",
"account.familiar_followers_many": "В подписках у {name1}, {name2}, и ещё {othersCount, plural, one {# человека, которого вы знаете} other {# человек, которых вы знаете}}",
"account.familiar_followers_one": "В подписках у {name1}",
@ -36,7 +36,7 @@
"account.featured": "Рекомендации",
"account.featured.accounts": "Профили",
"account.featured.hashtags": "Хештеги",
"account.featured_tags.last_status_at": "Последний пост {date}",
"account.featured_tags.last_status_at": "Последний пост опубликован {date}",
"account.featured_tags.last_status_never": "Нет постов",
"account.follow": "Подписаться",
"account.follow_back": "Подписаться в ответ",
@ -59,7 +59,7 @@
"account.mention": "Упомянуть @{name}",
"account.moved_to": "У {name} теперь новая учётная запись:",
"account.mute": "Игнорировать @{name}",
"account.mute_notifications_short": "Отключить уведомления",
"account.mute_notifications_short": "Скрыть уведомления",
"account.mute_short": "Игнорировать",
"account.muted": "Игнорируется",
"account.muting": "Игнорируется",
@ -83,7 +83,7 @@
"account.unendorse": "Не рекомендовать в профиле",
"account.unfollow": "Отписаться",
"account.unmute": "Не игнорировать @{name}",
"account.unmute_notifications_short": "Включить уведомления",
"account.unmute_notifications_short": "Показать уведомления",
"account.unmute_short": "Не игнорировать",
"account_note.placeholder": "Текст заметки",
"admin.dashboard.daily_retention": "Уровень удержания пользователей после регистрации, в днях",
@ -129,10 +129,10 @@
"annual_report.summary.thanks": "Спасибо за то, что были вместе с Mastodon!",
"attachments_list.unprocessed": "(не обработан)",
"audio.hide": "Скрыть аудио",
"block_modal.remote_users_caveat": "Мы попросим сервер {domain} уважать ваше решение, однако соблюдение им блокировки не гарантировано, поскольку некоторые серверы могут по-разному обрабатывать запросы. Публичные посты по-прежнему могут быть видны неавторизованным пользователям.",
"block_modal.remote_users_caveat": "Мы попросим сервер {domain} уважать ваше решение, однако нельзя гарантировать, что он будет соблюдать блокировку, поскольку некоторые серверы могут по-разному обрабатывать запросы. Публичные посты по-прежнему могут быть видны неавторизованным пользователям.",
"block_modal.show_less": "Показать меньше",
"block_modal.show_more": "Показать больше",
"block_modal.they_cant_mention": "Он не сможет упоминать вас или подписаться на вас.",
"block_modal.they_cant_mention": "Он не сможет ни упоминать вас, ни подписаться на вас.",
"block_modal.they_cant_see_posts": "Он не сможет видеть ваши посты, а вы не будете видеть его посты.",
"block_modal.they_will_know": "Он будет знать, что вы его блокируете.",
"block_modal.title": "Заблокировать пользователя?",
@ -141,21 +141,21 @@
"boost_modal.reblog": "Продвинуть пост?",
"boost_modal.undo_reblog": "Отменить продвижение?",
"bundle_column_error.copy_stacktrace": "Копировать отчёт об ошибке",
"bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это может быть вызвано ошибкой в нашем коде или проблемой совместимости браузера.",
"bundle_column_error.error.body": "Запрошенная страница не может быть отображена. Это могло произойти из-за ошибки в нашем коде или из-за проблемы совместимости браузера.",
"bundle_column_error.error.title": "О нет!",
"bundle_column_error.network.body": "При загрузке этой страницы произошла ошибка. Это может быть вызвано временными проблемами с вашим подключением к интернету или ошибкой на сервере.",
"bundle_column_error.network.body": "При загрузке этой страницы произошла ошибка. Она могла быть вызвана временными проблемами либо с вашим подключением к интернету, либо с этим сервером.",
"bundle_column_error.network.title": "Ошибка сети",
"bundle_column_error.retry": "Попробовать снова",
"bundle_column_error.return": "Вернуться на главную",
"bundle_column_error.routing.body": "Запрошенная страница не найдена. Вы уверены, что в адресной строке указан правильный URL?",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Закрыть",
"bundle_modal_error.message": "Что-то пошло не так при загрузке этой страницы.",
"bundle_modal_error.message": "Което пошло не так при загрузке этой страницы.",
"bundle_modal_error.retry": "Попробовать снова",
"closed_registrations.other_server_instructions": "Поскольку Mastodon децентрализован, вы можете зарегистрироваться на другом сервере и всё равно взаимодействовать с этим сервером.",
"closed_registrations.other_server_instructions": "Благодаря тому что Mastodon децентрализован, вы можете взаимодействовать с этим сервером, даже если зарегистрируетесь на другом сервере.",
"closed_registrations_modal.description": "Зарегистрироваться на {domain} сейчас не выйдет, но имейте в виду, что вам не нужна учётная запись именно на {domain}, чтобы использовать Mastodon.",
"closed_registrations_modal.find_another_server": "Найти другой сервер",
"closed_registrations_modal.preamble": "Mastodon децентрализован, поэтому независимо от того, где именно вы зарегистрируетесь, вы сможете подписываться и взаимодействовать с кем угодно на этом сервере. Вы даже можете создать свой собственный сервер!",
"closed_registrations_modal.preamble": "Mastodon децентрализован, поэтому независимо от того, где именно вы зарегистрируетесь, вы сможете подписываться на кого угодно и взаимодействовать с кем угодно на этом сервере. Вы даже можете создать свой собственный сервер!",
"closed_registrations_modal.title": "Регистрация в Mastodon",
"column.about": "О проекте",
"column.blocks": "Заблокированные пользователи",
@ -163,14 +163,14 @@
"column.community": "Локальная лента",
"column.create_list": "Создать список",
"column.direct": "Личные упоминания",
"column.directory": "Просмотр профилей",
"column.directory": "Каталог профилей",
"column.domain_blocks": "Заблокированные домены",
"column.edit_list": "Редактировать список",
"column.favourites": "Избранное",
"column.firehose": "Живая лента",
"column.follow_requests": "Запросы на подписку",
"column.home": "Главная",
"column.list_members": "Добавить или удалить из списка",
"column.list_members": "Пользователи в списке",
"column.lists": "Списки",
"column.mutes": "Игнорируемые пользователи",
"column.notifications": "Уведомления",
@ -240,7 +240,7 @@
"confirmations.missing_alt_text.title": "Добавить альтернативный текст?",
"confirmations.mute.confirm": "Игнорировать",
"confirmations.redraft.confirm": "Удалить и исправить",
"confirmations.redraft.message": "Вы уверены, что хотите удалить этот пост и создать его заново? Взаимодействия, такие как добавление в избранное или продвижение, будут потеряны, а ответы к оригинальному посту перестанут на него ссылаться.",
"confirmations.redraft.message": "Вы уверены, что хотите удалить этот пост и создать его заново? Взаимодействия, такие как добавление в избранное и продвижение, будут потеряны, а ответы к оригинальному посту перестанут на него ссылаться.",
"confirmations.redraft.title": "Удалить и создать пост заново?",
"confirmations.remove_from_followers.confirm": "Убрать подписчика",
"confirmations.remove_from_followers.message": "Пользователь {name} перестанет быть подписан на вас. Продолжить?",
@ -275,9 +275,9 @@
"domain_block_modal.title": "Заблокировать домен?",
"domain_block_modal.you_will_lose_num_followers": "Вы потеряете {followersCount, plural, one {{followersCountDisplay} подписчика} few {{followersCountDisplay} подписчика} other {{followersCountDisplay} подписчиков}} и {followingCount, plural, one {{followingCountDisplay} подписку} few {{followingCountDisplay} подписки} other {{followingCountDisplay} подписок}}.",
"domain_block_modal.you_will_lose_relationships": "Вы потеряете все подписки и всех подписчиков с этого сервера.",
"domain_block_modal.you_wont_see_posts": "Вы не будете видеть посты или уведомления от пользователей с этого сервера.",
"domain_block_modal.you_wont_see_posts": "Вы не будете видеть посты и уведомления от пользователей с этого сервера.",
"domain_pill.activitypub_lets_connect": "Благодаря ему вы можете связываться и взаимодействовать не только с пользователями Mastodon, но и с пользователями других платформ.",
"domain_pill.activitypub_like_language": "ActivityPub это язык, на котором Mastodon говорит с другими социальными сетями.",
"domain_pill.activitypub_like_language": "ActivityPub это язык, на котором Mastodon говорит с другими социальными сетями.",
"domain_pill.server": "Сервер",
"domain_pill.their_handle": "Адрес пользователя:",
"domain_pill.their_server": "Цифровой дом пользователя, где находятся все его посты.",
@ -319,8 +319,8 @@
"empty_column.direct": "Вы ещё не упоминали кого-либо и сами не были ни разу упомянуты лично. Все личные упоминания будут показаны здесь.",
"empty_column.domain_blocks": "Заблокированных доменов пока нет.",
"empty_column.explore_statuses": "Сейчас нет популярных постов. Проверьте позже!",
"empty_column.favourited_statuses": "Вы ещё не добавили ни один пост в избранное. Все добавленные вами в избранное посты будут показаны здесь.",
"empty_column.favourites": "Никто ещё не добавил этот пост в избранное. Все пользователи, добавившие этот пост в избранное, будут показаны здесь.",
"empty_column.favourited_statuses": "Вы ещё не добавили ни одного поста в избранное. Все добавленные вами в избранное посты будут показаны здесь.",
"empty_column.favourites": "Никто ещё не добавил этот пост в избранное. Все пользователи, которые добавят этот пост в избранное, будут показаны здесь.",
"empty_column.follow_requests": "Вам ещё не приходили запросы на подписку. Все новые запросы будут показаны здесь.",
"empty_column.followed_tags": "Вы ещё не подписались ни на один хештег. Все хештеги, на которые вы подписаны, будут показаны здесь.",
"empty_column.hashtag": "С этим хештегом пока ещё никто ничего не опубликовал.",
@ -346,7 +346,7 @@
"featured_carousel.post": "Пост",
"featured_carousel.previous": "Предыдущий",
"featured_carousel.slide": "{index} из {total}",
"filter_modal.added.context_mismatch_explanation": "Этот фильтр не применяется в том контексте, в котором вы видели этот пост. Если вы хотите, чтобы пост был отфильтрован в этом контексте, необходимо редактировать фильтр.",
"filter_modal.added.context_mismatch_explanation": "Этот фильтр не применяется в том контексте, в котором вы видели этот пост. Если вы хотите, чтобы пост был отфильтрован в текущем контексте, необходимо редактировать фильтр.",
"filter_modal.added.context_mismatch_title": "Несоответствие контекста!",
"filter_modal.added.expired_explanation": "Этот фильтр истёк. Чтобы он был применён, вам нужно изменить срок действия фильтра.",
"filter_modal.added.expired_title": "Истёкший фильтр!",
@ -358,7 +358,7 @@
"filter_modal.select_filter.context_mismatch": "не применяется в этом контексте",
"filter_modal.select_filter.expired": "истёкший",
"filter_modal.select_filter.prompt_new": "Новый фильтр: {name}",
"filter_modal.select_filter.search": "Поиск или название нового фильтра",
"filter_modal.select_filter.search": "Поиск (или введите название нового фильтра)",
"filter_modal.select_filter.subtitle": "Используйте существующий фильтр или создайте новый",
"filter_modal.select_filter.title": "Фильтровать этот пост",
"filter_modal.title.status": "Фильтровать пост",
@ -413,18 +413,18 @@
"hashtag.counter_by_uses": "{count, plural, one {{counter} пост} few {{counter} поста} other {{counter} постов}}",
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} пост} few {{counter} поста} other {{counter} постов}} сегодня",
"hashtag.feature": "Рекомендовать в профиле",
"hashtag.follow": "Подписаться на новые посты",
"hashtag.follow": "Подписаться на хештег",
"hashtag.mute": "Игнорировать #{hashtag}",
"hashtag.unfeature": "Не рекомендовать в профиле",
"hashtag.unfollow": "Отписаться от новых постов",
"hashtag.unfollow": "Отписаться от хештега",
"hashtags.and_other": "…и {count, plural, other {ещё #}}",
"hints.profiles.followers_may_be_missing": "Некоторые подписчики этого профиля могут отсутствовать.",
"hints.profiles.follows_may_be_missing": "Некоторые подписки этого профиля могут отсутствовать.",
"hints.profiles.posts_may_be_missing": "Некоторые посты в этом профиле могут отсутствовать.",
"hints.profiles.followers_may_be_missing": "Некоторые подписчики этого профиля могут здесь отсутствовать.",
"hints.profiles.follows_may_be_missing": "Некоторые подписки этого профиля могут здесь отсутствовать.",
"hints.profiles.posts_may_be_missing": "Некоторые посты в этом профиле могут здесь отсутствовать.",
"hints.profiles.see_more_followers": "Перейдите на {domain}, чтобы увидеть всех подписчиков",
"hints.profiles.see_more_follows": "Перейдите на {domain}, чтобы увидеть все подписки",
"hints.profiles.see_more_posts": "Перейдите на {domain}, чтобы увидеть все посты",
"hints.threads.replies_may_be_missing": "Некоторые ответы с других серверов могут отсутствовать.",
"hints.threads.replies_may_be_missing": "Некоторые ответы с других серверов могут здесь отсутствовать.",
"hints.threads.see_more": "Перейдите на {domain}, чтобы увидеть все ответы",
"home.column_settings.show_quotes": "Показывать цитирования",
"home.column_settings.show_reblogs": "Показывать продвижения",
@ -466,7 +466,7 @@
"intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
"intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",
"keyboard_shortcuts.back": "перейти назад",
"keyboard_shortcuts.blocked": "открыть список заблокированных",
"keyboard_shortcuts.blocked": "открыть список заблокированных пользователей",
"keyboard_shortcuts.boost": "продвинуть пост",
"keyboard_shortcuts.column": "фокус на одном из столбцов",
"keyboard_shortcuts.compose": "фокус на поле ввода",
@ -483,7 +483,7 @@
"keyboard_shortcuts.legend": "показать эту справку",
"keyboard_shortcuts.local": "перейти к локальной ленте",
"keyboard_shortcuts.mention": "упомянуть автора поста",
"keyboard_shortcuts.muted": "открыть список игнорируемых",
"keyboard_shortcuts.muted": "открыть список игнорируемых пользователей",
"keyboard_shortcuts.my_profile": "перейти к своему профилю",
"keyboard_shortcuts.notifications": "перейти к уведомлениям",
"keyboard_shortcuts.open_media": "открыть медиа",
@ -546,7 +546,7 @@
"mute_modal.they_can_mention_and_follow": "Он сможет упоминать вас и подписаться на вас, но вы этого не увидите.",
"mute_modal.they_wont_know": "Он не будет знать, что вы его игнорируете.",
"mute_modal.title": "Игнорировать пользователя?",
"mute_modal.you_wont_see_mentions": "Вы не увидите посты, которые его упоминают.",
"mute_modal.you_wont_see_mentions": "Вы не будете видеть посты, упоминающие его.",
"mute_modal.you_wont_see_posts": "Он по-прежнему сможет видеть ваши посты, но вы не будете видеть его посты.",
"navigation_bar.about": "О проекте",
"navigation_bar.account_settings": "Пароль и безопасность",
@ -607,9 +607,9 @@
"notification.moderation_warning": "Модераторы вынесли вам предупреждение",
"notification.moderation_warning.action_delete_statuses": "Некоторые ваши посты были удалены.",
"notification.moderation_warning.action_disable": "Ваша учётная запись была отключена.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые ваши посты были отмечены как контент деликатного характера.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые ваши посты были отмечены как содержимое деликатного характера.",
"notification.moderation_warning.action_none": "Модераторы вынесли вам предупреждение.",
"notification.moderation_warning.action_sensitive": "С этого момента все ваши новые посты будут отмечены как контент деликатного характера.",
"notification.moderation_warning.action_sensitive": "С этого момента все ваши новые посты будут отмечены как содержимое деликатного характера.",
"notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.",
"notification.moderation_warning.action_suspend": "Ваша учётная запись была заблокирована.",
"notification.own_poll": "Ваш опрос завершился",
@ -617,10 +617,10 @@
"notification.reblog": "{name} продвинул(а) ваш пост",
"notification.reblog.name_and_others_with_link": "{name} и ещё <a>{count, plural, one {# пользователь} few {# пользователя} other {# пользователей}}</a> продвинули ваш пост",
"notification.relationships_severance_event": "Разорвана связь с {name}",
"notification.relationships_severance_event.account_suspension": "Администратор сервера {from} заблокировал сервер {target}, поэтому вы больше не сможете получать обновления от людей с этого сервера или взаимодействовать с ними.",
"notification.relationships_severance_event.domain_block": "Администратор сервера {from} заблокировал сервер {target}, где размещены учётные записи у {followersCount} ваших подписчиков и {followingCount, plural, one {# пользователя, на которого вы подписаны} other {# пользователей, на которых вы подписаны}}.",
"notification.relationships_severance_event.account_suspension": "Администратор сервера {from} заблокировал сервер {target}, поэтому вы больше не сможете получать обновления от людей с этого сервера и взаимодействовать с ними.",
"notification.relationships_severance_event.domain_block": "Администратор сервера {from} заблокировал сервер {target}, где размещены учётные записи {followersCount} ваших подписчиков и {followingCount, plural, one {# пользователя, на которого вы подписаны} other {# пользователей, на которых вы подписаны}}.",
"notification.relationships_severance_event.learn_more": "Узнать больше",
"notification.relationships_severance_event.user_domain_block": "Вы заблокировали сервер {target}, где размещены учётные записи у {followersCount} ваших подписчиков и {followingCount, plural, one {# пользователя, на которого вы подписаны} other {# пользователей, на которых вы подписаны}}.",
"notification.relationships_severance_event.user_domain_block": "Вы заблокировали сервер {target}, где размещены учётные записи {followersCount} ваших подписчиков и {followingCount, plural, one {# пользователя, на которого вы подписаны} other {# пользователей, на которых вы подписаны}}.",
"notification.status": "{name} опубликовал(а) новый пост",
"notification.update": "{name} отредактировал(а) пост",
"notification_requests.accept": "Принять",
@ -691,11 +691,11 @@
"notifications.policy.filter_not_followers_title": "Люди, не подписанные на вас",
"notifications.policy.filter_not_following_hint": "Пока вы не одобрите их вручную",
"notifications.policy.filter_not_following_title": "Люди, на которых вы не подписаны",
"notifications.policy.filter_private_mentions_hint": "Фильтруются, если только это не ответ на ваше собственное упоминание или если вы подписаны на отправителя",
"notifications.policy.filter_private_mentions_hint": "Не фильтруются ответы на ваши собственные упоминания, а также личные упоминания от пользователей, на которых вы подписаны",
"notifications.policy.filter_private_mentions_title": "Нежелательные личные упоминания",
"notifications.policy.title": "Управление уведомлениями",
"notifications_permission_banner.enable": "Включить уведомления",
"notifications_permission_banner.how_to_control": "Чтобы получать уведомления, даже когда Mastodon закрыт, включите уведомления на рабочем столе. После того как вы их включите, вы сможете тонко настроить, о каких видах взаимодействий вы будете оповещены через уведомления на рабочем столе, нажав на кнопку {icon} выше.",
"notifications_permission_banner.how_to_control": "Чтобы получать уведомления, даже когда Mastodon закрыт, включите уведомления на рабочем столе. После того как вы их включите, вы сможете тонко настроить виды взаимодействий, о которых вы будете оповещены через уведомления на рабочем столе, нажав на кнопку {icon} выше.",
"notifications_permission_banner.title": "Будьте в курсе происходящего",
"onboarding.follows.back": "Назад",
"onboarding.follows.done": "Готово",
@ -707,7 +707,7 @@
"onboarding.profile.display_name": "Отображаемое имя",
"onboarding.profile.display_name_hint": "Ваше полное имя или псевдоним…",
"onboarding.profile.note": "О себе",
"onboarding.profile.note_hint": "Вы можете @упоминать других людей или использовать #хештеги…",
"onboarding.profile.note_hint": "Вы можете @упоминать других людей, а также использовать #хештеги…",
"onboarding.profile.save_and_continue": "Сохранить и продолжить",
"onboarding.profile.title": "Создайте свой профиль",
"onboarding.profile.upload_avatar": "Загрузить фото профиля",
@ -733,7 +733,7 @@
"privacy.public.long": "Для кого угодно в интернете",
"privacy.public.short": "Публичный",
"privacy.unlisted.additional": "Похоже на «Публичный» за исключением того, что пост не появится ни в живых лентах, ни в лентах хештегов, ни в разделе «Актуальное», ни в поиске Mastodon, даже если вы разрешили поиск по своим постам в настройках профиля.",
"privacy.unlisted.long": "Меньше алгоритмических фанфар",
"privacy.unlisted.long": "Без продвижения алгоритмами",
"privacy.unlisted.short": "Тихий публичный",
"privacy_policy.last_updated": "Последнее обновление: {date}",
"privacy_policy.title": "Политика конфиденциальности",
@ -741,22 +741,22 @@
"refresh": "Обновить",
"regeneration_indicator.please_stand_by": "Пожалуйста, подождите.",
"regeneration_indicator.preparing_your_home_feed": "Готовим вашу ленту…",
"relative_time.days": "{number} д",
"relative_time.days": "{number} д.",
"relative_time.full.days": "{number, plural, one {# день} many {# дней} other {# дня}} назад",
"relative_time.full.hours": "{number, plural, one {# час} many {# часов} other {# часа}} назад",
"relative_time.full.just_now": "только что",
"relative_time.full.minutes": "{number, plural, one {# минуту} many {# минут} other {# минуты}} назад",
"relative_time.full.seconds": "{number, plural, one {# секунду} many {# секунд} other {# секунды}} назад",
"relative_time.hours": "{number} ч",
"relative_time.hours": "{number} ч.",
"relative_time.just_now": "только что",
"relative_time.minutes": "{number} мин",
"relative_time.seconds": "{number} с",
"relative_time.minutes": "{number} мин.",
"relative_time.seconds": "{number} с.",
"relative_time.today": "сегодня",
"reply_indicator.attachments": "{count, plural, one {# вложение} few {# вложения} other {# вложений}}",
"reply_indicator.cancel": "Отмена",
"reply_indicator.poll": "Опрос",
"report.block": "Заблокировать",
"report.block_explanation": "Вы не будете видеть посты этого пользователя. Этот пользователь не сможет видеть ваши посты и не сможет подписаться на вас. Пользователь будет знать, что вы его блокируете.",
"report.block_explanation": "Вы не будете видеть посты этого пользователя. Этот пользователь не сможет ни видеть ваши посты, ни подписаться на вас. Пользователь будет знать, что вы его блокируете.",
"report.categories.legal": "Нарушение закона",
"report.categories.other": "Другое",
"report.categories.spam": "Спам",
@ -780,7 +780,7 @@
"report.reasons.other": "Другое",
"report.reasons.other_description": "Проблема не соответствует ни одной категории",
"report.reasons.spam": "Это спам",
"report.reasons.spam_description": "Вредоносные ссылки, фальшивая активность (накрутка) или повторяющиеся однообразные ответы",
"report.reasons.spam_description": "Вредоносные ссылки, фальшивая активность или повторяющиеся однообразные ответы",
"report.reasons.violation": "Нарушаются правила сервера",
"report.reasons.violation_description": "Выберите, если знаете, что подобное нарушает определённые правила",
"report.rules.subtitle": "Выберите все подходящие варианты",
@ -826,7 +826,7 @@
"search_results.all": "Все",
"search_results.hashtags": "Хештеги",
"search_results.no_results": "Ничего не найдено.",
"search_results.no_search_yet": "Попробуйте поискать посты, профили или хэштеги.",
"search_results.no_search_yet": "Попробуйте поискать посты, профили или хештеги.",
"search_results.see_all": "Показать все",
"search_results.statuses": "Посты",
"search_results.title": "Поиск \"{q}\"",
@ -836,7 +836,7 @@
"server_banner.is_one_of_many": "{domain} — это один из многих независимых серверов Mastodon, которые вы можете использовать, чтобы присоединиться к сети Fediverse.",
"server_banner.server_stats": "Статистика сервера:",
"sign_in_banner.create_account": "Зарегистрироваться",
"sign_in_banner.follow_anyone": "Подписывайтесь на кого угодно в федивёрсе и читайте ленту в хронологическом порядке. Никаких алгоритмов, рекламы или кликбейта.",
"sign_in_banner.follow_anyone": "Подписывайтесь на кого угодно в федивёрсе и читайте ленту в хронологическом порядке. Никаких алгоритмов, рекламы и кликбейта.",
"sign_in_banner.mastodon_is": "Mastodon — лучший способ быть в курсе всего происходящего.",
"sign_in_banner.sign_in": "Войти",
"sign_in_banner.sso_redirect": "Вход/Регистрация",
@ -847,7 +847,7 @@
"status.bookmark": "Добавить в закладки",
"status.cancel_reblog_private": "Отменить продвижение",
"status.cannot_reblog": "Этот пост не может быть продвинут",
"status.continued_thread": "Продолжение треда",
"status.continued_thread": "Продолжение предыдущего поста",
"status.copy": "Скопировать ссылку на пост",
"status.delete": "Удалить",
"status.detailed_status": "Подробный просмотр обсуждения",
@ -860,8 +860,8 @@
"status.favourite": "Добавить в избранное",
"status.favourites": "{count, plural, one {звёздочка} few {звёздочки} other {звёздочек}}",
"status.filter": "Фильтровать этот пост",
"status.history.created": "{name} создал(а) {date}",
"status.history.edited": "{name} отредактировал(а) {date}",
"status.history.created": "{name} создал(а) пост {date}",
"status.history.edited": "{name} отредактировал(а) пост {date}",
"status.load_more": "Загрузить ещё",
"status.media.open": "Нажмите, чтобы открыть.",
"status.media.show": "Нажмите, чтобы показать",
@ -874,13 +874,17 @@
"status.pin": "Закрепить в профиле",
"status.quote_error.filtered": "Скрыто одним из ваших фильтров",
"status.quote_error.not_found": "Пост не может быть показан.",
"status.quote_error.pending_approval": "Разрешение на цитирование от автора оригинального поста пока не получено.",
"status.quote_error.rejected": "Автор оригинального поста запретил его цитировать.",
"status.quote_error.removed": "Пост был удалён его автором.",
"status.quote_error.unauthorized": "Этот пост для вас недоступен.",
"status.quote_post_author": "Пост пользователя {name}",
"status.read_more": "Читать далее",
"status.reblog": "Продвинуть",
"status.reblog_private": "Продвинуть для своей аудитории",
"status.reblogged_by": "{name} продвинул(а)",
"status.reblogs": "{count, plural, one {продвижение} few {продвижения} other {продвижений}}",
"status.reblogs.empty": "Никто ещё не продвинул этот пост. Все пользователи, продвинувшие этот пост, будут показаны здесь.",
"status.reblogs.empty": "Никто ещё не продвинул этот пост. Все пользователи, которые продвинут этот пост, будут показаны здесь.",
"status.redraft": "Удалить и исправить",
"status.remove_bookmark": "Убрать из закладок",
"status.remove_favourite": "Убрать из избранного",
@ -900,7 +904,7 @@
"status.uncached_media_warning": "Предварительный просмотр недоступен",
"status.unmute_conversation": "Не игнорировать обсуждение",
"status.unpin": "Открепить от профиля",
"subscribed_languages.lead": "Посты лишь на выбранных языках будут появляться в вашей домашней ленте и в списках после изменения. Снимите выбор, чтобы получать посты на всех языках.",
"subscribed_languages.lead": "Посты лишь на выбранных языках будут появляться в вашей домашней ленте и списках после изменения. Снимите выбор, чтобы получать посты на всех языках.",
"subscribed_languages.save": "Сохранить изменения",
"subscribed_languages.target": "Изменить языки подписки для {target}",
"tabs_bar.home": "Главная",

View File

@ -220,6 +220,12 @@
"confirmations.delete_list.message": "Är du säker på att du vill radera denna lista permanent?",
"confirmations.delete_list.title": "Ta bort listan?",
"confirmations.discard_draft.confirm": "Släng bort och fortsätt",
"confirmations.discard_draft.edit.cancel": "Fortsätt redigera",
"confirmations.discard_draft.edit.message": "Fortsätter du, kommer ändringarna till inlägget att förkastas.",
"confirmations.discard_draft.edit.title": "Vill du förkasta ändringarna i inlägget?",
"confirmations.discard_draft.post.cancel": "Återuppta utkast",
"confirmations.discard_draft.post.message": "Fortsätter du, kommer inlägget du skriver att förkastas.",
"confirmations.discard_draft.post.title": "Vill du förkasta utkastet?",
"confirmations.discard_edit_media.confirm": "Kasta",
"confirmations.discard_edit_media.message": "Du har osparade ändringar till mediabeskrivningen eller förhandsgranskningen, kasta bort dem ändå?",
"confirmations.follow_to_list.confirm": "Följ och lägg till i listan",
@ -558,6 +564,8 @@
"navigation_bar.follows_and_followers": "Följer och följare",
"navigation_bar.import_export": "Importera och exportera",
"navigation_bar.lists": "Listor",
"navigation_bar.live_feed_local": "Direkt flöde (lokalt)",
"navigation_bar.live_feed_public": "Direkt flöde (publikt)",
"navigation_bar.logout": "Logga ut",
"navigation_bar.moderation": "Moderering",
"navigation_bar.more": "Fler",
@ -797,6 +805,7 @@
"report_notification.categories.violation": "Regelöverträdelse",
"report_notification.categories.violation_sentence": "regelöverträdelse",
"report_notification.open": "Öppna rapport",
"search.clear": "Rensa sökning",
"search.no_recent_searches": "Inga sökningar nyligen",
"search.placeholder": "Sök",
"search.quick_action.account_search": "Profiler som matchar {x}",

View File

@ -98,7 +98,7 @@
"alert.rate_limited.message": "Vui lòng thử lại sau {retry_time, time, medium}.",
"alert.rate_limited.title": "Vượt giới hạn",
"alert.unexpected.message": "Đã xảy ra lỗi không mong muốn.",
"alert.unexpected.title": "Ốiii!",
"alert.unexpected.title": "Ốii!",
"alt_text_badge.title": "Văn bản thay thế",
"alt_text_modal.add_alt_text": "Thêm văn bản thay thế",
"alt_text_modal.add_text_from_image": "Thêm văn bản từ ảnh",
@ -276,7 +276,7 @@
"domain_block_modal.you_will_lose_num_followers": "Bạn sẽ mất {followersCount, plural, other {{followersCountDisplay} người theo dõi}} và {followingCount, plural, other {{followingCountDisplay} người bạn theo dõi}}.",
"domain_block_modal.you_will_lose_relationships": "Bạn sẽ mất tất cả người theo dõi và những người bạn theo dõi từ máy chủ này.",
"domain_block_modal.you_wont_see_posts": "Bạn không còn thấy tút hoặc thông báo từ thành viên máy chủ này.",
"domain_pill.activitypub_lets_connect": "Nó cho phép bạn kết nối và tương tác với mọi người không chỉ trên Mastodon mà còn trên các ứng dụng xã hội khác.",
"domain_pill.activitypub_lets_connect": "Nó cho phép bạn kết nối và tương tác với mọi người, không chỉ trên Mastodon mà còn trên các nền tảng khác.",
"domain_pill.activitypub_like_language": "ActivityPub giống như ngôn ngữ Mastodon giao tiếp với các mạng xã hội khác.",
"domain_pill.server": "Máy chủ",
"domain_pill.their_handle": "Địa chỉ Mastodon:",

View File

@ -218,6 +218,9 @@
"confirmations.delete_list.confirm": "删除",
"confirmations.delete_list.message": "确定要永久删除此列表吗?",
"confirmations.delete_list.title": "确定要删除列表?",
"confirmations.discard_draft.confirm": "放弃并继续",
"confirmations.discard_draft.edit.cancel": "恢复编辑",
"confirmations.discard_draft.post.cancel": "恢复草稿",
"confirmations.discard_edit_media.confirm": "丢弃",
"confirmations.discard_edit_media.message": "你还有未保存的媒体描述或预览修改,仍要丢弃吗?",
"confirmations.follow_to_list.confirm": "关注并添加到列表",
@ -534,8 +537,10 @@
"mute_modal.you_wont_see_mentions": "你看不到提及对方的嘟文。",
"mute_modal.you_wont_see_posts": "对方可以看到你的嘟文,但是你看不到对方的。",
"navigation_bar.about": "关于",
"navigation_bar.account_settings": "密码与安全",
"navigation_bar.administration": "管理",
"navigation_bar.advanced_interface": "在高级网页界面中打开",
"navigation_bar.automated_deletion": "自动删除嘟文",
"navigation_bar.blocks": "已屏蔽的用户",
"navigation_bar.bookmarks": "书签",
"navigation_bar.direct": "私下提及",

View File

@ -375,7 +375,7 @@
"follow_suggestions.dismiss": "不再顯示",
"follow_suggestions.featured_longer": "{domain} 團隊精選",
"follow_suggestions.friends_of_friends_longer": "受您跟隨之使用者愛戴的風雲人物",
"follow_suggestions.hints.featured": "這個個人檔案是 {domain} 管理團隊精心挑選。",
"follow_suggestions.hints.featured": "這個個人檔案是 {domain} 管理團隊精心挑選。",
"follow_suggestions.hints.friends_of_friends": "這個個人檔案於您跟隨的帳號中很受歡迎。",
"follow_suggestions.hints.most_followed": "這個個人檔案是 {domain} 中最受歡迎的帳號之一。",
"follow_suggestions.hints.most_interactions": "這個個人檔案最近於 {domain} 受到非常多關注。",

View File

@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
? accountJSON.username
: accountJSON.display_name;
const accountNote =
accountJSON.note && accountJSON.note !== '<p></p>' ? accountJSON.note : '';
return AccountFactory({
...accountJSON,
moved: moved?.id,
@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
escapeTextContentForBrowser(displayName),
emojiMap,
),
note_emojified: emojify(accountJSON.note, emojiMap),
note_plain: unescapeHTML(accountJSON.note),
note_emojified: emojify(accountNote, emojiMap),
note_plain: unescapeHTML(accountNote),
url:
accountJSON.url.startsWith('http://') ||
accountJSON.url.startsWith('https://')

View File

@ -939,6 +939,7 @@ body > [data-popper-placement] {
line-height: 20px;
letter-spacing: 0.1px;
color: $highlight-text-color;
background-color: var(--input-background-color);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
@ -1856,7 +1857,10 @@ body > [data-popper-placement] {
}
.status__quote {
--quote-margin: 36px;
// --status-gutter-width is currently only set inside of
// .notification-ungrouped, so everywhere else this will fall back
// to the pixel values
--quote-margin: var(--status-gutter-width, 36px);
position: relative;
margin-block-start: 16px;
@ -1867,7 +1871,7 @@ body > [data-popper-placement] {
border: var(--nested-card-border);
@container (width > 460px) {
--quote-margin: 56px;
--quote-margin: var(--status-gutter-width, 56px);
}
}
@ -2848,7 +2852,6 @@ a.account__display-name {
&__pane {
height: 100%;
overflow: hidden;
pointer-events: none;
display: flex;
justify-content: flex-end;
min-width: 285px;
@ -2860,7 +2863,6 @@ a.account__display-name {
&__inner {
position: fixed;
width: 285px;
pointer-events: auto;
height: 100%;
}
}
@ -3874,16 +3876,18 @@ a.account__display-name {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 12px;
font-size: 16px;
font-weight: 400;
padding: 12px;
text-decoration: none;
overflow: hidden;
white-space: nowrap;
border: 0;
background: transparent;
color: $secondary-text-color;
background: transparent;
border: 0;
border-left: 4px solid transparent;
box-sizing: border-box;
&:hover,
&:focus,
@ -10816,21 +10820,23 @@ noscript {
}
}
.status {
.status:not(.status--is-quote) {
border: 0;
padding: 0;
&__avatar {
width: 40px;
height: 40px;
}
}
.status__wrapper-direct {
background: transparent;
}
$icon-margin: 48px; // 40px avatar + 8px gap
.status {
// 40px avatar + 8px gap
--status-gutter-width: 48px;
}
.status--is-quote {
--status-gutter-width: 0;
}
.status__content,
.status__action-bar,
@ -10844,16 +10850,16 @@ noscript {
.hashtag-bar,
.content-warning,
.filter-warning {
margin-inline-start: $icon-margin;
width: calc(100% - $icon-margin);
margin-inline-start: var(--status-gutter-width);
width: calc(100% - var(--status-gutter-width));
}
.more-from-author {
width: calc(100% - $icon-margin + 2px);
width: calc(100% - var(--status-gutter-width) + 2px);
}
.status__content__read-more-button {
margin-inline-start: $icon-margin;
margin-inline-start: var(--status-gutter-width);
}
.notification__report {

View File

@ -28,7 +28,7 @@ class AnnualReport::Archetype < AnnualReport::Source
end
def polls_count
@polls_count ||= report_statuses.where.not(poll_id: nil).count
@polls_count ||= report_statuses.only_polls.count
end
def reblogs_count
@ -36,7 +36,7 @@ class AnnualReport::Archetype < AnnualReport::Source
end
def replies_count
@replies_count ||= report_statuses.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count
@replies_count ||= report_statuses.where.not(in_reply_to_id: nil).not_replying_to_account(@account).count
end
def standalone_count

View File

@ -18,7 +18,7 @@ class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
private
def commonly_interacted_with_accounts
report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count
report_statuses.not_replying_to_account(@account).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count
end
def minimum_interaction_count

View File

@ -2,20 +2,44 @@
class AnnualReport::TopStatuses < AnnualReport::Source
def generate
top_reblogs = base_scope.order(reblogs_count: :desc).first&.id
top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id
top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id
{
top_statuses: {
by_reblogs: top_reblogs&.to_s,
by_favourites: top_favourites&.to_s,
by_replies: top_replies&.to_s,
by_reblogs: status_identifier(most_reblogged_status),
by_favourites: status_identifier(most_favourited_status),
by_replies: status_identifier(most_replied_status),
},
}
end
private
def status_identifier(status)
status.id.to_s if status.present?
end
def most_reblogged_status
base_scope
.order(reblogs_count: :desc)
.first
end
def most_favourited_status
base_scope
.excluding(most_reblogged_status)
.order(favourites_count: :desc)
.first
end
def most_replied_status
base_scope
.excluding(most_reblogged_status, most_favourited_status)
.order(replies_count: :desc)
.first
end
def base_scope
report_statuses.public_visibility.joins(:status_stat)
report_statuses
.public_visibility
.joins(:status_stat)
end
end

View File

@ -6,7 +6,7 @@ class AnnualReport::TypeDistribution < AnnualReport::Source
type_distribution: {
total: report_statuses.count,
reblogs: report_statuses.only_reblogs.count,
replies: report_statuses.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count,
replies: report_statuses.where.not(in_reply_to_id: nil).not_replying_to_account(@account).count,
standalone: report_statuses.without_replies.without_reblogs.count,
},
}

View File

@ -161,7 +161,7 @@ class AccountStatusesCleanupPolicy < ApplicationRecord
end
def without_poll_scope
Status.where(poll_id: nil)
Status.without_polls
end
def without_popular_scope

View File

@ -3,10 +3,12 @@
module Account::AttributionDomains
extend ActiveSupport::Concern
ATTRIBUTION_DOMAINS_LIMIT = 100
included do
normalizes :attribution_domains, with: ->(arr) { arr.filter_map { |str| str.to_s.strip.delete_prefix('http://').delete_prefix('https://').delete_prefix('*.').presence }.uniq }
validates :attribution_domains, domain: true, length: { maximum: 100 }, if: -> { local? && will_save_change_to_attribution_domains? }
validates :attribution_domains, domain: true, length: { maximum: ATTRIBUTION_DOMAINS_LIMIT }, if: -> { local? && will_save_change_to_attribution_domains? }
end
def can_be_attributed_from?(domain)

View File

@ -10,12 +10,6 @@ module DatabaseViewRecord
concurrently: true,
cascade: false
)
rescue ActiveRecord::StatementInvalid
Scenic.database.refresh_materialized_view(
table_name,
concurrently: false,
cascade: false
)
end
end

View File

@ -4,14 +4,8 @@ module FollowLimitable
extend ActiveSupport::Concern
included do
validates_with FollowLimitValidator, on: :create, unless: :bypass_follow_limit?
end
validates_with FollowLimitValidator, on: :create, unless: :bypass_follow_limit
def bypass_follow_limit=(value)
@bypass_follow_limit = value
end
def bypass_follow_limit?
@bypass_follow_limit
attribute :bypass_follow_limit, :boolean, default: false
end
end

View File

@ -17,12 +17,14 @@ class CustomFilterStatus < ApplicationRecord
belongs_to :custom_filter
belongs_to :status
validates :status, uniqueness: { scope: :custom_filter }
validate :validate_status_access
validates :status_id, uniqueness: { scope: :custom_filter_id }
validate :validate_status_access, if: [:custom_filter_account, :status]
delegate :account, to: :custom_filter, prefix: true, allow_nil: true
private
def validate_status_access
errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show?
errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter_account, status).show?
end
end

View File

@ -14,7 +14,7 @@ class FollowRecommendationMute < ApplicationRecord
belongs_to :account
belongs_to :target_account, class_name: 'Account'
validates :target_account, uniqueness: { scope: :account_id }
validates :target_account_id, uniqueness: { scope: :account_id }
after_commit :invalidate_follow_recommendations_cache

View File

@ -24,7 +24,7 @@ class GeneratedAnnualReport < ApplicationRecord
end
def view!
update!(viewed_at: Time.now.utc)
touch(:viewed_at)
end
def account_ids

View File

@ -31,7 +31,7 @@ class Quote < ApplicationRecord
belongs_to :quoted_account, class_name: 'Account', optional: true
before_validation :set_accounts
before_validation :set_activity_uri, only: :create, if: -> { account.local? && quoted_account&.remote? }
validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? }
validate :validate_visibility
@ -69,4 +69,8 @@ class Quote < ApplicationRecord
errors.add(:quoted_status_id, :visibility_mismatch)
end
def set_activity_uri
self.activity_uri = [ActivityPub::TagManager.instance.uri_for(account), '/quote_requests/', SecureRandom.uuid].join
end
end

View File

@ -121,7 +121,10 @@ class Status < ApplicationRecord
scope :without_replies, -> { not_reply.or(reply_to_account) }
scope :not_reply, -> { where(reply: false) }
scope :only_reblogs, -> { where.not(reblog_of_id: nil) }
scope :only_polls, -> { where.not(poll_id: nil) }
scope :without_polls, -> { where(poll_id: nil) }
scope :reply_to_account, -> { where(arel_table[:in_reply_to_account_id].eq arel_table[:account_id]) }
scope :not_replying_to_account, ->(account) { where.not(in_reply_to_account: account) }
scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }

View File

@ -49,8 +49,8 @@ class Tag < ApplicationRecord
validates :name, presence: true, format: { with: HASHTAG_NAME_RE }
validates :display_name, format: { with: HASHTAG_NAME_RE }
validate :validate_name_change, if: -> { !new_record? && name_changed? }
validate :validate_display_name_change, if: -> { !new_record? && display_name_changed? }
validate :validate_name_change, on: :update, if: :name_changed?
validate :validate_display_name_change, on: :update, if: :display_name_changed?
scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
scope :usable, -> { where(usable: [true, nil]) }

View File

@ -90,6 +90,7 @@ class User < ApplicationRecord
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: nil
has_many :backups, inverse_of: :user, dependent: nil
has_many :invites, inverse_of: :user, dependent: nil
has_many :login_activities, inverse_of: :user, dependent: :destroy
has_many :markers, inverse_of: :user, dependent: :destroy
has_many :webauthn_credentials, dependent: :destroy
has_many :ips, class_name: 'UserIp', inverse_of: :user, dependent: nil

View File

@ -35,6 +35,7 @@ class UserSettings
setting :expand_content_warnings, default: false
setting :display_media, default: 'default', in: %w(default show_all hide_all)
setting :auto_play, default: false
setting :emoji_style, default: 'auto', in: %w(auto native twemoji)
end
namespace :notification_emails do

View File

@ -7,7 +7,7 @@ class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer
attribute :virtual_object, key: :object
def id
object.activity_uri || [ActivityPub::TagManager.instance.uri_for(object.target_account), '#quote_requests/', object.id].join
object.activity_uri
end
def type

View File

@ -5,7 +5,7 @@ class InitialStateSerializer < ActiveModel::Serializer
attributes :meta, :compose, :accounts,
:media_attachments, :settings,
:languages
:languages, :features
attribute :critical_updates_pending, if: -> { object&.role&.can?(:view_devops) && SoftwareUpdate.check_enabled? }
@ -85,6 +85,10 @@ class InitialStateSerializer < ActiveModel::Serializer
LanguagesHelper::SUPPORTED_LOCALES.map { |(key, value)| [key, value[0], value[1]] }
end
def features
Mastodon::Feature.enabled_features
end
private
def default_meta_store

View File

@ -66,6 +66,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
update_interaction_policies!
update_poll!(allow_significant_changes: false)
queue_poll_notifications!
update_quote_approval!
update_counts!
end
end
@ -270,6 +271,23 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
end
end
# This method is only concerned with approval and skips other meaningful changes,
# as it is used instead of `update_quote!` in implicit updates
def update_quote_approval!
quote_uri = @status_parser.quote_uri
return unless quote_uri.present? && @status.quote.present?
quote = @status.quote
return if quote.quoted_status.present? && ActivityPub::TagManager.instance.uri_for(quote.quoted_status) != quote_uri
approval_uri = @status_parser.quote_approval_uri
approval_uri = nil if unsupported_uri_scheme?(approval_uri)
quote.update(approval_uri: approval_uri, state: :pending, legacy: @status_parser.legacy_quote?) if quote.approval_uri != @status_parser.quote_approval_uri
fetch_and_verify_quote!(quote, quote_uri)
end
def update_quote!
quote_uri = @status_parser.quote_uri

View File

@ -56,7 +56,7 @@
.fields-group
= f.input :date_of_birth,
as: :date_of_birth,
hint: t('simple_form.hints.user.date_of_birth', count: Setting.min_age.to_i),
hint: t('simple_form.hints.user.date_of_birth', count: Setting.min_age.to_i, domain: site_hostname),
required: true,
wrapper: :with_block_label

View File

@ -31,6 +31,17 @@
label: I18n.t('simple_form.labels.defaults.setting_theme'),
wrapper: :with_label
- if Mastodon::Feature.modern_emojis_enabled?
.fields-group
= f.simple_fields_for :settings, current_user.settings do |ff|
= ff.input :'web.emoji_style',
collection: %w(auto twemoji native),
include_blank: false,
hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'),
label: I18n.t('simple_form.labels.defaults.setting_emoji_style'),
label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) },
wrapper: :with_label
- unless I18n.locale == :en
.flash-message.translation-prompt
#{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: '_blank', rel: 'noopener')}

View File

@ -5,24 +5,14 @@ require 'linzer/message/adapter/http_gem/response'
module Linzer::Message::Adapter
module ActionDispatch
class Response < Linzer::Message::Adapter::Abstract
def initialize(operation, **_options) # rubocop:disable Lint/MissingSuper
@operation = operation
end
def header(name)
@operation.headers[name]
end
def attach!(signature)
signature.to_h.each { |h, v| @operation.headers[h] = v }
end
class Response < Linzer::Message::Adapter::Generic::Response
private
# Incomplete, but sufficient for FASP
def [](field_name)
return @operation.status if field_name == '@status'
@operation.headers[field_name]
def derived(name)
case name.value
when '@status' then @operation.status
end
end
end
end

View File

@ -15,6 +15,9 @@ be:
user/invite_request:
text: Прычына
errors:
attributes:
domain:
invalid: не з’яўляецца сапраўдным даменным імем
models:
account:
attributes:
@ -37,7 +40,7 @@ be:
attributes:
account_id:
taken: ужо ў спісе
must_be_following: мусіць быць падпісаным уліковым запісам
must_be_following: мусіць быць карыстальнікам у вашых падпісках
status:
attributes:
reblog:

View File

@ -49,8 +49,14 @@ en-GB:
attributes:
reblog:
taken: of post already exists
terms_of_service:
attributes:
effective_date:
too_soon: is too soon, must be later than %{date}
user:
attributes:
date_of_birth:
below_limit: is below the age limit
email:
blocked: uses a disallowed e-mail provider
unreachable: does not seem to exist

View File

@ -18,7 +18,7 @@ be:
link_verified_on: Права ўласнасці на гэтую спасылку праверана %{date}
nothing_here: Тут нічога няма!
pin_errors:
following: Вы павінны быць падпісаны на чалавека, якога жадаеце рэкамендаваць
following: Трэба падпісацца на чалавека, якога вы хочаце рэкамендаваць
posts:
few: Допісы
many: Допісаў
@ -28,6 +28,8 @@ be:
admin:
account_actions:
action: Выканаць дзеянне
already_silenced: Гэты ўліковы запіс ужо абмежаваны.
already_suspended: Гэты ўліковы запіс ужо прыпынены.
title: Мадэраваць %{acct}
account_moderation_notes:
create: Пакінуць нататку
@ -107,7 +109,7 @@ be:
no_role_assigned: Роля не прызначана
not_subscribed: Не падпісаны
pending: Чакае праверкі
perform_full_suspension: Выключыць
perform_full_suspension: Прыпыніць
previous_strikes: Ранейшыя скаргі
previous_strikes_description_html:
few: Гэты ўліковы запіс мае <strong>%{count}</strong> скаргі.
@ -164,7 +166,7 @@ be:
unconfirmed_email: Непацверджаны адрас эл. пошты
undo_sensitized: Прыбраць прымусовую пазнаку далікатнасці
undo_silenced: Адмяніць ліміт
undo_suspension: Разблакаваць карыстальніка
undo_suspension: Разблакіраваць карыстальніка
unsilenced_msg: З уліковага запісу %{username} зняты абмежаванні
unsubscribe: Адпісацца
unsuspended_msg: Уліковы запіс %{username} адноўлены
@ -189,7 +191,7 @@ be:
create_domain_block: Стварыць даменны блок
create_email_domain_block: Стварыць даменны блок электроннай пошты
create_ip_block: Стварыць IP правіла
create_relay: Стварыць паўтор
create_relay: Стварыць рэтранслятар
create_unavailable_domain: Стварыць недаступны Дамен
create_user_role: Стварыць ролю
demote_user: Панізіць карыстальніка
@ -201,22 +203,22 @@ be:
destroy_email_domain_block: Выдаліць даменны блок электроннай пошты
destroy_instance: Вычысціць дамен
destroy_ip_block: Выдаліць IP правіла
destroy_relay: Выдаліць паўтор
destroy_relay: Выдаліць рэтранслятар
destroy_status: Выдаліць допіс
destroy_unavailable_domain: Выдаліць недаступны дамен
destroy_user_role: Выдаліць ролю
disable_2fa_user: Адключыць двухэтапнае спраўджанне
disable_custom_emoji: Адключыць адвольныя эмодзі
disable_relay: Выключыць паўтор
disable_relay: Выключыць рэтранслятар
disable_sign_in_token_auth_user: Адключыць аўтарызацыю праз электронную пошту для карыстальніка
disable_user: Адключыць карыстальніка
enable_custom_emoji: Уключыць адвольныя эмодзі
enable_relay: Уключыць паўтор
enable_relay: Уключыць рэтранслятар
enable_sign_in_token_auth_user: Уключыць аўтарызацыю праз электронную пошту для карыстальніка
enable_user: Уключыць карыстальніка
memorialize_account: Запомніць уліковы запіс
promote_user: Павысіць правы Карыстальніка
publish_terms_of_service: Апублікаваць Умовы выкарыстаньня
publish_terms_of_service: Апублікаваць умовы выкарыстання
reject_appeal: Адхіліць абскарджанне
reject_user: Адмовіць карыстальніку
remove_avatar_user: Выдаліць аватар
@ -226,13 +228,13 @@ be:
resolve_report: Вырашыць скаргу
sensitive_account: Уліковы запіс прымусова пазначаны як далікатны
silence_account: Абмежаваць уліковы запіс
suspend_account: Выключыць уліковы запіс
suspend_account: Прыпыніць уліковы запіс
unassigned_report: Прыбраць прызначэнне скаргі
unblock_email_account: Разблакіраваць электронны адрас
unsensitive_account: Прымусовая пазнака далікатнасці прыбраная
unsilence_account: Уліковы запіс больш не абмежаваны
unsuspend_account: Аднавіць уліковы запіс
update_announcement: Абнавіць аб'яву
update_announcement: Абнавіць абяву
update_custom_emoji: Абнавіць адвольныя эмодзі
update_domain_block: Актуалізаваць блакіроўку дамена
update_ip_block: Абнавіць IP правіла
@ -254,34 +256,34 @@ be:
create_domain_block_html: "%{name} заблакіраваў дамен %{target}"
create_email_domain_block_html: "%{name} заблакіраваў дамен эл. пошты %{target}"
create_ip_block_html: "%{name} стварыў правіла для IP %{target}"
create_relay_html: "%{name} стварыў(-а) паўтор %{target}"
create_relay_html: "%{name} стварыў(-ла) рэтранслятар %{target}"
create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}"
create_user_role_html: "%{name} зрабіў ролю %{target}"
demote_user_html: "%{name} прыбраў карыстальніка %{target}"
destroy_announcement_html: "%{name} выдаліў аб'яву %{target}"
destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}"
destroy_custom_emoji_html: "%{name} выдаліў(а) эмоджі %{target}"
destroy_domain_allow_html: "%{name} зняў дазвол на аб'яднанне з даменам %{target}\n"
destroy_custom_emoji_html: "%{name} выдаліў(-ла) эмодзі %{target}"
destroy_domain_allow_html: "%{name} зняў(-ла) дазвол на аб’яднанне з даменам %{target}"
destroy_domain_block_html: "%{name} разблакаваў дамен %{target}"
destroy_email_domain_block_html: "%{name} разблакіраваў дамен эл. пошты %{target}"
destroy_instance_html: "%{name} цалкам прыбраў дамен %{target}"
destroy_ip_block_html: "%{name} выдаліў правіла для IP %{target}"
destroy_relay_html: "%{name} выдаліў(-ла) паўтор %{target}"
destroy_relay_html: "%{name} выдаліў(-ла) рэтранслятар %{target}"
destroy_status_html: "%{name} выдаліў допіс %{target}"
destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}"
destroy_user_role_html: "%{name} выдаліў ролю %{target}"
disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}"
disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}"
disable_relay_html: "%{name} выключыў(-ла) паўтор %{target}"
disable_relay_html: "%{name} выключыў(-ла) рэтранслятар %{target}"
disable_sign_in_token_auth_user_html: "%{name} адключыў уваход праз эл. пошту для %{target}"
disable_user_html: "%{name} адключыў уваход для карыстальніка %{target}"
enable_custom_emoji_html: "%{name} уключыў эмодзі %{target}"
enable_relay_html: "%{name} уключыў(-ла) паўтор %{target}"
enable_relay_html: "%{name} уключыў(-ла) рэтранслятар %{target}"
enable_sign_in_token_auth_user_html: "%{name} уключыў уваход праз эл. пошту для %{target}"
enable_user_html: "%{name} уключыў уваход для карыстальніка %{target}"
memorialize_account_html: Карыстальнік %{name} пераключыў уліковы запіс %{target} у старонку памяці
promote_user_html: "%{name} павысіў карыстальніка %{target}"
publish_terms_of_service_html: "%{name} апублікаваў абнаўленьне ўмоваў абслугоўваньня"
publish_terms_of_service_html: "%{name} апублікаваў(-ла) абнаўленні ва ўмовах выкарыстання"
reject_appeal_html: "%{name} адхіліў запыт на абскарджанне %{target}"
reject_user_html: "%{name} адхіліў рэгістрацыю з %{target}"
remove_avatar_user_html: "%{name} выдаліў аватар %{target}"
@ -291,13 +293,13 @@ be:
resolve_report_html: "%{name} вырашыў скаргу %{target}"
sensitive_account_html: "%{name} адзначыў медыя %{target} як далікатнае"
silence_account_html: "%{name} абмежаваў уліковы запіс %{target}"
suspend_account_html: Уліковы запіс %{target} выключаны %{name}
suspend_account_html: Уліковы запіс %{target} прыпынены %{name}
unassigned_report_html: "%{name} знял(а) прызначэнне скаргі %{target}"
unblock_email_account_html: "%{name} разблакіраваў эл. пошту %{target}"
unsensitive_account_html: "%{name} зняў адзнаку далікатнае з медыя %{target}"
unsilence_account_html: "%{name} зняў ліміт з уліковага запісу %{target}"
unsuspend_account_html: Уліковы запіс %{target} адноўлены %{name}
update_announcement_html: "%{name} абнавіў аб'яву %{target}"
update_announcement_html: "%{name} абнавіў(-ла) абяву %{target}"
update_custom_emoji_html: "%{name} абнавіў эмодзі %{target}"
update_domain_block_html: "%{name} абнавіў блакіроўку дамена для %{target}"
update_ip_block_html: "%{name} змяніў правіла для IP %{target}"
@ -311,7 +313,7 @@ be:
title: Аўдыт
unavailable_instance: "(імя дамена недаступнае)"
announcements:
destroyed_msg: Аб'ява паспяхова выдалена!
destroyed_msg: Аб’ява выдалена!
edit:
title: Рэдагаваць абвестку
empty: Не знайдзена ніводнай абвесткі
@ -325,26 +327,26 @@ be:
scheduled_msg: Аб'ява запланавана да публікацыі!
title: Аб'явы
unpublish: Зняць з публікацыі
unpublished_msg: Аб'ява паспяхова схована!
updated_msg: Аб'ява паспяхова адноўлена!
unpublished_msg: Аб’ява схавана!
updated_msg: Аб’ява абноўлена!
critical_update_pending: Чакаецца абнаўленне
custom_emojis:
assign_category: Прызначыць катэгорыю
by_domain: Дамен
copied_msg: Лакальная копія эмодзі паспяхова створана
copied_msg: Створана лакальная копія эмодзі
copy: Скапіраваць
copy_failed_msg: Немагчыма зрабіць лакальную копію гэтага эмодзі
create_new_category: Стварыць новую катэгорыю
created_msg: Эмодзі паспяхова створана!
delete: Выдаліць
destroyed_msg: Смайлік паспяхова прыбраны!
destroyed_msg: Уласнае эмодзі знішчана!
disable: Адключыць
disabled: Адключана
disabled_msg: Смайлік паспяхова адключаны
emoji: Эмодзі
enable: Уключыць
enabled: Уключана
enabled_msg: Смайлік паспяхова ўключаны
enabled_msg: Эмодзі паспяхова ўключана
image_hint: PNG або GIF да %{size}
list: Паказваць
listed: Паказваецца
@ -423,7 +425,7 @@ be:
domain: Дамен
edit: Рэдагаваць блакіраванне дамену
existing_domain_block: Вы ўжо налажылі стражэйшыя абмежаванні на %{name}.
existing_domain_block_html: Вы ўжо налажылі стражэйшыя абмежаванні на %{name}, спачатку вам неабходна <a href="%{unblock_url}">разблакаваць гэты акаўнт</a>.
existing_domain_block_html: Вы ўжо наклалі больш строгія абмежаванні на %{name}, спачатку вам трэба <a href="%{unblock_url}">разблакіраваць гэты ўліковы запіс</a>.
export: Экспарт
import: Імпарт
new:
@ -433,7 +435,7 @@ be:
desc_html: "<strong>Абмежаванне</strong> зробіць допісы людзей з гэтага дамену нябачнымі для тых, хто на іх не падпісаны. <strong>Выключэнне</strong> выдаліць усё змесціва, медыя і даныя профіляў дамену з вашага серверу. <strong>«Нічога»</strong> проста адхіліць медыя файлы."
noop: Пуста
silence: Абмежаваць
suspend: Выключыць
suspend: Прыпыніць
title: Новы блок дамену
no_domain_block_selected: Блакіроўкі даменаў не былі змененыя, таму што ні адзін з іх не быў выбраны
not_permitted: Вам забаронена выконваць гэта дзеянне
@ -478,7 +480,7 @@ be:
no_file: Файл не выбраны
export_domain_blocks:
import:
description_html: Вы збіраецеся імпартаваць спіс даменных блокаў. Калі ласка, праглядзіце гэты спіс вельмі ўважліва, асабліва калі вы асабіста не з'яўляецеся аўтарам гэтага спісу.
description_html: Вы збіраецеся імпартаваць спіс даменных блокаў. Праглядзіце гэты спіс вельмі ўважліва, асабліва калі вы асабіста не з’яўляецеся аўтарам гэтага спіса.
existing_relationships_warning: Наяўныя зносіны
private_comment_description_html: 'Каб дапамагчы вам адсочваць, адкуль паходзяць імпартаваныя блокі, імпартаваныя блокі будуць створаны з наступным прыватным каментарыем: <q>%{comment}</q>'
private_comment_template: Імпартавана з %{source} %{date}
@ -487,6 +489,26 @@ be:
new:
title: Імпарт блакіровак дамену
no_file: Файл не выбраны
fasp:
debug:
callbacks:
delete: Выдаліць
ip: IP-адрас
providers:
active: Актыўны
base_url: Базавы URL-адрас
delete: Выдаліць
edit: Рэдагаваць пастаўшчыка
finish_registration: Завяршыць рэгістрацыю
name: Назва
providers: Пастаўшчыкі
public_key_fingerprint: Лічбавы адбітак публічнага ключа
registrations:
confirm: Пацвердзіць
reject: Адхіліць
save: Захаваць
sign_in: Увайсці
title: FASP
follow_recommendations:
description_html: "<strong>Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт</strong>. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы."
language: Для мовы
@ -527,7 +549,7 @@ be:
reject_media: Адхіліць мультымедыя
reject_reports: Адхіліць справаздачы
silence: Ліміт
suspend: Выключыць
suspend: Прыпыніць
policy: Палітыка
reason: Публічная прычына
title: Палітыкі кантэнту
@ -561,6 +583,11 @@ be:
all: Усе
limited: Абмежаваныя
title: Мадэрацыя
moderation_notes:
create: Дадаць нататку мадэратара
created_msg: Нататка мадэратара для экзэмпляра сервера створана!
destroyed_msg: Нататка мадэратара экзэмпляра сервера выдалена!
title: Нататкі мадэратараў
private_comment: Прыватны каментарый
public_comment: Публічны каментарый
purge: Ачысціць
@ -601,7 +628,7 @@ be:
relays:
add_new: Дадаць новы рэтранслятар
delete: Выдаліць
description_html: "<strong>Федэрацыйны рэтранслятар</strong> - гэта прамежкавы сервер, які абменьваецца вялікімі аб'ёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і публікуюць на ім. <strong>Гэта можа дапамагчы малым і сярэднім серверам выяўляць кантэнт з fediverse</strong>, для чаго лакальным карыстальнікам у адваротным выпадку трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах."
description_html: "<strong>Федэрацыйны рэтранслятар</strong> - гэта прамежкавы сервер, які абменьваецца вялікімі абёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і робяць публікацыі на ім. <strong>Гэта можа дапамагчы малым і сярэднім серверам выяўляць змесціва з fediverse</strong>, бо ў іншым выпадку лакальным карыстальнікам трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах."
disable: Адключыць
disabled: Адключана
enable: Уключыць
@ -635,11 +662,11 @@ be:
suspend_description_html: Уліковы запіс і ўсё яго змесціва будзе недаступна і ў далейшым выдалены, узаемадзеянне з ім будзе немагчыма. Магчыма адмяніць на працягу 30 дзён. Закрывае ўсе скаргі на гэты ўліковы запіс.
actions_description_html: Вырашыце, якія дзеянні распачаць, каб вырашыць гэтую скаргу. Калі вы прымеце меры пакарання ў дачыненні да ўліковага запісу, пра які паведамляецца, ім будзе адпраўлена апавяшчэнне па электроннай пошце, за выключэннем выпадкаў, калі выбрана катэгорыя <strong>Спам</strong>.
actions_description_remote_html: Вырашыце як паступіць з гэтай скаргай. Гэта паўплывае толькі на тое як <strong>ваш</strong> сервер звязваецца з аддалёным уліковым запісам і апрацоўвае яго кантэнт.
actions_no_posts: У гэтай скаргі няма зьвязаных паведамленьняў для выдаленьня
actions_no_posts: Да гэтай скаргі не прывязаны допісы да выдалення
add_to_report: Дадаць яшчэ дэталяў да скаргі
already_suspended_badges:
local: Ужо прыпынена на гэтым сэрвэры
remote: Ужо прыпынена на іх сэрвэры
local: Ужо прыпынена на гэтым серверы
remote: Ужо прыпынена на іх серверы
are_you_sure: Вы ўпэўнены?
assign_to_self: Прызначыць мне
assigned: Прызначаны мадэратар
@ -668,7 +695,7 @@ be:
delete: Выдаліць
placeholder: Апішыце, якія дзеянні былі зроблены, або любыя іншыя звязаныя абнаўленні...
title: Нататкі
notes_description_html: Праглядвайце і пакідайце нататкі іншым мадэратарам і сабе ў будучыні
notes_description_html: Праглядайце і пакідайце нататкі для іншых мадэратараў і сябе ў будучыні
processed_msg: 'Скарга #%{id} паспяхова апрацавана'
quick_actions_description_html: 'Выканайце хуткае дзеянне або пракруціце ўніз, каб убачыць змесціва, на якое пададзена скарга:'
remote_user_placeholder: аддалены карыстальнік з %{instance}
@ -738,12 +765,12 @@ be:
delete_user_data_description: Дазваляе карыстальнікам без затрымкі выдаляць даныя іншых карыстальнікаў
invite_users: Запрашэнне карыстальнікаў
invite_users_description: Дазваляе запрашаць новых людзей на сервер
manage_announcements: Кіраванне аб'явамі
manage_announcements_description: Дазваляе кіраваць аб'явамі на серверы
manage_announcements: Кіраванне абявамі
manage_announcements_description: Дазваляе кіраваць абявамі на серверы
manage_appeals: Кіраванне апеляцыямі
manage_appeals_description: Дазваляе карыстальнікам разглядаць апеляцыі на дзеянні мадэратара
manage_blocks: Кіраванне блакіроўкамі
manage_blocks_description: Дазваляе блакаваць пэўныя паштовыя правайдэры і IP адрасы
manage_blocks_description: Дазваляе блакіраваць пэўных пастаўшчыкоў паслуг электроннай пошты і IP адрасы
manage_custom_emojis: Кіраванне адвольнымі эмодзі
manage_custom_emojis_description: Дазваляе кіраваць адвольнымі эмодзі на серверы
manage_federation: Кіраваць федэрацыяй
@ -775,11 +802,14 @@ be:
title: Ролі
rules:
add_new: Дадаць правіла
add_translation: Дадаць пераклад
delete: Выдаліць
description_html: Большасць сцвярджаюць, што прачыталі ўмовы абслугоўвання і згаджаюцца з імі, але звычайна людзі не чытаюць іх да канца, пакуль не ўзнікне праблема. <strong>Таму зрабіце правілы вашага сервера простымі з першага погляду, прадставіўшы іх у выглядзе маркіраванага спісу.</strong> Старайцеся рабіць правілы кароткімі і простымі, але не разбіваць іх на шмат асобных пунктаў.
edit: Рэдагаваць правіла
empty: Правілы сервера яшчэ не вызначаны.
title: Правілы сервера
translation: Пераклад
translations: Пераклады
settings:
about:
manage_rules: Кіраваць правіламі сервера
@ -793,7 +823,7 @@ be:
preamble: Брэндынг вашага сервера адрознівае яго ад іншых сервераў у сетцы. Гэтая інфармацыя можа адлюстроўвацца ў розных асяроддзях, напрыклад, у вэб-інтэрфейсе Mastodon, уласных праграмах, у папярэднім праглядзе спасылак на іншых вэб-сайтах і ў праграмах абмену паведамленнямі і гэтак далей. Па гэтай прычыне лепш трымаць гэтую інфармацыю яснай, кароткай і сціслай.
title: Брэндынг
captcha_enabled:
desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, <strong>гэта можа зрабіць працэс рэгістрацыі значна менш даступным для некаторых людзей, асабліва інвалідаў</strong>. Па гэтых прычынах, калі ласка, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння.
desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, <strong>гэта можыць зменшыць даступнасць працэсу рэгістрацыі для некаторых людзей (асабліва інвалідаў)</strong>. Па гэтых прычынах, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння.
title: Патрабаваць ад новых карыстальнікаў рашэння CAPTCHA для пацверджання іх уліковага запісу
content_retention:
danger_zone: Небяспечная зона
@ -805,11 +835,12 @@ be:
discovery:
follow_recommendations: Выконвайце рэкамендацыі
preamble: Прадстаўленне цікавага кантэнту дапамагае прыцягнуць новых карыстальнікаў, якія могуць не ведаць нікога на Mastodon. Кантралюйце працу розных функцый выяўлення на вашым серверы.
privacy: Прыватнасць
profile_directory: Дырэкторыя профіляў
public_timelines: Публічная паслядоўнасць публікацый
publish_statistics: Апублікаваць статыстыку
title: Выяўленне
trends: Актуальныя
trends: Трэнды
domain_blocks:
all: Для ўсіх
disabled: Нікому
@ -854,7 +885,7 @@ be:
add_to_report: 'Дадаць да скаргі #%{id}'
remove_from_report: Выдаліць са справаздачы
report: Справаздача
contents: Зьмест
contents: Змест
deleted: Выдалены
favourites: Упадабаныя
history: Гісторыя версій
@ -863,7 +894,7 @@ be:
media:
title: Медыя
metadata: Метаданыя
no_history: Гэты пост не рэдагаваўся
no_history: Гэты допіс не рэдагаваўся
no_status_selected: Ніводная публікацыя не была зменена, бо ніводная не была выбрана
open: Адкрыць допіс
original_status: Зыходны допіс
@ -872,7 +903,7 @@ be:
status_changed: Допіс зменены
status_title: Допіс карыстальніка @%{name}
title: Допісы карыстальніка - @%{name}
trending: Папулярныя
trending: Трэндавае
view_publicly: Глядзець публічна
visibility: Бачнасць
with_media: З медыя
@ -890,13 +921,13 @@ be:
appeal_rejected: Абскарджанне адхілена
system_checks:
database_schema_check:
message_html: Ёсць незавершаныя міграцыі базы дадзеных. Калі ласка, запусціце іх, каб пераканацца, што дадатак паводзіць сябе належным чынам
message_html: Ёсць незавершаныя міграцыі базы даных. Запусціце іх, каб пераканацца, што праграма паводзіць сябе належным чынам
elasticsearch_health_red:
message_html: Кластар Elasticsearch нездаровы (чырвоны статус), функцыі пошуку недаступныя
elasticsearch_health_yellow:
message_html: Кластар Elasticsearch нездаровы (жоўты статус), магчыма, неабходна высветліць прычыну
elasticsearch_index_mismatch:
message_html: Супастаўленне індэксаў Elasticsearch састарэла. Калі ласка, выканайце каманду <code>tootctl search deploy --only=%{value}</code>
message_html: Супастаўленне індэксаў Elasticsearch састарэла. Выканайце каманду <code>tootctl search deploy --only=%{value}</code>
elasticsearch_preset:
action: Падрабязней у дакументацыі
message_html: Ваш кластар Elasticsearch мае больш за адзін вузел, але Mastodon не наладжаны на іх выкарыстанне.
@ -906,7 +937,7 @@ be:
elasticsearch_reset_chewy:
message_html: Ваш сістэмны індэкс Elasticsearch састарэлы з-за змены налад. Для яго абнаўлення выканайце каманду <code>tootctl search deploy --reset-chewy</code>.
elasticsearch_running_check:
message_html: Немагчыма падключыцца да Elasticsearch. Калі ласка, праверце, што ён запушчаны, або адключыце паўнатэкставы пошук
message_html: Немагчыма падключыцца да Elasticsearch. Праверце, што ён запушчаны, або адключыце пошук па ўсім тэксце
elasticsearch_version_check:
message_html: 'Несумяшчальная версія Elasticsearch: %{value}'
version_comparison: Elasticsearch %{running_version} выяўлены, але патрабуецца %{required_version}
@ -916,11 +947,11 @@ be:
sidekiq_process_check:
message_html: Не працуе працэс Sidekiq для %{value} чаргі. Калі ласка праверце вашу канфігурацыю Sidekiq
software_version_check:
action: Праверыць наяўнасьць абнаўленьняў
message_html: Даступна абнаўленьне Mastodon.
action: Праверыць наяўнасць абнаўленняў
message_html: Даступна абнаўленне Mastodon.
software_version_critical_check:
action: Прагледзець даступныя абнаўленні
message_html: Даступна крытычнае абнаўленне Mastodon, калі ласка, зрабіце абнаўленне як мага хутчэй.
message_html: Даступна крытычнае абнаўленне Mastodon, абнавіцеся як мага хутчэй.
software_version_patch_check:
action: Прагледзець даступныя абнаўленні
message_html: Даступна абнаўленне Mastodon з выпраўленнем памылак.
@ -932,13 +963,13 @@ be:
message_html: "<strong>Ваша сховішча не наладжана. Прыватнасць карыстальнікаў пад пагрозай.</strong>"
tags:
moderation:
not_trendable: Не ў трэндзе
not_trendable: Не можа трапіць у трэнды
not_usable: Непрыгодныя
pending_review: Чакае праверкі
review_requested: Патрабуюць прагледжвання
reviewed: Прагледжаныя
title: Стан
trendable: У трэндзе
trendable: Можа трапіць у трэнды
unreviewed: Непрагледжаныя
usable: Прыгодныя
name: Назва
@ -951,18 +982,26 @@ be:
title: Хэштэгі
updated_msg: Налады хэштэгаў паспяхова змененыя
terms_of_service:
back: Вернуцца да ўмоваў абслугоўваньня
changelog: Што зьмянілася
create: Выкарыстоўвайце свой уласны
current: Цякучы
back: Вернуцца да ўмоў выкарыстання
changelog: Што змянілася
create: Выкарыстоўвайце свой тэкст
current: Бягучыя
draft: Чарнавік
generate: Выкарыстаць шаблон
generates:
action: Зґенераваць
action: Згенерыраваць
history: Гісторыя
live: Дзейнічае
notify_users: Апавясціць карыстальнікаў
publish: Апублікаваць
published_on_html: Апублікавана %{date}
save_draft: Захаваць чарнавік
title: Умовы выкарыстання
title: Адміністрацыя
trends:
allow: Дазволіць
approved: Пацверджаны
confirm_allow: Вы ўпэўнены, што хочаце дазволіць выбраныя тэгі?
disallow: Забараніць
links:
allow: Дазволіць спасылка
@ -978,15 +1017,15 @@ be:
many: Абагулілі %{count} чалавек за апошні тыдзень
one: Абагуліў адзін чалавек за апошні тыдзень
other: Абагулілі %{count} чалавек за апошні тыдзень
title: Актуальныя спасылкі
title: Трэндавыя спасылкі
usage_comparison: Выкарыстоўвалася %{today} разоў сёння, у параўнанні з %{yesterday} учора
not_allowed_to_trend: Забаронена выходзіць у актуальныя
not_allowed_to_trend: Забаронена выходзіць у трэнды
only_allowed: Толькі дазволенае
pending_review: Чакае праверкі
preview_card_providers:
allowed: Спасылкі ад гэтага выдаўца не будуць у трэндзе
description_html: Спасылкі з гэтых даменаў часта абагульняюцца на вашым серверы. Спасылкі не трапяць у публічныя трэнды, калі дамен спасылкі не ўхвалены. Вашае ўхваленне (ці адхіленне) распаўсюдзіцца на субдамены.
rejected: Спасылкі ад гэтага выдаўца не будуць у трэнде
rejected: Спасылкі ад гэтага выдаўца не будуць у трэндзе
title: Выдаўцы
rejected: Адхілена
statuses:
@ -1002,7 +1041,7 @@ be:
many: Пашыраны або ўпадабаны %{friendly_count} разоў
one: Пашыраны або ўпадабаны %{friendly_count} раз
other: Пашыраны або ўпадабаны %{friendly_count} разоў
title: Актуальныя допісы
title: Трэндавыя допісы
tags:
current_score: Бягучы рэзультат %{score}
dashboard:
@ -1015,11 +1054,11 @@ be:
listable: Можа быць прапанавана
no_tag_selected: Ніводны тэг не быў зменены, бо ніводны не быў выбраны
not_listable: Не будзе прапанавана
not_trendable: Не з'явіцца ў трэндах
not_trendable: Не з’явіцца сярод трэндаў
not_usable: Немагчыма выкарыстаць
peaked_on_and_decaying: На піку %{date}, зараз спадае
title: Актуальныя хэштэгі
trendable: Можа з'явіцца сярод трэндаў
trendable: Можа зявіцца сярод трэндаў
trending_rank: 'Папулярнае #%{rank}'
usable: Магчыма выкарыстаць
usage_comparison: Выкарыстоўвалася %{today} разоў сёння, у параўнанні з %{yesterday} учора
@ -1029,7 +1068,7 @@ be:
one: Выкарыстаў адзін чалавек за апошні тыдзень
other: Выкарысталі %{count} чалавек за апошні тыдзень
title: Рэкамендацыі і трэнды
trending: Папулярныя
trending: Трэндавае
warning_presets:
add_new: Дадаць новы
delete: Выдаліць
@ -1070,7 +1109,7 @@ be:
none: папярэджанне
sensitive: пазначыць уліковы запіс як далікатны
silence: абмежаваць уліковы запіс
suspend: выключыць уліковы запіс
suspend: каб прыпыніць гэты ўліковы запіс
body: "%{target} абскарджвае рашэнне мадэратара %{action_taken_by} ад %{date}, якая была %{type}. Яны напісалі:"
next_steps: Вы можаце ўхваліць апеляцыю каб адмяніць рашэнне мадэратараў ці ігнараваць яе.
subject: "%{username} абскарджвае рашэнне мадэратараў на %{instance}"
@ -1090,12 +1129,12 @@ be:
new_trends:
body: 'Гэтыя элементы трэба праверыць, перш чым публікаваць:'
new_trending_links:
title: Папулярныя спасылкі
title: Трэндавыя спасылкі
new_trending_statuses:
title: Папулярныя допісы
title: Трэндавыя допісы
new_trending_tags:
title: Папулярныя хэштэгі
subject: Новае ў папулярным для разгляду %{instance}
title: Трэндавыя хэштэгі
subject: Новыя трэнды да прагляду %{instance}
aliases:
add_new: Стварыць псеўданім
created_msg: Створаны новы псеўданім. Цяпер вы можаце пачаць пераход са старога ўліковага запісу.
@ -1147,7 +1186,7 @@ be:
welcome_title: Вітаем, %{name}!
wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу.
delete_account: Выдаліць уліковы запіс
delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце <a href="%{path}">працягнуць тут</a>. Ад вас будзе запатрабавана пацвярджэнне.
delete_account_html: Калі вы жадаеце выдаліць свой уліковы запіс, вы можаце <a href="%{path}">працягнуць тут</a>. Ад вас спатрэбіцца пацвярджэнне.
description:
prefix_invited_by_user: "@%{name} запрашае вас далучыцца да гэтага сервера ў Mastodon!"
prefix_sign_up: Зарэгістравацца ў Mastodon сёння!
@ -1166,7 +1205,7 @@ be:
or_log_in_with: Або ўвайсці з дапамогай
progress:
confirm: Пацвердзіць email
details: Вашы дадзеныя
details: Вашы даныя
review: Наш водгук
rules: Прыняць правілы
providers:
@ -1207,6 +1246,10 @@ be:
view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу
too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз.
use_security_key: Выкарыстаеце ключ бяспекі
author_attribution:
example_title: Прыклад тэксту
s_blog: Блог %{name}
title: Пазначэнне аўтарства
challenge:
confirm: Працягнуць
hint_html: "<strong>Парада:</strong> Мы не будзем запытваць ваш пароль зноўку на працягу наступнай гадзіны."
@ -1276,7 +1319,7 @@ be:
none: Папярэджанне
sensitive: Пазначэнне ўліковага запісу як далікатнага
silence: Абмежаванне ўліковага запісу
suspend: Выключэнне ўліковага запісу
suspend: Прыпыненне ўліковага запісу
your_appeal_approved: Ваша абскарджанне было ўхвалена
your_appeal_pending: Вы адправілі апеляцыю
your_appeal_rejected: Ваша абскарджанне было адхілена
@ -1284,6 +1327,10 @@ be:
basic_information: Асноўная інфармацыя
hint_html: "<strong>Наладзьце тое, што людзі будуць бачыць у вашым профілі і побач з вашымі паведамленнямі.</strong> Іншыя людзі з большай верагоднасцю будуць сачыць і ўзаемадзейнічаць з вамі, калі ў вас ёсць запоўнены профіль і фота профілю."
other: Іншае
emoji_styles:
auto: Аўтаматычны
native: Мясцовы
twemoji: Twemoji
errors:
'400': Запыт, які вы адправілі, памылковы або няправільны.
'403': У вас няма дазволу на прагляд гэтай старонкі.
@ -1398,10 +1445,10 @@ be:
other: Выберыце %{count} элементаў, якія адпавядаюць вашаму пошуку.
today: сёння
validation_errors:
few: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылкі ніжэй
many: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылак ніжэй
one: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце памылку ніжэй
other: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылак ніжэй
few: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылкі ніжэй
many: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылак ніжэй
one: Штосьці тут пакуль не сыходзіцца! Праглядзіце памылку ніжэй
other: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылак ніжэй
imports:
errors:
empty: Пусты CSV файл
@ -1413,7 +1460,7 @@ be:
imported: Імпартавана
mismatched_types_warning: Здаецца, вы выбралі няправільны тып для гэтага імпарту, праверце яшчэ раз.
modes:
merge: Аб'яднаць
merge: Абяднаць
merge_long: Захаваць існуючыя запісы і дадаць новыя
overwrite: Перазапісаць
overwrite_long: Замяніць бягучыя запісы на новыя
@ -1489,7 +1536,7 @@ be:
unsubscribe:
action: Так, адпісацца
complete: Адпісаны
confirmation_html: Вы ўпэўнены, што жадаеце адмовіцца ад %{type} з Mastodon на дамене %{domain} на вашу электронную пошту %{email}? Вы заўсёды можаце паўторна падпісацца у <a href="%{settings_path}">наладах апавяшчэнняў па электроннай пошце</a>.
confirmation_html: Вы ўпэўнены, што жадаеце адмовіцца ад атрымання %{type} з Mastodon на дамене %{domain} на сваю электронную пошту %{email}? Вы заўсёды можаце паўторна падпісацца ў <a href="%{settings_path}">наладах апавяшчэнняў па электроннай пошце</a>.
emails:
notification_emails:
favourite: апавяшчэнні на пошту пра упадабанае
@ -1529,7 +1576,7 @@ be:
set_redirect: Задаць перанакіраванне
warning:
backreference_required: Спачатку трэба наладзіць зваротнае спасыланне новага ўліковага запісу на бягучы
before: 'Перш чым працягнуць, калі ласка, уважліва прачытайце гэтыя заўвагі:'
before: 'Перш чым працягнуць, уважліва прачытайце гэтыя заўвагі:'
cooldown: Пасля «пераезду» будзе перыяд чакання, на працягу якога вы не зможаце зноў «пераехаць»
disabled_account: Пасля гэтага ваш бягучы ўліковы запіс не будзе цалкам даступны. Аднак у вас будзе доступ да экспарту даных, а таксама да паўторнай актывацыі.
followers: Гэтае дзеянне будзе «пераносіць» усіх падпісчыкаў з бягучага ўліковага запісу на новы
@ -1642,9 +1689,9 @@ be:
title: Вы пакідаеце %{instance}.
relationships:
activity: Актыўнасць ул. запісу
confirm_follow_selected_followers: Вы ўпэўнены, што жадаеце падпісацца на выбраных падпісчыкаў?
confirm_remove_selected_followers: Вы ўпэўнены, што жадаеце выдаліць выбраных падпісчыкаў?
confirm_remove_selected_follows: Вы ўпэўнены, што жадаеце выдаліць выбраныя падпіскі?
confirm_follow_selected_followers: Вы ўпэўнены, што хочаце падпісацца на выбраных падпісчыкаў?
confirm_remove_selected_followers: Вы ўпэўнены, што хочаце выдаліць выбраных падпісчыкаў?
confirm_remove_selected_follows: Вы ўпэўнены, што хочаце выдаліць выбраныя падпіскі?
dormant: Занядбаны
follow_failure: Вы не можаце падпісацца на некаторыя акаўнты.
follow_selected_followers: Падпісацца на выбраных падпісчыкаў
@ -1674,6 +1721,7 @@ be:
scheduled_statuses:
over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў
over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў
too_soon: дата публікацыі мусіць быць у будучыні
self_destruct:
lead_html: На жаль, дамен <strong>%{domain}</strong> зачыняецца назаўсёды. Калі ў вас быў уліковы запіс, вы не зможаце працягваць выкарыстоўваць яго, але вы ўсё яшчэ можаце запытаць рэзервовае капіраванне вашых даных.
title: Гэты сервер зачыняецца
@ -1789,10 +1837,14 @@ be:
in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе.
over_character_limit: перавышаная колькасць сімвалаў у %{max}
pin_errors:
direct: Допісы, бачныя толькі згаданым карыстальнікам, не могуць быць замацаваныя
direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць
limit: Вы ўжо замацавалі максімальную колькасць допісаў
ownership: Немагчыма замацаваць чужы допіс
reblog: Немагчыма замацаваць пашырэнне
quote_policies:
followers: Падпісчыкі і згаданыя карыстальнікі
nobody: Толькі згаданыя карыстальнікі
public: Усе
title: '%{name}: "%{quote}"'
visibilities:
direct: Асабіста
@ -1845,7 +1897,7 @@ be:
tags:
does_not_match_previous_name: не супадае з папярэднім імям
terms_of_service:
title: Умовы абслугоўваньня
title: Умовы выкарыстання
themes:
contrast: Mastodon (высокі кантраст)
default: Mastodon (цёмная)
@ -1874,7 +1926,7 @@ be:
otp: Праграма аўтэнтыфікацыі
recovery_codes: Абнавіць коды аднаўлення
recovery_codes_regenerated: Новыя коды аднаўлення паспяхова створаныя
recovery_instructions_html: Калі раптам вы страціце доступ да вашага тэлефона, вы можаце скарыстацца адным з кодаў аднаўлення ніжэй каб аднавіць доступ да вашага ўліковага запісу. <strong>Захоўвайце іх у бяспечным месцы</strong>. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі.
recovery_instructions_html: Калі раптам вы страціце доступ да свайго тэлефона, вы можаце скарыстаць адзін з кодаў аднаўлення ніжэй каб аднавіць доступ да свайго ўліковага запісу. <strong>Захоўвайце іх у бяспечным месцы</strong>. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі.
webauthn: Ключы бяспекі
user_mailer:
appeal_approved:
@ -1895,10 +1947,10 @@ be:
title: Ваш архіў можна спампаваць
failed_2fa:
details: 'Вось падрабязнасці ўваходу:'
explanation: Хтосьці спрабаваў увайсці ў ваш уліковы запіс, але ўвёў няправільны другі фактар аўтэнтыфікацыі.
explanation: Хтосьці спрабаваў увайсці ў ваш уліковы запіс, але ўвёў няправільны часовы пароль.
further_actions_html: Калі гэта не вы, мы рэкамендуем неадкладна %{action}, бо ён можа быць скампраметаваны.
subject: Збой аўтэнтыфікацыі па другім фактары
title: Няўдалая аўтэнтыфікацыя па другім фактары
subject: Збой двухфактарнай аўтэнтыфікацыі
title: Двухфактарная аўтэнтыфікацыя не ўдалася
suspicious_sign_in:
change_password: змяніць свой пароль
details: 'Вось падрабязнасці ўваходу:'
@ -1906,6 +1958,9 @@ be:
further_actions_html: Калі гэта былі не вы, раім вам неадкладна %{action}, а таксама ўключыць двухфактарную аўтэнтыфікацыю, каб захаваць бяспеку вашага ўліковага запісу.
subject: У вас уліковы запіс зайшлі з новага IP-адрасу
title: Новы ўваход
terms_of_service_changed:
sign_off: Каманда %{domain}
title: Важнае абнаўленне
warning:
appeal: Падаць апеляцыю
appeal_description: "Калі вы лічыце гэта памылкай, вы можаце падаць апеляцыю \nсупрацоўнікам %{instance}."
@ -1936,7 +1991,7 @@ be:
none: Папярэджанне
sensitive: Уліковыя запісы, пазначаныя як далікатныя
silence: Уліковы запіс абмежаваны
suspend: Уліковы запіс выключаны
suspend: Уліковы запіс прыпынены
welcome:
apps_android_action: Спампаваць з Google Play
apps_ios_action: Спампваваць з App Store
@ -1968,9 +2023,9 @@ be:
many: "%{people} чалавек за апошнія 2 дні"
one: "%{people} чалавек за апошнія 2 дні"
other: "%{people} чалавека за апошнія 2 дні"
hashtags_subtitle: Даведайцеся што было папулярна ў апошнія 2 дні
hashtags_subtitle: Даведайцеся што было ў трэндзе за апошнія 2 дні
hashtags_title: Папулярныя хэштэгі
hashtags_view_more: Прагледзець іншыя папулярныя хэштэгі
hashtags_view_more: Прагледзець іншыя трэндавыя хэштэгі
post_action: Стварыць
post_step: Скажыце ўсім прывітанне з дапамогай тэксту, фатаграфій, відэа і апытанняў.
post_title: Стварыце свой першы допіс
@ -1983,7 +2038,7 @@ be:
follow_limit_reached: Вы не можаце падпісацца на большую колькасць людзей чым %{limit}
go_to_sso_account_settings: Перайдзіце ў налады ідэнтыфікацыі вашага ўліковага запісу
invalid_otp_token: Няправільны код двухфактарнай аўтэнтыфікацыі
otp_lost_help_html: Калі вы страцілі доступ да абодвух, вы можаце скарыстацца %{email}
otp_lost_help_html: Калі вы страцілі доступ да абодвух, вы можаце звязацца з намі праз %{email}
rate_limited: Занадта шмат спробаў аўтэнтыфікацыі, паспрабуйце пазней.
seamless_external_login: Вы ўвайшлі праз знешні сэрвіс, таму налады пароля і эл. пошты недаступныя.
signed_in_as: 'Увайшлі як:'

View File

@ -396,6 +396,9 @@ br:
created_at: Deiziad
title_actions:
none: Diwall
emoji_styles:
auto: Emgefreek
twemoji: Twemoji
exports:
archive_takeout:
date: Deiziad

View File

@ -578,6 +578,12 @@ ca:
all: Totes
limited: Limitades
title: Moderació
moderation_notes:
create: Afegeix una nota de moderació
created_msg: S'ha creat la nota de moderació d'instància.
description_html: Mireu i deixeu notes per als altres moderadors i per a un mateix
destroyed_msg: S'ha esborrat la nota de moderació d'instància.
title: Notes de moderació
private_comment: Comentari privat
public_comment: Comentari públic
purge: Purga
@ -803,6 +809,8 @@ ca:
preamble: Proporciona informació detallada sobre com funciona, com es modera i com es financia el servidor.
rules_hint: Hi ha un àrea dedicada a les normes a les que s'espera que els teus usuaris s'hi adhereixin.
title: Quant a
allow_referrer_origin:
title: Permeteu que llocs externs vegin el vostre servidor de Mastodon com a font de trànsit
appearance:
preamble: Personalitza l'interfície web de Mastodon.
title: Aparença
@ -1339,6 +1347,10 @@ ca:
basic_information: Informació bàsica
hint_html: "<strong>Personalitza el que la gent veu en el teu perfil públic i a prop dels teus tuts..</strong> És més probable que altres persones et segueixin i interaccionin amb tu quan tens emplenat el teu perfil i amb la teva imatge."
other: Altres
emoji_styles:
auto: Automàtic
native: Nadiu
twemoji: Twemoji
errors:
'400': La sol·licitud que vas emetre no era vàlida o no era correcta.
'403': No tens permís per a veure aquesta pàgina.
@ -1474,6 +1486,28 @@ ca:
lists_html:
one: Esteu a punt de <strong>reemplaçar les vostres llistes</strong> amb contactes de <strong>%{filename}</strong>. S'afegirà <strong>%{count} compte</strong> a les noves llistes.
other: Esteu a punt de <strong>reemplaçar les vostres llistes</strong> amb contactes de <strong>%{filename}</strong>. S'afegiran fins a <strong>%{count} comptes</strong> a les noves llistes.
muting_html:
one: Esteu a punt de <strong>reemplaçar la vostra llista de comptes silenciats</strong> amb fins a <strong>%{count} compte</strong> de <strong>%{filename}</strong>.
other: Esteu a punt de <strong>reemplaçar la vostra llista de comptes silenciats</strong> amb fins a <strong>%{count} comptes</strong> de <strong>%{filename}</strong>.
preambles:
blocking_html:
one: Esteu a punt de <strong>blocar</strong> fins a <strong>%{count} compte</strong> de <strong>%{filename}</strong>.
other: Esteu a punt de <strong>blocar</strong> fins a <strong>%{count} comptes</strong> de <strong>%{filename}</strong>.
bookmarks_html:
one: Esteu a punt d'afegir fins a <strong>%{count} publicació</strong> de <strong>%{filename}</strong> als vostres <strong>marcadors</strong>.
other: Esteu a punt d'afegir fins a <strong>%{count} publicacions</strong> de <strong>%{filename}</strong> als vostres <strong>marcadors</strong>.
domain_blocking_html:
one: Esteu a punt de <strong>blocar</strong> fins a <strong>%{count} domini</strong> de <strong>%{filename}</strong>.
other: Esteu a punt de <strong>blocar</strong> fins a <strong>%{count} dominis</strong> de <strong>%{filename}</strong>.
following_html:
one: Esteu a punt de <strong>seguir</strong> fins a <strong>%{count} compte</strong> de <strong>%{filename}</strong>.
other: Esteu a punt de <strong>seguir</strong> fins a <strong>%{count} comptes</strong> de <strong>%{filename}</strong>.
lists_html:
one: Esteu a punt d'afegir <strong>%{count} compte</strong> de <strong>%{filename}</strong> a les vostres <strong>llistes</strong>. Es crearan noves llistes si no n'hi ha cap on afegir-lo.
other: Esteu a punt d'afegir <strong>%{count} comptes</strong> de <strong>%{filename}</strong> a les vostres <strong>llistes</strong>. Es crearan noves llistes si no n'hi ha cap on afegir-los.
muting_html:
one: Esteu a punt de <strong>silenciar</strong> fins a <strong>%{count} compte</strong> de <strong>%{filename}</strong>.
other: Esteu a punt de <strong>silenciar</strong> fins a <strong>%{count} comptes</strong> de <strong>%{filename}</strong>.
preface: Pots importar algunes les dades que has exportat des d'un altre servidor, com ara el llistat de les persones que estàs seguint o bloquejant.
recent_imports: Importacions recents
states:

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