From 335eeb62393c5156181d433f13e8ed483bfac84f Mon Sep 17 00:00:00 2001 From: Estel Date: Tue, 17 Feb 2026 16:55:11 +0800 Subject: [PATCH 01/60] Add try_files directive comments to nginx.conf (#37866) --- dist/nginx.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dist/nginx.conf b/dist/nginx.conf index a421e00006066c..af21f931afc740 100644 --- a/dist/nginx.conf +++ b/dist/nginx.conf @@ -82,31 +82,37 @@ server { location ^~ /avatars/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /emoji/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /headers/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /ocr/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /packs/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /sounds/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; + # try_files $uri @mastodon; } location ^~ /system/ { @@ -114,6 +120,7 @@ server { add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; add_header X-Content-Type-Options nosniff; add_header Content-Security-Policy "default-src 'none'; form-action 'none'"; + # try_files $uri @mastodon; } location ^~ /api/v1/streaming { From 1618c28a480ecbb92362229be5a06f7560036b81 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:01:19 +0000 Subject: [PATCH 02/60] Update dependency dotenv to v17.3.1 (#37855) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 782b949dad2bb7..986d84bfed6857 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6598,9 +6598,9 @@ __metadata: linkType: hard "dotenv@npm:^17.0.0": - version: 17.2.4 - resolution: "dotenv@npm:17.2.4" - checksum: 10c0/901aeee9cb40860291bdb452f6ca66aada78438331a026b0bd86fd41b94a79752dbc6a8971f4f26e6cafef11b4a27bb12ea99c0cbe7dfa61791f194727f799e5 + version: 17.3.1 + resolution: "dotenv@npm:17.3.1" + checksum: 10c0/c78e0c2d5a549c751e544cc60e2b95e7cb67e0c551f42e094d161c6b297aa44b630a3c2dcacf5569e529a6c2a6b84e2ab9be8d37b299d425df5a18b81ce4a35f languageName: node linkType: hard From 2cb8471d81e2ac3fb29a96062035608f9dbde135 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Feb 2026 04:01:59 -0500 Subject: [PATCH 03/60] Update doorkeeper i18n for invalid code challenge method (#37827) --- config/locales/doorkeeper.en.yml | 5 ++++- spec/system/oauth_spec.rb | 31 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index 3b3b141afa7889..f0a47651cb4a96 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -83,7 +83,10 @@ en: access_denied: The resource owner or authorization server denied the request. credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured. invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method. - invalid_code_challenge_method: The code challenge method must be S256, plain is unsupported. + invalid_code_challenge_method: + one: The code_challenge_method must be %{challenge_methods}. + other: The code_challenge_method must be one of %{challenge_methods}. + zero: The authorization server does not support PKCE as there are no accepted code_challenge_method values. invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. invalid_redirect_uri: The redirect uri included is not valid. invalid_request: diff --git a/spec/system/oauth_spec.rb b/spec/system/oauth_spec.rb index 19235eab425630..bba4b03ace3e9d 100644 --- a/spec/system/oauth_spec.rb +++ b/spec/system/oauth_spec.rb @@ -98,28 +98,27 @@ context 'when using plain code challenge method' do let(:pkce_code_challenge_method) { 'plain' } - it 'does not include the PKCE values in the response' do + it 'shows an error message and does not include the PKCE values or authorize button' do subject - expect(page).to have_no_css('.oauth-prompt input[name=code_challenge]') - expect(page).to have_no_css('.oauth-prompt input[name=code_challenge_method]') - end - - it 'does not include the authorize button' do - subject - - expect(page).to have_no_css('.oauth-prompt button[type="submit"]') - end - - it 'includes an error message' do - subject + expect(page) + .to have_no_css('.oauth-prompt input[name=code_challenge]') + .and have_no_css('.oauth-prompt input[name=code_challenge_method]') + .and have_no_css('.oauth-prompt button[type="submit"]') within '.form-container .flash-message' do - # FIXME: Replace with doorkeeper.errors.messages.invalid_code_challenge_method.one for Doorkeeper > 5.8.0 - # see: https://github.com/doorkeeper-gem/doorkeeper/pull/1747 - expect(page).to have_content(I18n.t('doorkeeper.errors.messages.invalid_code_challenge_method')) + expect(page) + .to have_content(doorkeeper_invalid_code_message) end end + + def doorkeeper_invalid_code_message + I18n.t( + 'doorkeeper.errors.messages.invalid_code_challenge_method', + challenge_methods: Doorkeeper.configuration.pkce_code_challenge_methods.join(', '), + count: Doorkeeper.configuration.pkce_code_challenge_methods.length + ) + end end context 'when the user has yet to enable TOTP' do From 7f28dd117944166b14cb97674995e988d8c6c31d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Feb 2026 04:03:46 -0500 Subject: [PATCH 04/60] Use validation matchers for `FollowLimitValidator` spec (#37792) --- .../validators/follow_limit_validator_spec.rb | 80 ++++++------------- 1 file changed, 24 insertions(+), 56 deletions(-) diff --git a/spec/validators/follow_limit_validator_spec.rb b/spec/validators/follow_limit_validator_spec.rb index e069b0ed3ab217..f6c525d5b4da99 100644 --- a/spec/validators/follow_limit_validator_spec.rb +++ b/spec/validators/follow_limit_validator_spec.rb @@ -3,76 +3,44 @@ require 'rails_helper' RSpec.describe FollowLimitValidator do - describe '#validate' do - context 'with a nil account' do - it 'does not add validation errors to base' do - follow = Fabricate.build(:follow, account: nil) + subject { Fabricate.build(:follow) } - follow.valid? + context 'with a nil account' do + it { is_expected.to allow_values(nil).for(:account).against(:base) } + end - expect(follow.errors[:base]).to be_empty - end - end + context 'with a non-local account' do + let(:account) { Account.new(domain: 'host.example') } - context 'with a non-local account' do - it 'does not add validation errors to base' do - follow = Fabricate.build(:follow, account: Account.new(domain: 'host.example')) + it { is_expected.to allow_values(account).for(:account).against(:base) } + end - follow.valid? + context 'with a local account' do + let(:account) { Account.new } - expect(follow.errors[:base]).to be_empty - end - end + context 'when the followers count is under the limit' do + before { account.following_count = described_class::LIMIT - 100 } - context 'with a local account' do - let(:account) { Account.new } + it { is_expected.to allow_values(account).for(:account).against(:base) } + end - context 'when the followers count is under the limit' do - before do - allow(account).to receive(:following_count).and_return(described_class::LIMIT - 100) - end + context 'when the following count is over the limit' do + before { account.following_count = described_class::LIMIT + 100 } - it 'does not add validation errors to base' do - follow = Fabricate.build(:follow, account: account) + context 'when the followers count is low' do + before { account.followers_count = 10 } - follow.valid? + it { is_expected.to_not allow_values(account).for(:account).against(:base).with_message(limit_reached_message) } - expect(follow.errors[:base]).to be_empty + def limit_reached_message + I18n.t('users.follow_limit_reached', limit: described_class::LIMIT) end end - context 'when the following count is over the limit' do - before do - allow(account).to receive(:following_count).and_return(described_class::LIMIT + 100) - end - - context 'when the followers count is low' do - before do - allow(account).to receive(:followers_count).and_return(10) - end - - it 'adds validation errors to base' do - follow = Fabricate.build(:follow, account: account) - - follow.valid? - - expect(follow.errors[:base]).to include(I18n.t('users.follow_limit_reached', limit: described_class::LIMIT)) - end - end + context 'when the followers count is high' do + before { account.followers_count = 100_000 } - context 'when the followers count is high' do - before do - allow(account).to receive(:followers_count).and_return(100_000) - end - - it 'does not add validation errors to base' do - follow = Fabricate.build(:follow, account: account) - - follow.valid? - - expect(follow.errors[:base]).to be_empty - end - end + it { is_expected.to allow_values(account).for(:account).against(:base) } end end end From 0a6412faf91cc1aa210563317867457df17c6dfe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:21:35 +0100 Subject: [PATCH 05/60] New Crowdin Translations (automated) (#37884) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 9 +++++ app/javascript/mastodon/locales/da.json | 9 +++++ app/javascript/mastodon/locales/de.json | 9 +++++ app/javascript/mastodon/locales/el.json | 9 +++++ app/javascript/mastodon/locales/en-GB.json | 9 +++++ app/javascript/mastodon/locales/es-AR.json | 9 +++++ app/javascript/mastodon/locales/es-MX.json | 9 +++++ app/javascript/mastodon/locales/es.json | 11 +++++- app/javascript/mastodon/locales/et.json | 42 ++++++++++++++-------- app/javascript/mastodon/locales/fi.json | 9 +++++ app/javascript/mastodon/locales/fo.json | 9 +++++ app/javascript/mastodon/locales/fr-CA.json | 9 +++++ app/javascript/mastodon/locales/fr.json | 9 +++++ app/javascript/mastodon/locales/ga.json | 9 +++++ app/javascript/mastodon/locales/hu.json | 9 +++++ app/javascript/mastodon/locales/is.json | 9 +++++ app/javascript/mastodon/locales/sq.json | 8 +++++ app/javascript/mastodon/locales/sv.json | 5 +++ app/javascript/mastodon/locales/tr.json | 3 ++ app/javascript/mastodon/locales/vi.json | 9 +++++ app/javascript/mastodon/locales/zh-CN.json | 9 +++++ app/javascript/mastodon/locales/zh-TW.json | 9 +++++ config/locales/et.yml | 9 +++-- config/locales/simple_form.be.yml | 1 + config/locales/simple_form.de.yml | 1 + config/locales/simple_form.el.yml | 1 + config/locales/simple_form.en-GB.yml | 1 + config/locales/simple_form.es-AR.yml | 1 + config/locales/simple_form.es-MX.yml | 1 + config/locales/simple_form.es.yml | 1 + config/locales/simple_form.et.yml | 2 ++ config/locales/simple_form.fi.yml | 1 + config/locales/simple_form.fo.yml | 1 + config/locales/simple_form.fr-CA.yml | 1 + config/locales/simple_form.fr.yml | 1 + config/locales/simple_form.ga.yml | 1 + config/locales/simple_form.gl.yml | 1 + config/locales/simple_form.he.yml | 1 + config/locales/simple_form.is.yml | 1 + config/locales/simple_form.it.yml | 1 + config/locales/simple_form.sq.yml | 1 + config/locales/simple_form.tr.yml | 1 + config/locales/simple_form.vi.yml | 1 + config/locales/simple_form.zh-CN.yml | 1 + config/locales/simple_form.zh-TW.yml | 1 + 45 files changed, 237 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 8fc6a7a13a9bf7..ad4183f01df723 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon дэцэнтралізаваны, так што дзе б вы ні стварылі ўліковы запіс, вы зможаце падпісвацца і камунікаваць з кім хочаце на гэтым серверы. Вы нават можаце стварыць свой!", "closed_registrations_modal.title": "Рэгістрацыя ў Mastodon", "collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}", + "collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя", + "collections.accounts.empty_title": "Гэтая калекцыя пустая", "collections.collection_description": "Апісанне", "collections.collection_name": "Назва", "collections.collection_topic": "Тэма", + "collections.confirm_account_removal": "Упэўненыя, што хочаце прыбраць гэты ўліковы запіс з гэтай калекцыі?", "collections.content_warning": "Папярэджанне аб змесціве", "collections.continue": "Працягнуць", "collections.create.accounts_subtitle": "Можна дадаць толькі ўліковыя запісы, на якія Вы падпісаныя і якія далі дазвол на тое, каб іх можна было знайсці.", @@ -261,6 +264,9 @@ "collections.edit_details": "Змяніць асноўныя звесткі", "collections.edit_settings": "Змяніць налады", "collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.", + "collections.hints.accounts_counter": "{count} / {max} уліковых запісаў", + "collections.hints.add_more_accounts": "Дадайце як мінімум {count, plural, one {# уліковы запіс} few{# ўліковыя запісы} other {# уліковых запісаў}}, каб працягнуць", + "collections.hints.can_not_remove_more_accounts": "У калекцыі мусіць быць як мінімум {count, plural, one {# уліковы запіс} few{# ўліковыя запісы} other {# уліковых запісаў}}. Немагчыма прыбраць больш уліковых запісаў.", "collections.last_updated_at": "Апошняе абнаўленне: {date}", "collections.manage_accounts": "Кіраванне ўліковымі запісамі", "collections.manage_accounts_in_collection": "Кіраванне ўліковымі запісамі ў гэтай калекцыі", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Максімум 100 сімвалаў", "collections.new_collection": "Новая калекцыя", "collections.no_collections_yet": "Пакуль няма калекцый.", + "collections.remove_account": "Прыбраць гэты ўліковы запіс", + "collections.search_accounts_label": "Шукайце ўліковыя запісы, каб дадаць іх сюды…", + "collections.search_accounts_max_reached": "Вы дадалі максімальную колькасць уліковых запісаў", "collections.topic_hint": "Дадайце хэштэг, які дапаможа іншым зразумець галоўную тэму гэтай калекцыі.", "collections.view_collection": "Глядзець калекцыю", "collections.visibility_public": "Публічная", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 893efa8846ade5..2d2f62ea72e0a8 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon er decentraliseret, så uanset hvor du opretter din konto, vil du være i stand til at følge og interagere med hvem som helst på denne server. Du kan endda selv være vært for den!", "closed_registrations_modal.title": "Oprettelse på Mastodon", "collections.account_count": "{count, plural, one {# konto} other {# konti}}", + "collections.accounts.empty_description": "Tilføj op til {count} konti, du følger", + "collections.accounts.empty_title": "Denne samling er tom", "collections.collection_description": "Beskrivelse", "collections.collection_name": "Navn", "collections.collection_topic": "Emne", + "collections.confirm_account_removal": "Er du sikker på, at du vil fjerne denne konto fra denne samling?", "collections.content_warning": "Indholdsadvarsel", "collections.continue": "Fortsæt", "collections.create.accounts_subtitle": "Kun konti, du følger, og som har tilmeldt sig opdagelse, kan tilføjes.", @@ -261,6 +264,9 @@ "collections.edit_details": "Rediger grundlæggende oplysninger", "collections.edit_settings": "Rediger indstillinger", "collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.", + "collections.hints.accounts_counter": "{count} / {max} konti", + "collections.hints.add_more_accounts": "Tilføj mindst {count, plural, one {# konto} other {# konti}} for at fortsætte", + "collections.hints.can_not_remove_more_accounts": "Samlinger skal indeholde mindst {count, plural, one {# konto} other {# konti}}. Det er ikke muligt at fjerne flere konti.", "collections.last_updated_at": "Senest opdateret: {date}", "collections.manage_accounts": "Administrer konti", "collections.manage_accounts_in_collection": "Administrer konti i denne samling", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Begrænset til 100 tegn", "collections.new_collection": "Ny samling", "collections.no_collections_yet": "Ingen samlinger endnu.", + "collections.remove_account": "Fjern denne konto", + "collections.search_accounts_label": "Søg efter konti for at tilføje…", + "collections.search_accounts_max_reached": "Du har tilføjet det maksimale antal konti", "collections.topic_hint": "Tilføj et hashtag, der hjælper andre med at forstå det overordnede emne for denne samling.", "collections.view_collection": "Vis samling", "collections.visibility_public": "Offentlig", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 50c11fe92a33a5..aadabedb85366f 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon ist dezentralisiert, das heißt, unabhängig davon, wo du dein Konto erstellst, kannst du jedem Profil auf diesem Server folgen und mit ihm interagieren. Du kannst sogar deinen eigenen Mastodon-Server hosten!", "closed_registrations_modal.title": "Bei Mastodon registrieren", "collections.account_count": "{count, plural, one {# Konto} other {# Konten}}", + "collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu", + "collections.accounts.empty_title": "Diese Sammlung ist leer", "collections.collection_description": "Beschreibung", "collections.collection_name": "Titel", "collections.collection_topic": "Thema", + "collections.confirm_account_removal": "Möchtest du dieses Konto wirklich aus der Sammlung entfernen?", "collections.content_warning": "Inhaltswarnung", "collections.continue": "Fortfahren", "collections.create.accounts_subtitle": "Du kannst nur Profile hinzufügen, denen du folgst und die das Hinzufügen gestatten.", @@ -261,6 +264,9 @@ "collections.edit_details": "Allgemeine Informationen bearbeiten", "collections.edit_settings": "Einstellungen bearbeiten", "collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.", + "collections.hints.accounts_counter": "{count} / {max} Konten", + "collections.hints.add_more_accounts": "Füge mindestens {count, plural, one {# Konto} other {# Konten}} hinzu, um fortzufahren", + "collections.hints.can_not_remove_more_accounts": "Sammlungen müssen mindestens {count, plural, one {# Konto} other {# Konten}} enthalten. Weitere Konten zu entfernen, ist daher nicht erlaubt.", "collections.last_updated_at": "Aktualisiert: {date}", "collections.manage_accounts": "Profile verwalten", "collections.manage_accounts_in_collection": "Profile in dieser Sammlung verwalten", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Maximal 100 Zeichen", "collections.new_collection": "Neue Sammlung", "collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.", + "collections.remove_account": "Dieses Konto entfernen", + "collections.search_accounts_label": "Konten suchen, um sie hinzuzufügen …", + "collections.search_accounts_max_reached": "Du hast die Höchstzahl an Konten hinzugefügt", "collections.topic_hint": "Ein Hashtag für diese Sammlung kann anderen dabei helfen, dein Anliegen besser einordnen zu können.", "collections.view_collection": "Sammlungen anzeigen", "collections.visibility_public": "Öffentlich", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 72c02dc1ac774b..fb34eb9ae45709 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Το Mastodon είναι αποκεντρωμένο, οπότε ανεξάρτητα από το πού θα δημιουργήσεις τον λογαριασμό σου, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με οποιονδήποτε σε αυτόν τον διακομιστή. Μπορείς ακόμη και να κάνεις τον δικό σου!", "closed_registrations_modal.title": "Εγγραφή στο Mastodon", "collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}", + "collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε", + "collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή", "collections.collection_description": "Περιγραφή", "collections.collection_name": "Όνομα", "collections.collection_topic": "Θέμα", + "collections.confirm_account_removal": "Σίγουρα θέλετε να αφαιρέσετε αυτόν τον λογαριασμό από αυτή τη συλλογή;", "collections.content_warning": "Προειδοποίηση περιεχομένου", "collections.continue": "Συνέχεια", "collections.create.accounts_subtitle": "Μόνο οι λογαριασμοί που ακολουθείτε που έχουν επιλέξει ανακάλυψη μπορούν να προστεθούν.", @@ -261,6 +264,9 @@ "collections.edit_details": "Επεξεργασία βασικών στοιχείων", "collections.edit_settings": "Επεξεργασία ρυθμίσεων", "collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.", + "collections.hints.accounts_counter": "{count} / {max} λογαριασμοί", + "collections.hints.add_more_accounts": "Προσθέστε τουλάχιστον {count, plural, one {# λογαριασμό} other {# λογαριασμούς}} για να συνεχίσετε", + "collections.hints.can_not_remove_more_accounts": "Οι συλλογές πρέπει να περιέχουν τουλάχιστον {count, plural, one {# λογαριασμό} other {# λογαριασμούς}}. Δεν είναι δυνατή η αφαίρεση περισσότερων λογαριασμών.", "collections.last_updated_at": "Τελευταία ενημέρωση: {date}", "collections.manage_accounts": "Διαχείριση λογαριασμών", "collections.manage_accounts_in_collection": "Διαχείριση λογαριασμών σε αυτήν τη συλλογή", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Όριο 100 χαρακτήρων", "collections.new_collection": "Νέα συλλογή", "collections.no_collections_yet": "Καμία συλλογή ακόμη.", + "collections.remove_account": "Αφαίρεση λογαριασμού", + "collections.search_accounts_label": "Αναζήτηση λογαριασμών για προσθήκη…", + "collections.search_accounts_max_reached": "Έχετε προσθέσει τον μέγιστο αριθμό λογαριασμών", "collections.topic_hint": "Προσθέστε μια ετικέτα που βοηθά άλλους να κατανοήσουν το κύριο θέμα αυτής της συλλογής.", "collections.view_collection": "Προβολή συλλογής", "collections.visibility_public": "Δημόσια", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 1909d348fb3f5f..4060a666138cf0 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon is decentralised, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!", "closed_registrations_modal.title": "Signing up on Mastodon", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", + "collections.accounts.empty_description": "Add up to {count} accounts you follow", + "collections.accounts.empty_title": "This collection is empty", "collections.collection_description": "Description", "collections.collection_name": "Name", "collections.collection_topic": "Topic", + "collections.confirm_account_removal": "Are you sure you want to remove this account from this collection?", "collections.content_warning": "Content warning", "collections.continue": "Continue", "collections.create.accounts_subtitle": "Only accounts you follow who have opted into discovery can be added.", @@ -261,6 +264,9 @@ "collections.edit_details": "Edit basic details", "collections.edit_settings": "Edit settings", "collections.error_loading_collections": "There was an error when trying to load your collections.", + "collections.hints.accounts_counter": "{count} / {max} accounts", + "collections.hints.add_more_accounts": "Add at least {count, plural, one {# account} other {# accounts}} to continue", + "collections.hints.can_not_remove_more_accounts": "Collections must contain at least {count, plural, one {# account} other {# accounts}}. Removing more accounts is not possible.", "collections.last_updated_at": "Last updated: {date}", "collections.manage_accounts": "Manage accounts", "collections.manage_accounts_in_collection": "Manage accounts in this collection", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 characters limit", "collections.new_collection": "New collection", "collections.no_collections_yet": "No collections yet.", + "collections.remove_account": "Remove this account", + "collections.search_accounts_label": "Search for accounts to add…", + "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.", "collections.view_collection": "View collection", "collections.visibility_public": "Public", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index c69f0c27ecdb4e..bc46279ca9dc51 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde creés tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso podés montar tu propio servidor!", "closed_registrations_modal.title": "Registrarse en Mastodon", "collections.account_count": "{count, plural, one {# hora} other {# horas}}", + "collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís", + "collections.accounts.empty_title": "Esta colección está vacía", "collections.collection_description": "Descripción", "collections.collection_name": "Nombre", "collections.collection_topic": "Tema", + "collections.confirm_account_removal": "¿Estás seguro de que querés eliminar esta cuenta de esta colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", "collections.create.accounts_subtitle": "Solo las cuentas que seguís —las cuales optaron por ser descubiertas— pueden ser agregadas.", @@ -261,6 +264,9 @@ "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.", + "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.add_more_accounts": "Agregá, al menos, {count, plural, one {# cuenta} other {# cuentas}} para continuar", + "collections.hints.can_not_remove_more_accounts": "Las colecciones deben contener, al menos, {count, plural, one {# cuenta} other {# cuentas}}. No es posible eliminar más cuentas.", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.manage_accounts_in_collection": "Administrar cuentas en esta colección", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Límite de 100 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "No hay colecciones aún.", + "collections.remove_account": "Eliminar esta cuenta", + "collections.search_accounts_label": "Buscar cuentas para agregar…", + "collections.search_accounts_max_reached": "Agregaste el número máximo de cuentas", "collections.topic_hint": "Agregá una etiqueta que ayude a otros usuarios a entender el tema principal de esta colección.", "collections.view_collection": "Abrir colección", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index a5441535ae4a9e..c00ce75d71e4b7 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!", "closed_registrations_modal.title": "Registrarse en Mastodon", "collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}", + "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues", + "collections.accounts.empty_title": "Esta colección está vacía", "collections.collection_description": "Descripción", "collections.collection_name": "Nombre", "collections.collection_topic": "Tema", + "collections.confirm_account_removal": "¿Estás seguro/a de que quieres eliminar esta cuenta de esta colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", "collections.create.accounts_subtitle": "Solo se pueden agregar cuentas que sigas y que hayan optado por aparecer en los resultados de búsqueda.", @@ -261,6 +264,9 @@ "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.", + "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.add_more_accounts": "Añade al menos {count, plural,one {# cuenta} other {# cuentas}} para continuar", + "collections.hints.can_not_remove_more_accounts": "Las colecciones deben contener al menos {count, plural,one {# cuenta} other {# cuentas}}. No es posible eliminar más cuentas.", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.manage_accounts_in_collection": "Administrar cuentas en esta colección", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Limitado a 100 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "No hay colecciones todavía.", + "collections.remove_account": "Eliminar esta cuenta", + "collections.search_accounts_label": "Buscar cuentas para añadir…", + "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.topic_hint": "Agrega una etiqueta que ayude a los demás a comprender el tema principal de esta colección.", "collections.view_collection": "Ver colección", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 1768f07cf81e4b..e98c685ae56419 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!", "closed_registrations_modal.title": "Registrarse en Mastodon", "collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}", + "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas", + "collections.accounts.empty_title": "Esta colección está vacía", "collections.collection_description": "Descripción", "collections.collection_name": "Nombre", "collections.collection_topic": "Tema", + "collections.confirm_account_removal": "¿Estás seguro de que quieres eliminar esta cuenta de la colección?", "collections.content_warning": "Advertencia de contenido", "collections.continue": "Continuar", "collections.create.accounts_subtitle": "Solo pueden añadirse cuentas que sigues y que han activado el descubrimiento.", @@ -261,6 +264,9 @@ "collections.edit_details": "Editar datos básicos", "collections.edit_settings": "Cambiar ajustes", "collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.", + "collections.hints.accounts_counter": "{count} / {max} cuentas", + "collections.hints.add_more_accounts": "¡Añade al menos {count, plural, one {# cuenta} other {# cuentas}} para continuar", + "collections.hints.can_not_remove_more_accounts": "Las colecciones deben contener al menos {count, plural, one {# cuenta} other {# cuentas}}. No es posible eliminar más cuentas.", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Administrar cuentas", "collections.manage_accounts_in_collection": "Administrar cuentas en esta colección", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Limitado a 100 caracteres", "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "Aún no hay colecciones.", + "collections.remove_account": "Borrar esta cuenta", + "collections.search_accounts_label": "Buscar cuentas para añadir…", + "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", "collections.topic_hint": "Añadir una etiqueta que ayude a otros a entender el tema principal de esta colección.", "collections.view_collection": "Ver colección", "collections.visibility_public": "Pública", @@ -454,7 +463,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", "empty_column.account_about.me": "Aún no has añadido ninguna información sobre ti.", - "empty_column.account_about.other": "{acct} aún no ha añadido ninguna información sobre sí mismo/a.", + "empty_column.account_about.other": "{acct} aún no ha añadido ninguna información sobre sí mismo/a/e.", "empty_column.account_featured.me": "Aún no has destacado nada. ¿Sabías que puedes destacar las etiquetas que más usas e incluso las cuentas de tus amigos en tu perfil?", "empty_column.account_featured.other": "{acct} aún no ha destacado nada. ¿Sabías que puedes destacar las etiquetas que más usas e incluso las cuentas de tus amigos en tu perfil?", "empty_column.account_featured_other.unknown": "Esta cuenta aún no ha destacado nada.", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index c9c8064adfe9fc..9d6fd0fb27e579 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -13,6 +13,7 @@ "about.not_available": "See info ei ole selles serveris saadavaks tehtud.", "about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}", "about.rules": "Serveri reeglid", + "account.about": "Teave", "account.account_note_header": "Isiklik märge", "account.activity": "Tegevus", "account.add_note": "Lisa isiklik märge", @@ -29,9 +30,9 @@ "account.block_short": "Blokeeri", "account.blocked": "Blokeeritud", "account.blocking": "Blokeeritud kasutaja", - "account.cancel_follow_request": "Võta jälgimistaotlus tagasi", + "account.cancel_follow_request": "Võta jälgimissoov tagasi", "account.copy": "Kopeeri profiili link", - "account.direct": "Maini privaatselt @{name}", + "account.direct": "Maini privaatselt kasutajat @{name}", "account.disable_notifications": "Ära teavita, kui @{name} postitab", "account.domain_blocking": "Blokeeritud domeen", "account.edit_note": "Muuda isiklikku märget", @@ -66,9 +67,9 @@ "account.followers_you_know_counter": "{counter} kasutaja(t), keda sa tead", "account.following": "Jälgib", "account.following_counter": "{count, plural, one {{counter} jälgib} other {{counter} jälgib}}", - "account.follows.empty": "See kasutaja ei jälgi veel kedagi.", + "account.follows.empty": "See kasutaja ei jälgi veel mitte kedagi.", "account.follows_you": "Jälgib sind", - "account.go_to_profile": "Mine profiilile", + "account.go_to_profile": "Vaata profiili", "account.hide_reblogs": "Peida @{name} jagamised", "account.in_memoriam": "In Memoriam.", "account.joined_long": "Liitus {date}", @@ -103,6 +104,13 @@ "account.muted": "Summutatud", "account.muting": "Summutatud konto", "account.mutual": "Te jälgite teineteist", + "account.name.help.domain": "{domain} on server, kus antud konto koos profiiliga asub ning kust postitused saavad alguse.", + "account.name.help.domain_self": "{domain} on server, kus sinu konto koos profiiliga asub ning kust sinu postitused saavad alguse.", + "account.name.help.footer": "Nii nagu võid saata teiste e-posti serverite kasutajatele e-kirju, saad ka suhelda teiste Mastodoni serverite kasutajatega - ja tegelikult ka muude sotsiaalvõrkude kasutajatega, kus on kasutusel sama lahendus, nagu Mastodon pruugib (ActivityPubi protokoll).", + "account.name.help.header": "Kasutajatunnus on nagu e-posti aadress", + "account.name.help.username": "{username} on selle konto kasutajanimi tema serveris. Kellelgi teisel mõnes muus serveris võib olla sama kasutajanimi.", + "account.name.help.username_self": "{username} on sinu kasutajanimi selles serveris. Kellelgi teisel mõnes muus serveris võib olla sama kasutajanimi.", + "account.name_info": "Mida see tähendab?", "account.no_bio": "Kirjeldust pole lisatud.", "account.node_modal.callout": "Isiklikud märked on nähtavad vaid sulle.", "account.node_modal.edit_title": "Muuda isiklikku märget", @@ -116,7 +124,7 @@ "account.posts": "Postitused", "account.posts_with_replies": "Postitused ja vastused", "account.remove_from_followers": "Eemalda {name} jälgijate seast", - "account.report": "Raporteeri @{name}", + "account.report": "Teata kasutajast {name}", "account.requested_follow": "{name} on soovinud sinu jälgimist", "account.requests_to_follow_you": "soovib sind jälgida", "account.share": "Jaga @{name} profiili", @@ -163,7 +171,7 @@ "annual_report.announcement.title": "{year}. aasta Mastodoni kokkuvõte on valmis", "annual_report.nav_item.badge": "Uus", "annual_report.shared_page.donate": "Toeta rahaliselt", - "annual_report.shared_page.footer": "Loodud {heart} Mastodoni meeskonna poolt", + "annual_report.shared_page.footer": "Loodud suure {heart}-ga Mastodoni meeskonna poolt", "annual_report.shared_page.footer_server_info": "{username} kasutab {domain}-i, üht paljudest kogukondadest, mis toimivad Mastodonil.", "annual_report.summary.archetype.booster.desc_public": "{name} jätkas postituste otsimist, et neid edendada, tugevdades teisi loojaid täiusliku täpsusega.", "annual_report.summary.archetype.booster.desc_self": "Sa jätkasid postituste otsimist, et neid edendada, tugevdades teisi loojaid täiusliku täpsusega.", @@ -185,25 +193,25 @@ "annual_report.summary.archetype.reveal_description": "Täname, et oled Mastodoni liige! Aeg on välja selgitada, millist põhitüüpi sa {year}. kehastasid.", "annual_report.summary.archetype.title_public": "Kasutaja {name} põhitüüp", "annual_report.summary.archetype.title_self": "Sinu põhitüüp", - "annual_report.summary.close": "Sule", + "annual_report.summary.close": "Sulge", "annual_report.summary.copy_link": "Kopeeri link", "annual_report.summary.followers.new_followers": "{count, plural, one {uus jälgija} other {uut jälgijat}}", "annual_report.summary.highlighted_post.boost_count": "Seda postitust on jagatud {count, plural, one {ühe korra} other {# korda}}.", "annual_report.summary.highlighted_post.favourite_count": "Seda postitust on lemmikuks märgitud {count, plural, one {ühe korra} other {# korda}}.", "annual_report.summary.highlighted_post.reply_count": "See postitus on saanud {count, plural, one {ühe vastuse} other {# vastust}}.", "annual_report.summary.highlighted_post.title": "Kõige populaarsemad postitused", - "annual_report.summary.most_used_app.most_used_app": "enim kasutatud äpp", + "annual_report.summary.most_used_app.most_used_app": "enimkasutatud rakendus", "annual_report.summary.most_used_hashtag.most_used_hashtag": "enim kasutatud teemaviide", "annual_report.summary.most_used_hashtag.used_count": "Sa lisasid selle teemaviite {count, plural, one {ühele postitusele} other {#-le postitusele}}.", - "annual_report.summary.most_used_hashtag.used_count_public": "{name} kasutas seda silti {count, plural, one {ühes postituses} other {# postituses}}.", - "annual_report.summary.new_posts.new_posts": "uus postitus", + "annual_report.summary.most_used_hashtag.used_count_public": "{name} kasutas seda teemaviidet {count, plural, one {ühes postituses} other {#-s postituses}}.", + "annual_report.summary.new_posts.new_posts": "uued postitused", "annual_report.summary.percentile.text": "See paneb su top {domain} kasutajate hulka.", "annual_report.summary.percentile.we_wont_tell_bernie": "Vägev.", "annual_report.summary.share_elsewhere": "Jaga mujal", "annual_report.summary.share_message": "Minu arhetüüp on {archetype}!", "annual_report.summary.share_on_mastodon": "Jaga Mastodonis", "attachments_list.unprocessed": "(töötlemata)", - "audio.hide": "Peida audio", + "audio.hide": "Peida heliriba", "block_modal.remote_users_caveat": "Serverile {domain} edastatakse palve otsust järgida. Ometi pole see tagatud, kuna mõned serverid võivad blokeeringuid käsitleda omal moel. Avalikud postitused võivad tuvastamata kasutajatele endiselt näha olla.", "block_modal.show_less": "Kuva vähem", "block_modal.show_more": "Kuva rohkem", @@ -232,9 +240,10 @@ "carousel.slide": "Slaid {current, number} / {max, number}", "closed_registrations.other_server_instructions": "Kuna Mastodon on detsentraliseeritud, võib konto teha teise serverisse ja sellegipoolest siinse kontoga suhelda.", "closed_registrations_modal.description": "Praegu ei ole võimalik teha {domain} peale kontot, aga pea meeles, et sul ei pea olema just {domain} konto, et Mastodoni kasutada.", - "closed_registrations_modal.find_another_server": "Leia teine server", - "closed_registrations_modal.preamble": "Mastodon on detsentraliseeritud, mis tähendab, et konto võib luua ükskõik kuhu, kuid ikkagi saab jälgida ja suhelda igaühega sellel serveril. Võib isegi oma serveri püsti panna!", + "closed_registrations_modal.find_another_server": "Leia mõni muu server", + "closed_registrations_modal.preamble": "Mastodon on hajutatud võrk, mis tähendab, et konto võid luua ükskõik kuhu, kuid ikkagi saad jälgida ja suhelda igaühega selles serveris. Võid isegi oma serveri püsti panna!", "closed_registrations_modal.title": "Mastodoni registreerumine", + "collections.account_count": "{count, plural, one {# kasutajakonto} other {# kasutajakontot}}", "collections.collection_description": "Kirjeldus", "collections.collection_name": "Nimi", "collections.collection_topic": "Teema", @@ -252,6 +261,7 @@ "collections.edit_details": "Muuda põhiandmeid", "collections.edit_settings": "Muuda seadistusi", "collections.error_loading_collections": "Sinu kogumike laadimisel tekkis viga.", + "collections.last_updated_at": "Viimati uuendatud: {date}", "collections.manage_accounts": "Halda kasutajakontosid", "collections.manage_accounts_in_collection": "Halda selle kogumiku kontosid", "collections.mark_as_sensitive": "Märgi delikaatseks", @@ -274,7 +284,7 @@ "column.create_list": "Loo loend", "column.direct": "Privaatsed mainimised", "column.directory": "Sirvi profiile", - "column.domain_blocks": "Peidetud domeenid", + "column.domain_blocks": "Blokeeritud domeenid", "column.edit_list": "Muuda loendit", "column.favourites": "Lemmikud", "column.firehose": "Postitused reaalajas", @@ -443,6 +453,8 @@ "emoji_button.search_results": "Otsitulemused", "emoji_button.symbols": "Sümbolid", "emoji_button.travel": "Reisimine & kohad", + "empty_column.account_about.me": "Sa pole enda kohta veel mitte mingit teavet lisanud.", + "empty_column.account_about.other": "{acct} pole enda kohta veel mitte mingit teavet lisanud.", "empty_column.account_featured.me": "Sa pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid teemaviiteid või sõbra kasutajakontot?", "empty_column.account_featured.other": "{acct} pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid teemaviiteid või sõbra kasutajakontot?", "empty_column.account_featured_other.unknown": "See kasutajakonto pole veel midagi esile tõstnud.", @@ -526,6 +538,8 @@ "follow_suggestions.view_all": "Vaata kõiki", "follow_suggestions.who_to_follow": "Keda jälgida", "followed_tags": "Jälgitavad teemaviited", + "followers.hide_other_followers": "See kasutaja eelistab mitte avaldada oma teisi jälgijaid", + "following.hide_other_following": "See kasutaja eelistab mitte avaldada oma teisi jälgitavaid", "footer.about": "Teave", "footer.about_mastodon": "Mastodoni kohta", "footer.about_server": "{domain} kohta", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 11d6fe0e769dc9..0853c5d24da3ee 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon on hajautettu, joten riippumatta siitä, missä luot tilisi, voit seurata ja olla vuorovaikutuksessa kenen tahansa kanssa tällä palvelimella. Voit jopa isännöidä palvelinta!", "closed_registrations_modal.title": "Rekisteröityminen Mastodoniin", "collections.account_count": "{count, plural, one {# tili} other {# tiliä}}", + "collections.accounts.empty_description": "Lisää enintään {count} seuraamaasi tiliä", + "collections.accounts.empty_title": "Tämä kokoelma on tyhjä", "collections.collection_description": "Kuvaus", "collections.collection_name": "Nimi", "collections.collection_topic": "Aihe", + "collections.confirm_account_removal": "Haluatko varmasti poistaa tämän tilin tästä kokoelmasta?", "collections.content_warning": "Sisältövaroitus", "collections.continue": "Jatka", "collections.create.accounts_subtitle": "Lisätä voi vain tilejä, joita seuraat ja jotka ovat valinneet tulla löydetyiksi.", @@ -261,6 +264,9 @@ "collections.edit_details": "Muokkaa perustietoja", "collections.edit_settings": "Muokkaa asetuksia", "collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.", + "collections.hints.accounts_counter": "{count} / {max} tiliä", + "collections.hints.add_more_accounts": "Jatka lisäämällä vähintään {count, plural, one {# tili} other {# tiliä}}", + "collections.hints.can_not_remove_more_accounts": "Kokoelmien on sisällettävä vähintään {count, plural, one {# tili} other {# tiliä}}. Enempää tilejä ei ole mahdollista poistaa.", "collections.last_updated_at": "Päivitetty viimeksi {date}", "collections.manage_accounts": "Hallitse tilejä", "collections.manage_accounts_in_collection": "Hallitse tässä kokoelmassa olevia tilejä", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 merkin rajoitus", "collections.new_collection": "Uusi kokoelma", "collections.no_collections_yet": "Ei vielä kokoelmia.", + "collections.remove_account": "Poista tämä tili", + "collections.search_accounts_label": "Hae lisättäviä tilejä…", + "collections.search_accounts_max_reached": "Olet lisännyt enimmäismäärän tilejä", "collections.topic_hint": "Lisää aihetunniste, joka auttaa muita ymmärtämään tämän kokoelman pääaiheen.", "collections.view_collection": "Näytä kokoelma", "collections.visibility_public": "Julkinen", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 1e96971b0c52a3..38cf42d50083e6 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon er desentraliserað, so óansæð hvar tú stovnar tína kontu, so ber til hjá tær at fylgja og virka saman við einum og hvørjum á hesum ambætaranum. Tað ber enntá til at hýsa tí sjálvi!", "closed_registrations_modal.title": "At stovna kontu á Mastodon", "collections.account_count": "{count, plural, one {# konta} other {# kontur}}", + "collections.accounts.empty_description": "Legg afturat upp til {count} kontur, sum tú fylgir", + "collections.accounts.empty_title": "Hetta savnið er tómt", "collections.collection_description": "Lýsing", "collections.collection_name": "Navn", "collections.collection_topic": "Evni", + "collections.confirm_account_removal": "Er tú vís/ur í, at tú vilt strika hesa kontuna frá hesum savninum?", "collections.content_warning": "Innihaldsávaring", "collections.continue": "Halt fram", "collections.create.accounts_subtitle": "Einans kontur, sum tú fylgir og sum hava játtað at blíva uppdagaðar, kunnu leggjast afturat.", @@ -261,6 +264,9 @@ "collections.edit_details": "Rætta grundleggjandi smálutir", "collections.edit_settings": "Rætta stillingar", "collections.error_loading_collections": "Ein feilur hendi, tá tú royndi at finna fram søvnini hjá tær.", + "collections.hints.accounts_counter": "{count} / {max} kontur", + "collections.hints.add_more_accounts": "Legg minst {count, plural, one {# kontu} other {# kontur}} afturat fyri at halda fram", + "collections.hints.can_not_remove_more_accounts": "Søvn mugu innihalda minst {count, plural, one {# kontu} other {# kontur}}. Ikki møguligt at strika fleiri kontur.", "collections.last_updated_at": "Seinast dagført: {date}", "collections.manage_accounts": "Umsit kontur", "collections.manage_accounts_in_collection": "Umsit kontur í hesum savninum", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Í mesta lagi 100 tekn", "collections.new_collection": "Nýtt savn", "collections.no_collections_yet": "Eingi søvn enn.", + "collections.remove_account": "Strika hesa kontuna", + "collections.search_accounts_label": "Leita eftir kontum at leggja afturat…", + "collections.search_accounts_max_reached": "Tú hevur lagt afturat mesta talið av kontum", "collections.topic_hint": "Legg afturat eitt frámerki, sum hjálpir øðrum at skilja høvuðevnið í hesum savninum.", "collections.view_collection": "Vís savn", "collections.visibility_public": "Alment", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 53e48e07bd9586..7b83533de2a85d 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon est décentralisé, donc peu importe où vous créez votre compte, vous serez en mesure de suivre et d'interagir avec quiconque sur ce serveur. Vous pouvez même l'héberger vous-même!", "closed_registrations_modal.title": "S'inscrire sur Mastodon", "collections.account_count": "{count, plural, one {# compte} other {# comptes}}", + "collections.accounts.empty_description": "Ajouter jusqu'à {count} comptes que vous suivez", + "collections.accounts.empty_title": "Cette collection est vide", "collections.collection_description": "Description", "collections.collection_name": "Nom", "collections.collection_topic": "Sujet", + "collections.confirm_account_removal": "Voulez-vous vraiment supprimer ce compte de la collection ?", "collections.content_warning": "Avertissement au public", "collections.continue": "Continuer", "collections.create.accounts_subtitle": "Seuls les comptes que vous suivez et qui ont autorisé leur découverte peuvent être ajoutés.", @@ -261,6 +264,9 @@ "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", + "collections.hints.accounts_counter": "{count} / {max} comptes", + "collections.hints.add_more_accounts": "Ajouter au moins {count, plural, one {# compte} other {# comptes}} pour continuer", + "collections.hints.can_not_remove_more_accounts": "Les collections doivent contenir au moins {count, plural, one {# compte} other {# comptes}}. Il n'est pas possible de supprimer plus de comptes.", "collections.last_updated_at": "Dernière mise à jour : {date}", "collections.manage_accounts": "Gérer les comptes", "collections.manage_accounts_in_collection": "Gérer les comptes de cette collection", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Maximum 100 caractères", "collections.new_collection": "Nouvelle collection", "collections.no_collections_yet": "Aucune collection pour le moment.", + "collections.remove_account": "Supprimer ce compte", + "collections.search_accounts_label": "Chercher des comptes à ajouter…", + "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.view_collection": "Voir la collection", "collections.visibility_public": "Publique", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 7e18bf2c43a14a..eee947ec48b16b 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon est décentralisé : peu importe où vous créez votre compte, vous serez en mesure de suivre et d'interagir avec quiconque sur ce serveur. Vous pouvez même l'héberger !", "closed_registrations_modal.title": "Inscription sur Mastodon", "collections.account_count": "{count, plural, one {# compte} other {# comptes}}", + "collections.accounts.empty_description": "Ajouter jusqu'à {count} comptes que vous suivez", + "collections.accounts.empty_title": "Cette collection est vide", "collections.collection_description": "Description", "collections.collection_name": "Nom", "collections.collection_topic": "Sujet", + "collections.confirm_account_removal": "Voulez-vous vraiment supprimer ce compte de la collection ?", "collections.content_warning": "Avertissement au public", "collections.continue": "Continuer", "collections.create.accounts_subtitle": "Seuls les comptes que vous suivez et qui ont autorisé leur découverte peuvent être ajoutés.", @@ -261,6 +264,9 @@ "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", + "collections.hints.accounts_counter": "{count} / {max} comptes", + "collections.hints.add_more_accounts": "Ajouter au moins {count, plural, one {# compte} other {# comptes}} pour continuer", + "collections.hints.can_not_remove_more_accounts": "Les collections doivent contenir au moins {count, plural, one {# compte} other {# comptes}}. Il n'est pas possible de supprimer plus de comptes.", "collections.last_updated_at": "Dernière mise à jour : {date}", "collections.manage_accounts": "Gérer les comptes", "collections.manage_accounts_in_collection": "Gérer les comptes de cette collection", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Maximum 100 caractères", "collections.new_collection": "Nouvelle collection", "collections.no_collections_yet": "Aucune collection pour le moment.", + "collections.remove_account": "Supprimer ce compte", + "collections.search_accounts_label": "Chercher des comptes à ajouter…", + "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.view_collection": "Voir la collection", "collections.visibility_public": "Publique", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 5ef324052f1c5b..2165b5bf74a94f 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Ós rud é go bhfuil Mastodon díláraithe, is cuma cá háit a chruthaíonn tú do chuntas, beidh tú in ann idirghníomhú le haon duine ar an bhfreastalaí seo agus iad a leanúint. Is féidir fiú é a féin-óstáil!", "closed_registrations_modal.title": "Cláraigh le Mastodon", "collections.account_count": "{count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}}", + "collections.accounts.empty_description": "Cuir suas le {count} cuntas leis a leanann tú", + "collections.accounts.empty_title": "Tá an bailiúchán seo folamh", "collections.collection_description": "Cur síos", "collections.collection_name": "Ainm", "collections.collection_topic": "Topaic", + "collections.confirm_account_removal": "An bhfuil tú cinnte gur mian leat an cuntas seo a bhaint den bhailiúchán seo?", "collections.content_warning": "Rabhadh ábhair", "collections.continue": "Lean ar aghaidh", "collections.create.accounts_subtitle": "Ní féidir ach cuntais a leanann tú atá roghnaithe le fionnachtain a chur leis.", @@ -261,6 +264,9 @@ "collections.edit_details": "Cuir sonraí bunúsacha in eagar", "collections.edit_settings": "Socruithe a chur in eagar", "collections.error_loading_collections": "Tharla earráid agus iarracht á déanamh do bhailiúcháin a luchtú.", + "collections.hints.accounts_counter": "{count} / {max} cuntais", + "collections.hints.add_more_accounts": "Cuir ar a laghad {count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}} leis chun leanúint ar aghaidh", + "collections.hints.can_not_remove_more_accounts": "Ní mór go mbeadh ar a laghad {count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}} i mbailiúcháin. Ní féidir tuilleadh cuntas a bhaint.", "collections.last_updated_at": "Nuashonraithe go deireanach: {date}", "collections.manage_accounts": "Bainistigh cuntais", "collections.manage_accounts_in_collection": "Bainistigh cuntais sa bhailiúchán seo", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Teorainn 100 carachtar", "collections.new_collection": "Bailiúchán nua", "collections.no_collections_yet": "Gan aon bhailiúcháin fós.", + "collections.remove_account": "Bain an cuntas seo", + "collections.search_accounts_label": "Cuardaigh cuntais le cur leis…", + "collections.search_accounts_max_reached": "Tá an líon uasta cuntas curtha leis agat", "collections.topic_hint": "Cuir haischlib leis a chabhraíonn le daoine eile príomhábhar an bhailiúcháin seo a thuiscint.", "collections.view_collection": "Féach ar bhailiúchán", "collections.visibility_public": "Poiblí", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 7484dabcd2a0a5..d4255ab4c74cf2 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "A Mastodon decentralizált, így teljesen mindegy, hol hozod létre a fiókodat, követhetsz és kapcsolódhatsz bárkivel ezen a kiszolgálón is. Saját magad is üzemeltethetsz kiszolgálót!", "closed_registrations_modal.title": "Regisztráció a Mastodonra", "collections.account_count": "{count, plural, one {# fiók} other {# fiók}}", + "collections.accounts.empty_description": "Adj hozzá legfeljebb {count} követett fiókot", + "collections.accounts.empty_title": "Ez a gyűjtemény üres", "collections.collection_description": "Leírás", "collections.collection_name": "Név", "collections.collection_topic": "Téma", + "collections.confirm_account_removal": "Biztos, hogy eltávolítod ezt a fiókot ebből a gyűjteményből?", "collections.content_warning": "Tartalmi figyelmeztetés", "collections.continue": "Folytatás", "collections.create.accounts_subtitle": "Csak azok a követett fiókok adhatóak hozzá, melyek engedélyezték a felfedezést.", @@ -261,6 +264,9 @@ "collections.edit_details": "Alapvető részletek szerkesztése", "collections.edit_settings": "Beállítások szerkesztése", "collections.error_loading_collections": "Hiba történt a gyűjtemények betöltése során.", + "collections.hints.accounts_counter": "{count} / {max} fiók", + "collections.hints.add_more_accounts": "Adj hozzá legalább {count, plural, one {# fiókot} other {# fiókot}} a folytatáshoz", + "collections.hints.can_not_remove_more_accounts": "A gyűjteményeknek legalább {count, plural, one {# fiókot} other {# fiókot}} kell tartalmazniuk. Több fiók eltávolítása nem lehetséges.", "collections.last_updated_at": "Utoljára frissítve: {date}", "collections.manage_accounts": "Fiókok kezelése", "collections.manage_accounts_in_collection": "Gyűjteményben szereplő fiókok kezelése", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 karakteres korlát", "collections.new_collection": "Új gyűjtemény", "collections.no_collections_yet": "Még nincsenek gyűjtemények.", + "collections.remove_account": "Fiók eltávolítása", + "collections.search_accounts_label": "Hozzáadandó fiókok keresése…", + "collections.search_accounts_max_reached": "Elérte a hozzáadott fiókok maximális számát", "collections.topic_hint": "Egy hashtag hozzáadása segít másoknak abban, hogy megértsék a gyűjtemény fő témáját.", "collections.view_collection": "Gyűjtemény megtekintése", "collections.visibility_public": "Nyilvános", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index c5aa09c96eb526..6b226b64dcf965 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon er ekki miðstýrt, svo það skiptir ekki máli hvar þú býrð til aðgang; þú munt get fylgt eftir og haft samskipti við hvern sem er á þessum þjóni. Þú getur jafnvel hýst þinn eigin Mastodon þjón!", "closed_registrations_modal.title": "Að nýskrá sig á Mastodon", "collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}", + "collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með", + "collections.accounts.empty_title": "Þetta safn er tómt", "collections.collection_description": "Lýsing", "collections.collection_name": "Nafn", "collections.collection_topic": "Umfjöllunarefni", + "collections.confirm_account_removal": "Ertu viss um að þú viljir fjarlægja þennan aðgang úr þessu safni?", "collections.content_warning": "Viðvörun vegna efnis", "collections.continue": "Halda áfram", "collections.create.accounts_subtitle": "Einungis er hægt að bæta við notendum sem hafa samþykkt að vera með í opinberri birtingu.", @@ -261,6 +264,9 @@ "collections.edit_details": "Breyta grunnupplýsingum", "collections.edit_settings": "Breyta stillingum", "collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.", + "collections.hints.accounts_counter": "{count} / {max} aðgangar", + "collections.hints.add_more_accounts": "Bættu við að minnsta kosti {count, plural, one {# aðgangi} other {# aðgöngum}} til að halda áfram", + "collections.hints.can_not_remove_more_accounts": "Söfn verða að innihalda að minnsta kosti {count, plural, one {# aðgang} other {# aðganga}}. Ekki er hægt að fjarlægja fleiri aðganga.", "collections.last_updated_at": "Síðast uppfært: {date}", "collections.manage_accounts": "Sýsla með notandaaðganga", "collections.manage_accounts_in_collection": "Sýsla með notendaaðganga í þessu safni", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 stafa takmörk", "collections.new_collection": "Nýtt safn", "collections.no_collections_yet": "Engin söfn ennþá.", + "collections.remove_account": "Fjarlægja þennan aðgang", + "collections.search_accounts_label": "Leita að aðgöngum til að bæta við…", + "collections.search_accounts_max_reached": "Þú hefur þegar bætt við leyfilegum hámarksfjölda aðganga", "collections.topic_hint": "Bættu við myllumerki sem hjálpar öðrum að skilja aðalefni þessa safns.", "collections.view_collection": "Skoða safn", "collections.visibility_public": "Opinbert", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index b4a58e37c6eb67..220f50ab6a1672 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -242,9 +242,11 @@ "closed_registrations_modal.preamble": "Mastodon-i është i decentralizuar, ndaj pavarësisht se ku krijoni llogarinë tuaj, do të jeni në gjendje të ndiqni dhe ndërveproni me këdo në këtë shërbyes. Mundeni madje edhe ta strehoni ju vetë!", "closed_registrations_modal.title": "Po regjistroheni në Mastodon", "collections.account_count": "{count, plural, one {# llogari} other {# llogari}}", + "collections.accounts.empty_title": "Ky koleksion është i zbrazët", "collections.collection_description": "Përshkrim", "collections.collection_name": "Emër", "collections.collection_topic": "Temë", + "collections.confirm_account_removal": "Jeni i sigurt se doni të hiqet kjo llogari nga ky koleksion?", "collections.content_warning": "Sinjalizim lënde", "collections.continue": "Vazhdo", "collections.create.accounts_subtitle": "Mund të shtohen vetëm llogari që ju ndiqni të cilat kanë zgjedhur të jenë të zbulueshme.", @@ -259,6 +261,9 @@ "collections.edit_details": "Përpunoni hollësi bazë", "collections.edit_settings": "Përpunoni rregullime", "collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.", + "collections.hints.accounts_counter": "{count} / {max} llogari", + "collections.hints.add_more_accounts": "Që të vazhdohet, shtoni të paktën {count, plural, one {# llogari} other {# llogari}}", + "collections.hints.can_not_remove_more_accounts": "Koleksionet duhet të përmbajnë të paktën {count, plural, one {# llogari} other {# llogari}}. S’është e mundshme heqja e më tepër llogarive.", "collections.last_updated_at": "Përditësuar së fundi më: {date}", "collections.manage_accounts": "Administroni llogari", "collections.manage_accounts_in_collection": "Administroni llogari në këtë koleksion", @@ -267,6 +272,9 @@ "collections.name_length_hint": "Kufi prej 100 shenjash", "collections.new_collection": "Koleksion i ri", "collections.no_collections_yet": "Ende pa koleksione.", + "collections.remove_account": "Hiqe këtë llogari", + "collections.search_accounts_label": "Kërkoni për llogari për shtim…", + "collections.search_accounts_max_reached": "Keni shtuar numrin maksimum të llogarive", "collections.topic_hint": "Shtoni një hashtag që ndihmon të tjerët të kuptojnë temën kryesore të këtij koleksion.", "collections.view_collection": "Shiheni koleksionin", "collections.visibility_public": "Publik", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 5a59dbda6a63e6..22255265ee118c 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -194,11 +194,16 @@ "closed_registrations_modal.find_another_server": "Hitta en annan server", "closed_registrations_modal.preamble": "Mastodon är decentraliserat så oavsett var du skapar ditt konto kommer du att kunna följa och interagera med någon på denna server. Du kan också köra din egen server!", "closed_registrations_modal.title": "Registrera sig på Mastodon", + "collections.accounts.empty_description": "Lägg till upp till {count} konton som du följer", "collections.create_a_collection_hint": "Skapa en samling för att rekommendera eller dela dina favoritkonton med andra.", "collections.create_collection": "Skapa samling", "collections.delete_collection": "Radera samling", "collections.error_loading_collections": "Det uppstod ett fel när dina samlingar skulle laddas.", + "collections.hints.accounts_counter": "{count} / {max} konton", "collections.no_collections_yet": "Inga samlingar än.", + "collections.remove_account": "Ta bort detta konto", + "collections.search_accounts_label": "Sök efter konton för att lägga till…", + "collections.search_accounts_max_reached": "Du har lagt till maximalt antal konton", "collections.view_collection": "Visa samling", "column.about": "Om", "column.blocks": "Blockerade användare", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index f83c8440d4240e..809307d03887e3 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -13,6 +13,7 @@ "about.not_available": "Bu sunucuda bu bilgi kullanıma sunulmadı.", "about.powered_by": "{mastodon} destekli merkeziyetsiz sosyal ağ", "about.rules": "Sunucu kuralları", + "account.about": "Hakkında", "account.account_note_header": "Kişisel not", "account.activity": "Aktivite", "account.add_note": "Kişisel bir not ekle", @@ -452,6 +453,8 @@ "emoji_button.search_results": "Arama sonuçları", "emoji_button.symbols": "Semboller", "emoji_button.travel": "Seyahat ve Yerler", + "empty_column.account_about.me": "Henüz kendinle ilgili herhangi bir bilgi eklemedin.", + "empty_column.account_about.other": "{acct} henüz kendisiyle ilgili herhangi bir bilgi eklemedi.", "empty_column.account_featured.me": "Henüz hiçbir şeyi öne çıkarmadınız. En çok kullandığınız etiketleri ve hatta arkadaşlarınızın hesaplarını profilinizde öne çıkarabileceğinizi biliyor muydunuz?", "empty_column.account_featured.other": "{acct} henüz hiçbir şeyi öne çıkarmadı. En çok kullandığınız etiketleri ve hatta arkadaşlarınızın hesaplarını profilinizde öne çıkarabileceğinizi biliyor muydunuz?", "empty_column.account_featured_other.unknown": "Bu hesap henüz hiçbir şeyi öne çıkarmadı.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 38e6c49f1be585..d0fb3aac97095b 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon liên hợp nên bất kể bạn tạo tài khoản ở đâu, bạn cũng sẽ có thể theo dõi và tương tác với tài khoản trên máy chủ này. Bạn thậm chí có thể tự mở máy chủ!", "closed_registrations_modal.title": "Đăng ký Mastodon", "collections.account_count": "{count, plural, other {# tài khoản}}", + "collections.accounts.empty_description": "Thêm tối đa {count} tài khoản mà bạn theo dõi", + "collections.accounts.empty_title": "Collection này trống", "collections.collection_description": "Mô tả", "collections.collection_name": "Tên", "collections.collection_topic": "Chủ đề", + "collections.confirm_account_removal": "Bạn có chắc muốn gỡ tài khoản này khỏi collection?", "collections.content_warning": "Nội dung ẩn", "collections.continue": "Tiếp tục", "collections.create.accounts_subtitle": "Chỉ những tài khoản bạn theo dõi và đã chọn tham gia chương trình khám phá mới có thể được thêm vào.", @@ -261,6 +264,9 @@ "collections.edit_details": "Sửa thông tin cơ bản", "collections.edit_settings": "Sửa cài đặt", "collections.error_loading_collections": "Đã xảy ra lỗi khi tải những collection của bạn.", + "collections.hints.accounts_counter": "{count} / {max} tài khoản", + "collections.hints.add_more_accounts": "Thêm tối thiểu {count, plural, other {# tài khoản}} để tiếp tục", + "collections.hints.can_not_remove_more_accounts": "Bộ sưu tập phải chứa tối thiểu {count, plural, other {# tài khoản}}. Không thể gỡ nữa.", "collections.last_updated_at": "Lần cuối cập nhật: {date}", "collections.manage_accounts": "Quản lý tài khoản", "collections.manage_accounts_in_collection": "Quản lý tài khoản trong collection này", @@ -269,6 +275,9 @@ "collections.name_length_hint": "Giới hạn 100 ký tự", "collections.new_collection": "Collection mới", "collections.no_collections_yet": "Chưa có collection.", + "collections.remove_account": "Gỡ tài khoản này", + "collections.search_accounts_label": "Tìm tài khoản để thêm…", + "collections.search_accounts_max_reached": "Bạn đã đạt đến số lượng tài khoản tối đa", "collections.topic_hint": "Thêm hashtag giúp người khác hiểu chủ đề chính của collection này.", "collections.view_collection": "Xem collection", "collections.visibility_public": "Công khai", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 987a42deb94bd2..749f2ee916358f 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以无论在哪个实例创建账号,都可以关注本服务器上的账号并与之交流。 或者你还可以自己搭建实例!", "closed_registrations_modal.title": "注册 Mastodon 账号", "collections.account_count": "{count, plural, other {# 个账号}}", + "collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个", + "collections.accounts.empty_title": "收藏列表为空", "collections.collection_description": "说明", "collections.collection_name": "名称", "collections.collection_topic": "话题", + "collections.confirm_account_removal": "你确定要将从收藏列表中移除此账号吗?", "collections.content_warning": "内容警告", "collections.continue": "继续", "collections.create.accounts_subtitle": "只有你关注的且已经主动加入发现功能的账号可以添加。", @@ -261,6 +264,9 @@ "collections.edit_details": "编辑基本信息", "collections.edit_settings": "编辑设置", "collections.error_loading_collections": "加载你的收藏列表时发生错误。", + "collections.hints.accounts_counter": "{count} / {max} 个账号", + "collections.hints.add_more_accounts": "添加至少 {count, plural, other {# 个账号}}以继续", + "collections.hints.can_not_remove_more_accounts": "收藏列表必须包含至少 {count, plural, other {# 个账号}}。无法移除更多账号。", "collections.last_updated_at": "最后更新:{date}", "collections.manage_accounts": "管理账户", "collections.manage_accounts_in_collection": "管理此收藏列表内的账户", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100字限制", "collections.new_collection": "新建收藏列表", "collections.no_collections_yet": "尚无收藏列表。", + "collections.remove_account": "移除此账号", + "collections.search_accounts_label": "搜索要添加的账号…", + "collections.search_accounts_max_reached": "你添加的账号数量已达上限", "collections.topic_hint": "添加话题标签,帮助他人了解此收藏列表的主题。", "collections.view_collection": "查看收藏列表", "collections.visibility_public": "公开", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index f344962e083e02..fed0065f889a61 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架設自己的伺服器!", "closed_registrations_modal.title": "註冊 Mastodon", "collections.account_count": "{count, plural, other {# 個帳號}}", + "collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號", + "collections.accounts.empty_title": "此收藏是名單空的", "collections.collection_description": "說明", "collections.collection_name": "名稱", "collections.collection_topic": "主題", + "collections.confirm_account_removal": "您是否確定要自此收藏名單中移除此帳號?", "collections.content_warning": "內容警告", "collections.continue": "繼續", "collections.create.accounts_subtitle": "僅能加入您跟隨並選擇加入探索功能之帳號。", @@ -261,6 +264,9 @@ "collections.edit_details": "編輯基本資料", "collections.edit_settings": "編輯設定", "collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。", + "collections.hints.accounts_counter": "{count} / {max} 個帳號", + "collections.hints.add_more_accounts": "加入至少 {count, plural, other {# 個帳號}}以繼續", + "collections.hints.can_not_remove_more_accounts": "收藏名單必須至少包含 {count, plural, other {# 個帳號}}。無法移除更多帳號。", "collections.last_updated_at": "最後更新:{date}", "collections.manage_accounts": "管理帳號", "collections.manage_accounts_in_collection": "管理此收藏名單之帳號", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 字限制", "collections.new_collection": "新增收藏名單", "collections.no_collections_yet": "您沒有任何收藏名單。", + "collections.remove_account": "移除此帳號", + "collections.search_accounts_label": "搜尋帳號以加入...", + "collections.search_accounts_max_reached": "您新增之帳號數已達上限", "collections.topic_hint": "新增主題標籤以協助其他人瞭解此收藏名單之主題。", "collections.view_collection": "檢視收藏名單", "collections.visibility_public": "公開", diff --git a/config/locales/et.yml b/config/locales/et.yml index 6d6ac75d7c455b..2867586d703860 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1,7 +1,7 @@ --- et: about: - about_mastodon_html: 'Tuleviku sotsiaalvõrgustik: Reklaamivaba, korporatiivse järelvalveta, eetiline kujundus ning detsentraliseeritus! Mastodonis omad sa enda andmeid ka päriselt!' + about_mastodon_html: 'Tuleviku sotsiaalvõrgustik: Reklaamivaba, korporatiivse jälitamiseta, eetiline kujundus ning hajutatus! Mastodonis omad sa enda andmeid ka päriselt!' contact_missing: Määramata contact_unavailable: Pole saadaval hosted_on: Mastodoni teenus serveris %{domain} @@ -18,7 +18,7 @@ et: instance_actor_flash: See on serveri enda virtuaalne konto. See ei esinda ühtegi kindlat kasutajat, vaid seda kasutatakse födereerumisel. Seda kontot ei tohi kustutada. last_active: viimati aktiivne link_verified_on: Selle lingi autorsust kontrolliti %{date} - nothing_here: Siin pole midagi! + nothing_here: Siin pole mitte midagi! pin_errors: following: Pead olema juba selle kasutaja jälgija, keda soovitad posts: @@ -802,6 +802,7 @@ et: view_devops_description: Lubab kasutajail ligipääsu Sidekiq ja pgHero töölaudadele view_feeds: Vaata postituste ja teemade voogu reaalajas view_feeds_description: Sõltumata serveri seadistustest luba kasutajatel vaadata postituste ja teemade voogu reaalajas + requires_2fa: Eeldab kaheastmelise autentimise kasutamist title: Rollid rules: add_new: Lisa reegel @@ -2022,6 +2023,8 @@ et: past_preamble_html: Peale sinu viimast külastust oleme muutnud oma kasutustingimusi. Palun vaata muutunud tingimused üle. review_link: Vaata üle kasutustingimused title: "%{domain} saidi kasutustingimused muutuvad" + themes: + default: Mastodon time: formats: default: "%d. %B, %Y. aastal, kell %H:%M" @@ -2046,6 +2049,8 @@ et: recovery_codes: Taastekoodide varundamine recovery_codes_regenerated: Taastekoodid edukalt taasloodud recovery_instructions_html: Kui telefon peaks kaotsi minema, on võimalik kontole sisenemisel kasutada ühte järgnevatest taastekoodidest. Hoia taastekoode turvaliselt. Näiteks võib neid prindituna hoida koos teiste tähtsate dokumentidega. + resume_app_authorization: Jätka rakenduse autentimist + role_requirement: "%{domain} teenus eeldab, et Mastodoni kasutamiseks lülitad sisse kaheastmelise autentimise." webauthn: Turvavõtmed user_mailer: announcement_published: diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index b391af1373c185..fc6cbcaf51d6ac 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -226,6 +226,7 @@ be: email: Адрас электроннай пошты expires_in: Заканчваецца пасля fields: Метаданыя профілю + filter_action: Фільтраваць дзеянне header: Загаловак honeypot: "%{label} (не запаўняць)" inbox_url: URL паштовай скрыні-рэтранслятара diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index e78f8d8a659c5f..c41e41f690f676 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -224,6 +224,7 @@ de: email: E-Mail-Adresse expires_in: Läuft ab fields: Zusatzfelder + filter_action: Auswirkung header: Titelbild honeypot: "%{label} (nicht ausfüllen)" inbox_url: URL des Relais-Posteingangs diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 0362c3f00f1dd0..ebbeb3e00106a1 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -224,6 +224,7 @@ el: email: Διεύθυνση email expires_in: Λήξη μετά από fields: Επιπλέον πεδία + filter_action: Ενέργεια φίλτρου header: Εικόνα κεφαλίδας honeypot: "%{label} (μη συμπληρώνετε)" inbox_url: Το URL του inbox του ανταποκριτή (relay) diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index bc2c055e0a3494..cb376e4f9a0c45 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -224,6 +224,7 @@ en-GB: email: Email address expires_in: Expire after fields: Profile metadata + filter_action: Filter action header: Header honeypot: "%{label} (do not fill in)" inbox_url: URL of the relay inbox diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index 0ebce51d4d2b2a..5a70754484b0f3 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -224,6 +224,7 @@ es-AR: email: Dirección de correo electrónico expires_in: Vence después de fields: Campos extras + filter_action: Filtrar acción header: Cabecera honeypot: "%{label} (no rellenar)" inbox_url: Dirección web de la bandeja de entrada del relé diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index eb41bbef7227d6..362949480c7acb 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -224,6 +224,7 @@ es-MX: email: Dirección de correo electrónico expires_in: Expirar tras fields: Metadatos de perfil + filter_action: Filtrar acción header: Imagen de encabezado honeypot: "%{label} (no rellenar)" inbox_url: URL de la entrada de relés diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index f6480508b1c16a..30da06b1d72f60 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -224,6 +224,7 @@ es: email: Dirección de correo electrónico expires_in: Expirar tras fields: Metadatos de perfil + filter_action: Acción de filtro header: Imagen de encabezado honeypot: "%{label} (no rellenar)" inbox_url: URL de la entrada de relés diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index 58fcc42d02e06e..b83d9e4a402e1f 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -164,6 +164,7 @@ et: name: Rolli avalik nimi, kui roll on märgitud avalikuks kuvamiseks märgina permissions_as_keys: Selle rolliga kasutajatel on ligipääs... position: Kõrgem roll otsustab teatud olukordades konfliktide lahendamise. Teatud toiminguid saab teha ainult madalama prioriteediga rollidega + require_2fa: Selle rolliga kasutajad peavad sisse lülitama kaheastmelise autentimise username_block: allow_with_approval: Kohese liitumise asemel peavad vastavusekohased liitumised saama eeleeva heakskiidu comparison: Kui lubad blokeerimise osalise vastavuse alusel, siis palun arvesta Scunthorpe'i probleemi tekkimise võimalusega @@ -387,6 +388,7 @@ et: name: Nimi permissions_as_keys: Load position: Positsioon + require_2fa: Eelda kaheastmelise autentimise kasutamist username_block: allow_with_approval: Luba kinnitamisega registreerimine comparison: Võrdlemise meetod diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 68bc26d2f1f9a8..9854ef31a77c82 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -224,6 +224,7 @@ fi: email: Sähköpostiosoite expires_in: Vanhenee fields: Lisäkentät + filter_action: Suodattimen toimi header: Otsakekuva honeypot: "%{label} (älä täytä)" inbox_url: Välittäjän postilaatikon URL-⁠osoite diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index 05fb26700885cc..a8799f98247790 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -224,6 +224,7 @@ fo: email: Teldubrævabústaður expires_in: Endar aftan á fields: Metadátur hjá vanganum + filter_action: Filtrera atgerð header: Høvd honeypot: "%{label} (ikki fylla út)" inbox_url: URL'ur hjá innbakkanum hjá reiðlagnum diff --git a/config/locales/simple_form.fr-CA.yml b/config/locales/simple_form.fr-CA.yml index 980fd4fb208215..2f4ef67b300d04 100644 --- a/config/locales/simple_form.fr-CA.yml +++ b/config/locales/simple_form.fr-CA.yml @@ -224,6 +224,7 @@ fr-CA: email: Adresse courriel expires_in: Expire après fields: Métadonnées du profil + filter_action: Action du filtre header: Image d’en-tête honeypot: "%{label} (ne pas remplir)" inbox_url: URL de la boîte de relais diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 1795970c9b5ef2..4375b37d3d3a6b 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -224,6 +224,7 @@ fr: email: Adresse de courriel expires_in: Expire après fields: Métadonnées du profil + filter_action: Action du filtre header: Image d’en-tête honeypot: "%{label} (ne pas remplir)" inbox_url: URL de la boîte de relais diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index 5889d7a8272a14..daa4e62f31b547 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -227,6 +227,7 @@ ga: email: Seoladh ríomhphoist expires_in: In éag tar éis fields: Réimsí breise + filter_action: Gníomh scagaire header: Ceanntásc honeypot: "%{label} (ná líon isteach)" inbox_url: URL an bhosca isteach sealaíochta diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index a6b3fee8e7a356..c308eb32d96ef8 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -224,6 +224,7 @@ gl: email: Enderezo de correo expires_in: Caduca tras fields: Metadatos do perfil + filter_action: Acción de filtrado header: Cabeceira honeypot: "%{label} (non completar)" inbox_url: URL da caixa de entrada do repetidor diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index 2f6b0ef39d38b2..7f4943c80d6c01 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -226,6 +226,7 @@ he: email: כתובת דוא"ל expires_in: תפוגה לאחר fields: מטא-נתונים על הפרופיל + filter_action: פעולות סינון header: תמונת נושא honeypot: "%{label} (לא למלא)" inbox_url: קישורית לתיבת ממסר diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index f9cb599723e9cd..34121f6e8c071e 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -224,6 +224,7 @@ is: email: Tölvupóstfang expires_in: Rennur út eftir fields: Lýsigögn notandasniðs + filter_action: Aðgerð síu header: Síðuhaus honeypot: "%{label} (ekki fylla út)" inbox_url: URL-slóð á innhólf endurvarpa diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 23414db3a19a8c..657547d0b0a1aa 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -224,6 +224,7 @@ it: email: Indirizzo email expires_in: Scade dopo fields: Metadati del profilo + filter_action: Azione del filtro header: Intestazione honeypot: "%{label} (non compilare)" inbox_url: URL della inbox del ripetitore diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index e876f40d34c447..b36bdb59ec7765 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -223,6 +223,7 @@ sq: email: Adresë email expires_in: Skadon pas fields: Tejtëdhëna profili + filter_action: Veprim filtri header: Krye honeypot: "%{label} (mos plotësoni gjë këtu)" inbox_url: URL e Të marrëve të relesë diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 13e954d729c19f..f40034407b19fa 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -224,6 +224,7 @@ tr: email: E-posta adresi expires_in: Bitiş tarihi fields: Profil meta verisi + filter_action: Eylemi filtrele header: Kapak resmi honeypot: "%{label} (doldurmayın)" inbox_url: Aktarıcı gelen kutusunun URL'si diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index f6007c34ae61f0..bb43d41c1e9caf 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -223,6 +223,7 @@ vi: email: Địa chỉ email expires_in: Hết hạn sau fields: Metadata + filter_action: Lọc hành động header: Ảnh bìa honeypot: "%{label} (đừng điền vào)" inbox_url: Hộp thư relay diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index 6a1c73439a4cec..c2e16f2459c7af 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -223,6 +223,7 @@ zh-CN: email: 邮箱地址 expires_in: 失效时间 fields: 个人资料附加信息 + filter_action: 过滤器操作 header: 封面图 honeypot: "%{label} (请勿填写)" inbox_url: 中继站收件箱的 URL diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 72416c17ea06bd..08c75fb8e3673e 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -223,6 +223,7 @@ zh-TW: email: 電子郵件地址 expires_in: 失效時間 fields: 額外欄位 + filter_action: 過濾器動作 header: 封面圖片 honeypot: "%{label} (請勿填寫)" inbox_url: 中繼收件匣 URL From 4a6d17ad7bfea59d451b64b90eb794055c965557 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 17 Feb 2026 11:27:36 +0100 Subject: [PATCH 06/60] Fix hashtag matching by replacing negative lookbehind with positive lookbehind (#37684) --- app/models/tag.rb | 2 +- spec/models/tag_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index 9924d132e64b25..9c4e0510a94567 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -41,7 +41,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}".freeze - HASHTAG_RE = %r{(? Date: Tue, 17 Feb 2026 11:54:21 +0100 Subject: [PATCH 07/60] Add `GET /api/v1/donation_campaigns` (#37880) --- .../api/v1/donation_campaigns_controller.rb | 86 ++++++++++++++ config/mastodon.yml | 3 + config/routes/api.rb | 1 + .../api/v1/donation_campaigns_spec.rb | 105 ++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 app/controllers/api/v1/donation_campaigns_controller.rb create mode 100644 spec/requests/api/v1/donation_campaigns_spec.rb diff --git a/app/controllers/api/v1/donation_campaigns_controller.rb b/app/controllers/api/v1/donation_campaigns_controller.rb new file mode 100644 index 00000000000000..cdd7503b304659 --- /dev/null +++ b/app/controllers/api/v1/donation_campaigns_controller.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +class Api::V1::DonationCampaignsController < Api::BaseController + before_action :require_user! + + STOPLIGHT_COOL_OFF_TIME = 60 + STOPLIGHT_FAILURE_THRESHOLD = 10 + + def index + return head 204 if api_url.blank? + + json = from_cache + return render json: json if json.present? + + campaign = fetch_campaign + return head 204 if campaign.nil? + + save_to_cache!(campaign) + + render json: campaign + end + + private + + def api_url + Rails.configuration.x.donation_campaigns.api_url + end + + def seed + @seed ||= Random.new(current_account.id).rand(100) + end + + def from_cache + key = Rails.cache.read(request_key, raw: true) + return if key.blank? + + campaign = Rails.cache.read("donation_campaign:#{key}", raw: true) + Oj.load(campaign) if campaign.present? + end + + def save_to_cache!(campaign) + return if campaign.blank? + + Rails.cache.write_multi( + { + request_key => campaign_key(campaign), + "donation_campaign:#{campaign_key(campaign)}" => Oj.dump(campaign), + }, + expires_in: 1.hour, + raw: true + ) + end + + def fetch_campaign + stoplight_wrapper.run do + url = Addressable::URI.parse(api_url) + url.query_values = { platform: 'web', seed: seed, locale: locale, environment: Rails.configuration.x.donation_campaigns.environment }.compact + + Request.new(:get, url.to_s).perform do |res| + return Oj.load(res.body_with_limit, mode: :strict) if res.code == 200 + end + end + rescue *Mastodon::HTTP_CONNECTION_ERRORS, Oj::ParseError + nil + end + + def stoplight_wrapper + Stoplight( + 'donation_campaigns', + cool_off_time: STOPLIGHT_COOL_OFF_TIME, + threshold: STOPLIGHT_FAILURE_THRESHOLD + ) + end + + def request_key + "donation_campaign_request:#{seed}:#{locale}" + end + + def campaign_key(campaign) + "#{campaign['id']}:#{campaign['locale']}" + end + + def locale + I18n.locale.to_s + end +end diff --git a/config/mastodon.yml b/config/mastodon.yml index 4585e1f2aee6e4..0177bf85e5e939 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -4,6 +4,9 @@ shared: limited_federation_mode: <%= (ENV.fetch('LIMITED_FEDERATION_MODE', nil) || ENV.fetch('WHITELIST_MODE', nil)) == 'true' %> self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil)&.to_json %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check')&.to_json %> + donation_campaigns: + api_url: <%= ENV.fetch('DONATION_CAMPAIGNS_URL', nil)&.to_json %> + environment: <%= ENV.fetch('DONATION_CAMPAIGNS_ENVIRONMENT', nil)&.to_json %> source: base_url: <%= ENV.fetch('SOURCE_BASE_URL', nil)&.to_json %> repository: <%= ENV.fetch('GITHUB_REPOSITORY', 'mastodon/mastodon') %> diff --git a/config/routes/api.rb b/config/routes/api.rb index 3fa1aa7af205f2..83555680dfabef 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -74,6 +74,7 @@ resources :suggestions, only: [:index, :destroy] resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :preferences, only: [:index] + resources :donation_campaigns, only: [:index] resources :annual_reports, only: [:index, :show] do member do diff --git a/spec/requests/api/v1/donation_campaigns_spec.rb b/spec/requests/api/v1/donation_campaigns_spec.rb new file mode 100644 index 00000000000000..2ab3fb8e8a68e8 --- /dev/null +++ b/spec/requests/api/v1/donation_campaigns_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Donation campaigns' do + include_context 'with API authentication' + + describe 'GET /api/v1/donation_campaigns' do + context 'when not authenticated' do + it 'returns http unprocessable entity' do + get '/api/v1/donation_campaigns' + + expect(response) + .to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when no donation campaign API is set up' do + it 'returns http empty' do + get '/api/v1/donation_campaigns', headers: headers + + expect(response) + .to have_http_status(204) + end + end + + context 'when a donation campaign API is set up' do + let(:api_url) { 'https://example.org/donations' } + let(:seed) { Random.new(user.account_id).rand(100) } + + around do |example| + original = Rails.configuration.x.donation_campaigns.api_url + Rails.configuration.x.donation_campaigns.api_url = api_url + + example.run + + Rails.configuration.x.donation_campaigns.api_url = original + end + + context 'when the donation campaign API does not return a campaign' do + before do + stub_request(:get, "#{api_url}?platform=web&seed=#{seed}&locale=en").to_return(status: 204) + end + + it 'returns http empty' do + get '/api/v1/donation_campaigns', headers: headers + + expect(response) + .to have_http_status(204) + end + end + + context 'when the donation campaign API returns a campaign' do + let(:campaign_json) do + { + 'id' => 'campaign-1', + 'banner_message' => 'Hi', + 'banner_button_text' => 'Donate!', + 'donation_message' => 'Hi!', + 'donation_button_text' => 'Money', + 'donation_success_post' => 'Success post', + 'amounts' => { + 'one_time' => { + 'EUR' => [1, 2, 3], + 'USD' => [4, 5, 6], + }, + 'monthly' => { + 'EUR' => [1], + 'USD' => [2], + }, + }, + 'default_currency' => 'EUR', + 'donation_url' => 'https://sponsor.joinmastodon.org/donate/new', + 'locale' => 'en', + } + end + + before do + stub_request(:get, "#{api_url}?platform=web&seed=#{seed}&locale=en").to_return(body: Oj.dump(campaign_json), status: 200) + end + + it 'returns the expected campaign' do + get '/api/v1/donation_campaigns', headers: headers + + expect(response) + .to have_http_status(200) + + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to match(campaign_json) + + expect(Rails.cache.read("donation_campaign_request:#{seed}:en", raw: true)) + .to eq 'campaign-1:en' + + expect(Oj.load(Rails.cache.read('donation_campaign:campaign-1:en', raw: true))) + .to match(campaign_json) + end + end + end + end +end From 438602c488d2a4a7a73ddf19e6fe5815e760424b Mon Sep 17 00:00:00 2001 From: Nicholas La Roux Date: Tue, 17 Feb 2026 09:56:46 -0500 Subject: [PATCH 08/60] Upgrade `rubocop` from `v1.84.0` to `v1.84.2`, update config, and correct offences (#37795) --- .rubocop/layout.yml | 3 ++ Gemfile.lock | 2 +- app/chewy/public_statuses_index.rb | 6 ++-- .../admin/fasp/debug/callbacks_controller.rb | 4 +-- app/controllers/api/v1/blocks_controller.rb | 16 ++++----- .../api/v1/conversations_controller.rb | 28 +++++++-------- app/controllers/api/v1/mutes_controller.rb | 16 ++++----- .../api/v1_alpha/collections_controller.rb | 8 ++--- app/lib/fasp/request.rb | 6 ++-- app/lib/feed_manager.rb | 8 ++--- app/lib/vacuum/statuses_vacuum.rb | 8 ++--- app/models/concerns/account/interactions.rb | 12 +++---- .../concerns/fasp/provider/debug_concern.rb | 2 +- app/models/media_attachment.rb | 10 +++--- app/models/tag.rb | 4 +-- app/models/trends/preview_card_filter.rb | 6 ++-- app/models/trends/status_filter.rb | 6 ++-- app/models/trends/tag_filter.rb | 6 ++-- .../activitypub/fetch_all_replies_service.rb | 8 ++--- .../activitypub/post_upgrade_worker.rb | 8 ++--- app/workers/move_worker.rb | 34 +++++++++---------- ...42_remove_invalid_web_push_subscription.rb | 6 ++-- lib/mastodon/redis_configuration.rb | 18 +++++----- lib/paperclip/attachment_extensions.rb | 2 +- lib/paperclip/color_extractor.rb | 8 ++--- .../otp_authentication_controller_spec.rb | 4 +-- .../webauthn_credentials_controller_spec.rb | 2 +- spec/requests/activitypub/outboxes_spec.rb | 12 +++---- spec/requests/activitypub/replies_spec.rb | 4 +-- spec/services/bulk_import_service_spec.rb | 4 +-- spec/system/admin/software_updates_spec.rb | 2 +- 31 files changed, 133 insertions(+), 130 deletions(-) diff --git a/.rubocop/layout.yml b/.rubocop/layout.yml index 487879ca2c1134..93966749952bc2 100644 --- a/.rubocop/layout.yml +++ b/.rubocop/layout.yml @@ -4,3 +4,6 @@ Layout/FirstHashElementIndentation: Layout/LineLength: Max: 300 # Default of 120 causes a duplicate entry in generated todo file + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented diff --git a/Gemfile.lock b/Gemfile.lock index d734898522251e..47156e1f0afffd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -755,7 +755,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.7) - rubocop (1.84.0) + rubocop (1.84.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) diff --git a/app/chewy/public_statuses_index.rb b/app/chewy/public_statuses_index.rb index 09a4dfc09320a3..c847b0283d4c07 100644 --- a/app/chewy/public_statuses_index.rb +++ b/app/chewy/public_statuses_index.rb @@ -53,9 +53,9 @@ class PublicStatusesIndex < Chewy::Index } index_scope ::Status.unscoped - .kept - .indexable - .includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card) + .kept + .indexable + .includes(:media_attachments, :preloadable_poll, :tags, preview_cards_status: :preview_card) root date_detection: false do field(:id, type: 'long') diff --git a/app/controllers/admin/fasp/debug/callbacks_controller.rb b/app/controllers/admin/fasp/debug/callbacks_controller.rb index 28aba5e48925b1..acba4c51d83f6f 100644 --- a/app/controllers/admin/fasp/debug/callbacks_controller.rb +++ b/app/controllers/admin/fasp/debug/callbacks_controller.rb @@ -5,8 +5,8 @@ def index authorize [:admin, :fasp, :provider], :update? @callbacks = Fasp::DebugCallback - .includes(:fasp_provider) - .order(created_at: :desc) + .includes(:fasp_provider) + .order(created_at: :desc) end def destroy diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index d7516c927bc714..e79b292e5f0efc 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -18,14 +18,14 @@ def load_accounts def paginated_blocks @paginated_blocks ||= Block.eager_load(target_account: [:account_stat, :user]) - .joins(:target_account) - .merge(Account.without_suspended) - .where(account: current_account) - .paginate_by_max_id( - limit_param(DEFAULT_ACCOUNTS_LIMIT), - params[:max_id], - params[:since_id] - ) + .joins(:target_account) + .merge(Account.without_suspended) + .where(account: current_account) + .paginate_by_max_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params[:max_id], + params[:since_id] + ) end def next_path diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index 60db082a8e71a1..5f09d0c8864329 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -37,20 +37,20 @@ def set_conversation def paginated_conversations AccountConversation.where(account: current_account) - .includes( - account: [:account_stat, user: :role], - last_status: [ - :media_attachments, - :status_stat, - :tags, - { - preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } }, - active_mentions: :account, - account: [:account_stat, user: :role], - }, - ] - ) - .to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + .includes( + account: [:account_stat, user: :role], + last_status: [ + :media_attachments, + :status_stat, + :tags, + { + preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } }, + active_mentions: :account, + account: [:account_stat, user: :role], + }, + ] + ) + .to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) end def next_path diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index d2b50e333662a3..2c213ca20217df 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -18,14 +18,14 @@ def load_accounts def paginated_mutes @paginated_mutes ||= Mute.eager_load(target_account: [:account_stat, :user]) - .joins(:target_account) - .merge(Account.without_suspended) - .where(account: current_account) - .paginate_by_max_id( - limit_param(DEFAULT_ACCOUNTS_LIMIT), - params[:max_id], - params[:since_id] - ) + .joins(:target_account) + .merge(Account.without_suspended) + .where(account: current_account) + .paginate_by_max_id( + limit_param(DEFAULT_ACCOUNTS_LIMIT), + params[:max_id], + params[:since_id] + ) end def next_path diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb index feea6c6b32e639..1ca1cd6923f0a9 100644 --- a/app/controllers/api/v1_alpha/collections_controller.rb +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -72,10 +72,10 @@ def set_account def set_collections @collections = @account.collections - .with_tag - .order(created_at: :desc) - .offset(offset_param) - .limit(limit_param(DEFAULT_COLLECTIONS_LIMIT)) + .with_tag + .order(created_at: :desc) + .offset(offset_param) + .limit(limit_param(DEFAULT_COLLECTIONS_LIMIT)) @collections = @collections.discoverable unless @account == current_account end diff --git a/app/lib/fasp/request.rb b/app/lib/fasp/request.rb index 51950a004a2786..526a8bf31b58b3 100644 --- a/app/lib/fasp/request.rb +++ b/app/lib/fasp/request.rb @@ -27,9 +27,9 @@ def perform_request(verb, path, body: nil) headers = request_headers(verb, url, body) key = Linzer.new_ed25519_key(@provider.server_private_key_pem, @provider.remote_identifier) response = HTTP - .headers(headers) - .use(http_signature: { key:, covered_components: COVERED_COMPONENTS }) - .send(verb, url, body:) + .headers(headers) + .use(http_signature: { key:, covered_components: COVERED_COMPONENTS }) + .send(verb, url, body:) validate!(response) @provider.delivery_failure_tracker.track_success! diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index ab5ee106c7e1d2..18a58156c39d0c 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -214,10 +214,10 @@ def unmerge_tag_from_home(from_tag, into_account) # This is a bit tricky because we need posts tagged with this hashtag that are not # also tagged with another followed hashtag or from a followed user scope = from_tag.statuses - .where(id: timeline_status_ids) - .where.not(account: into_account) - .where.not(account: into_account.following) - .tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id)) + .where(id: timeline_status_ids) + .where.not(account: into_account) + .where.not(account: into_account.following) + .tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id)) scope.select(:id, :reblog_of_id).reorder(nil).find_each do |status| remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?) diff --git a/app/lib/vacuum/statuses_vacuum.rb b/app/lib/vacuum/statuses_vacuum.rb index 92d3ccf4f40877..3e3d55731d45a5 100644 --- a/app/lib/vacuum/statuses_vacuum.rb +++ b/app/lib/vacuum/statuses_vacuum.rb @@ -18,8 +18,8 @@ def vacuum_statuses! # Side-effects not covered by foreign keys, such # as the search index, must be handled first. statuses.direct_visibility - .includes(mentions: :account) - .find_each(&:unlink_from_conversations!) + .includes(mentions: :account) + .find_each(&:unlink_from_conversations!) if Chewy.enabled? remove_from_index(statuses.ids, 'chewy:queue:StatusesIndex') remove_from_index(statuses.ids, 'chewy:queue:PublicStatusesIndex') @@ -33,8 +33,8 @@ def vacuum_statuses! def statuses_scope Status.unscoped.kept - .joins(:account).merge(Account.remote) - .where(statuses: { id: ...retention_period_as_id }) + .joins(:account).merge(Account.remote) + .where(statuses: { id: ...retention_period_as_id }) end def retention_period_as_id diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index e204ddb7957e4b..7020491f42dbfe 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -45,7 +45,7 @@ module Account::Interactions def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false) rel = active_relationships.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) - .find_or_create_by!(target_account: other_account) + .find_or_create_by!(target_account: other_account) rel.show_reblogs = reblogs unless reblogs.nil? rel.notify = notify unless notify.nil? @@ -58,7 +58,7 @@ def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false) rel = follow_requests.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) - .find_or_create_by!(target_account: other_account) + .find_or_create_by!(target_account: other_account) rel.show_reblogs = reblogs unless reblogs.nil? rel.notify = notify unless notify.nil? @@ -71,7 +71,7 @@ def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, ur def block!(other_account, uri: nil) block_relationships.create_with(uri: uri) - .find_or_create_by!(target_account: other_account) + .find_or_create_by!(target_account: other_account) end def mute!(other_account, notifications: nil, duration: 0) @@ -215,14 +215,14 @@ def status_matches_filters(status) def followers_for_local_distribution followers.local - .joins(:user) - .merge(User.signed_in_recently) + .joins(:user) + .merge(User.signed_in_recently) end def lists_for_local_distribution scope = lists.joins(account: :user) scope.where.not(list_accounts: { follow_id: nil }).or(scope.where(account_id: id)) - .merge(User.signed_in_recently) + .merge(User.signed_in_recently) end def remote_followers_hash(url) diff --git a/app/models/concerns/fasp/provider/debug_concern.rb b/app/models/concerns/fasp/provider/debug_concern.rb index eee046a17f44e0..2c0f73337d7f42 100644 --- a/app/models/concerns/fasp/provider/debug_concern.rb +++ b/app/models/concerns/fasp/provider/debug_concern.rb @@ -5,6 +5,6 @@ module Fasp::Provider::DebugConcern def perform_debug_call Fasp::Request.new(self) - .post('/debug/v0/callback/logs', body: { hello: 'world' }) + .post('/debug/v0/callback/logs', body: { hello: 'world' }) end end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index c0c768154b4eaf..b02e8381aecaef 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -216,11 +216,11 @@ class MediaAttachment < ApplicationRecord scope :updated_before, ->(value) { where(arel_table[:updated_at].lt(value)) } scope :without_local_interaction, lambda { where.not(Favourite.joins(:account).merge(Account.local).where(Favourite.arel_table[:status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) - .where.not(Bookmark.where(Bookmark.arel_table[:status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) - .where.not(Status.local.where(Status.arel_table[:in_reply_to_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) - .where.not(Status.local.where(Status.arel_table[:reblog_of_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) - .where.not(Quote.joins(:status).merge(Status.local).where(Quote.arel_table[:quoted_status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) - .where.not(Quote.joins(:quoted_status).merge(Status.local).where(Quote.arel_table[:status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) + .where.not(Bookmark.where(Bookmark.arel_table[:status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) + .where.not(Status.local.where(Status.arel_table[:in_reply_to_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) + .where.not(Status.local.where(Status.arel_table[:reblog_of_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) + .where.not(Quote.joins(:status).merge(Status.local).where(Quote.arel_table[:quoted_status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) + .where.not(Quote.joins(:quoted_status).merge(Status.local).where(Quote.arel_table[:status_id].eq(MediaAttachment.arel_table[:status_id])).select(1).arel.exists) } attr_accessor :skip_download diff --git a/app/models/tag.rb b/app/models/tag.rb index 9c4e0510a94567..b87fbc4246388f 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -135,8 +135,8 @@ def search_for(term, limit = 5, offset = 0, options = {}) query = query.merge(matching_name(stripped_term).or(reviewed)) if options[:exclude_unreviewed] query.order(Arel.sql('LENGTH(name)').asc, name: :asc) - .limit(limit) - .offset(offset) + .limit(limit) + .offset(offset) end def find_normalized(name) diff --git a/app/models/trends/preview_card_filter.rb b/app/models/trends/preview_card_filter.rb index ef36ba987846ea..2af01afca3e5fc 100644 --- a/app/models/trends/preview_card_filter.rb +++ b/app/models/trends/preview_card_filter.rb @@ -30,9 +30,9 @@ def results def initial_scope PreviewCard.select(PreviewCard.arel_table[Arel.star]) - .joins(:trend) - .eager_load(:trend) - .reorder(score: :desc) + .joins(:trend) + .eager_load(:trend) + .reorder(score: :desc) end def scope_for(key, value) diff --git a/app/models/trends/status_filter.rb b/app/models/trends/status_filter.rb index da240251fde71d..fc865265a0229f 100644 --- a/app/models/trends/status_filter.rb +++ b/app/models/trends/status_filter.rb @@ -30,9 +30,9 @@ def results def initial_scope Status.select(Status.arel_table[Arel.star]) - .joins(:trend) - .eager_load(:trend) - .reorder(score: :desc) + .joins(:trend) + .eager_load(:trend) + .reorder(score: :desc) end def scope_for(key, value) diff --git a/app/models/trends/tag_filter.rb b/app/models/trends/tag_filter.rb index 02d558ac254615..7177331963cc35 100644 --- a/app/models/trends/tag_filter.rb +++ b/app/models/trends/tag_filter.rb @@ -30,9 +30,9 @@ def results def initial_scope Tag.select(Tag.arel_table[Arel.star]) - .joins(:trend) - .eager_load(:trend) - .reorder(score: :desc) + .joins(:trend) + .eager_load(:trend) + .reorder(score: :desc) end def scope_for(key, value) diff --git a/app/services/activitypub/fetch_all_replies_service.rb b/app/services/activitypub/fetch_all_replies_service.rb index a956e7c706f2b9..ab5f52761b9b4e 100644 --- a/app/services/activitypub/fetch_all_replies_service.rb +++ b/app/services/activitypub/fetch_all_replies_service.rb @@ -33,10 +33,10 @@ def filter_replies(items) parent_id = Status.where(uri: @status_uri).pick(:id) unless parent_id.nil? unsubscribed_replies = Status - .where.not(uri: uris) - .where(in_reply_to_id: parent_id) - .unsubscribed - .pluck(:uri) + .where.not(uri: uris) + .where(in_reply_to_id: parent_id) + .unsubscribed + .pluck(:uri) uris.concat(unsubscribed_replies) end diff --git a/app/workers/activitypub/post_upgrade_worker.rb b/app/workers/activitypub/post_upgrade_worker.rb index 4154b8582b08ff..8de4a07ec12a83 100644 --- a/app/workers/activitypub/post_upgrade_worker.rb +++ b/app/workers/activitypub/post_upgrade_worker.rb @@ -7,9 +7,9 @@ class ActivityPub::PostUpgradeWorker def perform(domain) Account.where(domain: domain) - .where(protocol: :ostatus) - .where.not(last_webfingered_at: nil) - .in_batches - .update_all(last_webfingered_at: nil) + .where(protocol: :ostatus) + .where.not(last_webfingered_at: nil) + .in_batches + .update_all(last_webfingered_at: nil) end end diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index faf576c73121c0..76d3765c1c0328 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -48,11 +48,11 @@ def rewrite_follows! source_local_followers .where(account: @target_account.followers.local) .in_batches do |follows| - ListAccount.where(follow: follows).includes(:list).find_each do |list_account| - list_account.list.accounts << @target_account - rescue ActiveRecord::RecordInvalid - nil - end + ListAccount.where(follow: follows).includes(:list).find_each do |list_account| + list_account.list.accounts << @target_account + rescue ActiveRecord::RecordInvalid + nil + end end # Finally, handle the common case of accounts not following the new account @@ -60,18 +60,18 @@ def rewrite_follows! .where.not(account: @target_account.followers.local) .where.not(account_id: @target_account.id) .in_batches do |follows| - ListAccount.where(follow: follows).in_batches.update_all(account_id: @target_account.id) - num_moved += follows.update_all(target_account_id: @target_account.id) - - # Clear any relationship cache, since callbacks are not called - Rails.cache.delete_multi(follows.flat_map do |follow| - [ - ['relationships', follow.account_id, follow.target_account_id], - ['relationships', follow.target_account_id, follow.account_id], - ['relationships', follow.account_id, @target_account.id], - ['relationships', @target_account.id, follow.account_id], - ] - end) + ListAccount.where(follow: follows).in_batches.update_all(account_id: @target_account.id) + num_moved += follows.update_all(target_account_id: @target_account.id) + + # Clear any relationship cache, since callbacks are not called + Rails.cache.delete_multi(follows.flat_map do |follow| + [ + ['relationships', follow.account_id, follow.target_account_id], + ['relationships', follow.target_account_id, follow.account_id], + ['relationships', follow.account_id, @target_account.id], + ['relationships', @target_account.id, follow.account_id], + ] + end) end num_moved diff --git a/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb b/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb index c2397476ad0749..6c58a4f6d751c7 100644 --- a/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb +++ b/db/post_migrate/20190927124642_remove_invalid_web_push_subscription.rb @@ -5,9 +5,9 @@ class RemoveInvalidWebPushSubscription < ActiveRecord::Migration[5.2] def up invalid_web_push_subscriptions = Web::PushSubscription.where(endpoint: '') - .or(Web::PushSubscription.where(key_p256dh: '')) - .or(Web::PushSubscription.where(key_auth: '')) - .preload(:session_activation) + .or(Web::PushSubscription.where(key_p256dh: '')) + .or(Web::PushSubscription.where(key_auth: '')) + .preload(:session_activation) invalid_web_push_subscriptions.find_each do |web_push_subscription| web_push_subscription.session_activation&.update!(web_push_subscription_id: nil) web_push_subscription.destroy! diff --git a/lib/mastodon/redis_configuration.rb b/lib/mastodon/redis_configuration.rb index 7330f7d0e52f6b..43562cbca82f7e 100644 --- a/lib/mastodon/redis_configuration.rb +++ b/lib/mastodon/redis_configuration.rb @@ -17,15 +17,15 @@ def sidekiq def cache @cache ||= setup_config(prefix: 'CACHE_') - .merge({ - namespace: 'cache', - expires_in: 10.minutes, - connect_timeout: 5, - pool: { - size: Sidekiq.server? ? Sidekiq.default_configuration[:concurrency] : Integer(ENV['MAX_THREADS'] || 5), - timeout: 5, - }, - }) + .merge({ + namespace: 'cache', + expires_in: 10.minutes, + connect_timeout: 5, + pool: { + size: Sidekiq.server? ? Sidekiq.default_configuration[:concurrency] : Integer(ENV['MAX_THREADS'] || 5), + timeout: 5, + }, + }) end private diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb index 7141adc9edcd58..bb39d076c479ae 100644 --- a/lib/paperclip/attachment_extensions.rb +++ b/lib/paperclip/attachment_extensions.rb @@ -24,7 +24,7 @@ def post_process_style(name, style) # :nodoc: unadapted_file = @queued_for_write[name] @queued_for_write[name] = Paperclip.io_adapters - .for(@queued_for_write[name], @options[:adapter_options]) + .for(@queued_for_write[name], @options[:adapter_options]) unadapted_file.close if unadapted_file.respond_to?(:close) @queued_for_write[name] rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e diff --git a/lib/paperclip/color_extractor.rb b/lib/paperclip/color_extractor.rb index 1c9ef4bd3d6b3f..eb40d5091b57fe 100644 --- a/lib/paperclip/color_extractor.rb +++ b/lib/paperclip/color_extractor.rb @@ -221,10 +221,10 @@ def palette_from_im_histogram(result, quantity) total_frequencies = frequencies.sum.to_f frequencies.map.with_index { |f, i| [f / total_frequencies, hex_values[i]] } - .sort_by { |r| -r[0] } - .reject { |r| r[1].size == 8 && r[1].end_with?('00') } - .map { |r| ColorDiff::Color::RGB.new(*r[1][0..5].scan(/../).map { |c| c.to_i(16) }) } - .slice(0, quantity) + .sort_by { |r| -r[0] } + .reject { |r| r[1].size == 8 && r[1].end_with?('00') } + .map { |r| ColorDiff::Color::RGB.new(*r[1][0..5].scan(/../).map { |c| c.to_i(16) }) } + .slice(0, quantity) end def rgb_to_hex(rgb) diff --git a/spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb index a03c4a4adb2442..6a3ad54cb6cf89 100644 --- a/spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb @@ -63,7 +63,7 @@ expect do post :create, session: { challenge_passed_at: Time.now.utc } end.to not_change { user.reload.otp_secret } - .and(change { session[:new_otp_secret] }) + .and(change { session[:new_otp_secret] }) expect(response).to redirect_to(new_settings_two_factor_authentication_confirmation_path) end @@ -80,7 +80,7 @@ expect do post :create, session: { challenge_passed_at: Time.now.utc } end.to not_change { user.reload.otp_secret } - .and(change { session[:new_otp_secret] }) + .and(change { session[:new_otp_secret] }) expect(response).to redirect_to(new_settings_two_factor_authentication_confirmation_path) end diff --git a/spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb index cccf3c51d321b1..cd0df9e9177b32 100644 --- a/spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb @@ -200,7 +200,7 @@ def add_webauthn_credential(user) expect do post :create, params: { credential: new_webauthn_credential, nickname: nickname } end.to change { user.webauthn_credentials.count }.by(1) - .and not_change(user, :webauthn_id) + .and not_change(user, :webauthn_id) expect(response).to have_http_status(200) end diff --git a/spec/requests/activitypub/outboxes_spec.rb b/spec/requests/activitypub/outboxes_spec.rb index c109214635bdf2..cb6c460f9ed566 100644 --- a/spec/requests/activitypub/outboxes_spec.rb +++ b/spec/requests/activitypub/outboxes_spec.rb @@ -81,8 +81,8 @@ expect(response.parsed_body) .to include( orderedItems: be_an(Array) - .and(have_attributes(size: 2)) - .and(all(satisfy { |item| targets_public_collection?(item) })) + .and(have_attributes(size: 2)) + .and(all(satisfy { |item| targets_public_collection?(item) })) ) end @@ -133,8 +133,8 @@ expect(response.parsed_body) .to include( orderedItems: be_an(Array) - .and(have_attributes(size: 2)) - .and(all(satisfy { |item| targets_public_collection?(item) })) + .and(have_attributes(size: 2)) + .and(all(satisfy { |item| targets_public_collection?(item) })) ) end end @@ -155,8 +155,8 @@ expect(response.parsed_body) .to include( orderedItems: be_an(Array) - .and(have_attributes(size: 3)) - .and(all(satisfy { |item| targets_public_collection?(item) || targets_followers_collection?(item, account) })) + .and(have_attributes(size: 3)) + .and(all(satisfy { |item| targets_public_collection?(item) || targets_followers_collection?(item, account) })) ) end end diff --git a/spec/requests/activitypub/replies_spec.rb b/spec/requests/activitypub/replies_spec.rb index 4cd02b187d99bd..02832c049a28f2 100644 --- a/spec/requests/activitypub/replies_spec.rb +++ b/spec/requests/activitypub/replies_spec.rb @@ -100,8 +100,8 @@ first: be_a(Hash).and( include( items: be_an(Array) - .and(have_attributes(size: 1)) - .and(all(satisfy { |item| targets_public_collection?(item) })) + .and(have_attributes(size: 1)) + .and(all(satisfy { |item| targets_public_collection?(item) })) ) ) ) diff --git a/spec/services/bulk_import_service_spec.rb b/spec/services/bulk_import_service_spec.rb index f52fc4d7d5e83a..9adbd522dcae46 100644 --- a/spec/services/bulk_import_service_spec.rb +++ b/spec/services/bulk_import_service_spec.rb @@ -343,10 +343,10 @@ def stub_resolve_account_and_drain_workers .and_return(resolve_account_service_double) allow(resolve_account_service_double) .to receive(:call) - .with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + .with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } allow(resolve_account_service_double) .to receive(:call) - .with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + .with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } Import::RowWorker.drain end diff --git a/spec/system/admin/software_updates_spec.rb b/spec/system/admin/software_updates_spec.rb index e62b6a8cfecc78..d9e4bf36fa682f 100644 --- a/spec/system/admin/software_updates_spec.rb +++ b/spec/system/admin/software_updates_spec.rb @@ -17,7 +17,7 @@ expect(page).to have_title(I18n.t('admin.software_updates.title')) expect(page).to have_content('99.99.99') - .and have_no_content('3.5.0') + .and have_no_content('3.5.0') click_on I18n.t('admin.software_updates.release_notes') expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true) From 371946fa802d136b3b624b521ef49c90f8cbae36 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Feb 2026 09:59:31 -0500 Subject: [PATCH 09/60] Use validation matchers for `LanguageValidator` spec (#37886) --- spec/validators/language_validator_spec.rb | 54 ++++++---------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/spec/validators/language_validator_spec.rb b/spec/validators/language_validator_spec.rb index d19b33f27f835d..b79c948d53b492 100644 --- a/spec/validators/language_validator_spec.rb +++ b/spec/validators/language_validator_spec.rb @@ -3,59 +3,33 @@ require 'rails_helper' RSpec.describe LanguageValidator do + subject { record_class.new } + let(:record_class) do Class.new do include ActiveModel::Validations + def self.name = 'Record' + attr_accessor :locale validates :locale, language: true end end - let(:record) { record_class.new } - - describe '#validate_each' do - context 'with a nil value' do - it 'does not add errors' do - record.locale = nil - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end - - context 'with an array of values' do - it 'does not add errors with array of existing locales' do - record.locale = %w(en fr) - expect(record).to be_valid - expect(record.errors).to be_empty - end - - it 'adds errors with array having some non-existing locales' do - record.locale = %w(en fr missing) - - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:locale) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'with a nil value' do + it { is_expected.to allow_value(nil).for(:locale) } + end - context 'with a locale string' do - it 'does not add errors when string is an existing locale' do - record.locale = 'en' + context 'with an array of values' do + it { is_expected.to allow_value(%w(en fr)).for(:locale) } - expect(record).to be_valid - expect(record.errors).to be_empty - end + it { is_expected.to_not allow_value(%w(en fr missing)).for(:locale).with_message(:invalid) } + end - it 'adds errors when string is non-existing locale' do - record.locale = 'missing' + context 'with a locale string' do + it { is_expected.to allow_value('en').for(:locale) } - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:locale) - expect(record.errors.first.type).to eq(:invalid) - end - end + it { is_expected.to_not allow_value('missing').for(:locale).with_message(:invalid) } end end From 4b1f66418b0173dd5edcfe9153e00dfe2e4ab948 Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 17 Feb 2026 16:45:24 +0100 Subject: [PATCH 10/60] Profile editing: Add initial route (#37885) --- app/javascript/mastodon/components/column.tsx | 12 ++++- .../mastodon/components/column_header.tsx | 9 ++-- .../mastodon/components/follow_button.tsx | 18 +++++-- .../mastodon/features/account_edit/index.tsx | 53 +++++++++++++++++++ .../features/account_edit/styles.module.scss | 26 +++++++++ app/javascript/mastodon/features/ui/index.jsx | 5 +- .../features/ui/util/async-components.js | 5 ++ app/javascript/mastodon/hooks/useAccountId.ts | 4 ++ app/javascript/mastodon/locales/en.json | 2 + app/javascript/mastodon/utils/environment.ts | 2 +- config/routes/web_app.rb | 1 + 11 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 app/javascript/mastodon/features/account_edit/index.tsx create mode 100644 app/javascript/mastodon/features/account_edit/styles.module.scss diff --git a/app/javascript/mastodon/components/column.tsx b/app/javascript/mastodon/components/column.tsx index 01c75d85c008cb..2abe3425e4c722 100644 --- a/app/javascript/mastodon/components/column.tsx +++ b/app/javascript/mastodon/components/column.tsx @@ -1,6 +1,8 @@ import { forwardRef, useRef, useImperativeHandle } from 'react'; import type { Ref } from 'react'; +import classNames from 'classnames'; + import { scrollTop } from 'mastodon/scroll'; export interface ColumnRef { @@ -12,10 +14,11 @@ interface ColumnProps { children?: React.ReactNode; label?: string; bindToDocument?: boolean; + className?: string; } export const Column = forwardRef( - ({ children, label, bindToDocument }, ref: Ref) => { + ({ children, label, bindToDocument, className }, ref: Ref) => { const nodeRef = useRef(null); useImperativeHandle(ref, () => ({ @@ -39,7 +42,12 @@ export const Column = forwardRef( })); return ( -
+
{children}
); diff --git a/app/javascript/mastodon/components/column_header.tsx b/app/javascript/mastodon/components/column_header.tsx index 06ba29cd26e39c..64273ab2147e57 100644 --- a/app/javascript/mastodon/components/column_header.tsx +++ b/app/javascript/mastodon/components/column_header.tsx @@ -73,6 +73,7 @@ export interface Props { iconComponent?: IconProp; active?: boolean; children?: React.ReactNode; + className?: string; pinned?: boolean; multiColumn?: boolean; extraButton?: React.ReactNode; @@ -91,6 +92,7 @@ export const ColumnHeader: React.FC = ({ iconComponent, active, children, + className, pinned, multiColumn, extraButton, @@ -141,7 +143,7 @@ export const ColumnHeader: React.FC = ({ onPin?.(); }, [history, pinned, onPin]); - const wrapperClassName = classNames('column-header__wrapper', { + const wrapperClassName = classNames('column-header__wrapper', className, { active, }); @@ -256,7 +258,8 @@ export const ColumnHeader: React.FC = ({ } const hasIcon = icon && iconComponent; - const hasTitle = hasIcon && title; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const hasTitle = (hasIcon || backButton) && title; const component = (
@@ -270,7 +273,7 @@ export const ColumnHeader: React.FC = ({ className='column-header__title' type='button' > - {!backButton && ( + {!backButton && hasIcon && ( + {label} + + ); + } + return ( {label} diff --git a/app/javascript/mastodon/features/account_edit/index.tsx b/app/javascript/mastodon/features/account_edit/index.tsx new file mode 100644 index 00000000000000..838a07f131af5d --- /dev/null +++ b/app/javascript/mastodon/features/account_edit/index.tsx @@ -0,0 +1,53 @@ +import type { FC } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import { Column } from '@/mastodon/components/column'; +import { ColumnHeader } from '@/mastodon/components/column_header'; +import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; +import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error'; +import { useAccount } from '@/mastodon/hooks/useAccount'; +import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId'; + +import classes from './styles.module.scss'; + +export const AccountEdit: FC<{ multiColumn: boolean }> = ({ multiColumn }) => { + const accountId = useCurrentAccountId(); + const account = useAccount(accountId); + const intl = useIntl(); + + if (!accountId) { + return ; + } + + if (!account) { + return ( + + + + ); + } + + return ( + + + + + } + /> + + ); +}; diff --git a/app/javascript/mastodon/features/account_edit/styles.module.scss b/app/javascript/mastodon/features/account_edit/styles.module.scss new file mode 100644 index 00000000000000..3662b51443754c --- /dev/null +++ b/app/javascript/mastodon/features/account_edit/styles.module.scss @@ -0,0 +1,26 @@ +.column { + border: 1px solid var(--color-border-primary); + border-top-width: 0; +} + +.header { + :global(.column-header__buttons) { + align-items: center; + padding-inline-end: 16px; + height: auto; + } +} + +.nav { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 24px 24px 12px; + + > h1 { + flex-grow: 1; + font-weight: 600; + font-size: 15px; + } +} diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index c2fcd2d02fcd63..089e5764bc6c76 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -22,7 +22,7 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex import { layoutFromWindow } from 'mastodon/is_mobile'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { checkAnnualReport } from '@/mastodon/reducers/slices/annual_report'; -import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; +import { isClientFeatureEnabled, isServerFeatureEnabled } from '@/mastodon/utils/environment'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { clearHeight } from '../../actions/height_cache'; @@ -80,6 +80,7 @@ import { TermsOfService, AccountFeatured, AccountAbout, + AccountEdit, Quotes, } from './util/async-components'; import { ColumnsContextProvider } from './util/columns_context'; @@ -232,6 +233,8 @@ class SwitchingColumnsArea extends PureComponent { + {isClientFeatureEnabled('profile_editing') && } + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 55fcc29fa4bf03..6b0188f9b6af02 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -92,6 +92,11 @@ export function AccountAbout() { .then((module) => ({ default: module.AccountAbout })); } +export function AccountEdit() { + return import('../../account_edit') + .then((module) => ({ default: module.AccountEdit })); +} + export function Followers () { return import('../../followers'); } diff --git a/app/javascript/mastodon/hooks/useAccountId.ts b/app/javascript/mastodon/hooks/useAccountId.ts index cab8f4893403c3..028d8824456f11 100644 --- a/app/javascript/mastodon/hooks/useAccountId.ts +++ b/app/javascript/mastodon/hooks/useAccountId.ts @@ -55,3 +55,7 @@ export function useAccountId() { return accountId satisfies AccountId; } + +export function useCurrentAccountId() { + return useAppSelector((state) => state.meta.get('me', null) as string | null); +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 6070aecbe8c463..91e0fb79b252f0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -141,6 +141,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications_short": "Unmute notifications", "account.unmute_short": "Unmute", + "account_edit.column_button": "Done", + "account_edit.column_title": "Edit Profile", "account_note.placeholder": "Click to add note", "admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.monthly_retention": "User retention rate by month after sign-up", diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index 5f736fa80c5d90..58421817ade347 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -18,7 +18,7 @@ export function isServerFeatureEnabled(feature: ServerFeatures) { return initialState?.features.includes(feature) ?? false; } -type ClientFeatures = 'collections'; +type ClientFeatures = 'collections' | 'profile_editing'; export function isClientFeatureEnabled(feature: ClientFeatures) { try { diff --git a/config/routes/web_app.rb b/config/routes/web_app.rb index 2901e227151b4c..cb85dc8753924c 100644 --- a/config/routes/web_app.rb +++ b/config/routes/web_app.rb @@ -25,6 +25,7 @@ /notifications_v2/(*any) /notifications/(*any) /pinned + /profile/(*any) /public /public/local /public/remote From 3c3a1b16846cf9d755c13bd17265ffde5fa32d0c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Feb 2026 12:49:19 -0500 Subject: [PATCH 11/60] Use separate rules with same `groupName` for playwright updates (#37809) --- .github/renovate.json5 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ddca0bc239cd78..076b2f9b1e68a3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -154,9 +154,15 @@ groupName: 'opentelemetry-ruby (non-major)', }, { - // Group Playwright Ruby & JS deps in the same PR, as they need to be in sync - matchManagers: ['bundler', 'npm'], - matchPackageNames: ['playwright-ruby-client', 'playwright'], + // The ruby portion of the Playwright group + matchManagers: ['bundler'], + matchPackageNames: ['playwright-ruby-client'], + groupName: 'Playwright', + }, + { + // The node portion of the Playwright group + matchManagers: ['npm'], + matchPackageNames: ['playwright'], groupName: 'Playwright', }, // Add labels depending on package manager From 61b9bc4fac00f0cd1418a9fc67527d433b9f9ec6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:26:47 +0100 Subject: [PATCH 12/60] Update dependency rspec-rails to v8.0.3 (#37888) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 47156e1f0afffd..31e331ea6e8e96 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -470,7 +470,7 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.19.0) + nokogiri (1.19.1) mini_portile2 (~> 2.8.2) racc (~> 1.4) oj (3.16.15) @@ -631,7 +631,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.2.4) + rack (3.2.5) rack-attack (6.8.0) rack (>= 1.0, < 4) rack-cors (3.0.0) @@ -741,7 +741,7 @@ GEM rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.2) + rspec-rails (8.0.3) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) From f95cd68667d8dfec56afdf2a6f6bddc6f0182fd8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Feb 2026 05:33:26 -0500 Subject: [PATCH 13/60] Use validation matchers for `NoteLengthValidator` spec (#37891) --- spec/validators/note_length_validator_spec.rb | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/spec/validators/note_length_validator_spec.rb b/spec/validators/note_length_validator_spec.rb index c761c952802d43..b391390a5505e0 100644 --- a/spec/validators/note_length_validator_spec.rb +++ b/spec/validators/note_length_validator_spec.rb @@ -3,61 +3,61 @@ require 'rails_helper' RSpec.describe NoteLengthValidator do - subject { described_class.new(attributes: { note: true }, maximum: 500) } + subject { record_class.new } - describe '#validate' do - it 'adds an error when text is over configured character limit' do - text = 'a' * 520 - account = instance_double(Account, note: text, errors: activemodel_errors) + let(:record_class) do + Class.new do + include ActiveModel::Validations - subject.validate_each(account, 'note', text) - expect(account.errors).to have_received(:add) - end + def self.name = 'Record' - it 'reduces calculated length of auto-linkable space-separated URLs' do - text = [starting_string, example_link].join(' ') - account = instance_double(Account, note: text, errors: activemodel_errors) + attr_accessor :note - subject.validate_each(account, 'note', text) - expect(account.errors).to_not have_received(:add) + validates :note, note_length: { maximum: 100 } end + end - it 'does not reduce calculated length of non-autolinkable URLs' do - text = [starting_string, example_link].join - account = instance_double(Account, note: text, errors: activemodel_errors) + context 'when note is too long' do + let(:too_long) { 'a' * 200 } - subject.validate_each(account, 'note', text) - expect(account.errors).to have_received(:add) - end + it { is_expected.to_not allow_value(too_long).for(:note).with_message(too_long_message) } + end - it 'counts multi byte emoji as single character' do - text = '✨' * 500 - account = instance_double(Account, note: text, errors: activemodel_errors) + context 'when note has space separated linkable URLs' do + let(:text) { [starting_string, example_link].join(' ') } - subject.validate_each(account, 'note', text) - expect(account.errors).to_not have_received(:add) - end + it { is_expected.to allow_value(text).for(:note) } + end - it 'counts ZWJ sequence emoji as single character' do - text = '🏳️‍⚧️' * 500 - account = instance_double(Account, note: text, errors: activemodel_errors) + context 'when note has non-separated URLs' do + let(:text) { [starting_string, example_link].join } - subject.validate_each(account, 'note', text) - expect(account.errors).to_not have_received(:add) - end + it { is_expected.to_not allow_value(text).for(:note).with_message(too_long_message) } + end - private + context 'with multi-byte emoji' do + let(:text) { '✨' * 100 } - def starting_string - 'a' * 476 - end + it { is_expected.to allow_value(text).for(:note) } + end - def example_link - "http://#{'b' * 30}.com/example" - end + context 'with ZWJ sequence emoji' do + let(:text) { '🏳️‍⚧️' * 100 } - def activemodel_errors - instance_double(ActiveModel::Errors, add: nil) - end + it { is_expected.to allow_value(text).for(:note) } + end + + private + + def too_long_message + I18n.t('statuses.over_character_limit', max: 100) + end + + def starting_string + 'a' * 76 + end + + def example_link + "http://#{'b' * 30}.com/example" end end From b62ba9e29e4d9b334ad605efe3836bb9fd33f683 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:33:31 +0100 Subject: [PATCH 14/60] New Crowdin Translations (automated) (#37896) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 2 + app/javascript/mastodon/locales/ca.json | 1 + app/javascript/mastodon/locales/da.json | 2 + app/javascript/mastodon/locales/de.json | 6 ++- app/javascript/mastodon/locales/el.json | 2 + app/javascript/mastodon/locales/en-GB.json | 2 + app/javascript/mastodon/locales/es-AR.json | 2 + app/javascript/mastodon/locales/es-MX.json | 54 +++++++++++----------- app/javascript/mastodon/locales/fi.json | 2 + app/javascript/mastodon/locales/fo.json | 2 + app/javascript/mastodon/locales/gl.json | 11 +++++ app/javascript/mastodon/locales/he.json | 11 +++++ app/javascript/mastodon/locales/is.json | 2 + app/javascript/mastodon/locales/it.json | 11 +++++ app/javascript/mastodon/locales/nn.json | 24 ++++++++++ app/javascript/mastodon/locales/sq.json | 2 + app/javascript/mastodon/locales/sv.json | 2 + app/javascript/mastodon/locales/tr.json | 9 ++++ app/javascript/mastodon/locales/vi.json | 2 + app/javascript/mastodon/locales/zh-TW.json | 4 +- config/locales/doorkeeper.ar.yml | 1 - config/locales/doorkeeper.be.yml | 5 +- config/locales/doorkeeper.bg.yml | 1 - config/locales/doorkeeper.ca.yml | 1 - config/locales/doorkeeper.cs.yml | 1 - config/locales/doorkeeper.cy.yml | 1 - config/locales/doorkeeper.da.yml | 5 +- config/locales/doorkeeper.de.yml | 1 - config/locales/doorkeeper.el.yml | 5 +- config/locales/doorkeeper.en-GB.yml | 5 +- config/locales/doorkeeper.eo.yml | 1 - config/locales/doorkeeper.es-AR.yml | 1 - config/locales/doorkeeper.es-MX.yml | 17 ++++--- config/locales/doorkeeper.es.yml | 1 - config/locales/doorkeeper.et.yml | 1 - config/locales/doorkeeper.eu.yml | 1 - config/locales/doorkeeper.fa.yml | 1 - config/locales/doorkeeper.fi.yml | 5 +- config/locales/doorkeeper.fo.yml | 5 +- config/locales/doorkeeper.fr-CA.yml | 3 +- config/locales/doorkeeper.fr.yml | 3 +- config/locales/doorkeeper.fy.yml | 1 - config/locales/doorkeeper.ga.yml | 5 +- config/locales/doorkeeper.gd.yml | 1 - config/locales/doorkeeper.gl.yml | 5 +- config/locales/doorkeeper.he.yml | 5 +- config/locales/doorkeeper.hu.yml | 1 - config/locales/doorkeeper.ia.yml | 1 - config/locales/doorkeeper.io.yml | 1 - config/locales/doorkeeper.is.yml | 5 +- config/locales/doorkeeper.it.yml | 5 +- config/locales/doorkeeper.ja.yml | 1 - config/locales/doorkeeper.ko.yml | 1 - config/locales/doorkeeper.lt.yml | 1 - config/locales/doorkeeper.lv.yml | 1 - config/locales/doorkeeper.nl.yml | 1 - config/locales/doorkeeper.nn.yml | 1 - config/locales/doorkeeper.pl.yml | 1 - config/locales/doorkeeper.pt-BR.yml | 1 - config/locales/doorkeeper.pt-PT.yml | 1 - config/locales/doorkeeper.ro.yml | 1 - config/locales/doorkeeper.ru.yml | 1 - config/locales/doorkeeper.si.yml | 1 - config/locales/doorkeeper.sl.yml | 1 - config/locales/doorkeeper.sq.yml | 5 +- config/locales/doorkeeper.sr-Latn.yml | 1 - config/locales/doorkeeper.sr.yml | 1 - config/locales/doorkeeper.sv.yml | 1 - config/locales/doorkeeper.th.yml | 1 - config/locales/doorkeeper.tr.yml | 1 - config/locales/doorkeeper.uk.yml | 1 - config/locales/doorkeeper.vi.yml | 5 +- config/locales/doorkeeper.zh-CN.yml | 5 +- config/locales/doorkeeper.zh-TW.yml | 5 +- config/locales/nn.yml | 5 ++ config/locales/simple_form.ca.yml | 2 + config/locales/simple_form.da.yml | 1 + config/locales/simple_form.nn.yml | 4 ++ 78 files changed, 210 insertions(+), 89 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index ad4183f01df723..45acd59217f6ae 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -141,6 +141,8 @@ "account.unmute": "Не ігнараваць @{name}", "account.unmute_notifications_short": "Апавяшчаць", "account.unmute_short": "Не ігнараваць", + "account_edit.column_button": "Гатова", + "account_edit.column_title": "Рэдагаваць профіль", "account_note.placeholder": "Націсніце, каб дадаць нататку", "admin.dashboard.daily_retention": "Штодзённы паказчык утрымання карыстальнікаў пасля рэгістрацыі", "admin.dashboard.monthly_retention": "Штомесячны паказчык утрымання карыстальнікаў пасля рэгістрацыі", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 7a582f43b69197..581d5d4d0e1357 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -13,6 +13,7 @@ "about.not_available": "Aquesta informació no és disponible en aquest servidor.", "about.powered_by": "Xarxa social descentralitzada impulsada per {mastodon}", "about.rules": "Normes del servidor", + "account.about": "Quant a", "account.account_note_header": "Nota personal", "account.activity": "Activitat", "account.add_note": "Afegeix una nota personal", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 2d2f62ea72e0a8..56b5910a38399b 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -141,6 +141,8 @@ "account.unmute": "Vis @{name} igen", "account.unmute_notifications_short": "Vis notifikationer igen", "account.unmute_short": "Vis igen", + "account_edit.column_button": "Færdig", + "account_edit.column_title": "Rediger profil", "account_note.placeholder": "Klik for at tilføje notat", "admin.dashboard.daily_retention": "Brugerfastholdelsesrate pr. dag efter tilmelding", "admin.dashboard.monthly_retention": "Brugerfastholdelsesrate pr. måned efter tilmelding", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index aadabedb85366f..8c19179ba06221 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -141,6 +141,8 @@ "account.unmute": "Stummschaltung von @{name} aufheben", "account.unmute_notifications_short": "Stummschaltung der Benachrichtigungen aufheben", "account.unmute_short": "Stummschaltung aufheben", + "account_edit.column_button": "Erledigt", + "account_edit.column_title": "Profil bearbeiten", "account_note.placeholder": "Klicken, um private Anmerkung hinzuzufügen", "admin.dashboard.daily_retention": "Verweildauer der Nutzer*innen pro Tag seit der Registrierung", "admin.dashboard.monthly_retention": "Verweildauer der Nutzer*innen pro Monat seit der Registrierung", @@ -264,7 +266,7 @@ "collections.edit_details": "Allgemeine Informationen bearbeiten", "collections.edit_settings": "Einstellungen bearbeiten", "collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.", - "collections.hints.accounts_counter": "{count} / {max} Konten", + "collections.hints.accounts_counter": "{count}/{max} Konten", "collections.hints.add_more_accounts": "Füge mindestens {count, plural, one {# Konto} other {# Konten}} hinzu, um fortzufahren", "collections.hints.can_not_remove_more_accounts": "Sammlungen müssen mindestens {count, plural, one {# Konto} other {# Konten}} enthalten. Weitere Konten zu entfernen, ist daher nicht erlaubt.", "collections.last_updated_at": "Aktualisiert: {date}", @@ -276,7 +278,7 @@ "collections.new_collection": "Neue Sammlung", "collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.", "collections.remove_account": "Dieses Konto entfernen", - "collections.search_accounts_label": "Konten suchen, um sie hinzuzufügen …", + "collections.search_accounts_label": "Suche nach Konten, um sie hinzuzufügen …", "collections.search_accounts_max_reached": "Du hast die Höchstzahl an Konten hinzugefügt", "collections.topic_hint": "Ein Hashtag für diese Sammlung kann anderen dabei helfen, dein Anliegen besser einordnen zu können.", "collections.view_collection": "Sammlungen anzeigen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index fb34eb9ae45709..102cf7e7d5cb21 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -141,6 +141,8 @@ "account.unmute": "Άρση σίγασης @{name}", "account.unmute_notifications_short": "Σίγαση ειδοποιήσεων", "account.unmute_short": "Κατάργηση σίγασης", + "account_edit.column_button": "Έγινε", + "account_edit.column_title": "Επεξεργασία Προφίλ", "account_note.placeholder": "Κάνε κλικ για να προσθέσεις σημείωση", "admin.dashboard.daily_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά ημέρα", "admin.dashboard.monthly_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά μήνα", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 4060a666138cf0..1e2acb555901e1 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -141,6 +141,8 @@ "account.unmute": "Unmute @{name}", "account.unmute_notifications_short": "Unmute notifications", "account.unmute_short": "Unmute", + "account_edit.column_button": "Done", + "account_edit.column_title": "Edit Profile", "account_note.placeholder": "Click to add note", "admin.dashboard.daily_retention": "User retention rate by day after sign-up", "admin.dashboard.monthly_retention": "User retention rate by month after sign-up", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index bc46279ca9dc51..d6856b63acb28d 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -141,6 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.column_button": "Listo", + "account_edit.column_title": "Editar perfil", "account_note.placeholder": "Hacé clic par agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día, después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes, después del registro", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index c00ce75d71e4b7..4f2110f18104c9 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -72,11 +72,11 @@ "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar impulsos de @{name}", "account.in_memoriam": "En memoria.", - "account.joined_long": "Se unión el {date}", + "account.joined_long": "Se unió el {date}", "account.joined_short": "Se unió", "account.languages": "Cambiar idiomas suscritos", - "account.link_verified_on": "El proprietario de este enlace fue comprobado el {date}", - "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.", + "account.link_verified_on": "Se verificó la propiedad de este enlace el {date}", + "account.locked_info": "El estado de privacidad de esta cuenta está configurado como bloqueado. El propietario revisa manualmente quién puede seguirlo.", "account.media": "Multimedia", "account.mention": "Mencionar a @{name}", "account.menu.add_to_list": "Añadir a lista…", @@ -141,6 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.column_button": "Hecho", + "account_edit.column_title": "Editar perfil", "account_note.placeholder": "Haz clic para agregar una nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después de unirse", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después de unirse", @@ -296,9 +298,9 @@ "column.domain_blocks": "Dominios ocultados", "column.edit_list": "Editar lista", "column.favourites": "Favoritos", - "column.firehose": "Feeds en vivo", - "column.firehose_local": "Feed en vivo para este servidor", - "column.firehose_singular": "Feed en vivo", + "column.firehose": "Cronologías en vivo", + "column.firehose_local": "Cronología en vivo para este servidor", + "column.firehose_singular": "Cronología en vivo", "column.follow_requests": "Solicitudes de seguimiento", "column.home": "Inicio", "column.list_members": "Administrar miembros de la lista", @@ -313,7 +315,7 @@ "column_header.moveRight_settings": "Mover columna a la derecha", "column_header.pin": "Fijar", "column_header.show_settings": "Mostrar ajustes", - "column_header.unpin": "Desfijar", + "column_header.unpin": "Dejar de fijar", "column_search.cancel": "Cancelar", "combobox.close_results": "Cerrar resultados", "combobox.loading": "Cargando", @@ -431,7 +433,7 @@ "domain_block_modal.you_will_lose_num_followers": "Vas a perder {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} y {followingCount, plural, one {{followingCountDisplay} persona a la que sigues} other {{followingCountDisplay} personas a las que sigas}}.", "domain_block_modal.you_will_lose_relationships": "Perderás todos los seguidores y las personas que sigues de este servidor.", "domain_block_modal.you_wont_see_posts": "No verás publicaciones ni notificaciones de usuarios en este servidor.", - "domain_pill.activitypub_lets_connect": "Te permite conectar e interactuar con personas no sólo en Mastodon, sino también a través de diferentes aplicaciones sociales.", + "domain_pill.activitypub_lets_connect": "Te permite conectarte e interactuar con personas no solo en Mastodon, sino también en diferentes aplicaciones sociales.", "domain_pill.activitypub_like_language": "ActivityPub es como el idioma que Mastodon habla con otras redes sociales.", "domain_pill.server": "Servidor", "domain_pill.their_handle": "Su alias:", @@ -475,7 +477,7 @@ "empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.", "empty_column.community": "La cronología local está vacía. ¡Escribe algo públicamente para ponerla en marcha!", "empty_column.direct": "Aún no tienes ninguna mención privada. Cuando envíes o recibas una, aparecerá aquí.", - "empty_column.disabled_feed": "Este feed fue desactivado por los administradores de tu servidor.", + "empty_column.disabled_feed": "Esta cronología fue desactivada por los administradores de tu servidor.", "empty_column.domain_blocks": "Todavía no hay dominios ocultos.", "empty_column.explore_statuses": "Nada es tendencia en este momento. ¡Revisa más tarde!", "empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando le des favorito a una publicación se mostrarán acá.", @@ -506,9 +508,9 @@ "featured_carousel.header": "{count, plural,one {Publicación fijada}other {Publicaciones fijadas}}", "featured_carousel.slide": "Publicación {current, number} de {max, number}", "featured_tags.more_items": "+{count}", - "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que has accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.", + "filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que has accedido a esta publicación. Si deseas que la publicación también se filtre en este contexto, tendrás que editar el filtro.", "filter_modal.added.context_mismatch_title": "¡El contexto no coincide!", - "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, necesitaras cambiar la fecha de caducidad para que se aplique.", + "filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado; deberás cambiar la fecha de caducidad para que se aplique.", "filter_modal.added.expired_title": "¡Filtro expirado!", "filter_modal.added.review_and_configure": "Para revisar y configurar esta categoría de filtros, vaya a {settings_link}.", "filter_modal.added.review_and_configure_title": "Ajustes de filtro", @@ -746,7 +748,7 @@ "notification.admin.report_account_other": "{name} reportó {count, plural, one {una publicación} other {# publicaciones}} de {target}", "notification.admin.report_statuses": "{name} reportó {target} por {category}", "notification.admin.report_statuses_other": "{name} reportó {target}", - "notification.admin.sign_up": "{name} se unio", + "notification.admin.sign_up": "{name} se registró", "notification.admin.sign_up.name_and_others": "{name} y {count, plural, one {# otro} other {# otros}} se registraron", "notification.annual_report.message": "¡Tu #Wrapstodon {year} te espera! ¡Desvela los momentos más destacados y memorables de tu año en Mastodon!", "notification.annual_report.view": "Ver #Wrapstodon", @@ -866,17 +868,17 @@ "onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar gente a la que seguir, o inténtalo de nuevo más tarde.", "onboarding.follows.search": "Buscar", "onboarding.follows.title": "Sigue personas para comenzar", - "onboarding.profile.discoverable": "Make my profile discoverable", - "onboarding.profile.discoverable_hint": "Cuando aceptas ser descubierto en Mastodon, tus publicaciones pueden aparecer en resultados de búsqueda y tendencias, y tu perfil puede ser sugerido a personas con intereses similares a los tuyos.", - "onboarding.profile.display_name": "Nombre a mostrar", + "onboarding.profile.discoverable": "Hacer que mi perfil aparezca en búsquedas", + "onboarding.profile.discoverable_hint": "Cuando permites que tu perfil aparezca en búsquedas en Mastodon, tus publicaciones pueden aparecer en los resultados de búsqueda y en las tendencias, y tu perfil puede ser sugerido a personas con intereses similares a los tuyos.", + "onboarding.profile.display_name": "Nombre para mostrar", "onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…", "onboarding.profile.note": "Biografía", "onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…", "onboarding.profile.save_and_continue": "Guardar y continuar", "onboarding.profile.title": "Configuración del perfil", "onboarding.profile.upload_avatar": "Subir foto de perfil", - "onboarding.profile.upload_header": "Subir foto de cabecera", - "password_confirmation.exceeds_maxlength": "La contraseña de confirmación excede la longitud máxima de la contraseña", + "onboarding.profile.upload_header": "Subir encabezado de perfil", + "password_confirmation.exceeds_maxlength": "La contraseña de confirmación supera la longitud máxima permitida", "password_confirmation.mismatching": "La contraseña de confirmación no coincide", "picture_in_picture.restore": "Restaurar", "poll.closed": "Cerrada", @@ -892,14 +894,14 @@ "privacy.change": "Ajustar privacidad", "privacy.direct.long": "Todos los mencionados en la publicación", "privacy.direct.short": "Mención privada", - "privacy.private.long": "Sólo tus seguidores", + "privacy.private.long": "Solo tus seguidores", "privacy.private.short": "Seguidores", "privacy.public.long": "Cualquiera dentro y fuera de Mastodon", "privacy.public.short": "Público", "privacy.quote.anyone": "{visibility}, citas permitidas", "privacy.quote.disabled": "{visibility}, citas desactivadas", "privacy.quote.limited": "{visibility}, citas limitadas", - "privacy.unlisted.additional": "Esto se comporta exactamente igual que el público, excepto que el post no aparecerá en las cronologías en directo o en las etiquetas, la exploración o busquedas en Mastodon, incluso si está optado por activar la cuenta de usuario.", + "privacy.unlisted.additional": "Esto funciona exactamente igual que «público», excepto que la publicación no aparecerá en las transmisiones en vivo ni en las etiquetas, en «explorar» ni en la búsqueda de Mastodon, incluso si has optado por ello en toda tu cuenta.", "privacy.unlisted.long": "Oculto de los resultados de búsquedas, tendencias y cronologías públicas de Mastodon", "privacy.unlisted.short": "Pública, pero discreta", "privacy_policy.last_updated": "Actualizado por última vez {date}", @@ -917,7 +919,7 @@ "relative_time.days": "{number} d", "relative_time.full.days": "{number, plural, one {# día} other {# días hace}}", "relative_time.full.hours": "{number, plural, one {# hora} other {# horas}} hace", - "relative_time.full.just_now": "justo ahora", + "relative_time.full.just_now": "ahora mismo", "relative_time.full.minutes": "Hace {number, plural, one {# minute} other {# minutos}}", "relative_time.full.seconds": "Hace {number, plural, one {# second} other {# segundos}}", "relative_time.hours": "{number} h", @@ -932,7 +934,7 @@ "reply_indicator.cancel": "Cancelar", "reply_indicator.poll": "Encuesta", "report.block": "Bloquear", - "report.block_explanation": "No veras sus publicaciones. No podrán ver tus publicaciones ni seguirte. Podrán saber que están bloqueados.", + "report.block_explanation": "No verás sus publicaciones. Ellos no podrán ver tus publicaciones ni seguirte. Podrán saber que están bloqueados.", "report.categories.legal": "Legal", "report.categories.other": "Otro", "report.categories.spam": "Spam", @@ -944,7 +946,7 @@ "report.close": "Realizado", "report.comment.title": "¿Hay algo más que creas que deberíamos saber?", "report.forward": "Reenviar a {target}", - "report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?", + "report.forward_hint": "La cuenta es de otro servidor. ¿Enviar también una copia anónima del informe allí?", "report.mute": "Silenciar", "report.mute_explanation": "No verás sus publicaciones. Todavía pueden seguirte y ver tus publicaciones y no sabrán que están silenciados.", "report.next": "Siguiente", @@ -970,7 +972,7 @@ "report.thanks.title": "¿No quieres ver esto?", "report.thanks.title_actionable": "Gracias por denunciar, revisaremos esto.", "report.unfollow": "Dejar de seguir @{name}", - "report.unfollow_explanation": "Estás siguiendo esta cuenta. Para no ver sus publicaciones en tu inicio, deja de seguirla.", + "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.categories.legal": "Legal", "report_notification.categories.legal_sentence": "contenido ilegal", @@ -991,7 +993,7 @@ "search.quick_action.status_search": "Publicaciones que coinciden con {x}", "search.search_or_paste": "Buscar o pegar URL", "search_popout.full_text_search_disabled_message": "No disponible en {domain}.", - "search_popout.full_text_search_logged_out_message": "Sólo disponible al iniciar sesión.", + "search_popout.full_text_search_logged_out_message": "Solo disponible al iniciar sesión.", "search_popout.language_code": "Código de idioma ISO", "search_popout.options": "Opciones de búsqueda", "search_popout.quick_actions": "Acciones rápidas", @@ -1049,8 +1051,8 @@ "status.history.created": "{name} creó {date}", "status.history.edited": "{name} editado {date}", "status.load_more": "Cargar más", - "status.media.open": "Click para abrir", - "status.media.show": "Click para mostrar", + "status.media.open": "Haz clic para abrir", + "status.media.show": "Haz clic para mostrar", "status.media_hidden": "Contenido multimedia oculto", "status.mention": "Mencionar @{name}", "status.more": "Más", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 0853c5d24da3ee..2ab912530a8a84 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -141,6 +141,8 @@ "account.unmute": "Kumoa käyttäjän @{name} mykistys", "account.unmute_notifications_short": "Kumoa ilmoitusten mykistys", "account.unmute_short": "Kumoa mykistys", + "account_edit.column_button": "Valmis", + "account_edit.column_title": "Muokkaa profiilia", "account_note.placeholder": "Lisää muistiinpano napsauttamalla", "admin.dashboard.daily_retention": "Käyttäjien pysyvyys päivittäin rekisteröitymisen jälkeen", "admin.dashboard.monthly_retention": "Käyttäjien pysyvyys kuukausittain rekisteröitymisen jälkeen", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 38cf42d50083e6..62a6da238a54a5 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -141,6 +141,8 @@ "account.unmute": "Doyv ikki @{name}", "account.unmute_notifications_short": "Tendra fráboðanir", "account.unmute_short": "Doyv ikki", + "account_edit.column_button": "Liðugt", + "account_edit.column_title": "Rætta vanga", "account_note.placeholder": "Klikka fyri at leggja viðmerking afturat", "admin.dashboard.daily_retention": "Hvussu nógvir brúkarar eru eftir, síðani tey skrásettu seg, roknað í døgum", "admin.dashboard.monthly_retention": "Hvussu nógvir brúkarar eru eftir síðani tey skrásettu seg, roknað í mánaðum", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 8a089aa603a073..e9a5392676e33f 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -141,6 +141,8 @@ "account.unmute": "Deixar de silenciar a @{name}", "account.unmute_notifications_short": "Reactivar notificacións", "account.unmute_short": "Non silenciar", + "account_edit.column_button": "Feito", + "account_edit.column_title": "Editar perfil", "account_note.placeholder": "Preme para engadir nota", "admin.dashboard.daily_retention": "Ratio de retención de usuarias diaria após rexistrarse", "admin.dashboard.monthly_retention": "Ratio de retención de usuarias mensual após o rexistro", @@ -244,9 +246,12 @@ "closed_registrations_modal.preamble": "Mastodon é descentralizado, así que non importa onde crees a conta, poderás seguir e interactuar con calquera conta deste servidor. Incluso podes ter o teu servidor!", "closed_registrations_modal.title": "Crear conta en Mastodon", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", + "collections.accounts.empty_description": "Engade ata {count} contas que segues", + "collections.accounts.empty_title": "A colección está baleira", "collections.collection_description": "Descrición", "collections.collection_name": "Nome", "collections.collection_topic": "Temática", + "collections.confirm_account_removal": "Tes certeza de querer retirar esta conta desta colección?", "collections.content_warning": "Aviso sobre o contido", "collections.continue": "Continuar", "collections.create.accounts_subtitle": "Só se poden engadir contas que segues e que optaron por ser incluídas en descubrir.", @@ -261,6 +266,9 @@ "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar axustes", "collections.error_loading_collections": "Houbo un erro ao intentar cargar as túas coleccións.", + "collections.hints.accounts_counter": "{count} / {max} contas", + "collections.hints.add_more_accounts": "Engade polo menos {count, plural, one {# conta} other {# contas}} para continuar", + "collections.hints.can_not_remove_more_accounts": "As coleccións teñen que conter polo menos {count, plural, one {# conta} other {# contas}}. Non é posible retirar máis contas.", "collections.last_updated_at": "Última actualización: {date}", "collections.manage_accounts": "Xestionar contas", "collections.manage_accounts_in_collection": "Xestionar as contas nesta colección", @@ -269,6 +277,9 @@ "collections.name_length_hint": "Límite de 100 caracteres", "collections.new_collection": "Nova colección", "collections.no_collections_yet": "Aínda non tes coleccións.", + "collections.remove_account": "Retirar esta conta", + "collections.search_accounts_label": "Buscar contas para engadir…", + "collections.search_accounts_max_reached": "Acadaches o máximo de contas permitidas", "collections.topic_hint": "Engadir un cancelo para que axudar a que outras persoas coñezan a temática desta colección.", "collections.view_collection": "Ver colección", "collections.visibility_public": "Pública", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 67ddd1c7c74bc4..5a9bf3038946ab 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -141,6 +141,8 @@ "account.unmute": "הפסקת השתקת @{name}", "account.unmute_notifications_short": "הפעלת הודעות", "account.unmute_short": "ביטול השתקה", + "account_edit.column_button": "סיום", + "account_edit.column_title": "עריכת הפרופיל", "account_note.placeholder": "יש ללחוץ כדי להוסיף הערות", "admin.dashboard.daily_retention": "קצב שימור משתמשים יומי אחרי ההרשמה", "admin.dashboard.monthly_retention": "קצב שימור משתמשים (פר חודש) אחרי ההרשמה", @@ -244,9 +246,12 @@ "closed_registrations_modal.preamble": "מסטודון הוא רשת מבוזרת, כך שלא משנה היכן החשבון שלך, קיימת האפשרות לעקוב ולתקשר עם משתמשים בשרת הזה. אפשר אפילו להריץ שרת בעצמך!", "closed_registrations_modal.title": "להרשם למסטודון", "collections.account_count": "{count, plural, one {חשבון אחד} other {# חשבונות}}", + "collections.accounts.empty_description": "להוסיף עד ל־{count} חשבונות שאתם עוקבים אחריהם", + "collections.accounts.empty_title": "האוסף הזה ריק", "collections.collection_description": "תיאור", "collections.collection_name": "כינוי", "collections.collection_topic": "נושא", + "collections.confirm_account_removal": "בוודאות להסיר חשבון זה מהאוסף?", "collections.content_warning": "אזהרת תוכן", "collections.continue": "המשך", "collections.create.accounts_subtitle": "רק חשבונות נעקבים שבחרו להופיע ב\"תגליות\" ניתנים להוספה.", @@ -261,6 +266,9 @@ "collections.edit_details": "עריכת פרטים בסיסיים", "collections.edit_settings": "עריכת הגדרות", "collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.", + "collections.hints.accounts_counter": "{count} \\ {max} חשבונות", + "collections.hints.add_more_accounts": "הוסיפו לפחות {count, plural,one {חשבון אחד}other {# חשבונות}} כדי להמשיך", + "collections.hints.can_not_remove_more_accounts": "אוספים חייבים להכיל לפחות {count, plural,one {חשבון אחד}other {# חשבונות}}. הסרת חשבונות נוספים איננה אפשרית.", "collections.last_updated_at": "עדכון אחרון: {date}", "collections.manage_accounts": "ניהול חשבונות", "collections.manage_accounts_in_collection": "ניהול החשבונות שבאוסף זה", @@ -269,6 +277,9 @@ "collections.name_length_hint": "מגבלה של 100 תווים", "collections.new_collection": "אוסף חדש", "collections.no_collections_yet": "עוד אין אוספים.", + "collections.remove_account": "הסר חשבון זה", + "collections.search_accounts_label": "לחפש חשבונות להוספה…", + "collections.search_accounts_max_reached": "הגעת למספר החשבונות המירבי", "collections.topic_hint": "הוספת תגית שמסייעת לאחרים להבין את הנושא הראשי של האוסף.", "collections.view_collection": "צפיה באוסף", "collections.visibility_public": "פומבי", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 6b226b64dcf965..649128d9c5bede 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -141,6 +141,8 @@ "account.unmute": "Hætta að þagga niður í @{name}", "account.unmute_notifications_short": "Hætta að þagga í tilkynningum", "account.unmute_short": "Hætta að þagga niður", + "account_edit.column_button": "Lokið", + "account_edit.column_title": "Breyta notandasniði", "account_note.placeholder": "Smelltu til að bæta við minnispunkti", "admin.dashboard.daily_retention": "Hlutfall virkra notenda eftir nýskráningu eftir dögum", "admin.dashboard.monthly_retention": "Hlutfall virkra notenda eftir nýskráningu eftir mánuðum", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 901b9b11bbea41..cf37f6b8e4a8a9 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -141,6 +141,8 @@ "account.unmute": "Riattiva @{name}", "account.unmute_notifications_short": "Riattiva notifiche", "account.unmute_short": "Attiva audio", + "account_edit.column_button": "Fatto", + "account_edit.column_title": "Modifica il profilo", "account_note.placeholder": "Clicca per aggiungere una nota", "admin.dashboard.daily_retention": "Tasso di ritenzione dell'utente per giorno, dopo la registrazione", "admin.dashboard.monthly_retention": "Tasso di ritenzione dell'utente per mese, dopo la registrazione", @@ -244,9 +246,12 @@ "closed_registrations_modal.preamble": "Mastodon è decentralizzato, quindi, non importa dove crei il tuo profilo, potrai seguire e interagire con chiunque su questo server. Anche se sei tu stesso a ospitarlo!", "closed_registrations_modal.title": "Registrazione su Mastodon", "collections.account_count": "{count, plural, one {# account} other {# account}}", + "collections.accounts.empty_description": "Aggiungi fino a {count} account che segui", + "collections.accounts.empty_title": "Questa collezione è vuota", "collections.collection_description": "Descrizione", "collections.collection_name": "Nome", "collections.collection_topic": "Argomento", + "collections.confirm_account_removal": "Si è sicuri di voler rimuovere questo account da questa collezione?", "collections.content_warning": "Avviso sul contenuto", "collections.continue": "Continua", "collections.create.accounts_subtitle": "Possono essere aggiunti solo gli account che segui e che hanno aderito alla funzione di scoperta.", @@ -261,6 +266,9 @@ "collections.edit_details": "Modifica i dettagli di base", "collections.edit_settings": "Modifica impostazioni", "collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.", + "collections.hints.accounts_counter": "{count} / {max} account", + "collections.hints.add_more_accounts": "Aggiungi almeno {count, plural, one {# account} other {# account}} per continuare", + "collections.hints.can_not_remove_more_accounts": "Le collezioni devono contenere almeno {count, plural, one {# account} other {# account}}. La rimozione di altri account, non è possibile.", "collections.last_updated_at": "Ultimo aggiornamento: {date}", "collections.manage_accounts": "Gestisci account", "collections.manage_accounts_in_collection": "Gestisci gli account in questa collezione", @@ -269,6 +277,9 @@ "collections.name_length_hint": "Limite di 100 caratteri", "collections.new_collection": "Nuova collezione", "collections.no_collections_yet": "Nessuna collezione ancora.", + "collections.remove_account": "Rimuovi questo account", + "collections.search_accounts_label": "Cerca account da aggiungere…", + "collections.search_accounts_max_reached": "Hai aggiunto il numero massimo di account", "collections.topic_hint": "Aggiungi un hashtag che aiuti gli altri a comprendere l'argomento principale di questa collezione.", "collections.view_collection": "Visualizza la collezione", "collections.visibility_public": "Pubblica", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index c43224de39a321..7895d20484c61f 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -13,6 +13,7 @@ "about.not_available": "Denne informasjonen er ikkje gjort tilgjengeleg på denne tenaren.", "about.powered_by": "Desentraliserte sosiale medium drive av {mastodon}", "about.rules": "Tenarreglar", + "account.about": "Om", "account.account_note_header": "Personleg notat", "account.activity": "Aktivitet", "account.add_note": "Legg til eit personleg notat", @@ -87,6 +88,7 @@ "account.menu.hide_reblogs": "Gøym framhevingar på tidslina", "account.menu.mention": "Omtale", "account.menu.mute": "Demp konto", + "account.menu.note.description": "Berre synleg for deg", "account.menu.open_original_page": "Vis på {domain}", "account.menu.remove_follower": "Fjern fylgjar", "account.menu.report": "Rapporter kontoen", @@ -102,6 +104,13 @@ "account.muted": "Målbunden", "account.muting": "Dempa", "account.mutual": "De fylgjer kvarandre", + "account.name.help.domain": "{domain} er tenaren som lagrar brukarprofilen og innlegga.", + "account.name.help.domain_self": "{domain} er tenaren som lagrar brukarprofilen og innlegga dine.", + "account.name.help.footer": "På same måten som du kan senda epost til folk med ulike epostprogram og -kontoar, kan du kommunisera med folk på andre Mastodon-tenarar, og med folk på andre sosiale nettverk som samhandlar på same måte som Mastodon. Det er ActivityPub-protokollen.", + "account.name.help.header": "Ei brukaradresse er som ei epostadresse", + "account.name.help.username": "{username} er brukarnamnet til denne kontoen på tenaren deira. Folk på andre tenarar kan ha same brukarnamnet.", + "account.name.help.username_self": "{username} er brukarnamnet ditt på denne tenaren. Folk på andre tenarar kan ha same brukarnamnet.", + "account.name_info": "Kva tyder dette?", "account.no_bio": "Inga skildring er gjeven.", "account.node_modal.callout": "Berre du kan sjå personlege notat.", "account.node_modal.edit_title": "Rediger det personlege notatet", @@ -234,9 +243,13 @@ "closed_registrations_modal.find_another_server": "Finn ein annan tenar", "closed_registrations_modal.preamble": "Mastodon er desentralisert, så uansett kvar du opprettar ein konto, vil du kunne fylgje og samhandle med alle på denne tenaren. Du kan til og med ha din eigen tenar!", "closed_registrations_modal.title": "Registrer deg på Mastodon", + "collections.account_count": "{count, plural, one {# konto} other {# kontoar}}", + "collections.accounts.empty_description": "Legg til opp til {count} kontoar du fylgjer", + "collections.accounts.empty_title": "Denne samlinga er tom", "collections.collection_description": "Skildring", "collections.collection_name": "Namn", "collections.collection_topic": "Emne", + "collections.confirm_account_removal": "Er du sikker på at du vil fjerna denne brukarkontoen frå samlinga?", "collections.content_warning": "Innhaldsåtvaring", "collections.continue": "Hald fram", "collections.create.accounts_subtitle": "Du kan berre leggja til kontoar du fylgjer og som har sagt ja til å bli oppdaga.", @@ -251,6 +264,10 @@ "collections.edit_details": "Rediger grunnleggjande opplysingar", "collections.edit_settings": "Rediger innstillingar", "collections.error_loading_collections": "Noko gjekk gale då me prøvde å henta samlingane dine.", + "collections.hints.accounts_counter": "{count} av {max} kontoar", + "collections.hints.add_more_accounts": "Legg til minst {count, plural, one {# konto} other {# kontoar}} for å halda fram", + "collections.hints.can_not_remove_more_accounts": "Samlingar må innehalda minst {count, plural, one {# konto} other {# kontoar}}. Du kan ikkje fjerna fleire kontoar.", + "collections.last_updated_at": "Sist oppdatert: {date}", "collections.manage_accounts": "Handter kontoar", "collections.manage_accounts_in_collection": "Handter kontoar i denne samlinga", "collections.mark_as_sensitive": "Merk som ømtolig", @@ -258,6 +275,9 @@ "collections.name_length_hint": "Maks 100 teikn", "collections.new_collection": "Ny samling", "collections.no_collections_yet": "Du har ingen samlingar enno.", + "collections.remove_account": "Fjern denne kontoen", + "collections.search_accounts_label": "Søk etter kontoar å leggja til…", + "collections.search_accounts_max_reached": "Du har nådd grensa for kor mange kontoar du kan leggja til", "collections.topic_hint": "Legg til ein emneknagg som hjelper andre å forstå hovudemnet for denne samlinga.", "collections.view_collection": "Sjå samlinga", "collections.visibility_public": "Offentleg", @@ -442,6 +462,8 @@ "emoji_button.search_results": "Søkeresultat", "emoji_button.symbols": "Symbol", "emoji_button.travel": "Reise & stader", + "empty_column.account_about.me": "Du har ikkje skrive noko om deg sjølv enno.", + "empty_column.account_about.other": "{acct} har ikkje skrive noko om seg sjølv enno.", "empty_column.account_featured.me": "Du har ikkje valt ut noko enno. Visste du at du kan velja ut emneknaggar du bruker mykje, og til og med venekontoar på profilen din?", "empty_column.account_featured.other": "{acct} har ikkje valt ut noko enno. Visste du at du kan velja ut emneknaggar du bruker mykje, og til og med venekontoar på profilen din?", "empty_column.account_featured_other.unknown": "Denne kontoen har ikkje valt ut noko enno.", @@ -525,6 +547,8 @@ "follow_suggestions.view_all": "Vis alle", "follow_suggestions.who_to_follow": "Kven du kan fylgja", "followed_tags": "Fylgde emneknaggar", + "followers.hide_other_followers": "Denne personen har valt å ikkje syna dei andre fylgjarane sine", + "following.hide_other_following": "Denne personen har valt å ikkje syna kven andre dei fylgjer", "footer.about": "Om", "footer.about_mastodon": "Om Mastodon", "footer.about_server": "Om {domain}", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 220f50ab6a1672..e0ddb7fbaf9659 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -141,6 +141,8 @@ "account.unmute": "Ktheji zërin @{name}", "account.unmute_notifications_short": "Shfaqi njoftimet", "account.unmute_short": "Çheshtoje", + "account_edit.column_button": "U bë", + "account_edit.column_title": "Përpunoni Profil", "account_note.placeholder": "Klikoni për të shtuar shënim", "admin.dashboard.daily_retention": "Shkallë mbajtjeje përdoruesi, në ditë, pas regjistrimit", "admin.dashboard.monthly_retention": "Shkallë mbajtjeje përdoruesi, në muaj, pas regjistrimit", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 22255265ee118c..80a1fe8bf3fcbd 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -92,6 +92,8 @@ "account.unmute": "Sluta tysta @{name}", "account.unmute_notifications_short": "Aktivera aviseringsljud", "account.unmute_short": "Avtysta", + "account_edit.column_button": "Klar", + "account_edit.column_title": "Redigera profil", "account_note.placeholder": "Klicka för att lägga till anteckning", "admin.dashboard.daily_retention": "Användarlojalitet per dag efter registrering", "admin.dashboard.monthly_retention": "Användarlojalitet per månad efter registrering", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 809307d03887e3..6efc24ac7492dc 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -244,9 +244,12 @@ "closed_registrations_modal.preamble": "Mastodon merkeziyetsizdir, bu yüzden hesabınızı nerede oluşturursanız oluşturun, bu sunucudaki herhangi birini takip edebilecek veya onunla etkileşebileceksiniz. Hatta kendi sunucunuzu bile barındırabilirsiniz!", "closed_registrations_modal.title": "Mastodon'a kayıt olmak", "collections.account_count": "{count, plural, one {# hesap} other {# hesap}}", + "collections.accounts.empty_description": "Takip ettiğiniz hesapların sayısını {count} kadar artırın", + "collections.accounts.empty_title": "Bu koleksiyon boş", "collections.collection_description": "Açıklama", "collections.collection_name": "Ad", "collections.collection_topic": "Konu", + "collections.confirm_account_removal": "Bu hesabı bu koleksiyondan çıkarmak istediğinizden emin misiniz?", "collections.content_warning": "İçerik uyarısı", "collections.continue": "Devam et", "collections.create.accounts_subtitle": "Yalnızca keşif seçeneğini etkinleştirmiş takip ettiğiniz hesaplar eklenebilir.", @@ -261,6 +264,9 @@ "collections.edit_details": "Temel bilgileri düzenle", "collections.edit_settings": "Ayarları düzenle", "collections.error_loading_collections": "Koleksiyonlarınızı yüklemeye çalışırken bir hata oluştu.", + "collections.hints.accounts_counter": "{count} / {max} hesap", + "collections.hints.add_more_accounts": "Devam etmek için en az {count, plural, one {# hesap} other {# hesap}} ekleyin", + "collections.hints.can_not_remove_more_accounts": "Koleksiyonlar en azından {count, plural, one {# hesap} other {# hesap}} içermelidir. Daha fazla hesap çıkarmak mümkün değil.", "collections.last_updated_at": "Son güncelleme: {date}", "collections.manage_accounts": "Hesapları yönet", "collections.manage_accounts_in_collection": "Bu koleksiyondaki hesapları yönet", @@ -269,6 +275,9 @@ "collections.name_length_hint": "100 karakterle sınırlı", "collections.new_collection": "Yeni koleksiyon", "collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.", + "collections.remove_account": "Bu hesabı çıkar", + "collections.search_accounts_label": "Eklemek için hesap arayın…", + "collections.search_accounts_max_reached": "Maksimum hesabı eklediniz", "collections.topic_hint": "Bu koleksiyonun ana konusunu başkalarının anlamasına yardımcı olacak bir etiket ekleyin.", "collections.view_collection": "Koleksiyonu görüntüle", "collections.visibility_public": "Herkese açık", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index d0fb3aac97095b..09deb8244f1770 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -141,6 +141,8 @@ "account.unmute": "Bỏ phớt lờ @{name}", "account.unmute_notifications_short": "Bỏ phớt lờ thông báo", "account.unmute_short": "Bỏ phớt lờ", + "account_edit.column_button": "Xong", + "account_edit.column_title": "Sửa hồ sơ", "account_note.placeholder": "Nhấn để thêm", "admin.dashboard.daily_retention": "Tỉ lệ người dùng sau đăng ký ở lại theo ngày", "admin.dashboard.monthly_retention": "Tỉ lệ người dùng ở lại sau khi đăng ký", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index fed0065f889a61..b5798703f1635d 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -141,6 +141,8 @@ "account.unmute": "解除靜音 @{name}", "account.unmute_notifications_short": "解除靜音推播通知", "account.unmute_short": "解除靜音", + "account_edit.column_button": "完成", + "account_edit.column_title": "編輯個人檔案", "account_note.placeholder": "點擊以新增備註", "admin.dashboard.daily_retention": "註冊後使用者存留率(日)", "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)", @@ -245,7 +247,7 @@ "closed_registrations_modal.title": "註冊 Mastodon", "collections.account_count": "{count, plural, other {# 個帳號}}", "collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號", - "collections.accounts.empty_title": "此收藏是名單空的", + "collections.accounts.empty_title": "此收藏名單是空的", "collections.collection_description": "說明", "collections.collection_name": "名稱", "collections.collection_topic": "主題", diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml index 62633ed22cc381..b26829cab2a769 100644 --- a/config/locales/doorkeeper.ar.yml +++ b/config/locales/doorkeeper.ar.yml @@ -83,7 +83,6 @@ ar: access_denied: لقد رفض مالك المَورِدِ أو تصريح السيرفر طلبك. credential_flow_not_configured: فشل تدفق بيانات اعتماد كلمة سر مالك المورد بسبب عدم تهيئة Doorkeeper.configure.resource_owner_from_credentials. invalid_client: فشلت المصادقة مع العميل لأنه العميل مجهول أو لغياب المصادقة ضمن العميل أو أنّ أسلوب المصادقة غير مدعومة. - invalid_code_challenge_method: يجب أن تكون طريقة تحدي الكود S256، البسيط غير مدعوم. invalid_grant: إنّ التصريح المقدَّم غير صالح، سواء انتهت مدة صلاحيته أو تم إلغاؤه أو أنه لا يتطابق مع عنوان إعادة التحويل في طلب التصريح أو أنّ هذا التصريح قد تم تقديمه لعميل آخر. invalid_redirect_uri: إنّ عنوان إعادة التحويل غير صالح. invalid_request: diff --git a/config/locales/doorkeeper.be.yml b/config/locales/doorkeeper.be.yml index 45fa4ba1beb177..ef130993b22631 100644 --- a/config/locales/doorkeeper.be.yml +++ b/config/locales/doorkeeper.be.yml @@ -83,7 +83,10 @@ be: access_denied: Уласнік рэсурсу або сэрвер аўтарызацыі адхіліў ваш запыт. credential_flow_not_configured: Resource Owner Password Credentials flow не прайшоў з-за таго, што ўласцівасць Doorkeeper.configure.resource_owner_from_credentials была не вызначана. invalid_client: Збой аўтэнтыфікацыі кліента з-за невядомага кліента, адсутнасці аўтэнтыфікацыі кліента або метаду аўтэнтыфікацыі, які не падтрымліваецца. - invalid_code_challenge_method: Метад праверкі кода павінен быць S256, просты тэкст не падтрымліваецца. + invalid_code_challenge_method: + one: code_challenge_method павінен быць %{challenge_methods}. + other: code_challenge_method павінен быць адным з %{challenge_methods}. + zero: Сервер аўтарызацыі не падтрымлівае PKCE, паколькі няма прынятых значэнняў у code_challenge_method. invalid_grant: Прадастаўлены дазвол на аўтарызацыю несапраўдны, пратэрмінованы, быў адкліканы, не адпавядае URI перанакіравання, які выкарыстоўваецца ў запыце аўтарызацыі або быў выдадзены іншаму кліенту. invalid_redirect_uri: Прадстаўлены URI перанакіравання не сапраўдны. invalid_request: diff --git a/config/locales/doorkeeper.bg.yml b/config/locales/doorkeeper.bg.yml index c3977e58441e22..ed4761badd9b9f 100644 --- a/config/locales/doorkeeper.bg.yml +++ b/config/locales/doorkeeper.bg.yml @@ -83,7 +83,6 @@ bg: access_denied: Заявката беше отказана от собственика на ресурса или от сървъра за упълномощаване. credential_flow_not_configured: Resource Owner Password Credentials предизвика грешка, заради това, че настройките за Doorkeeper.configure.resource_owner_from_credentials липсват. invalid_client: Удостоверяването на клиента предизвика грешка, поради непознат клиент, липсващо клиентско удостоверяване, или заради това, че методът на удостоверяване не се поддържа. - invalid_code_challenge_method: Методът на предизвикателството на кода трябва да е S256, обикновен не се поддържа. invalid_grant: Предоставеното удостоверение за достъп е невалидно, изтекло, отхвърлено, не съвпада с пренасочващото URI, използвано в заявката за удостоверение, или е бил издадено от друг клиент. invalid_redirect_uri: Включеният пренасочващ Uri е невалиден. invalid_request: diff --git a/config/locales/doorkeeper.ca.yml b/config/locales/doorkeeper.ca.yml index 590339fe887949..11a6ac94b0e714 100644 --- a/config/locales/doorkeeper.ca.yml +++ b/config/locales/doorkeeper.ca.yml @@ -83,7 +83,6 @@ ca: access_denied: El propietari del recurs o servidor d'autorizació ha denegat la petició. credential_flow_not_configured: Les credencials de contrasenya del propietari del recurs han fallat degut a que Doorkeeper.configure.resource_owner_from_credentials està sense configurar. invalid_client: La autentificació del client ha fallat perquè és un client desconegut o no està inclòsa l'autentificació del client o el mètode d'autenticació no està confirmat. - invalid_code_challenge_method: El mètode de desafiament de codi ha de ser S256, no es permet que sigui pla. invalid_grant: La concessió d'autorizació oferta és invàlida, ha vençut, s'ha revocat, no coincideix amb l'URI de redirecció utilizada en la petició d'autorizació, o fou emesa per a un altre client. invalid_redirect_uri: L'uri de redirecció inclòsa no és vàlida. invalid_request: diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml index b435624334fce7..ff8d942120623c 100644 --- a/config/locales/doorkeeper.cs.yml +++ b/config/locales/doorkeeper.cs.yml @@ -83,7 +83,6 @@ cs: access_denied: Vlastník zdroje či autorizační server žádost zamítl. credential_flow_not_configured: Proud Resource Owner Password Credentials selhal, protože Doorkeeper.configure.resource_owner_from_credentials nebylo nakonfigurováno. invalid_client: Ověření klienta selhalo kvůli neznámému klientovi, chybějící klientské autentizaci či nepodporované autentizační metodě. - invalid_code_challenge_method: Metoda code challenge musí být S256, plain není podporována. invalid_grant: Poskytnuté oprávnění je neplatné, vypršela jeho platnost, bylo odvoláno, neshoduje se s URI přesměrování použitým v požadavku o autorizaci, nebo bylo uděleno jinému klientu. invalid_redirect_uri: Zahrnutá přesměrovací URI není platná. invalid_request: diff --git a/config/locales/doorkeeper.cy.yml b/config/locales/doorkeeper.cy.yml index 32f5055b6c0665..bfdb77e7f0ea87 100644 --- a/config/locales/doorkeeper.cy.yml +++ b/config/locales/doorkeeper.cy.yml @@ -83,7 +83,6 @@ cy: access_denied: Mae perchennog yr adnodd neu'r gweinydd awdurdodi wedi atal y cais. credential_flow_not_configured: Llif meini prawf cyfrinair perchennog yr adnodd wedi methu achos fod Doorkeeper.configure.resource_owner_from_credentials heb ei ffurfweddu. invalid_client: Methodd dilysu cleient oherwydd cleient anhysbys, dim dilysiad cleient wedi'i gynnwys, neu ddull dilysu heb ei gefnogi. - invalid_code_challenge_method: Rhaid i'r dull herio cod fod yn S256, nid oes cefnogaeth i'r plaen. invalid_grant: Mae'r grant awdurdodi ar yr amod yn annilys, wedi dod i ben, wedi'i ddirymu, nid yw'n cyfateb i'r URI ailgyfeirio a ddefnyddiwyd yn y cais am awdurdodiad, neu wedi'i roi i gleient arall. invalid_redirect_uri: Nid yw'r uri ailgyfeirio a gynhwysir yn ddilys. invalid_request: diff --git a/config/locales/doorkeeper.da.yml b/config/locales/doorkeeper.da.yml index 49917c8ebc598b..14c116f73315d9 100644 --- a/config/locales/doorkeeper.da.yml +++ b/config/locales/doorkeeper.da.yml @@ -83,7 +83,10 @@ da: access_denied: Ressourceejeren eller godkendelsesserveren afviste anmodningen. credential_flow_not_configured: Ressourceejeradgangskodeakkreditiver-flow mislykkedes grundet ikke-opsat Doorkeeper.configure.resource_owner_from_credentials. invalid_client: Klientgodkendelse mislykkedes grundet en ukendt klient, ingen inkluderet klientgodkendelse eller uunderstøttet godkendelsesmetode. - invalid_code_challenge_method: Kodeudfordringsmetoden skal være S256, simpel (plain) er uunderstøttet. + invalid_code_challenge_method: + one: code_challenge_method skal være %{challenge_methods}. + other: code_challenge_method skal være en af %{challenge_methods}. + zero: Autorisationsserveren understøtter ikke PKCE, da der ikke er nogen accepterede code_challenge_method-værdier. invalid_grant: Den leverede godkendelse er ugyldig, udløbet, ophævet, matcher ikke omdirigerings-URI'en brugt i godkendelsesanmodningen eller er udstedt til en anden klient. invalid_redirect_uri: Inkluderede ormdirigerings-URI er ugyldig. invalid_request: diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index 2cf7d9c2652892..e75ffb9d94283a 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -83,7 +83,6 @@ de: access_denied: Diese Anfrage wurde von den Inhaber*innen oder durch den Autorisierungsserver abgelehnt. credential_flow_not_configured: Das Konto konnte nicht gefunden werden, da Doorkeeper.configure.resource_owner_from_credentials nicht konfiguriert ist. invalid_client: 'Client-Authentisierung ist fehlgeschlagen: Client unbekannt, keine Authentisierung mitgeliefert oder Authentisierungsmethode wird nicht unterstützt.' - invalid_code_challenge_method: Die Code-Challenge-Methode muss „S256“ sein, „plain“ wird nicht unterstützt. invalid_grant: Die beigefügte Autorisierung ist ungültig, abgelaufen, wurde widerrufen oder einem anderen Client ausgestellt, oder der Weiterleitungs-URI stimmt nicht mit der Autorisierungs-Anfrage überein. invalid_redirect_uri: Der beigefügte Weiterleitungs-URI ist ungültig. invalid_request: diff --git a/config/locales/doorkeeper.el.yml b/config/locales/doorkeeper.el.yml index 1632767bc79d0f..34d3b8fb52d368 100644 --- a/config/locales/doorkeeper.el.yml +++ b/config/locales/doorkeeper.el.yml @@ -83,7 +83,10 @@ el: access_denied: Ο ιδιοκτήτης του πόρου ή του παρόχου έγκρισης απέρριψε το αίτημα. credential_flow_not_configured: Η ροή Resource Owner Password Credentials απέτυχε επειδή το Doorkeeper.configure.resource_owner_from_credentials δεν έχει ρυθμιστεί. invalid_client: Η ταυτοποίηση του πελάτη απέτυχε είτε λόγω άγνωστου πελάτη, είτε λόγω έλλειψης ταυτοποιημένου πελάτη ή λόγω μη υποστηριζόμενης μεθόδου ταυτοποίησης. - invalid_code_challenge_method: Η μέθοδος πρόκλησης κώδικα πρέπει να είναι S256, το απλό δεν υποστηρίζεται. + invalid_code_challenge_method: + one: Το code_challenge_method πρέπει να είναι %{challenge_methods}. + other: Το code_challenge_method πρέπει να είναι ένα από %{challenge_methods}. + zero: Ο διακομιστής εξουσιοδότησης δεν υποστηρίζει PKCE καθώς δεν υπάρχουν αποδεκτές τιμές code_challenge_method. invalid_grant: Η άδεια πιστοποίησης που δόθηκε είναι άκυρη, ληγμένη, έχει ανακληθεί, δεν συμφωνεί με το URI ανακατεύθυνσης που δόθηκε στο αίτημα πιστοποίησης ή εκδόθηκε προς άλλο πελάτη. invalid_redirect_uri: Το uri ανακατεύθυνσης που δόθηκε δεν είναι έγκυρο. invalid_request: diff --git a/config/locales/doorkeeper.en-GB.yml b/config/locales/doorkeeper.en-GB.yml index 5b4b99858cccdb..e9e54501bc94cc 100644 --- a/config/locales/doorkeeper.en-GB.yml +++ b/config/locales/doorkeeper.en-GB.yml @@ -83,7 +83,10 @@ en-GB: access_denied: The resource owner or authorisation server denied the request. credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured. invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method. - invalid_code_challenge_method: The code challenge method must be S256, plain is unsupported. + invalid_code_challenge_method: + one: The code_challenge_method must be %{challenge_methods}. + other: The code_challenge_method must be one of %{challenge_methods}. + zero: The authorisation server does not support PKCE as there are no accepted code_challenge_method values. invalid_grant: The provided authorisation grant is invalid, expired, revoked, does not match the redirection URI used in the authorisation request, or was issued to another client. invalid_redirect_uri: The redirect URI included is not valid. invalid_request: diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml index ede1ef815e0d81..e6692ca9babe74 100644 --- a/config/locales/doorkeeper.eo.yml +++ b/config/locales/doorkeeper.eo.yml @@ -83,7 +83,6 @@ eo: access_denied: La posedanto de la rimedo aŭ de la rajtiga servilo rifuzis vian peton. credential_flow_not_configured: La sendado de la identigiloj de la posedanto de la rimedo malsukcesis ĉar Doorkeeper.configure.resource_owner_from_credentials ne estis agordita. invalid_client: Klienta aŭtentigo malsukcesa pro nekonata kliento, neniu klienta aŭtentigo inkluzivita, aŭ nesubtenata aŭtentiga metodo. - invalid_code_challenge_method: La koda defia metodo devas esti S256, ebenaĵo estas nesubtenata. invalid_grant: La rajtiga konsento ne estas valida, ne plu estas valida, estis forigita, ne kongruas kun la plusenda URI uzita en la aŭtentiga peto, aŭ estis sendita al alia kliento. invalid_redirect_uri: La plusenda URI uzita ne estas valida. invalid_request: diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml index a6e1a46b80718c..be075121b06e70 100644 --- a/config/locales/doorkeeper.es-AR.yml +++ b/config/locales/doorkeeper.es-AR.yml @@ -83,7 +83,6 @@ es-AR: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso fallaron debido a que "Doorkeeper.configure.resource_owner_from_credentials" está sin configurar. invalid_client: La autenticación del cliente falló debido a que es un cliente desconocido, o no está incluída la autenticación del cliente, o el método de autenticación no está soportado. - invalid_code_challenge_method: El método de desafío de código debe ser S256, «plain» no está soportado. invalid_grant: La concesión de autorización ofrecida no es válida, venció, se revocó, no coincide con la dirección web de redireccionamiento usada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La dirección web de redireccionamiento incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.es-MX.yml b/config/locales/doorkeeper.es-MX.yml index eaf1bf69fb7673..bee17cc6a498dd 100644 --- a/config/locales/doorkeeper.es-MX.yml +++ b/config/locales/doorkeeper.es-MX.yml @@ -72,8 +72,8 @@ es-MX: revoke: "¿Está seguro?" index: authorized_at: Autorizado el %{date} - description_html: Estas son aplicaciones que pueden acceder a tu cuenta utilizando la API. Si hay alguna aplicación que no reconozcas aquí, o una aplicación esta teniendo comportamientos extraños, puedes revocar el acceso. - last_used_at: Usado por ultima vez el %{date} + description_html: Estas son aplicaciones que pueden acceder a tu cuenta mediante la API. Si hay aplicaciones que no reconoces aquí, o si alguna aplicación no funciona correctamente, puedes revocar su acceso. + last_used_at: Última vez utilizado el %{date} never_used: Nunca usado scopes: Permisos superapp: Interno @@ -82,8 +82,11 @@ es-MX: messages: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar. - invalid_client: La autentificación del cliente falló ya que es un cliente desconocido, no está incluída la autentificación del cliente o el método de autentificación no es compatible. - invalid_code_challenge_method: El método de desafío de código debe ser S256, «plain» no está soportado. + invalid_client: La autenticación del cliente falló debido a un cliente desconocido, no se incluyó la autenticación del cliente o el método de autenticación no es compatible. + invalid_code_challenge_method: + one: El code_challenge_method debe ser %{challenge_methods}. + other: El code_challenge_method debe ser uno de %{challenge_methods}. + zero: El servidor de autorización no admite PKCE, ya que no hay valores code_challenge_method aceptados. invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La URI de redirección incluida no es válida. invalid_request: @@ -97,7 +100,7 @@ es-MX: revoked: El autentificador de acceso fue revocado unknown: El autentificador de acceso es inválido resource_owner_authenticator_not_configured: El propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_authenticator está sin configurar. - server_error: El servidor de la autorización entontró una condición inesperada que le impidió cumplir con la solicitud. + server_error: El servidor de autorización encontró una condición inesperada que le impidió completar la solicitud. temporarily_unavailable: El servidor de la autorización es actualmente incapaz de manejar la petición debido a una sobrecarga temporal o un trabajo de mantenimiento del servidor. unauthorized_client: El cliente no está autorizado a realizar esta petición utilizando este método. unsupported_grant_type: El tipo de concesión de autorización no está soportado por el servidor de autorización. @@ -117,7 +120,7 @@ es-MX: access: read: Acceso de solo lectura read/write: Acceso de lectura y escritura - write: Acceso de sólo escritura + write: Acceso de solo escritura title: accounts: Cuentas admin/accounts: Administración de cuentas @@ -139,7 +142,7 @@ es-MX: profile: Tu perfil de Mastodon push: Notificaciones push reports: Reportes - search: Busqueda + search: Búsqueda statuses: Publicaciones layouts: admin: diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml index d582460d3fe7a9..57b8078e44bed8 100644 --- a/config/locales/doorkeeper.es.yml +++ b/config/locales/doorkeeper.es.yml @@ -83,7 +83,6 @@ es: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar. invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado. - invalid_code_challenge_method: El método de desafío de código debe ser S256, «plain» no está soportado. invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La URI de redirección incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.et.yml b/config/locales/doorkeeper.et.yml index df10f9a0cec65e..fb1cf2a614ba60 100644 --- a/config/locales/doorkeeper.et.yml +++ b/config/locales/doorkeeper.et.yml @@ -83,7 +83,6 @@ et: access_denied: Ressursi omanik või autoriseerimisserver lükkas taotluse tagasi. credential_flow_not_configured: Resource Owner Password Credentials vool ebaõnnestus, kuna Doorkeeper.configure.resource_owner_from_credentials ei ole seadistatud. invalid_client: Kliendi autentimine ebaõnnestus. Põhjus kas tundmatu klient, puudulik autentimine või toetamata autentimismeetod. - invalid_code_challenge_method: Code challenge meetod peab olema S256,, plain ei ole toetatud. invalid_grant: Antud autoriseerimisluba on vale, aegunud, tagasi võetud, ei kattu kasutatud ümbersuunamise URLid või oli antud teisele kliendile. invalid_redirect_uri: Antud ümbersuunamise URL ei ole õige. invalid_request: diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml index 0dd4fe4194e525..7b25004385c061 100644 --- a/config/locales/doorkeeper.eu.yml +++ b/config/locales/doorkeeper.eu.yml @@ -83,7 +83,6 @@ eu: access_denied: Baliabidearen jabeak edo baimenaren zerbitzariak eskaera ukatu du. credential_flow_not_configured: Baliabidearen jabearen pasahitza kredentzialen fluxuak huts egin du Doorkeeper.configure.resource_owner_from_credentials konfiguratu gabe dagoelako. invalid_client: Bezeroaren autentifikazioak huts egin du bezero ezezaguna delako, ez delako bezero autentifikazioa txertatu, edo autentifikazio metodoa ez delako onartzen. - invalid_code_challenge_method: Kodearen erronka metodoa S256 izan behar da, arrunta ez da onartzen. invalid_grant: Emandako baimena baliogabea da, iraungi edo indargabetu da, ez dator bat baimen-eskaeran erabilitako birbideratze URI-arekin edo beste bezero batek sortu du. invalid_redirect_uri: Sartutako birbideratze URI-a baliogabea da. invalid_request: diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml index be748880d926a6..8076819d469e9c 100644 --- a/config/locales/doorkeeper.fa.yml +++ b/config/locales/doorkeeper.fa.yml @@ -83,7 +83,6 @@ fa: access_denied: صاحب منبع یا کارساز تأیید هویت، درخواست را رد کردند. credential_flow_not_configured: جریان اعتبارنامهٔ گذرواژهٔ مالک منبع به دلیل پیکربندی نشده بودن Doorkeeper.configure.resource_owner_from_credentials شکست خورد. invalid_client: تأیید هویت کارخواه به دلیل کارخواه ناشناخته، عدم وجود تأیید هویت کاره یا روش تأیید هویت پشتیبانی‌نشده شکست خورد. - invalid_code_challenge_method: روش چالش کدی باید S256 باشد. متن خام پشتیبانی نمی‌شود. invalid_grant: اعطای دسترسی فراهم ‌شده نامعتبر، منقضی یا نامطابق با نشانی بازگشت استفاده‌شده در درخواست تأیید هویت بوده و یا برای کارخواهی دیگر صادر شده است. invalid_redirect_uri: نشانی بازگشت موجود، معتبر نیست. invalid_request: diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml index 39812d52e47057..f28b42bd1e1fb1 100644 --- a/config/locales/doorkeeper.fi.yml +++ b/config/locales/doorkeeper.fi.yml @@ -83,7 +83,10 @@ fi: access_denied: Resurssin omistaja tai valtuutuspalvelin hylkäsi pyynnön. credential_flow_not_configured: Resurssin omistajan salasanatietojen luku epäonnistui, koska asetusta Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu. invalid_client: Asiakasohjelman todennus epäonnistui, koska asiakas on tuntematon, asiakkaan todennus ei ollut mukana tai todennustapaa ei tueta. - invalid_code_challenge_method: Koodihaastemenetelmän tulee olla S256 – selväkielistä ei tueta. + invalid_code_challenge_method: + one: Muuttujan code_challenge_method arvon on oltava %{challenge_methods}. + other: 'Muuttujan code_challenge_method arvon on oltava jokin seuraavista: %{challenge_methods}.' + zero: Valtuutuspalvelin ei tue PKCE:tä, koska hyväksyttäviä code_challenge_method-arvoja ei ole. invalid_grant: Valtuutuslupa on virheellinen, vanhentunut, peruttu, valtuutuspyynnössä käytettyä uudelleenohjaus-URI:tä vastaamaton tai myönnetty toiselle asiakkaalle. invalid_redirect_uri: Uudelleenohjaus-URI on virheellinen. invalid_request: diff --git a/config/locales/doorkeeper.fo.yml b/config/locales/doorkeeper.fo.yml index b6c1998b3a109a..bf6f74841f164f 100644 --- a/config/locales/doorkeeper.fo.yml +++ b/config/locales/doorkeeper.fo.yml @@ -83,7 +83,10 @@ fo: access_denied: Tilfarseigarin ella váttanarambætarin noktaðu umbønina. credential_flow_not_configured: Resource Owner Password Credentials floymurin eydnaðist ikki. Orsøkin var, at Doorkeeper.configure.resource_owner_from_credentials var ikki sett upp. invalid_client: Viðskiftaraváttan miseydnaðist. Orsøkin var ein ókendur viðskiftari, at eingin viðskiftaraváttan var við ella at váttanarmannagongdin er ókend. - invalid_code_challenge_method: Koduavbjóðingarmetodan má vera S256, einfalt format riggar ikki. + invalid_code_challenge_method: + one: Code_challenge_method má vera %{challenge_methods}. + other: Code_challenge_method má vera ein av %{challenge_methods}. + zero: Váttanarambætarin stuðlar ikki uppundir PKCE av tí at tað eru eingi góðkend code_challenge_method virði. invalid_grant: Veitta váttanarheimildin er antin ógildug, útgingin, tikin aftur, samsvarar ikki við endursendingar-URI'ið, sum bleiv brúkt í váttanarumbønini ella var flýggjað øðrum viðskiftara. invalid_redirect_uri: Endursendingar-URI'ið, sum var viðheft, er ógyldugt. invalid_request: diff --git a/config/locales/doorkeeper.fr-CA.yml b/config/locales/doorkeeper.fr-CA.yml index 9279a8a26ace25..a63e280a1990fa 100644 --- a/config/locales/doorkeeper.fr-CA.yml +++ b/config/locales/doorkeeper.fr-CA.yml @@ -83,7 +83,8 @@ fr-CA: access_denied: Le/la propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du/de la propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: La méthode de contrôle du code doit être S256, le mode « en clair » n'est pas pris en charge. + invalid_code_challenge_method: + one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, révoquée, ne concorde pas avec l’URI de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URI de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml index d956225dbae481..fdc642d9cd83b8 100644 --- a/config/locales/doorkeeper.fr.yml +++ b/config/locales/doorkeeper.fr.yml @@ -83,7 +83,8 @@ fr: access_denied: Le propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: La méthode de contrôle du code doit être S256, le mode « en clair » n'est pas pris en charge. + invalid_code_challenge_method: + one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, annulée, ne concorde pas avec l’URL de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URL de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.fy.yml b/config/locales/doorkeeper.fy.yml index 180c7e733f119b..919dce5e4902bd 100644 --- a/config/locales/doorkeeper.fy.yml +++ b/config/locales/doorkeeper.fy.yml @@ -83,7 +83,6 @@ fy: access_denied: De boarne-eigener of autorisaasjeserver hat it fersyk wegere. credential_flow_not_configured: De wachtwurdgegevens-flow fan de boarne-eigener is mislearre, omdat Doorkeeper.configure.resource_owner_from_credentials net ynsteld is. invalid_client: Clientferifikaasje is mislearre troch in ûnbekende client, ûntbrekkende client-autentikaasje of in net stipe autentikaasjemetoade. - invalid_code_challenge_method: De koadechallengemetoade moat S256 wezen, plain wurdt net stipe. invalid_grant: De opjûne autorisaasje is ûnjildich, ferrûn, ynlutsen, komt net oerien mei de redirect-URI dy’t opjûn is of útjûn waard oan in oere client. invalid_redirect_uri: De opjûne redirect-URI is ûnjildich. invalid_request: diff --git a/config/locales/doorkeeper.ga.yml b/config/locales/doorkeeper.ga.yml index b8ee3497b79db1..ecc7ea96480296 100644 --- a/config/locales/doorkeeper.ga.yml +++ b/config/locales/doorkeeper.ga.yml @@ -83,7 +83,10 @@ ga: access_denied: Shéan úinéir na hacmhainne nó an freastalaí údaraithe an t-iarratas. credential_flow_not_configured: Theip ar shreabhadh Dintiúir Pasfhocal Úinéir Acmhainne toisc go raibh Doorkeeper.configure.resource_owner_from_credentials díchumraithe. invalid_client: Theip ar fhíordheimhniú cliant de bharr cliant anaithnid, níl fíordheimhniú cliant san áireamh, nó modh fíordheimhnithe nach dtacaítear leis. - invalid_code_challenge_method: Ní mór gur S256 an modh dúshlán cód, ach ní thacaítear leis. + invalid_code_challenge_method: + one: Ní mór don code_challenge_method a bheith %{challenge_methods}. + other: Ní mór don code_challenge_method a bheith ar cheann de %{challenge_methods}. + zero: Ní thacaíonn an freastalaí údaraithe le PKCE mar nach bhfuil aon luachanna code_challenge_method glactha ann. invalid_grant: Tá an deonú údaraithe ar choinníoll neamhbhailí, imithe in éag, cúlghairthe, nach ionann é agus an URI atreoraithe a úsáideadh san iarratas ar údarú, nó gur eisíodh é chuig cliant eile. invalid_redirect_uri: Níl an uri atreoraithe atá san áireamh bailí. invalid_request: diff --git a/config/locales/doorkeeper.gd.yml b/config/locales/doorkeeper.gd.yml index 8157a4e5fdabb5..6487669c283ded 100644 --- a/config/locales/doorkeeper.gd.yml +++ b/config/locales/doorkeeper.gd.yml @@ -83,7 +83,6 @@ gd: access_denied: Dhiùlt sealbhadair a’ ghoireis no am frithealaiche ùghdarrachaidh an t-iarrtas. credential_flow_not_configured: Dh’fhàillig le sruth cruthachadh teisteas facail-fhaire do shealbhadair a’ ghoireis ri linn Doorkeeper.configure.resource_owner_from_credentials gun rèiteachadh. invalid_client: Dh’fhàillig le dearbhadh a’ chliant ri linn cliant nach aithne dhuinn, dearbhadh cliant nach deach gabhail a-staigh no dòigh dearbhaidh ris nach cuirear taic. - invalid_code_challenge_method: Feumaidh an dòigh an dùbhlain a bhith na S256, chan eil taic ri plain. invalid_grant: Chan eil an t-ùghdarrachadh a chaidh a thoirt seachad dligheach, dh’fhalbh an ùine air, chaidh a chùl-ghairm no chan eil e a-rèir URI an ath-stiùiridh a chaidh a chleachdadh san iarrtas ùghdarrachaidh no chaidh fhoillseachadh le cliant eile. invalid_redirect_uri: Chan eil an URI ath-stiùiridh a chaidh a ghabhail a-staigh dligheach. invalid_request: diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml index 5d7148b84b0284..fc2d4aa9ecf603 100644 --- a/config/locales/doorkeeper.gl.yml +++ b/config/locales/doorkeeper.gl.yml @@ -83,7 +83,10 @@ gl: access_denied: O propietario do recurso ou o servidor autorizado denegaron a petición. credential_flow_not_configured: O fluxo do Contrasinal de Credenciais do Dono do Recurso fallou debido a que Doorkeeper.configure.resource_owner_from_credentials non están configuradas. invalid_client: A autenticación do cliente fallou por ser un cliente descoñecido, non se incluíu autenticación do cliente, ou o método de autenticación non está soportado. - invalid_code_challenge_method: O método de desafío para o código debe ser S256, texto claro non ten soporte. + invalid_code_challenge_method: + one: O code_challenge_method ten que ser %{challenge_methods}. + other: O code_challenge_method ten que ser un de entre %{challenge_methods}. + zero: O servidor de autorización non é compatible con PKCE xa que non se aceptaron valores code_challenge_method invalid_grant: A validación da autorización proporcionada non é valida, caducou, foi rexeitada, non coincide a redirección URI utilizada na petición de autorización, ou foi proporcionada para outro cliente. invalid_redirect_uri: A uri de redirección incluída non é válida. invalid_request: diff --git a/config/locales/doorkeeper.he.yml b/config/locales/doorkeeper.he.yml index a454c779f18f3f..3b7622651eb96b 100644 --- a/config/locales/doorkeeper.he.yml +++ b/config/locales/doorkeeper.he.yml @@ -83,7 +83,10 @@ he: access_denied: בעלי המשאב או שרת ההרשאה דחו את הבקשה. credential_flow_not_configured: התהליך "Resource Owner Password Credentials" נכשל בשל חוסר בתצורת Doorkeeper.configure.resource_owner_from_credentials. invalid_client: הרשאת הלקוח נכשלה עקב לקוח שאינו ידוע, חוסר בהרשאת לקוח או שיטת הרשאה שאינה נתמכת. - invalid_code_challenge_method: הצופן חייב להיות בשיטת S256, לא תומכים בבלתי מוצפן. + invalid_code_challenge_method: + one: הערך של code_challenge_method חייב להיות %{challenge_methods}. + other: 'הערך של code_challenge_method חייב להיות אחד מאלו: %{challenge_methods}.' + zero: שרת ההרשאה לא תומך PKCE שכן אין ערכי code_challenge_method נתמכים. invalid_grant: חוזה ההרשאה המצורף אינו חוקי, אינו תקף, מבוטל, או שאינו מתאים לקישורית ההפניה שבשימוש על ידי בקשת ההרשאה, או שהופק על ידי לקוח אחר. invalid_redirect_uri: קישורית ההפניה המצורפת אינה חוקית. invalid_request: diff --git a/config/locales/doorkeeper.hu.yml b/config/locales/doorkeeper.hu.yml index a13c362173df2a..17569a6598cbf7 100644 --- a/config/locales/doorkeeper.hu.yml +++ b/config/locales/doorkeeper.hu.yml @@ -83,7 +83,6 @@ hu: access_denied: Az erőforrás tulajdonosa vagy az engedélyező kiszolgáló elutasította a kérést. credential_flow_not_configured: Az erőforrás tulajdonos jelszóadatainak átadása megszakadt, mert a Doorkeeper.configure.resource_owner_from_credentials beállítatlan. invalid_client: A kliens hitelesítése megszakadt, mert ismeretlen a kliens, a kliens nem küldött hitelesítést, vagy a hitelesítés módja nem támogatott. - invalid_code_challenge_method: A kódkihívási módszernek S256-nak kell lennie, az egyszerű kód nem támogatott. invalid_grant: A biztosított hitelesítés érvénytelen, lejárt, visszavont, vagy nem egyezik a hitelesítési kérésben használt URI-val, vagy más kliensnek címezték. invalid_redirect_uri: Az átirányító URI nem valós. invalid_request: diff --git a/config/locales/doorkeeper.ia.yml b/config/locales/doorkeeper.ia.yml index efde9be9c05669..c2274b86b0aea7 100644 --- a/config/locales/doorkeeper.ia.yml +++ b/config/locales/doorkeeper.ia.yml @@ -83,7 +83,6 @@ ia: access_denied: Le proprietario del ressource o servitor de autorisation ha refusate le requesta. credential_flow_not_configured: Le processo de credentiales de contrasigno del proprietario del ressource ha fallite perque Doorkeeper.configure.resource_owner_from_credentials non es configurate. invalid_client: Le authentication del cliente ha fallite perque le cliente es incognite, necun authentication de cliente es includite, o le methodo de authentication non es supportate. - invalid_code_challenge_method: Le methodo de defia de codice debe esser S256. Le methodo simple (plain) non es supportate. invalid_grant: Le concession de autorisation fornite es invalide, expirate, revocate, non corresponde al URI de redirection usate in le requesta de autorisation, o ha essite emittite a un altere cliente. invalid_redirect_uri: Le URI de redirection includite non es valide. invalid_request: diff --git a/config/locales/doorkeeper.io.yml b/config/locales/doorkeeper.io.yml index 94efbfc9d7be84..2f8adf8ce3958f 100644 --- a/config/locales/doorkeeper.io.yml +++ b/config/locales/doorkeeper.io.yml @@ -83,7 +83,6 @@ io: access_denied: Moyenproprietanto o yurizservilo refuzis la demando. credential_flow_not_configured: Moyenproprietantpasvortidentesesofluo faliis pro ke Doorkeeper.configure.resource_owner_from_credentials ne ajustesis. invalid_client: Klientpermiso falias pro nesavita kliento, neinkluzita klientpermiso o nesuportita permismetodo. - invalid_code_challenge_method: La kodexchalenjmetodo mustas esar S256. invalid_grant: Provizita yurizo esis nevalida, expiris, deaprobesis, ne parigas uzita ridirektoligilo dum yurizdemando o facesis a altra kliento. invalid_redirect_uri: La inkluzita ridirektoligilo esas nevalida. invalid_request: diff --git a/config/locales/doorkeeper.is.yml b/config/locales/doorkeeper.is.yml index 1fabd1b55f3f13..bd806d07e14cfa 100644 --- a/config/locales/doorkeeper.is.yml +++ b/config/locales/doorkeeper.is.yml @@ -83,7 +83,10 @@ is: access_denied: Eigandi tilfangs eða auðkenningarþjónn höfnuðu beininni. credential_flow_not_configured: Flæði á lykilorðsauðkennum eiganda tilfangs (Resource Owner) brást vegna þess að Doorkeeper.configure.resource_owner_from_credentials er óskilgreint. invalid_client: Auðkenning á biðlara brást vegna þess að biðlarinn er óþekktur, að auðkenning biðlarans fylgdi ekki með, eða að notuð var óstudd auðkenningaraðferð. - invalid_code_challenge_method: Aðferð við ráðningu kóðans verður að vera í S256, hreinn texti er ekki studdur. + invalid_code_challenge_method: + one: code_challenge_method verður að vera %{challenge_methods}. + other: code_challenge_method verður að vera eitt af %{challenge_methods}. + zero: Auðkenningarþjónninn styður ekki PKCE þar sem það eru engin samþykkt gildi fyrir code_challenge_method. invalid_grant: Uppgefin auðkenningarheimild er ógild, útrunnin, afturkölluð, samsvarar ekki endurbirtingarslóðinni í auðkenningarbeiðninni, eða var gefin út til annars biðlara. invalid_redirect_uri: Endurbeiningarslóðin sem fylgdi er ekki gild. invalid_request: diff --git a/config/locales/doorkeeper.it.yml b/config/locales/doorkeeper.it.yml index 3a24465cdc2c43..6aa6624c0505db 100644 --- a/config/locales/doorkeeper.it.yml +++ b/config/locales/doorkeeper.it.yml @@ -83,7 +83,10 @@ it: access_denied: Il proprietario della risorsa o il server d'autorizzazione ha negato la richiesta. credential_flow_not_configured: Il processo delle Credenziali Password del Proprietario della Risorsa è fallito a causa della mancata configurazione di Doorkeeper.configure.resource_owner_from_credentials. invalid_client: Autenticazione del client fallita a causa di un client sconosciuto, di nessun'autenticazione del client inclusa o di un metodo d'autenticazione non supportato. - invalid_code_challenge_method: Il metodo di verifica del codice deve essere S256, semplice non è supportato. + invalid_code_challenge_method: + one: Il code_challenge_method deve essere %{challenge_methods}. + other: Il code_challenge_method deve essere uno dei %{challenge_methods}. + zero: Il server di autorizzazione non supporta PKCE poiché non sono presenti valori code_challenge_method accettati. invalid_grant: L'autorizzazione fornita non è valida, è scaduta, è stata revocata, non corrisponde all'URI di reindirizzamento utilizzato nella richiesta d'autorizzazione o è stata emessa a un altro client. invalid_redirect_uri: L'uri di reindirizzamento incluso non è valido. invalid_request: diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml index 1f94b9ede832b3..a788b5bf422fdc 100644 --- a/config/locales/doorkeeper.ja.yml +++ b/config/locales/doorkeeper.ja.yml @@ -83,7 +83,6 @@ ja: access_denied: リソースの所有者または認証サーバーが要求を拒否しました。 credential_flow_not_configured: リソース所有者のパスワード Doorkeeper.configure.resource_owner_from_credentials が設定されていないためクレデンシャルフローに失敗しました。 invalid_client: 不明なクライアントであるか、クライアント情報が含まれていない、またはサポートされていない認証方法のため、クライアントの認証に失敗しました。 - invalid_code_challenge_method: code challenge methodはS256のみが利用可能です。plainはサポートされていません。 invalid_grant: 指定された認証許可は無効であるか、期限切れ、取り消されている、リダイレクトURIの不一致、または別のクライアントに発行されています。 invalid_redirect_uri: 無効なリダイレクトURIが含まれています。 invalid_request: diff --git a/config/locales/doorkeeper.ko.yml b/config/locales/doorkeeper.ko.yml index dc1b8c08b1baa5..32be97749c0488 100644 --- a/config/locales/doorkeeper.ko.yml +++ b/config/locales/doorkeeper.ko.yml @@ -83,7 +83,6 @@ ko: access_denied: 리소스 소유자 또는 인증 서버가 요청을 거부했습니다. credential_flow_not_configured: Doorkeeper.configure.resource_owner_from_credentials의 설정이 되어있지 않아 리소스 소유자 암호 자격증명이 실패하였습니다. invalid_client: 클라이언트를 확인할 수 없기 때문에 인증이 실패하였습니다. 클라이언트 자격 증명이 포함되지 않았거나 지원되지 않는 메소드입니다. - invalid_code_challenge_method: 코드 챌린지는 S256이어야 합니다. 평문은 지원되지 않습니다. invalid_grant: 제공된 권한 부여가 잘못되거나, 만료되었거나, 취소되었거나, 권한 부여 요청에 사용된 리디렉션 URI가 일치하지 않거나, 다른 클라이언트에 지정되었습니다. invalid_redirect_uri: 리디렉션 URI가 올바르지 않습니다 invalid_request: diff --git a/config/locales/doorkeeper.lt.yml b/config/locales/doorkeeper.lt.yml index f957e4157ce95b..abc81d3dc3888d 100644 --- a/config/locales/doorkeeper.lt.yml +++ b/config/locales/doorkeeper.lt.yml @@ -83,7 +83,6 @@ lt: access_denied: Išteklių savininkas (-ė) arba leidžiamų serveris atmetė užklausą. credential_flow_not_configured: Išteklių savininko slaptažodžio kredencialų srautas nepavyko, nes Doorkeeper.configure.resource_owner_from_credentials nėra nesukonfigūruotas. invalid_client: Kliento programos tapatybės nustatymas nepavyko dėl nežinomo kliento programos, neįtraukto kliento programos tapatybės nustatymo arba nepalaikomo tapatybės nustatymo metodo. - invalid_code_challenge_method: Kodo iššūkio metodas turi būti S256. Paprastas nepalaikomas. invalid_grant: Pateiktas leidimo suteikimas yra netinkamas, nebegaliojantis, panaikintas, neatitinka leidimo užklausoje naudoto nukreipimo URI arba buvo išduotas kitam kliento programui. invalid_redirect_uri: Įtrauktas nukreipimo URI netinkamas. invalid_request: diff --git a/config/locales/doorkeeper.lv.yml b/config/locales/doorkeeper.lv.yml index a3d8902af49ed7..537e0291f2ecfe 100644 --- a/config/locales/doorkeeper.lv.yml +++ b/config/locales/doorkeeper.lv.yml @@ -83,7 +83,6 @@ lv: access_denied: Resursa īpašnieks vai pilnvarošanas serveris noraidīja pieprasījumu. credential_flow_not_configured: Resursa īpašnieka paroles akreditācijas datu plūsma neizdevās, jo Doorkeeper.configure.resource_owner_from_credentials nebija konfigurēts. invalid_client: Klienta autentifikācija neizdevās nezināma klienta, klienta autentifikācijas vai neatbalstītas autentifikācijas metodes dēļ. - invalid_code_challenge_method: Koda izaicinājuma veidam jābūt S256, vienkāršs netiek atbalstīts. invalid_grant: Sniegtais pilnvarošanas piešķīrums nav derīgs, tam ir beidzies derīgums, tas ir atsaukts, tas neatbilst pilnvarošanas pieprasījumā izmantotajam pārvirzīšanas URI vai tika izsniegts citam klientam. invalid_redirect_uri: Iekļauts novirzīšanas uri nav derīgs. invalid_request: diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml index c6453761f144c6..1d04e50f21ce65 100644 --- a/config/locales/doorkeeper.nl.yml +++ b/config/locales/doorkeeper.nl.yml @@ -83,7 +83,6 @@ nl: access_denied: De resource-eigenaar of autorisatie-server weigerde het verzoek. credential_flow_not_configured: De wachtwoordgegevens-flow van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_from_credentials niet is ingesteld. invalid_client: Clientverificatie is mislukt door een onbekende client, ontbrekende client-authenticatie of een niet ondersteunde authenticatie-methode. - invalid_code_challenge_method: De code challenge method moet S256 zijn, plain wordt niet ondersteund. invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect-URI die is opgegeven of werd uitgegeven aan een andere client. invalid_redirect_uri: De opgegeven redirect-URI is ongeldig. invalid_request: diff --git a/config/locales/doorkeeper.nn.yml b/config/locales/doorkeeper.nn.yml index 934bb870be72cf..3b8a9b0663bfba 100644 --- a/config/locales/doorkeeper.nn.yml +++ b/config/locales/doorkeeper.nn.yml @@ -83,7 +83,6 @@ nn: access_denied: Ressurseigaren eller autorisasjonstenaren avviste førespurnaden. credential_flow_not_configured: Flyten «Resource Owner Password Credentials» kunne ikkje fullførast sidan «Doorkeeper.configure.resource_owner_from_credentials» ikkje er konfigurert. invalid_client: Klientautentisering feila på grunn av ukjent klient, ingen inkludert autentisering, eller ikkje støtta autentiseringsmetode. - invalid_code_challenge_method: Kodeutfordringsmetoden må vera S256, klartekst er ikkje støtta. invalid_grant: Autoriseringa er ugyldig, utløpt, oppheva, stemmer ikkje med omdirigerings-URIen eller var tildelt ein annan klient. invalid_redirect_uri: Omdirigerings-URLen er ikkje gyldig. invalid_request: diff --git a/config/locales/doorkeeper.pl.yml b/config/locales/doorkeeper.pl.yml index 2ff1bb5f4509c5..b11528aeee2628 100644 --- a/config/locales/doorkeeper.pl.yml +++ b/config/locales/doorkeeper.pl.yml @@ -83,7 +83,6 @@ pl: access_denied: Właściciel zasobu lub serwer autoryzujący odrzuciły żądanie. credential_flow_not_configured: Ścieżka "Resource Owner Password Credentials" zakończyła się błędem, ponieważ Doorkeeper.configure.resource_owner_from_credentials nie został skonfigurowany. invalid_client: Autoryzacja klienta nie powiodła się z powodu nieznanego klienta, braku uwierzytelnienia klienta, lub niewspieranej metody uwierzytelniania. - invalid_code_challenge_method: Metodą wyzwania kodowego musi być S256, plain jest nieobsługiwany. invalid_grant: Grant uwierzytelnienia jest niepoprawny, przeterminowany, unieważniony, nie pasuje do URI przekierowwania użytego w żądaniu uwierzytelnienia, lub został wystawiony przez innego klienta. invalid_redirect_uri: URI przekierowania jest nieprawidłowy. invalid_request: diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml index a92819bf688c15..b3badf07c4a14d 100644 --- a/config/locales/doorkeeper.pt-BR.yml +++ b/config/locales/doorkeeper.pt-BR.yml @@ -83,7 +83,6 @@ pt-BR: access_denied: O proprietário do recurso ou servidor de autorização recusou a solicitação. credential_flow_not_configured: Fluxo das Credenciais de Senha do Proprietário do Recurso falhou porque Doorkeeper.configure.resource_owner_from_credentials não foi configurado. invalid_client: Autenticação do cliente falhou por causa de um cliente desconhecido, nenhum cliente de autenticação foi incluído ou o método de autenticação não é suportado. - invalid_code_challenge_method: O método de desafio de código deve ser S256; o método 'plain' não é suportado. invalid_grant: A garantia de autorização está inválida, expirou ou foi revogada, não é equivalente ao link de redirecionamento usado na solicitação de autorização ou foi emitido por outro cliente. invalid_redirect_uri: O link de redirecionamento é inválido. invalid_request: diff --git a/config/locales/doorkeeper.pt-PT.yml b/config/locales/doorkeeper.pt-PT.yml index ae7dc28b620b81..eeb3ee0cdd6fe4 100644 --- a/config/locales/doorkeeper.pt-PT.yml +++ b/config/locales/doorkeeper.pt-PT.yml @@ -83,7 +83,6 @@ pt-PT: access_denied: O proprietário do recurso ou servidor de autorização negou o pedido. credential_flow_not_configured: Falha no fluxo de credenciais da palavra-passe do proprietário do recurso porque Doorkeeper.configure.resource_owner_from_credentials não está configurado. invalid_client: A autenticação do cliente falhou devido a cliente desconhecido, sem autenticação de cliente incluída ou método de autenticação não suportado. - invalid_code_challenge_method: O método de validação do código tem de ser S256, o método simples não é suportado. invalid_grant: A concessão de autorização fornecida é inválida, expirou, foi revogada, não corresponde à URI de redirecionamento usada no pedido de autorização ou foi emitida para outro cliente. invalid_redirect_uri: A URI de redirecionamento incluída não é válida. invalid_request: diff --git a/config/locales/doorkeeper.ro.yml b/config/locales/doorkeeper.ro.yml index 08b7c3fa8d6d57..fde85d762b1782 100644 --- a/config/locales/doorkeeper.ro.yml +++ b/config/locales/doorkeeper.ro.yml @@ -83,7 +83,6 @@ ro: access_denied: Proprietarul de resurse sau serverul de autorizare a refuzat cererea. credential_flow_not_configured: Fluxul Resurselor de Acreditări Parole a eșuat din cauza faptului că Doorkeeper.configure.resource_owner_from_credentials nu este configurat. invalid_client: Autentificarea clientului a eșuat din cauza unui client necunoscut, nici o autentificare client inclusă, sau metodă de autentificare nesuportată. - invalid_code_challenge_method: Metoda de provocare a codului trebuie să fie S256, simplu nu este acceptată. invalid_grant: Acordarea autorizației furnizată este invalidă, expirată, revocată, nu corespunde URI-ului de redirecționare folosit în cererea de autorizare, sau a fost eliberat altui client. invalid_redirect_uri: Uri-ul de redirecționare inclus nu este valid. invalid_request: diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml index 30fbfabaf6d765..1bd640d9aa340b 100644 --- a/config/locales/doorkeeper.ru.yml +++ b/config/locales/doorkeeper.ru.yml @@ -83,7 +83,6 @@ ru: access_denied: Владелец ресурса или сервер авторизации ответил отказом на ваш запрос. credential_flow_not_configured: Процесс Resource Owner Password Credentials завершился неудачей, поскольку параметр конфигурации Doorkeeper.configure.resource_owner_from_credentials не был задан. invalid_client: 'Не удалось аутентифицировать клиент по одной из следующих причин: неизвестный клиент, отсутствует аутентификация клиента, неподдерживаемый метод аутентификации.' - invalid_code_challenge_method: Функция хеширования для механизма PKCE должна быть установлена в значение S256, метод PLAIN не поддерживается. invalid_grant: Предоставленное разрешение на авторизацию либо недействительно, либо истекло, либо отозвано, либо не соответствует использованному в запросе на авторизацию URI перенаправления, либо было выдано для другого клиента. invalid_redirect_uri: Предоставленный URI перенаправления недействителен. invalid_request: diff --git a/config/locales/doorkeeper.si.yml b/config/locales/doorkeeper.si.yml index 0677e23c2a26f7..ceaff56b404f97 100644 --- a/config/locales/doorkeeper.si.yml +++ b/config/locales/doorkeeper.si.yml @@ -83,7 +83,6 @@ si: access_denied: සම්පත් හිමිකරු හෝ අවසර සේවාදායකය ඉල්ලීම ප්‍රතික්ෂේප කළේය. credential_flow_not_configured: Doorkeeper.configure.resource_owner_from_credentials වින්‍යාස නොකිරීම හේතුවෙන් සම්පත් හිමිකරුගේ මුරපද අක්තපත්‍ර ප්‍රවාහය අසාර්ථක විය. invalid_client: නොදන්නා සේවාලාභියා නිසා සේවාලාභී සත්‍යාපනය අසාර්ථක විය, සේවාලාභී සත්‍යාපනය ඇතුළත් කර නැත, හෝ සහය නොදක්වන සත්‍යාපන ක්‍රමයක්. - invalid_code_challenge_method: කේත අභියෝගතා ක්‍රමය S256 විය යුතුය, plain සහාය නොදක්වයි. invalid_grant: සපයා ඇති අවසර දීමනාව වලංගු නැත, කල් ඉකුත් වී ඇත, අවලංගු කර ඇත, අවසර ඉල්ලීමේ භාවිතා කරන ලද යළි-යොමුවීම් URI සමඟ නොගැලපේ, නැතහොත් වෙනත් සේවාදායකයෙකුට නිකුත් කර ඇත. invalid_redirect_uri: ඇතුළත් කර ඇති යළි-යොමුවීම් uri වලංගු නොවේ. invalid_request: diff --git a/config/locales/doorkeeper.sl.yml b/config/locales/doorkeeper.sl.yml index 8b28d1532a2e90..31c2e878680b2e 100644 --- a/config/locales/doorkeeper.sl.yml +++ b/config/locales/doorkeeper.sl.yml @@ -83,7 +83,6 @@ sl: access_denied: Lastnik virov ali odobritveni strežnik je zavrnil zahtevo. credential_flow_not_configured: Pretok geselskih pooblastil lastnika virov ni uspel, ker Doorkeeper.configure.resource_owner_from_credentials ni nastavljen. invalid_client: Odobritev odjemalca ni uspela zaradi neznanega odjemalca, zaradi nevključitve odobritve odjemalca ali zaradi nepodprte metode odobritve. - invalid_code_challenge_method: Metoda za kodo mora biti S256, čistopis ni podprt. invalid_grant: Predložena odobritev je neveljavna, je potekla, je preklicana, se ne ujema z URI-jem za preusmeritev uporabljenim v zahtevi za odobritev, ali pa je bila izdana drugemu odjemalcu. invalid_redirect_uri: URI za preusmeritev ni veljaven. invalid_request: diff --git a/config/locales/doorkeeper.sq.yml b/config/locales/doorkeeper.sq.yml index f98cf88c630c87..bdf292ebecd84e 100644 --- a/config/locales/doorkeeper.sq.yml +++ b/config/locales/doorkeeper.sq.yml @@ -83,7 +83,10 @@ sq: access_denied: I zoti i burimit ose shërbyesi i autorizimit e hodhi poshtë kërkesën. credential_flow_not_configured: Rrjedha për Kredenciale Fjalëkalimi të të Zotit të Burimit dështoi për shkak se Doorkeeper.configure.resource_owner_from_credentials është i paformësuar. invalid_client: Mirëfilltësimi i klientit dështoi për shkak klienti të panjohur, mospërfshirjeje mirëfilltësimi klienti, ose metode të pambuluar mirëfilltësimi. - invalid_code_challenge_method: Metoda me kod duhet të jetë e llojit S256, e thjeshta nuk mbulohet. + invalid_code_challenge_method: + one: code_challenge_method duhet të jetë %{challenge_methods}. + other: code_challenge_method duhet të jetë një nga %{challenge_methods}. + zero: Shërbyesi i autorizimeve s’mbulon PKCE, ngaqë s’ka vlera code_challenge_method të pranuara. invalid_grant: Autorizimi i dhënë është i pavlefshëm, ka skaduar, është shfuqizuar, s’përputhet me URI-n e ridrejtimit të përdorur te kërkesa e autorizimit, ose është emetuar për klient tjetër. invalid_redirect_uri: URI e ridrejtimit s’është e vlefshme. invalid_request: diff --git a/config/locales/doorkeeper.sr-Latn.yml b/config/locales/doorkeeper.sr-Latn.yml index bce2b1c08371c6..25aba51d54fdae 100644 --- a/config/locales/doorkeeper.sr-Latn.yml +++ b/config/locales/doorkeeper.sr-Latn.yml @@ -82,7 +82,6 @@ sr-Latn: access_denied: Vlasnik resursa ili autorizacioni server su odbili zahtev. credential_flow_not_configured: Tok Resource Owner Password Credentials nije uspeo pošto je Doorkeeper.configure.resource_owner_from_credentials neiskonfigurisan. invalid_client: Klijentska identifikacija nije uspela zbog nepoznatog klijenta, zato što klijent nije uključio identifikaciju ili zato što je iskorišćen nepodržani identifikacioni metod. - invalid_code_challenge_method: Metod izazova koda mora biti S256, običan nije podržan. invalid_grant: Zadata identifikaciona dozvola je neispravna, istekla, opozvana, ne poklapa se sa adresom preusmeravanja ili je izdata nekog drugom klijentu. invalid_redirect_uri: Uključena adresa preusmeravanja nije ispravna. invalid_request: diff --git a/config/locales/doorkeeper.sr.yml b/config/locales/doorkeeper.sr.yml index b6ab6e61c0914a..5f5d403ae2c427 100644 --- a/config/locales/doorkeeper.sr.yml +++ b/config/locales/doorkeeper.sr.yml @@ -82,7 +82,6 @@ sr: access_denied: Власник ресурса или ауторизациони сервер су одбили захтев. credential_flow_not_configured: Ток Resource Owner Password Credentials није успео пошто је Doorkeeper.configure.resource_owner_from_credentials неисконфигурисан. invalid_client: Клијентска идентификација није успела због непознатог клијента, зато што клијент није укључио идентификацију или зато што је искоришћен неподржани идентификациони метод. - invalid_code_challenge_method: Метод изазова кода мора бити S256, обичан није подржан. invalid_grant: Задата идентификациона дозвола је неисправна, истекла, опозвана, не поклапа се са адресом преусмеравања или је издата неког другом клијенту. invalid_redirect_uri: Укључена адреса преусмеравања није исправна. invalid_request: diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml index 3c8b08ff268d32..01a441b556c0c7 100644 --- a/config/locales/doorkeeper.sv.yml +++ b/config/locales/doorkeeper.sv.yml @@ -83,7 +83,6 @@ sv: access_denied: Resursägaren eller behörighetsservern nekade begäran. credential_flow_not_configured: Resurs Ägare Lösenord Credentials flöde misslyckades på grund av att Doorkeeper.configure.resource_owner_from_credentials är okonfigurerad. invalid_client: Klientautentisering misslyckades på grund av okänd klient, ingen klientautentisering inkluderad eller icke godkänd autentiseringsmetod. - invalid_code_challenge_method: Kodutmaningsmetoden måste vara S256, en slät stöds inte. invalid_grant: Det beviljade godkännandetillskottet är ogiltigt, upphört, återkallat, matchar inte den omdirigering URI som användes i auktorisationsförfrågan eller har utfärdats till en annan klient. invalid_redirect_uri: Den omdirigerade uri är inte giltig. invalid_request: diff --git a/config/locales/doorkeeper.th.yml b/config/locales/doorkeeper.th.yml index 3b52e170ea3eb0..2040bc9c735592 100644 --- a/config/locales/doorkeeper.th.yml +++ b/config/locales/doorkeeper.th.yml @@ -83,7 +83,6 @@ th: access_denied: เจ้าของทรัพยากรหรือเซิร์ฟเวอร์การอนุญาตปฏิเสธคำขอ credential_flow_not_configured: โฟลว์ข้อมูลประจำตัวรหัสผ่านเจ้าของทรัพยากรล้มเหลวเนื่องจากไม่ได้กำหนดค่า Doorkeeper.configure.resource_owner_from_credentials invalid_client: การรับรองความถูกต้องไคลเอ็นต์ล้มเหลวเนื่องจากไคลเอ็นต์ที่ไม่รู้จัก ไม่ได้รวมการรับรองความถูกต้องไคลเอ็นต์ หรือวิธีการรับรองความถูกต้องที่ไม่รองรับ - invalid_code_challenge_method: วิธีการทดสอบรหัสต้องเป็น S256 ไม่รองรับแบบธรรมดา invalid_grant: การให้การรับรองความถูกต้องที่ให้มาไม่ถูกต้อง หมดอายุแล้ว เพิกถอนแล้ว ไม่ตรงกับ URI การเปลี่ยนเส้นทางที่ใช้ในคำขอการรับรองความถูกต้อง หรือออกให้ไคลเอ็นต์อื่น invalid_redirect_uri: URI การเปลี่ยนเส้นทางที่รวมอยู่ไม่ถูกต้อง invalid_request: diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml index f7f67564d20949..d663f025ac91a6 100644 --- a/config/locales/doorkeeper.tr.yml +++ b/config/locales/doorkeeper.tr.yml @@ -83,7 +83,6 @@ tr: access_denied: Kaynak sahibi veya yetkilendirme sunucusu isteği reddetti. credential_flow_not_configured: Kaynak Sahibi Parolası Kimlik Bilgileri akışı Doorkeeper.configure.resource_owner_from_credentials 'ın yapılandırılmamış olması nedeniyle başarısız oldu. invalid_client: İstemcinin kimlik doğrulaması bilinmeyen istemci, istemci kimlik doğrulamasının dahil olmaması veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle başarısız oldu. - invalid_code_challenge_method: Kod zorluk metodu S256 olmalı, düz yöntem desteklenmiyor. invalid_grant: Sağlanan yetkilendirme izni geçersiz, süresi dolmuş, iptal edilmiş, yetkilendirme isteğinde kullanılan yönlendirme URL'siyle eşleşmiyor veya başka bir istemciye verilmiş. invalid_redirect_uri: Dahil edilmiş yönlendirme uri'si geçersiz. invalid_request: diff --git a/config/locales/doorkeeper.uk.yml b/config/locales/doorkeeper.uk.yml index 1e18eaef9a1918..afcafc16c086f4 100644 --- a/config/locales/doorkeeper.uk.yml +++ b/config/locales/doorkeeper.uk.yml @@ -83,7 +83,6 @@ uk: access_denied: Власник ресурсу або сервер авторизації відхилив Ваш запит. credential_flow_not_configured: Не вдалося перевірити парольні дані клієнту через неналаштований параметр Doorkeeper.configure.resource_owner_from_credentials. invalid_client: Не вдалося аутентифікувати клієнта (клієнт невідомий, аутентифікацію клієнта не увімкнено, або непідтримуваний метод аутентифікації). - invalid_code_challenge_method: Метод виклику коду повинен бути S256, простий не підтримується. invalid_grant: Наданий санкціонований дозвіл недійсний, прострочений, анульований, не відповідає URI перенаправлення, що використовується в запиті авторизації, або був виданий іншому клієнту. invalid_redirect_uri: Включений uri перенаправлення не є дійсним. invalid_request: diff --git a/config/locales/doorkeeper.vi.yml b/config/locales/doorkeeper.vi.yml index 25bf7b66894ab0..3098f36e8c2bb5 100644 --- a/config/locales/doorkeeper.vi.yml +++ b/config/locales/doorkeeper.vi.yml @@ -83,7 +83,10 @@ vi: access_denied: Chủ sở hữu tài nguyên hoặc máy chủ đã từ chối yêu cầu. credential_flow_not_configured: Resource Owner Password Credentials không thành công do Doorkeeper.configure.resource_owner_from_credentials không được định cấu hình. invalid_client: Xác minh ứng dụng khách không thành công do máy khách mơ hồ, không bao gồm xác thực ứng dụng khách hoặc phương thức xác thực không được hỗ trợ. - invalid_code_challenge_method: Phương pháp thử thách mã phải là S256, phương pháp plain không được hỗ trợ. + invalid_code_challenge_method: + one: Code_challenge_method phải là %{challenge_methods}. + other: Code_challenge_method phải là một trong %{challenge_methods}. + zero: Máy chủ xác thực không hỗ trợ PKCE vì không chấp nhận giá trị code_challenge_method. invalid_grant: Yêu cầu không hợp lệ, hết hạn, bị gỡ hoặc không khớp với tài khoản đã cấp phép. Hoặc xung đột với ứng dụng khác. invalid_redirect_uri: URL chuyển hướng không hợp lệ. invalid_request: diff --git a/config/locales/doorkeeper.zh-CN.yml b/config/locales/doorkeeper.zh-CN.yml index 2c096720c2d165..fcba0f9c561628 100644 --- a/config/locales/doorkeeper.zh-CN.yml +++ b/config/locales/doorkeeper.zh-CN.yml @@ -83,7 +83,10 @@ zh-CN: access_denied: 资源所有者或验证服务器拒绝了此请求 credential_flow_not_configured: 由于 Doorkeeper.configure.resource_owner_from_credentials 尚未配置,应用验证授权流程失败。 invalid_client: 由于应用信息未知、未提交认证信息或使用了不支持的认证方式,认证失败 - invalid_code_challenge_method: 代码验证方法必须是 S256,不支持明文。 + invalid_code_challenge_method: + one: code_challenge_method 必须是 %{challenge_methods}。 + other: code_challenge_method 必须是 %{challenge_methods} 的其中之一。 + zero: 授权服务器不支持 PKCE,因为没有被接受的 code_challenge_method 值。 invalid_grant: 授权方式无效、过期或已被撤销、与授权请求中的回调地址不一致,或使用了其他应用的回调地址 invalid_redirect_uri: 登录回调地址无效。 invalid_request: diff --git a/config/locales/doorkeeper.zh-TW.yml b/config/locales/doorkeeper.zh-TW.yml index 5af93e1e07b72e..6e0950fdda5e7d 100644 --- a/config/locales/doorkeeper.zh-TW.yml +++ b/config/locales/doorkeeper.zh-TW.yml @@ -83,7 +83,10 @@ zh-TW: access_denied: 資源持有者或授權伺服器拒絕請求。 credential_flow_not_configured: 因為 Doorkeeper.configure.resource_owner_from_credentials 未設定,所以資源持有者密碼認證程序失敗。 invalid_client: 用戶端驗證失敗,可能是因為未知的用戶端程式、未包含用戶端驗證、或使用了不支援的認證方法。 - invalid_code_challenge_method: code challenge 方式必須為 S256 (SHA256),不支援 plain 方式。 + invalid_code_challenge_method: + one: code_challenge_method 必須是 %{challenge_methods}。 + other: code_challenge_method 必須是 %{challenge_methods} 其中之一。 + zero: 授權伺服器不支援 PKCE ,因為沒有被接受的 code_challenge_method 值。 invalid_grant: 授權申請不正確、逾期、已被註銷、與授權請求內的重新導向 URI 不符、或屬於別的用戶端程式。 invalid_redirect_uri: 包含的重新導向 URI 是不正確的。 invalid_request: diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 17473de7670c10..5daacf6fdfe83e 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -802,6 +802,7 @@ nn: view_devops_description: Gir brukere tilgang til Sidekiq og pgHero-dashbord view_feeds: Sjå direktestraumar og emnestraumar view_feeds_description: La brukarar sjå direkte- og emnestraumane uansett innstillingar på tenaren + requires_2fa: Krev tofaktorinnlogging title: Roller rules: add_new: Legg til regel @@ -2020,6 +2021,8 @@ nn: past_preamble_html: Me har endra brukarvilkåra våre sidan du var her sist. Det er fint viss du ser på dei oppdaterte vilkåra. review_link: Les gjennom brukarvilkåra title: Brukarvilkåra på %{domain} er endra + themes: + default: Mastodon time: formats: default: "%d.%b %Y, %H:%M" @@ -2044,6 +2047,8 @@ nn: recovery_codes: Reservekoder recovery_codes_regenerated: Generering av reservekoder fullført recovery_instructions_html: Hvis du skulle miste tilgang til telefonen din, kan du bruke en av gjenopprettingskodene nedenfor til å gjenopprette tilgang til din konto. Oppbevar gjenopprettingskodene sikkert, for eksempel ved å skrive dem ut og gjemme dem på et lurt sted bare du vet om. + resume_app_authorization: Hald fram å godkjenna applikasojnen + role_requirement: "%{domain} krev at du set opp tofaktorinnlogging før du kan bruka Mastodon." webauthn: Sikkerhetsnøkler user_mailer: announcement_published: diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 1be0bc621b6f9a..15904105561d4e 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -213,6 +213,7 @@ ca: email: Adreça de correu electrònic expires_in: Expira després fields: Metadades del perfil + filter_action: Acció de filtratge header: Capçalera honeypot: "%{label} (no omplir)" inbox_url: Enllaç de la safata d'entrada del relay @@ -228,6 +229,7 @@ ca: setting_aggregate_reblogs: Agrupar impulsos en les línies de temps setting_always_send_emails: Envia'm sempre notificacions per correu electrònic setting_auto_play_gif: Reprodueix automàticament els GIF animats + setting_color_scheme: Esquema de colors setting_contrast: Contrast setting_default_language: Llengua dels tuts setting_default_privacy: Visibilitat de la publicació diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index bb753f33fef2eb..4a236d374a4201 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -224,6 +224,7 @@ da: email: E-mailadresse expires_in: Udløb efter fields: Profilmetadata + filter_action: Filterhandling header: Bannerbillede honeypot: "%{label} (udfyld ikke)" inbox_url: URL til videreformidlingsindbakken diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index c289e799b3fb4a..f30f0b6cc1ec52 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -164,6 +164,7 @@ nn: name: Offentleg namn på rolla, dersom rolla skal visast som eit emblem permissions_as_keys: Brukarar med denne rolla vil ha tilgang til... position: Høgare rolle avgjer konfliktløysing i visse situasjonar. Visse handlingar kan berre utførast på roller med lågare prioritet + require_2fa: Brukarar med denne rolla må setja opp tofaktorinnlogging for å bruka Mastodon username_block: allow_with_approval: I staden for å hindra registreringar i det heile, må du godkjenna registreringar som passar comparison: Ver merksam på Scunthorpe-problemet når du blokkerer delvise treff @@ -223,6 +224,7 @@ nn: email: E-post-adresse expires_in: Vert ugyldig etter fields: Profilmetadata + filter_action: Filterhandling header: Overskrift honeypot: "%{label} (ikke fyll ut)" inbox_url: URL-addressen til overgangsinnboksen @@ -239,6 +241,7 @@ nn: setting_always_send_emails: Alltid send epostvarsel setting_auto_play_gif: Spel av animerte GIF-ar automatisk setting_boost_modal: Kontroller korleis du framhevar innlegg + setting_color_scheme: Fargepalett setting_contrast: Kontrast setting_default_language: Språk på innlegg setting_default_privacy: Innleggsvising @@ -386,6 +389,7 @@ nn: name: Namn permissions_as_keys: Løyve position: Prioritet + require_2fa: Krev tofaktorinnlogging username_block: allow_with_approval: Tillat registreringar med godkjenning comparison: Samanlikningsmetode From 079f8615fe6208c01ee2dfc072b8cd528d9f0429 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 18 Feb 2026 13:05:02 +0100 Subject: [PATCH 15/60] Profile redesign: Design fixes (#37892) --- app/javascript/images/icons/icon_verified.svg | 6 +- .../mastodon/components/dropdown_menu.tsx | 6 +- .../components/account_header.tsx | 5 +- .../account_timeline/components/fields.tsx | 144 ++++++++++-------- .../account_timeline/components/menu.tsx | 3 + .../components/redesign.module.scss | 75 +++++---- .../account_timeline/v2/styles.module.scss | 1 + .../features/ui/components/actions_modal.tsx | 5 +- app/javascript/mastodon/models/account.ts | 2 +- 9 files changed, 150 insertions(+), 97 deletions(-) diff --git a/app/javascript/images/icons/icon_verified.svg b/app/javascript/images/icons/icon_verified.svg index 65873b9dc43495..62bdcb57102428 100644 --- a/app/javascript/images/icons/icon_verified.svg +++ b/app/javascript/images/icons/icon_verified.svg @@ -2,9 +2,9 @@ - - - + + + diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx index 6ed138c301595f..b5ee0db34ed8b1 100644 --- a/app/javascript/mastodon/components/dropdown_menu.tsx +++ b/app/javascript/mastodon/components/dropdown_menu.tsx @@ -311,6 +311,7 @@ interface DropdownProps { status?: ImmutableMap; needsStatusRefresh?: boolean; forceDropdown?: boolean; + className?: string; renderItem?: RenderItemFn; renderHeader?: RenderHeaderFn; onOpen?: // Must use a union type for the full function as a union with void is not allowed. @@ -335,6 +336,7 @@ export const Dropdown = ({ status, needsStatusRefresh, forceDropdown = false, + className, renderItem, renderHeader, onOpen, @@ -434,6 +436,7 @@ export const Dropdown = ({ modalProps: { actions: items, onClick: handleItemClick, + className, }, }), ); @@ -462,6 +465,7 @@ export const Dropdown = ({ handleClose, statusId, needsStatusRefresh, + className, ], ); @@ -515,7 +519,7 @@ export const Dropdown = ({ popperConfig={popperConfig} > {({ props, arrowProps, placement }) => ( -
+
diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index 5d22acc09a2f45..539546759d547b 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -15,8 +15,7 @@ import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; import { Icon } from '@/mastodon/components/icon'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useAccount } from '@/mastodon/hooks/useAccount'; -import type { Account } from '@/mastodon/models/account'; -import { isValidUrl } from '@/mastodon/utils/checks'; +import type { Account, AccountFieldShape } from '@/mastodon/models/account'; import type { OnElementHandler } from '@/mastodon/utils/html'; import { cleanExtraEmojis } from '../../emoji/normalize'; @@ -76,8 +75,8 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { [account.emojis], ); const textHasCustomEmoji = useCallback( - (text: string) => { - if (!emojis) { + (text?: string | null) => { + if (!emojis || !text) { return false; } for (const emoji of Object.keys(emojis)) { @@ -92,62 +91,96 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { const htmlHandlers = useElementHandledLink({ hashtagAccountId: account.id, }); - const intl = useIntl(); + + if (account.fields.isEmpty()) { + return null; + } return (
- {account.fields.map( - ( - { name, name_emojified, value_emojified, value_plain, verified_at }, - key, - ) => ( -
- - - {verified_at && ( - - )} -
- ), - )} + {account.fields.map((field, key) => ( + + ))}
); }; +const FieldRow: FC< + { + textHasCustomEmoji: (text?: string | null) => boolean; + htmlHandlers: ReturnType; + } & AccountFieldShape +> = ({ + textHasCustomEmoji, + htmlHandlers, + name, + name_emojified, + value_emojified, + value_plain, + verified_at, +}) => { + const intl = useIntl(); + const [showAll, setShowAll] = useState(false); + const handleClick = useCallback(() => { + setShowAll((prev) => !prev); + }, []); + + return ( + /* eslint-disable -- This method of showing field contents is not very accessible, but it's what we've got for now */ +
+ +
+ + + {verified_at && ( + + )} +
+
+ ); +}; + const FieldHTML: FC< { - as: 'dd' | 'dt'; + as?: 'span' | 'dt'; text: string; textEmojified: string; textHasCustomEmoji: boolean; @@ -164,11 +197,6 @@ const FieldHTML: FC< onElement, ...props }) => { - const [showAll, setShowAll] = useState(false); - const handleClick = useCallback(() => { - setShowAll((prev) => !prev); - }, []); - const handleElement: OnElementHandler = useCallback( (element, props, children, extra) => { if (element instanceof HTMLAnchorElement) { @@ -186,17 +214,13 @@ const FieldHTML: FC< }, [onElement, textHasCustomEmoji], ); + return ( diff --git a/app/javascript/mastodon/features/account_timeline/components/menu.tsx b/app/javascript/mastodon/features/account_timeline/components/menu.tsx index f03878eb8c8eec..1fcd2f691e81c4 100644 --- a/app/javascript/mastodon/features/account_timeline/components/menu.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/menu.tsx @@ -42,6 +42,8 @@ import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import { isRedesignEnabled } from '../common'; +import classes from './redesign.module.scss'; + export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { const intl = useIntl(); const { signedIn, permissions } = useIdentity(); @@ -86,6 +88,7 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => { items={menuItems} icon='ellipsis-v' iconComponent={MoreHorizIcon} + className={classes.buttonMenu} /> ); }; diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index a1ee8275075c03..5d7a5fa65008c9 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -24,11 +24,10 @@ .name { flex-grow: 1; - font-size: 22px; - white-space: initial; - line-height: normal; > h1 { + font-size: 22px; + line-height: normal; white-space: initial; } } @@ -149,11 +148,33 @@ $button-fallback-breakpoint: #{$button-breakpoint} + 55px; border-top: 1px solid var(--color-border-primary); } +.buttonMenu { + // Override the modal for mobile. + &:global(.actions-modal) { + max-height: none; + } + + li :global(.icon) { + width: 20px; + height: 20px; + } +} + +.bio { + font-size: 15px; +} + .badge { background-color: var(--color-bg-secondary); border: none; color: var(--color-text-secondary); font-weight: 500; + padding: 4px; + font-size: 13px; + + :global(.account__header__badges) > & { + line-height: 1; + } > span { font-weight: unset; @@ -194,12 +215,13 @@ svg.badgeIcon { .fieldList { display: grid; - grid-template-columns: 160px 1fr min-content; + grid-template-columns: 160px 1fr; column-gap: 12px; - margin: 4px 0 16px; + margin: 16px 0; + border-top: 0.5px solid var(--color-border-primary); @container (width < 420px) { - grid-template-columns: 100px 1fr min-content; + grid-template-columns: 100px 1fr; } } @@ -208,11 +230,10 @@ svg.badgeIcon { grid-column: 1 / -1; align-items: start; grid-template-columns: subgrid; - padding: 0 4px; + padding: 8px; + border-bottom: 0.5px solid var(--color-border-primary); > :is(dt, dd) { - margin: 8px 0; - &:not(.fieldShowAll) { display: -webkit-box; -webkit-box-orient: vertical; @@ -227,43 +248,34 @@ svg.badgeIcon { color: var(--color-text-secondary); } - &:not(.fieldVerified) > dd { - grid-column: span 2; + > dd { + display: flex; + align-items: center; + gap: 4px; } a { - font-weight: 500; - color: var(--color-text-brand); + color: inherit; text-decoration: none; - transition: 0.2s ease-in-out; &:hover, &:focus { - color: var(--color-text-brand-soft); + text-decoration: underline; } } } .fieldVerified { - background-color: var(--color-bg-brand-softer); -} - -.fieldLink:is(dd, dt) { - margin: 0; -} - -.fieldLink > a { - display: block; - padding: 8px 0; + background-color: var(--color-bg-success-softer); } .fieldVerifiedIcon { width: 16px; height: 16px; - margin-top: 8px; } .fieldNumbersWrapper { + font-size: 13px; padding: 0; a { @@ -323,10 +335,15 @@ svg.badgeIcon { border-bottom: 1px solid var(--color-border-primary); display: flex; gap: 12px; - padding: 0 12px; + padding: 0 24px; - @container (width >= 500px) { - padding: 0 24px; + @container (width < 500px) { + padding: 0 12px; + + a { + flex: 1 1 0px; + text-align: center; + } } a { diff --git a/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss b/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss index e16eca5254d1a0..a57a5e738a0f93 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss +++ b/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss @@ -39,6 +39,7 @@ cursor: pointer; display: flex; align-items: center; + font-size: 15px; } } diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.tsx b/app/javascript/mastodon/features/ui/components/actions_modal.tsx index 13deedcd1a88a4..8d85f36f0cde94 100644 --- a/app/javascript/mastodon/features/ui/components/actions_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/actions_modal.tsx @@ -11,8 +11,9 @@ import { export const ActionsModal: React.FC<{ actions: MenuItem[]; onClick: React.MouseEventHandler; -}> = ({ actions, onClick }) => ( -
+ className?: string; +}> = ({ actions, onClick, className }) => ( +
    {actions.map((option, i: number) => { if (option === null) { diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index bd099b23342fd9..b834a022ad0457 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -14,7 +14,7 @@ import { CustomEmojiFactory } from './custom_emoji'; import type { CustomEmoji } from './custom_emoji'; // AccountField -interface AccountFieldShape extends Required { +export interface AccountFieldShape extends Required { name_emojified: string; value_emojified: string; value_plain: string | null; From bd64ca2583615579670b1d4bea8c9f49bd726f74 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 18 Feb 2026 14:18:44 +0100 Subject: [PATCH 16/60] Add new profile settings (#37890) --- app/models/account.rb | 3 +++ app/serializers/rest/account_serializer.rb | 3 ++- .../20260217154542_add_profile_settings_to_accounts.rb | 9 +++++++++ db/schema.rb | 5 ++++- 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20260217154542_add_profile_settings_to_accounts.rb diff --git a/app/models/account.rb b/app/models/account.rb index 7623e2398cc4f0..a1bafc8fd6affd 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -46,6 +46,9 @@ # reviewed_at :datetime # sensitized_at :datetime # shared_inbox_url :string default(""), not null +# show_featured :boolean default(TRUE), not null +# show_media :boolean default(TRUE), not null +# show_media_replies :boolean default(TRUE), not null # silenced_at :datetime # suspended_at :datetime # suspension_origin :integer diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 3fa541f445d48c..e97baa8612d6fb 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -8,7 +8,8 @@ class REST::AccountSerializer < ActiveModel::Serializer attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :indexable, :group, :created_at, :note, :url, :uri, :avatar, :avatar_static, :avatar_description, :header, :header_static, :header_description, - :followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections + :followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections, + :show_media, :show_media_replies, :show_featured has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? diff --git a/db/migrate/20260217154542_add_profile_settings_to_accounts.rb b/db/migrate/20260217154542_add_profile_settings_to_accounts.rb new file mode 100644 index 00000000000000..dc3057567634df --- /dev/null +++ b/db/migrate/20260217154542_add_profile_settings_to_accounts.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddProfileSettingsToAccounts < ActiveRecord::Migration[8.0] + def change + add_column :accounts, :show_media, :boolean, null: false, default: true + add_column :accounts, :show_media_replies, :boolean, null: false, default: true + add_column :accounts, :show_featured, :boolean, null: false, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 470b3e1efe6fb7..8a53b24d0ad061 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_02_12_131934) do +ActiveRecord::Schema[8.0].define(version: 2026_02_17_154542) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -203,6 +203,9 @@ t.integer "feature_approval_policy", default: 0, null: false t.string "avatar_description", default: "", null: false t.string "header_description", default: "", null: false + t.boolean "show_media", default: true, null: false + t.boolean "show_media_replies", default: true, null: false + t.boolean "show_featured", default: true, null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index ["domain", "id"], name: "index_accounts_on_domain_and_id" From 488e0b2617ced09e7609e6eec2f0ca46a54a88ca Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 18 Feb 2026 14:19:39 +0100 Subject: [PATCH 17/60] Add collection detail page (#37897) --- .../mastodon/api_types/collections.ts | 6 +- .../__snapshots__/avatar-test.jsx.snap | 8 +- .../mastodon/components/account/index.tsx | 2 +- app/javascript/mastodon/components/avatar.tsx | 8 +- .../collection_list_item.module.scss} | 19 +- .../detail/collection_list_item.tsx | 67 +++++++ .../collections/detail/collection_menu.tsx | 91 +++++++++ .../features/collections/detail/index.tsx | 178 ++++++++++++++++++ .../collections/detail/styles.module.scss | 74 ++++++++ .../features/collections/editor/details.tsx | 2 +- .../features/collections/editor/settings.tsx | 2 +- .../mastodon/features/collections/index.tsx | 118 +----------- app/javascript/mastodon/features/ui/index.jsx | 11 +- .../features/ui/util/async-components.js | 10 +- app/javascript/mastodon/locales/en.json | 5 + .../400-24px/more_vert-fill.svg | 1 + .../material-icons/400-24px/more_vert.svg | 1 + 17 files changed, 459 insertions(+), 144 deletions(-) rename app/javascript/mastodon/features/collections/{styles.module.scss => detail/collection_list_item.module.scss} (80%) create mode 100644 app/javascript/mastodon/features/collections/detail/collection_list_item.tsx create mode 100644 app/javascript/mastodon/features/collections/detail/collection_menu.tsx create mode 100644 app/javascript/mastodon/features/collections/detail/index.tsx create mode 100644 app/javascript/mastodon/features/collections/detail/styles.module.scss create mode 100644 app/javascript/material-icons/400-24px/more_vert-fill.svg create mode 100644 app/javascript/material-icons/400-24px/more_vert.svg diff --git a/app/javascript/mastodon/api_types/collections.ts b/app/javascript/mastodon/api_types/collections.ts index ec6eaabaa07930..23f835f5fc91c6 100644 --- a/app/javascript/mastodon/api_types/collections.ts +++ b/app/javascript/mastodon/api_types/collections.ts @@ -11,14 +11,14 @@ export interface ApiCollectionJSON { account_id: string; id: string; - uri: string; + uri: string | null; local: boolean; item_count: number; name: string; description: string; - tag?: ApiTagJSON; - language: string; + tag: ApiTagJSON | null; + language: string | null; sensitive: boolean; discoverable: boolean; diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap index c25dc59c868018..1b6647f91f1e0b 100644 --- a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar-test.jsx.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[` > Autoplay > renders a animated avatar 1`] = ` -
    > Autoplay > renders a animated avatar 1`] = ` onLoad={[Function]} src="/animated/alice.gif" /> -
    + `; exports[` > Still > renders a still avatar 1`] = ` -
    > Still > renders a still avatar 1`] = ` onLoad={[Function]} src="/static/alice.jpg" /> -
    + `; diff --git a/app/javascript/mastodon/components/account/index.tsx b/app/javascript/mastodon/components/account/index.tsx index dafad0f707902a..7cceb2ce25ff24 100644 --- a/app/javascript/mastodon/components/account/index.tsx +++ b/app/javascript/mastodon/components/account/index.tsx @@ -297,7 +297,7 @@ export const Account: React.FC = ({ >
    = ({ }, [setError]); const avatar = ( -
    = ({ )} {counter && ( -
    {counter} -
    + )} -
    + ); if (withLink) { diff --git a/app/javascript/mastodon/features/collections/styles.module.scss b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss similarity index 80% rename from app/javascript/mastodon/features/collections/styles.module.scss rename to app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss index ebcab70d593349..74eac9f3e1e9c7 100644 --- a/app/javascript/mastodon/features/collections/styles.module.scss +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss @@ -1,4 +1,4 @@ -.collectionItemWrapper { +.wrapper { display: flex; align-items: center; gap: 16px; @@ -7,13 +7,13 @@ border-bottom: 1px solid var(--color-border-primary); } -.collectionItemContent { +.content { position: relative; flex-grow: 1; padding: 15px 5px; } -.collectionItemLink { +.link { display: block; margin-bottom: 2px; font-size: 15px; @@ -33,16 +33,19 @@ } } -.collectionItemInfo { +.info { + font-size: 13px; + color: var(--color-text-secondary); +} + +.metaList { --gap: 0.75ch; display: flex; gap: var(--gap); - font-size: 13px; - color: var(--color-text-secondary); - & > li:not(:first-child)::before { + & > li:not(:last-child)::after { content: '·'; - margin-inline-end: var(--gap); + margin-inline-start: var(--gap); } } diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx new file mode 100644 index 00000000000000..51a7e672549882 --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx @@ -0,0 +1,67 @@ +import { useId } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; + +import classes from './collection_list_item.module.scss'; +import { CollectionMenu } from './collection_menu'; + +export const CollectionMetaData: React.FC<{ + collection: ApiCollectionJSON; + className?: string; +}> = ({ collection, className }) => { + return ( +
      + + + ), + }} + tagName='li' + /> +
    + ); +}; + +export const CollectionListItem: React.FC<{ + collection: ApiCollectionJSON; +}> = ({ collection }) => { + const { id, name } = collection; + const linkId = useId(); + + return ( +
    +
    +

    + + {name} + +

    + +
    + + +
    + ); +}; diff --git a/app/javascript/mastodon/features/collections/detail/collection_menu.tsx b/app/javascript/mastodon/features/collections/detail/collection_menu.tsx new file mode 100644 index 00000000000000..cf65be54963fc3 --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/collection_menu.tsx @@ -0,0 +1,91 @@ +import { useCallback, useMemo } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react'; +import { openModal } from 'mastodon/actions/modal'; +import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { IconButton } from 'mastodon/components/icon_button'; +import { useAppDispatch } from 'mastodon/store'; + +import { messages as editorMessages } from '../editor'; + +const messages = defineMessages({ + view: { + id: 'collections.view_collection', + defaultMessage: 'View collection', + }, + delete: { + id: 'collections.delete_collection', + defaultMessage: 'Delete collection', + }, + more: { id: 'status.more', defaultMessage: 'More' }, +}); + +export const CollectionMenu: React.FC<{ + collection: ApiCollectionJSON; + context: 'list' | 'collection'; + className?: string; +}> = ({ collection, context, className }) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const { id, name } = collection; + + const handleDeleteClick = useCallback(() => { + dispatch( + openModal({ + modalType: 'CONFIRM_DELETE_COLLECTION', + modalProps: { + name, + id, + }, + }), + ); + }, [dispatch, id, name]); + + const menu = useMemo(() => { + const commonItems = [ + { + text: intl.formatMessage(editorMessages.manageAccounts), + to: `/collections/${id}/edit`, + }, + { + text: intl.formatMessage(editorMessages.editDetails), + to: `/collections/${id}/edit/details`, + }, + { + text: intl.formatMessage(editorMessages.editSettings), + to: `/collections/${id}/edit/settings`, + }, + null, + { + text: intl.formatMessage(messages.delete), + action: handleDeleteClick, + dangerous: true, + }, + ]; + + if (context === 'list') { + return [ + { text: intl.formatMessage(messages.view), to: `/collections/${id}` }, + null, + ...commonItems, + ]; + } else { + return commonItems; + } + }, [intl, id, handleDeleteClick, context]); + + return ( + + + + ); +}; diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx new file mode 100644 index 00000000000000..7b04800aab9378 --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -0,0 +1,178 @@ +import { useCallback, useEffect } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; + +import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; +import ShareIcon from '@/material-icons/400-24px/share.svg?react'; +import { showAlert } from 'mastodon/actions/alerts'; +import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; +import { Account } from 'mastodon/components/account'; +import { Avatar } from 'mastodon/components/avatar'; +import { Column } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import { LinkedDisplayName } from 'mastodon/components/display_name'; +import { IconButton } from 'mastodon/components/icon_button'; +import ScrollableList from 'mastodon/components/scrollable_list'; +import { Tag } from 'mastodon/components/tags/tag'; +import { useAccount } from 'mastodon/hooks/useAccount'; +import { me } from 'mastodon/initial_state'; +import { fetchCollection } from 'mastodon/reducers/slices/collections'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +import { CollectionMetaData } from './collection_list_item'; +import { CollectionMenu } from './collection_menu'; +import classes from './styles.module.scss'; + +const messages = defineMessages({ + empty: { + id: 'collections.accounts.empty_title', + defaultMessage: 'This collection is empty', + }, + loading: { + id: 'collections.detail.loading', + defaultMessage: 'Loading collection…', + }, + share: { + id: 'collections.detail.share', + defaultMessage: 'Share this collection', + }, + accounts: { + id: 'collections.detail.accounts_heading', + defaultMessage: 'Accounts', + }, +}); + +const AuthorNote: React.FC<{ id: string }> = ({ id }) => { + const account = useAccount(id); + const author = ( + + + + + ); + + if (id === me) { + return ( +

    + +

    + ); + } + return ( +

    + +

    + ); +}; + +const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ + collection, +}) => { + const intl = useIntl(); + const { name, description, tag } = collection; + const dispatch = useAppDispatch(); + + const handleShare = useCallback(() => { + dispatch(showAlert({ message: 'Collection sharing not yet implemented' })); + }, [dispatch]); + + return ( +
    +
    +
    + {tag && ( + // TODO: Make non-interactive tag component + + )} +

    {name}

    +
    +
    + + +
    +
    + {description &&

    {description}

    } + + +

    {intl.formatMessage(messages.accounts)}

    +
    + ); +}; + +export const CollectionDetailPage: React.FC<{ + multiColumn?: boolean; +}> = ({ multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { id } = useParams<{ id?: string }>(); + const collection = useAppSelector((state) => + id ? state.collections.collections[id] : undefined, + ); + + const isLoading = !!id && !collection; + + useEffect(() => { + if (id) { + void dispatch(fetchCollection({ collectionId: id })); + } + }, [dispatch, id]); + + const pageTitle = collection?.name ?? intl.formatMessage(messages.loading); + + return ( + + + + : null + } + > + {collection?.items.map(({ account_id }) => + account_id ? ( + + ) : null, + )} + + + + {pageTitle} + + + + ); +}; diff --git a/app/javascript/mastodon/features/collections/detail/styles.module.scss b/app/javascript/mastodon/features/collections/detail/styles.module.scss new file mode 100644 index 00000000000000..cb94f2894c8d56 --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/styles.module.scss @@ -0,0 +1,74 @@ +.header { + padding: 16px; + border-bottom: 1px solid var(--color-border-primary); +} + +.titleWithMenu { + display: flex; + align-items: start; + gap: 10px; +} + +.titleWrapper { + flex-grow: 1; + min-width: 0; +} + +.tag { + margin-bottom: 4px; + margin-inline-start: -8px; +} + +.name { + font-size: 28px; + line-height: 1.2; + overflow-wrap: anywhere; +} + +.description { + font-size: 15px; + margin-top: 8px; +} + +.headerButtonWrapper { + display: flex; + gap: 8px; +} + +.iconButton { + box-sizing: content-box; + padding: 5px; + border-radius: 4px; + border: 1px solid var(--color-border-primary); +} + +.authorNote { + margin-top: 8px; + font-size: 13px; + color: var(--color-text-secondary); +} + +.metaData { + margin-top: 16px; + font-size: 15px; +} + +.displayNameWithAvatar { + display: inline-flex; + gap: 4px; + align-items: baseline; + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:focus { + text-decoration: none; + } + } + + > :global(.account__avatar) { + align-self: center; + } +} diff --git a/app/javascript/mastodon/features/collections/editor/details.tsx b/app/javascript/mastodon/features/collections/editor/details.tsx index f931a42c6c406d..9d5a94b8aaebb8 100644 --- a/app/javascript/mastodon/features/collections/editor/details.tsx +++ b/app/javascript/mastodon/features/collections/editor/details.tsx @@ -68,7 +68,7 @@ export const CollectionDetails: React.FC<{ }; void dispatch(updateCollection({ payload })).then(() => { - history.push(`/collections`); + history.push(`/collections/${id}`); }); } else { const payload: Partial = { diff --git a/app/javascript/mastodon/features/collections/editor/settings.tsx b/app/javascript/mastodon/features/collections/editor/settings.tsx index 10fb295f835e84..184b51187c15d0 100644 --- a/app/javascript/mastodon/features/collections/editor/settings.tsx +++ b/app/javascript/mastodon/features/collections/editor/settings.tsx @@ -68,7 +68,7 @@ export const CollectionSettings: React.FC<{ }; void dispatch(updateCollection({ payload })).then(() => { - history.push(`/collections`); + history.push(`/collections/${id}`); }); } else { const payload: ApiCreateCollectionPayload = { diff --git a/app/javascript/mastodon/features/collections/index.tsx b/app/javascript/mastodon/features/collections/index.tsx index dd26174f381ba6..607d7fe4f3f04e 100644 --- a/app/javascript/mastodon/features/collections/index.tsx +++ b/app/javascript/mastodon/features/collections/index.tsx @@ -1,22 +1,16 @@ -import { useEffect, useMemo, useCallback, useId } from 'react'; +import { useEffect } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { Link } from 'react-router-dom'; import AddIcon from '@/material-icons/400-24px/add.svg?react'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; -import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react'; -import { openModal } from 'mastodon/actions/modal'; -import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; -import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; -import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import ScrollableList from 'mastodon/components/scrollable_list'; import { fetchAccountCollections, @@ -24,119 +18,13 @@ import { } from 'mastodon/reducers/slices/collections'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; +import { CollectionListItem } from './detail/collection_list_item'; import { messages as editorMessages } from './editor'; -import classes from './styles.module.scss'; const messages = defineMessages({ heading: { id: 'column.collections', defaultMessage: 'My collections' }, - view: { - id: 'collections.view_collection', - defaultMessage: 'View collection', - }, - delete: { - id: 'collections.delete_collection', - defaultMessage: 'Delete collection', - }, - more: { id: 'status.more', defaultMessage: 'More' }, }); -const CollectionItem: React.FC<{ - collection: ApiCollectionJSON; -}> = ({ collection }) => { - const dispatch = useAppDispatch(); - const intl = useIntl(); - - const { id, name } = collection; - - const handleDeleteClick = useCallback(() => { - dispatch( - openModal({ - modalType: 'CONFIRM_DELETE_COLLECTION', - modalProps: { - name, - id, - }, - }), - ); - }, [dispatch, id, name]); - - const menu = useMemo( - () => [ - { text: intl.formatMessage(messages.view), to: `/collections/${id}` }, - null, - { - text: intl.formatMessage(editorMessages.manageAccounts), - to: `/collections/${id}/edit`, - }, - { - text: intl.formatMessage(editorMessages.editDetails), - to: `/collections/${id}/edit/details`, - }, - { - text: intl.formatMessage(editorMessages.editSettings), - to: `/collections/${id}/edit/settings`, - }, - null, - { - text: intl.formatMessage(messages.delete), - action: handleDeleteClick, - dangerous: true, - }, - ], - [intl, id, handleDeleteClick], - ); - - const linkId = useId(); - - return ( -
    -
    -

    - - {name} - -

    -
      - - - ), - }} - tagName='li' - /> -
    -
    - - -
    - ); -}; - export const Collections: React.FC<{ multiColumn?: boolean; }> = ({ multiColumn }) => { @@ -202,7 +90,7 @@ export const Collections: React.FC<{ bindToDocument={!multiColumn} > {collections.map((item) => ( - + ))} diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 089e5764bc6c76..5a9cebe5f4b71c 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -65,6 +65,7 @@ import { ListEdit, ListMembers, Collections, + CollectionDetail, CollectionsEditor, Blocks, DomainBlocks, @@ -269,12 +270,12 @@ class SwitchingColumnsArea extends PureComponent { {areCollectionsEnabled() && - + [ + , + , + + ] } - {areCollectionsEnabled() && - - } - diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 6b0188f9b6af02..2beedaba264b76 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -44,13 +44,19 @@ export function Lists () { return import('../../lists'); } -export function Collections () { +export function Collections() { return import('../../collections').then( module => ({default: module.Collections}) ); } -export function CollectionsEditor () { +export function CollectionDetail() { + return import('../../collections/detail/index').then( + module => ({default: module.CollectionDetailPage}) + ); +} + +export function CollectionsEditor() { return import('../../collections/editor').then( module => ({default: module.CollectionEditorPage}) ); diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 91e0fb79b252f0..9352ae5166d202 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -263,6 +263,11 @@ "collections.create_collection": "Create collection", "collections.delete_collection": "Delete collection", "collections.description_length_hint": "100 characters limit", + "collections.detail.accounts_heading": "Accounts", + "collections.detail.curated_by_author": "Curated by {author}", + "collections.detail.curated_by_you": "Curated by you", + "collections.detail.loading": "Loading collection…", + "collections.detail.share": "Share this collection", "collections.edit_details": "Edit basic details", "collections.edit_settings": "Edit settings", "collections.error_loading_collections": "There was an error when trying to load your collections.", diff --git a/app/javascript/material-icons/400-24px/more_vert-fill.svg b/app/javascript/material-icons/400-24px/more_vert-fill.svg new file mode 100644 index 00000000000000..e172f878a6cd80 --- /dev/null +++ b/app/javascript/material-icons/400-24px/more_vert-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/more_vert.svg b/app/javascript/material-icons/400-24px/more_vert.svg new file mode 100644 index 00000000000000..e172f878a6cd80 --- /dev/null +++ b/app/javascript/material-icons/400-24px/more_vert.svg @@ -0,0 +1 @@ + \ No newline at end of file From 9e40d3ef37554a5ebdaf3ead485c5e13c8ddc926 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Feb 2026 08:43:21 -0500 Subject: [PATCH 18/60] Use validation matchers for `ReactionValidator` spec (#37900) --- spec/validators/reaction_validator_spec.rb | 50 ++++++++++------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/spec/validators/reaction_validator_spec.rb b/spec/validators/reaction_validator_spec.rb index c4d4a517799bc0..d17c4b0505dfc9 100644 --- a/spec/validators/reaction_validator_spec.rb +++ b/spec/validators/reaction_validator_spec.rb @@ -3,41 +3,35 @@ require 'rails_helper' RSpec.describe ReactionValidator do - let(:announcement) { Fabricate(:announcement) } + subject { Fabricate.build :announcement_reaction } - describe '#validate' do - it 'adds error when not a valid unicode emoji' do - reaction = announcement.announcement_reactions.build(name: 'F') - subject.validate(reaction) - expect(reaction.errors).to_not be_empty - end + context 'when not valid unicode emoji' do + it { is_expected.to_not allow_value('F').for(:name).with_message(I18n.t('reactions.errors.unrecognized_emoji')) } + end - it 'does not add error when non-unicode emoji is a custom emoji' do - custom_emoji = Fabricate(:custom_emoji) - reaction = announcement.announcement_reactions.build(name: custom_emoji.shortcode, custom_emoji_id: custom_emoji.id) - subject.validate(reaction) - expect(reaction.errors).to be_empty - end + context 'when non-unicode emoji is a custom emoji' do + let!(:custom_emoji) { Fabricate :custom_emoji } - it 'adds error when reaction limit count has already been reached' do - stub_const 'ReactionValidator::LIMIT', 2 - %w(🐘 ❤️).each do |name| - announcement.announcement_reactions.create!(name: name, account: Fabricate(:account)) - end + it { is_expected.to allow_value(custom_emoji.shortcode).for(:name) } + end - reaction = announcement.announcement_reactions.build(name: '😘') - subject.validate(reaction) - expect(reaction.errors).to_not be_empty - end + describe 'limiting reactions' do + subject { Fabricate.build :announcement_reaction, announcement: } + + let(:announcement) { Fabricate :announcement } + + before { stub_const 'ReactionValidator::LIMIT', 2 } - it 'does not add error when new reaction is part of the existing ones' do - %w(🐘 ❤️ 🙉 😍 😋 😂 😞 👍).each do |name| - announcement.announcement_reactions.create!(name: name, account: Fabricate(:account)) + context 'when limit has been reached' do + before { %w(🐘 ❤️).each { |name| Fabricate :announcement_reaction, name:, announcement: } } + + context 'with emoji already used' do + it { is_expected.to allow_value('❤️').for(:name) } end - reaction = announcement.announcement_reactions.build(name: '😋') - subject.validate(reaction) - expect(reaction.errors).to be_empty + context 'with emoji not already used' do + it { is_expected.to_not allow_value('😘').for(:name).against(:base).with_message(I18n.t('reactions.errors.limit_reached')) } + end end end end From a411b7eccb435f8dd6a45ca64a7482da6a7faea0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Feb 2026 08:44:59 -0500 Subject: [PATCH 19/60] Use validation matchers for `PollOptionsValidator` spec (#37901) --- .../validators/poll_options_validator_spec.rb | 76 ++++++------------- 1 file changed, 25 insertions(+), 51 deletions(-) diff --git a/spec/validators/poll_options_validator_spec.rb b/spec/validators/poll_options_validator_spec.rb index cc03e9d67320b5..866405e1091cb4 100644 --- a/spec/validators/poll_options_validator_spec.rb +++ b/spec/validators/poll_options_validator_spec.rb @@ -3,69 +3,43 @@ require 'rails_helper' RSpec.describe PollOptionsValidator do - describe '#validate' do - before do - validator.validate(poll) - end + subject { Fabricate.build :poll } - let(:validator) { described_class.new } - let(:poll) { instance_double(Poll, options: options, expires_at: expires_at, errors: errors) } - let(:errors) { instance_double(ActiveModel::Errors, add: nil) } - let(:options) { %w(foo bar) } - let(:expires_at) { 1.day.from_now } + context 'when poll has unique valid options' do + it { is_expected.to allow_values(%w(One Two)).for(:options) } + end - it 'has no errors' do - expect(errors).to_not have_received(:add) - end + context 'when poll has too few options' do + it { is_expected.to_not allow_values([]).for(:options).with_message(I18n.t('polls.errors.too_few_options')) } + end - context 'when the poll has duplicate options' do - let(:options) { %w(foo foo) } + context 'when poll has too many options' do + before { stub_const 'PollOptionsValidator::MAX_OPTIONS', 2 } - it 'adds errors' do - expect(errors).to have_received(:add) - end - end + it { is_expected.to_not allow_values(%w(One Two Three)).for(:options).with_message(I18n.t('polls.errors.too_many_options', max: 2)) } + end - context 'when the poll has no options' do - let(:options) { [] } + context 'when poll has duplicate options' do + it { is_expected.to_not allow_values(%w(One One One)).for(:options).with_message(I18n.t('polls.errors.duplicate_options')) } + end - it 'adds errors' do - expect(errors).to have_received(:add) - end - end + describe 'poll option length limits' do + let(:limit) { 5 } - context 'when the poll has too many options' do - let(:options) { Array.new(described_class::MAX_OPTIONS + 1) { |i| "option #{i}" } } + before { stub_const 'PollOptionsValidator::MAX_OPTION_CHARS', limit } - it 'adds errors' do - expect(errors).to have_received(:add) - end + context 'when poll has acceptable length options' do + it { is_expected.to allow_values(%w(One Two)).for(:options) } end - describe 'character length of poll options' do - context 'when poll has acceptable length options' do - let(:options) { %w(test this) } - - it 'has no errors' do - expect(errors).to_not have_received(:add) - end - end + context 'when poll has multibyte and ZWJ emoji options' do + let(:options) { ['✨' * limit, '🏳️‍⚧️' * limit] } - context 'when poll has multibyte and ZWJ emoji options' do - let(:options) { ['✨' * described_class::MAX_OPTION_CHARS, '🏳️‍⚧️' * described_class::MAX_OPTION_CHARS] } - - it 'has no errors' do - expect(errors).to_not have_received(:add) - end - end - - context 'when poll has options that are too long' do - let(:options) { ['ok', 'a' * (described_class::MAX_OPTION_CHARS**2)] } + it { is_expected.to allow_values(options).for(:options) } + end - it 'has errors' do - expect(errors).to have_received(:add) - end - end + context 'when poll has options that are too long' do + it { is_expected.to_not allow_values(%w(Airplane Two Three)).for(:options).with_message(I18n.t('polls.errors.over_character_limit', max: limit)) } end end end From e8ecf1719c9a906c03e541b8a0bcb0d843b701cc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Feb 2026 10:09:38 -0500 Subject: [PATCH 20/60] Use validation matchers for `StatusPinValidator` spec (#37904) --- spec/validators/status_pin_validator_spec.rb | 66 +++++++------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/spec/validators/status_pin_validator_spec.rb b/spec/validators/status_pin_validator_spec.rb index e50a952db802d6..05084e059f0227 100644 --- a/spec/validators/status_pin_validator_spec.rb +++ b/spec/validators/status_pin_validator_spec.rb @@ -3,55 +3,37 @@ require 'rails_helper' RSpec.describe StatusPinValidator do - describe '#validate' do - before do - subject.validate(pin) - end + subject { Fabricate.build :status_pin } - let(:pin) { instance_double(StatusPin, account: account, errors: errors, status: status, account_id: pin_account_id) } - let(:status) { instance_double(Status, reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') } - let(:account) { instance_double(Account, status_pins: status_pins, local?: local) } - let(:status_pins) { instance_double(Array, count: count) } - let(:errors) { instance_double(ActiveModel::Errors, add: nil) } - let(:pin_account_id) { 1 } - let(:status_account_id) { 1 } - let(:visibility) { 'public' } - let(:local) { false } - let(:reblog) { false } - let(:count) { 0 } - - context 'when pin.status.reblog?' do - let(:reblog) { true } - - it 'calls errors.add' do - expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.reblog')) - end - end + context 'when status is a reblog' do + let(:status) { Fabricate.build :status, reblog: Fabricate(:status) } - context 'when pin.account_id != pin.status.account_id' do - let(:pin_account_id) { 1 } - let(:status_account_id) { 2 } + it { is_expected.to_not allow_value(status).for(:status).against(:base).with_message(I18n.t('statuses.pin_errors.reblog')) } + end - it 'calls errors.add' do - expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.ownership')) - end - end + context 'when pin account is not status account' do + before { subject.save } - context 'when pin.status.direct_visibility?' do - let(:visibility) { 'direct' } + let(:status) { Fabricate :status, account: Fabricate(:account) } - it 'calls errors.add' do - expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.direct')) - end - end + it { is_expected.to_not allow_value(status).for(:status).against(:base).with_message(I18n.t('statuses.pin_errors.ownership')) } + end + + context 'when status visibility is direct' do + let(:status) { Fabricate.build :status, visibility: :direct } + + it { is_expected.to_not allow_value(status).for(:status).against(:base).with_message(I18n.t('statuses.pin_errors.direct')) } + end + + describe 'status pin limits' do + before { stub_const 'StatusPinValidator::PIN_LIMIT', 2 } + + context 'when account has reached the limit' do + before { Fabricate.times 2, :status_pin, account: } - context 'when pin account is local and has too many pins' do - let(:count) { described_class::PIN_LIMIT + 1 } - let(:local) { true } + let(:account) { subject.account } - it 'calls errors.add' do - expect(errors).to have_received(:add).with(:base, I18n.t('statuses.pin_errors.limit')) - end + it { is_expected.to_not allow_value(account).for(:account).against(:base).with_message(I18n.t('statuses.pin_errors.limit')) } end end end From 66052e3ddd21ffea614e28c05ed05c930a3de985 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 18 Feb 2026 10:22:22 -0500 Subject: [PATCH 21/60] Use validation matchers for `StatusLengthValidator` spec (#37905) --- .../status_length_validator_spec.rb | 183 +++++++----------- 1 file changed, 74 insertions(+), 109 deletions(-) diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb index 050b7500bbad41..4efd688e4ad83a 100644 --- a/spec/validators/status_length_validator_spec.rb +++ b/spec/validators/status_length_validator_spec.rb @@ -3,123 +3,88 @@ require 'rails_helper' RSpec.describe StatusLengthValidator do - describe '#validate' do - before { stub_const("#{described_class}::MAX_CHARS", 500) } # Example values below are relative to this baseline - - it 'does not add errors onto remote statuses' do - status = instance_double(Status, local?: false) - allow(status).to receive(:errors) - - subject.validate(status) - - expect(status).to_not have_received(:errors) - end - - it 'does not add errors onto local reblogs' do - status = instance_double(Status, local?: false, reblog?: true) - allow(status).to receive(:errors) - - subject.validate(status) - - expect(status).to_not have_received(:errors) - end - - it 'adds an error when content warning is over character limit' do - status = status_double(spoiler_text: 'a' * 520) - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'adds an error when text is over character limit' do - status = status_double(text: 'a' * 520) - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'adds an error when text and content warning are over character limit total' do - status = status_double(spoiler_text: 'a' * 250, text: 'b' * 251) - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'reduces calculated length of auto-linkable space-separated URLs' do - text = [starting_string, example_link].join(' ') - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to_not have_received(:add) - end - - it 'does not reduce calculated length of non-autolinkable URLs' do - text = [starting_string, example_link].join - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'does not reduce calculated length of count overly long URLs' do - text = "http://example.com/valid?#{'#foo?' * 1000}" - status = status_double(text: text) - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'counts only the front part of remote usernames' do - text = ('a' * 475) + " @alice@#{'b' * 30}.com" - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to_not have_received(:add) - end - - it 'does count both parts of remote usernames for overly long domains' do - text = "@alice@#{'b' * 500}.com" - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to have_received(:add) - end - - it 'counts multi byte emoji as single character' do - text = '✨' * 500 - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to_not have_received(:add) - end - - it 'counts ZWJ sequence emoji as single character' do - text = '🏳️‍⚧️' * 500 - status = status_double(text: text) - - subject.validate(status) - expect(status.errors).to_not have_received(:add) - end + subject { Fabricate.build :status } + + before { stub_const 'StatusLengthValidator::MAX_CHARS', 100 } + + let(:over_limit_text) { 'a' * described_class::MAX_CHARS * 2 } + + context 'when status is remote' do + before { subject.update! account: Fabricate(:account, domain: 'host.example') } + + it { is_expected.to allow_value(over_limit_text).for(:text) } + it { is_expected.to allow_value(over_limit_text).for(:spoiler_text).against(:text) } end - private + context 'when status is a local reblog' do + before { subject.update! reblog: Fabricate(:status) } - def starting_string - 'a' * 476 + it { is_expected.to allow_value(over_limit_text).for(:text) } + it { is_expected.to allow_value(over_limit_text).for(:spoiler_text).against(:text) } end - def example_link - "http://#{'b' * 30}.com/example" + context 'when text is over character limit' do + it { is_expected.to_not allow_value(over_limit_text).for(:text).with_message(too_long_message) } + end + + context 'when content warning text is over character limit' do + it { is_expected.to_not allow_value(over_limit_text).for(:spoiler_text).against(:text).with_message(too_long_message) } + end + + context 'when text and content warning combine to exceed limit' do + before { subject.text = 'a' * 50 } + + it { is_expected.to_not allow_value('a' * 55).for(:spoiler_text).against(:text).with_message(too_long_message) } + end + + context 'when text has space separated linkable URLs' do + let(:text) { [starting_string, example_link].join(' ') } + + it { is_expected.to allow_value(text).for(:text) } + end + + context 'when text has non-separated URLs' do + let(:text) { [starting_string, example_link].join } + + it { is_expected.to_not allow_value(text).for(:text).with_message(too_long_message) } end - def status_double(spoiler_text: '', text: '') - instance_double( - Status, - spoiler_text: spoiler_text, - text: text, - errors: activemodel_errors, - local?: true, - reblog?: false - ) + context 'with excessively long URLs' do + let(:text) { "http://example.com/valid?#{'#foo?' * 1000}" } + + it { is_expected.to_not allow_value(text).for(:text).with_message(too_long_message) } + end + + context 'when remote account usernames cause limit excess' do + let(:text) { ('a' * 75) + " @alice@#{'b' * 30}.com" } + + it { is_expected.to allow_value(text).for(:text) } + end + + context 'when remote usernames are attached to long domains' do + let(:text) { "@alice@#{'b' * Extractor::MAX_DOMAIN_LENGTH * 2}.com" } + + it { is_expected.to_not allow_value(text).for(:text).with_message(too_long_message) } + end + + context 'with special character strings' do + let(:multibyte_emoji) { '✨' * described_class::MAX_CHARS } + let(:zwj_sequence) { '🏳️‍⚧️' * described_class::MAX_CHARS } + + it { is_expected.to allow_values(multibyte_emoji, zwj_sequence).for(:text) } end - def activemodel_errors - instance_double(ActiveModel::Errors, add: nil) + private + + def too_long_message + I18n.t('statuses.over_character_limit', max: described_class::MAX_CHARS) + end + + def starting_string + 'a' * 76 + end + + def example_link + "http://#{'b' * 30}.com/example" end end From 3df8fb8fe9d33c7fbe51aa0a418b8b9ca46bb61b Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 18 Feb 2026 16:28:49 +0100 Subject: [PATCH 22/60] Fix visibility of video overlay controls in light mode (#37906) --- app/javascript/styles/mastodon/components.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cd3c97527917cc..cfa5d068d3250c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -4192,7 +4192,8 @@ a.account__display-name { cursor: pointer; & > div { - background: rgb(from var(--color-shadow-primary) r g b / 60%); + color: var(--color-text-on-media); + background: rgb(from var(--color-bg-media) r g b / 60%); border-radius: 8px; padding: 12px 9px; backdrop-filter: $backdrop-blur-filter; @@ -4205,7 +4206,7 @@ a.account__display-name { button, a { display: inline; - color: var(--color-text-primary); + color: var(--color-text-on-media); background: transparent; border: 0; padding: 0 8px; @@ -4216,7 +4217,7 @@ a.account__display-name { &:hover, &:active, &:focus { - color: var(--color-text-primary); + color: var(--color-text-on-media); } } From 093528ef17d6f2e7073b5b4eba13b97fb477b4c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:11:41 +0100 Subject: [PATCH 23/60] New Crowdin Translations (automated) (#37915) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 5 +++++ app/javascript/mastodon/locales/da.json | 7 ++++++- app/javascript/mastodon/locales/de.json | 5 +++++ app/javascript/mastodon/locales/el.json | 5 +++++ app/javascript/mastodon/locales/en-GB.json | 5 +++++ app/javascript/mastodon/locales/es-AR.json | 5 +++++ app/javascript/mastodon/locales/es-MX.json | 5 +++++ app/javascript/mastodon/locales/es.json | 7 +++++++ app/javascript/mastodon/locales/fi.json | 5 +++++ app/javascript/mastodon/locales/fr-CA.json | 7 +++++++ app/javascript/mastodon/locales/fr.json | 7 +++++++ app/javascript/mastodon/locales/ga.json | 7 +++++++ app/javascript/mastodon/locales/gl.json | 5 +++++ app/javascript/mastodon/locales/he.json | 5 +++++ app/javascript/mastodon/locales/is.json | 5 +++++ app/javascript/mastodon/locales/it.json | 5 +++++ app/javascript/mastodon/locales/ko.json | 4 ++++ app/javascript/mastodon/locales/nl.json | 16 ++++++++++++++++ app/javascript/mastodon/locales/sq.json | 5 +++++ app/javascript/mastodon/locales/sv.json | 1 + app/javascript/mastodon/locales/tr.json | 2 ++ app/javascript/mastodon/locales/vi.json | 5 +++++ app/javascript/mastodon/locales/zh-CN.json | 7 +++++++ app/javascript/mastodon/locales/zh-TW.json | 5 +++++ config/locales/da.yml | 6 +++--- config/locales/doorkeeper.es-AR.yml | 4 ++++ config/locales/doorkeeper.es.yml | 4 ++++ config/locales/doorkeeper.fr-CA.yml | 2 -- config/locales/doorkeeper.fr.yml | 2 -- config/locales/doorkeeper.nl.yml | 4 ++++ config/locales/doorkeeper.tr.yml | 4 ++++ config/locales/ko.yml | 7 +++++++ config/locales/simple_form.ko.yml | 2 ++ config/locales/simple_form.nl.yml | 1 + 34 files changed, 163 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 45acd59217f6ae..dbdf43ab608523 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -263,6 +263,11 @@ "collections.create_collection": "Стварыць калекцыю", "collections.delete_collection": "Выдаліць калекцыю", "collections.description_length_hint": "Максімум 100 сімвалаў", + "collections.detail.accounts_heading": "Уліковыя запісы", + "collections.detail.curated_by_author": "Курыруе {author}", + "collections.detail.curated_by_you": "Курыруеце Вы", + "collections.detail.loading": "Загружаецца калекцыя…", + "collections.detail.share": "Падзяліцца гэтай калекцыяй", "collections.edit_details": "Змяніць асноўныя звесткі", "collections.edit_settings": "Змяніць налады", "collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 56b5910a38399b..095b3f916a5503 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -20,7 +20,7 @@ "account.add_or_remove_from_list": "Tilføj eller fjern fra lister", "account.badges.admin": "Admin", "account.badges.blocked": "Blokeret", - "account.badges.bot": "Automatisert", + "account.badges.bot": "Automatiseret", "account.badges.domain_blocked": "Blokeret domæne", "account.badges.group": "Gruppe", "account.badges.muted": "Skjult", @@ -263,6 +263,11 @@ "collections.create_collection": "Opret samling", "collections.delete_collection": "Slet samling", "collections.description_length_hint": "Begrænset til 100 tegn", + "collections.detail.accounts_heading": "Konti", + "collections.detail.curated_by_author": "Kurateret af {author}", + "collections.detail.curated_by_you": "Kurateret af dig", + "collections.detail.loading": "Indlæser samling…", + "collections.detail.share": "Del denne samling", "collections.edit_details": "Rediger grundlæggende oplysninger", "collections.edit_settings": "Rediger indstillinger", "collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 8c19179ba06221..0f5945775e4c96 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -263,6 +263,11 @@ "collections.create_collection": "Sammlung erstellen", "collections.delete_collection": "Sammlung löschen", "collections.description_length_hint": "Maximal 100 Zeichen", + "collections.detail.accounts_heading": "Konten", + "collections.detail.curated_by_author": "Kuratiert von {author}", + "collections.detail.curated_by_you": "Kuratiert von dir", + "collections.detail.loading": "Sammlung wird geladen …", + "collections.detail.share": "Sammlung teilen", "collections.edit_details": "Allgemeine Informationen bearbeiten", "collections.edit_settings": "Einstellungen bearbeiten", "collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 102cf7e7d5cb21..5178130cf0c78d 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -263,6 +263,11 @@ "collections.create_collection": "Δημιουργία συλλογής", "collections.delete_collection": "Διαγραφή συλλογής", "collections.description_length_hint": "Όριο 100 χαρακτήρων", + "collections.detail.accounts_heading": "Λογαριασμοί", + "collections.detail.curated_by_author": "Επιμέλεια από {author}", + "collections.detail.curated_by_you": "Επιμέλεια από εσάς", + "collections.detail.loading": "Γίνεται φόρτωση της συλλογής…", + "collections.detail.share": "Κοινοποιήστε αυτήν τη συλλογή", "collections.edit_details": "Επεξεργασία βασικών στοιχείων", "collections.edit_settings": "Επεξεργασία ρυθμίσεων", "collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 1e2acb555901e1..b7dd6253116807 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -263,6 +263,11 @@ "collections.create_collection": "Create collection", "collections.delete_collection": "Delete collection", "collections.description_length_hint": "100 characters limit", + "collections.detail.accounts_heading": "Accounts", + "collections.detail.curated_by_author": "Curated by {author}", + "collections.detail.curated_by_you": "Curated by you", + "collections.detail.loading": "Loading collection…", + "collections.detail.share": "Share this collection", "collections.edit_details": "Edit basic details", "collections.edit_settings": "Edit settings", "collections.error_loading_collections": "There was an error when trying to load your collections.", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index d6856b63acb28d..e2c41f62599941 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Límite de 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Curado por {author}", + "collections.detail.curated_by_you": "Curado por vos", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 4f2110f18104c9..2c5afab168c13c 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Limitado a 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Seleccionado por {author}", + "collections.detail.curated_by_you": "Seleccionado por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar configuración", "collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index e98c685ae56419..bb0f07ea57b412 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -141,6 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.column_button": "Hecho", + "account_edit.column_title": "Editar perfil", "account_note.placeholder": "Haz clic para añadir nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro", @@ -261,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Limitado a 100 caracteres", + "collections.detail.accounts_heading": "Cuentas", + "collections.detail.curated_by_author": "Seleccionado por {author}", + "collections.detail.curated_by_you": "Seleccionado por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar datos básicos", "collections.edit_settings": "Cambiar ajustes", "collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 2ab912530a8a84..ab8a2728ab6fc5 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -263,6 +263,11 @@ "collections.create_collection": "Luo kokoelma", "collections.delete_collection": "Poista kokoelma", "collections.description_length_hint": "100 merkin rajoitus", + "collections.detail.accounts_heading": "Tilit", + "collections.detail.curated_by_author": "Koonnut {author}", + "collections.detail.curated_by_you": "Itse kokoamasi", + "collections.detail.loading": "Ladataan kokoelmaa…", + "collections.detail.share": "Jaa tämä kokoelma", "collections.edit_details": "Muokkaa perustietoja", "collections.edit_settings": "Muokkaa asetuksia", "collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 7b83533de2a85d..521008913b8d42 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -141,6 +141,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Ne plus masquer les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.column_button": "Terminé", + "account_edit.column_title": "Modifier le profil", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des comptes par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des comptes par mois après inscription", @@ -261,6 +263,11 @@ "collections.create_collection": "Créer une collection", "collections.delete_collection": "Supprimer la collection", "collections.description_length_hint": "Maximum 100 caractères", + "collections.detail.accounts_heading": "Comptes", + "collections.detail.curated_by_author": "Organisée par {author}", + "collections.detail.curated_by_you": "Organisée par vous", + "collections.detail.loading": "Chargement de la collection…", + "collections.detail.share": "Partager la collection", "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index eee947ec48b16b..1a581e9c9df1e6 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -141,6 +141,8 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Réactiver les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.column_button": "Terminé", + "account_edit.column_title": "Modifier le profil", "account_note.placeholder": "Cliquez pour ajouter une note", "admin.dashboard.daily_retention": "Taux de rétention des utilisateur·rice·s par jour après inscription", "admin.dashboard.monthly_retention": "Taux de rétention des utilisateur·rice·s par mois après inscription", @@ -261,6 +263,11 @@ "collections.create_collection": "Créer une collection", "collections.delete_collection": "Supprimer la collection", "collections.description_length_hint": "Maximum 100 caractères", + "collections.detail.accounts_heading": "Comptes", + "collections.detail.curated_by_author": "Organisée par {author}", + "collections.detail.curated_by_you": "Organisée par vous", + "collections.detail.loading": "Chargement de la collection…", + "collections.detail.share": "Partager la collection", "collections.edit_details": "Modifier les informations générales", "collections.edit_settings": "Modifier les paramètres", "collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 2165b5bf74a94f..e05a85e0ae7a8b 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -141,6 +141,8 @@ "account.unmute": "Díbhalbhaigh @{name}", "account.unmute_notifications_short": "Díbhalbhaigh fógraí", "account.unmute_short": "Díbhalbhaigh", + "account_edit.column_button": "Déanta", + "account_edit.column_title": "Cuir Próifíl in Eagar", "account_note.placeholder": "Cliceáil chun nóta a chuir leis", "admin.dashboard.daily_retention": "Ráta coinneála an úsáideora de réir an lae tar éis clárú", "admin.dashboard.monthly_retention": "Ráta coinneála na n-úsáideoirí de réir na míosa tar éis dóibh clárú", @@ -261,6 +263,11 @@ "collections.create_collection": "Cruthaigh bailiúchán", "collections.delete_collection": "Scrios bailiúchán", "collections.description_length_hint": "Teorainn 100 carachtar", + "collections.detail.accounts_heading": "Cuntais", + "collections.detail.curated_by_author": "Curtha i dtoll a chéile ag {author}", + "collections.detail.curated_by_you": "Curtha i dtoll a chéile agatsa", + "collections.detail.loading": "Ag lódáil an bhailiúcháin…", + "collections.detail.share": "Comhroinn an bailiúchán seo", "collections.edit_details": "Cuir sonraí bunúsacha in eagar", "collections.edit_settings": "Socruithe a chur in eagar", "collections.error_loading_collections": "Tharla earráid agus iarracht á déanamh do bhailiúcháin a luchtú.", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index e9a5392676e33f..54263a5d8c66e3 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crear colección", "collections.delete_collection": "Eliminar colección", "collections.description_length_hint": "Límite de 100 caracteres", + "collections.detail.accounts_heading": "Contas", + "collections.detail.curated_by_author": "Seleccionadas por {author}", + "collections.detail.curated_by_you": "Seleccionadas por ti", + "collections.detail.loading": "Cargando colección…", + "collections.detail.share": "Compartir esta colección", "collections.edit_details": "Editar detalles básicos", "collections.edit_settings": "Editar axustes", "collections.error_loading_collections": "Houbo un erro ao intentar cargar as túas coleccións.", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 5a9bf3038946ab..a5808b3ba19883 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -263,6 +263,11 @@ "collections.create_collection": "יצירת אוסף", "collections.delete_collection": "מחיקת האוסף", "collections.description_length_hint": "מגבלה של 100 תווים", + "collections.detail.accounts_heading": "חשבונות", + "collections.detail.curated_by_author": "נאצר על ידי {author}", + "collections.detail.curated_by_you": "נאצר על ידיך", + "collections.detail.loading": "טוען אוסף…", + "collections.detail.share": "שיתוף אוסף", "collections.edit_details": "עריכת פרטים בסיסיים", "collections.edit_settings": "עריכת הגדרות", "collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 649128d9c5bede..92cdb3088b96ba 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -263,6 +263,11 @@ "collections.create_collection": "Búa til safn", "collections.delete_collection": "Eyða safni", "collections.description_length_hint": "100 stafa takmörk", + "collections.detail.accounts_heading": "Aðgangar", + "collections.detail.curated_by_author": "Safnað saman af {author}", + "collections.detail.curated_by_you": "Safnað saman af þér", + "collections.detail.loading": "Hleð inn safni…", + "collections.detail.share": "Deila þessu safni", "collections.edit_details": "Breyta grunnupplýsingum", "collections.edit_settings": "Breyta stillingum", "collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index cf37f6b8e4a8a9..846a860fd5551f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -263,6 +263,11 @@ "collections.create_collection": "Crea la collezione", "collections.delete_collection": "Cancella la collezione", "collections.description_length_hint": "Limite di 100 caratteri", + "collections.detail.accounts_heading": "Account", + "collections.detail.curated_by_author": "Curata da {author}", + "collections.detail.curated_by_you": "Curata da te", + "collections.detail.loading": "Caricamento della collezione…", + "collections.detail.share": "Condividi questa collezione", "collections.edit_details": "Modifica i dettagli di base", "collections.edit_settings": "Modifica impostazioni", "collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index f5bda95ad249a5..9731c0756d61ff 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -16,7 +16,9 @@ "account.about": "정보", "account.account_note_header": "개인 메모", "account.activity": "활동", + "account.add_note": "개인 메모 추가", "account.add_or_remove_from_list": "리스트에 추가 혹은 삭제", + "account.badges.admin": "관리자", "account.badges.blocked": "차단함", "account.badges.bot": "자동화됨", "account.badges.domain_blocked": "차단한 도메인", @@ -33,6 +35,7 @@ "account.direct": "@{name} 님에게 개인 멘션", "account.disable_notifications": "@{name} 의 게시물 알림 끄기", "account.domain_blocking": "도메인 차단함", + "account.edit_note": "개인 메모 편집", "account.edit_profile": "프로필 편집", "account.edit_profile_short": "수정", "account.enable_notifications": "@{name} 의 게시물 알림 켜기", @@ -45,6 +48,7 @@ "account.featured.hashtags": "해시태그", "account.featured_tags.last_status_at": "{date}에 마지막으로 게시", "account.featured_tags.last_status_never": "게시물 없음", + "account.filters.all": "모든 활동", "account.filters.boosts_toggle": "부스트 보기", "account.filters.replies_toggle": "답글 보기", "account.follow": "팔로우", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index ad10a2f1ca0fb3..a6110685216e27 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -141,6 +141,8 @@ "account.unmute": "@{name} niet langer negeren", "account.unmute_notifications_short": "Meldingen niet langer negeren", "account.unmute_short": "Niet langer negeren", + "account_edit.column_button": "Klaar", + "account_edit.column_title": "Profiel bewerken", "account_note.placeholder": "Klik om een opmerking toe te voegen", "admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie", "admin.dashboard.monthly_retention": "Retentiegraad van gebruikers per maand, vanaf registratie", @@ -244,9 +246,12 @@ "closed_registrations_modal.preamble": "Mastodon is gedecentraliseerd. Op welke server je ook een account hebt, je kunt overal vandaan mensen op deze server volgen en er mee interactie hebben. Je kunt zelfs zelf een Mastodon-server hosten!", "closed_registrations_modal.title": "Registreren op Mastodon", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", + "collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt", + "collections.accounts.empty_title": "Deze verzameling is leeg", "collections.collection_description": "Omschrijving", "collections.collection_name": "Naam", "collections.collection_topic": "Onderwerp", + "collections.confirm_account_removal": "Weet je zeker dat je dit account uit deze verzameling wilt verwijderen?", "collections.content_warning": "Inhoudswaarschuwing", "collections.continue": "Doorgaan", "collections.create.accounts_subtitle": "Alleen accounts die je volgt en ontdekt willen worden, kunnen worden toegevoegd.", @@ -258,9 +263,17 @@ "collections.create_collection": "Verzameling aanmaken", "collections.delete_collection": "Verzameling verwijderen", "collections.description_length_hint": "Maximaal 100 karakters", + "collections.detail.accounts_heading": "Accounts", + "collections.detail.curated_by_author": "Samengesteld door {author}", + "collections.detail.curated_by_you": "Samengesteld door jou", + "collections.detail.loading": "Verzameling laden…", + "collections.detail.share": "Deze verzameling delen", "collections.edit_details": "Basisgegevens bewerken", "collections.edit_settings": "Instellingen bewerken", "collections.error_loading_collections": "Er is een fout opgetreden bij het laden van je verzamelingen.", + "collections.hints.accounts_counter": "{count} / {max} accounts", + "collections.hints.add_more_accounts": "Voeg ten minste {count, plural, one {# account} other {# accounts}} toe om door te gaan", + "collections.hints.can_not_remove_more_accounts": "Verzamelingen moeten ten minste {count, plural, one {# account} other {# accounts}} bevatten. Meer accounts verwijderen is niet mogelijk.", "collections.last_updated_at": "Laatst bijgewerkt: {date}", "collections.manage_accounts": "Accounts beheren", "collections.manage_accounts_in_collection": "Accounts in deze verzameling beheren", @@ -269,6 +282,9 @@ "collections.name_length_hint": "100 tekens limiet", "collections.new_collection": "Nieuwe verzameling", "collections.no_collections_yet": "Nog geen verzamelingen.", + "collections.remove_account": "Deze account verwijderen", + "collections.search_accounts_label": "Zoek naar accounts om toe te voegen…", + "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze collectie te begrijpen.", "collections.view_collection": "Verzameling bekijken", "collections.visibility_public": "Openbaar", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index e0ddb7fbaf9659..51a2f78547de56 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -260,6 +260,11 @@ "collections.create_collection": "Krijoni koleksion", "collections.delete_collection": "Fshije koleksionin", "collections.description_length_hint": "Kufi prej 100 shenjash", + "collections.detail.accounts_heading": "Llogari", + "collections.detail.curated_by_author": "Në kujdesin e {author}", + "collections.detail.curated_by_you": "Nën kujdesin tuaj", + "collections.detail.loading": "Po ngarkohet koleksion…", + "collections.detail.share": "Ndajeni këtë koleksion me të tjerë", "collections.edit_details": "Përpunoni hollësi bazë", "collections.edit_settings": "Përpunoni rregullime", "collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 80a1fe8bf3fcbd..7d9b2d2024541a 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -200,6 +200,7 @@ "collections.create_a_collection_hint": "Skapa en samling för att rekommendera eller dela dina favoritkonton med andra.", "collections.create_collection": "Skapa samling", "collections.delete_collection": "Radera samling", + "collections.detail.accounts_heading": "Konton", "collections.error_loading_collections": "Det uppstod ett fel när dina samlingar skulle laddas.", "collections.hints.accounts_counter": "{count} / {max} konton", "collections.no_collections_yet": "Inga samlingar än.", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 6efc24ac7492dc..34ee18014de35b 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -141,6 +141,8 @@ "account.unmute": "@{name} adlı kişinin sesini aç", "account.unmute_notifications_short": "Bildirimlerin sesini aç", "account.unmute_short": "Susturmayı kaldır", + "account_edit.column_button": "Tamamlandı", + "account_edit.column_title": "Profili Düzenle", "account_note.placeholder": "Not eklemek için tıklayın", "admin.dashboard.daily_retention": "Kayıttan sonra günlük kullanıcı saklama oranı", "admin.dashboard.monthly_retention": "Kayıttan sonra aylık kullanıcı saklama oranı", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 09deb8244f1770..a956d58b000c77 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -263,6 +263,11 @@ "collections.create_collection": "Tạo collection", "collections.delete_collection": "Xóa collection", "collections.description_length_hint": "Giới hạn 100 ký tự", + "collections.detail.accounts_heading": "Tài khoản", + "collections.detail.curated_by_author": "Tuyển chọn bởi {author}", + "collections.detail.curated_by_you": "Tuyển chọn bởi bạn", + "collections.detail.loading": "Đang tải collection…", + "collections.detail.share": "Chia sẻ collection này", "collections.edit_details": "Sửa thông tin cơ bản", "collections.edit_settings": "Sửa cài đặt", "collections.error_loading_collections": "Đã xảy ra lỗi khi tải những collection của bạn.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 749f2ee916358f..9ecfba8a6a385e 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -141,6 +141,8 @@ "account.unmute": "不再隐藏 @{name}", "account.unmute_notifications_short": "恢复通知", "account.unmute_short": "取消隐藏", + "account_edit.column_button": "完成", + "account_edit.column_title": "修改个人资料", "account_note.placeholder": "点击添加备注", "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)", "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)", @@ -261,6 +263,11 @@ "collections.create_collection": "创建收藏列表", "collections.delete_collection": "删除收藏列表", "collections.description_length_hint": "100字限制", + "collections.detail.accounts_heading": "账号", + "collections.detail.curated_by_author": "由 {author} 精心挑选", + "collections.detail.curated_by_you": "由你精心挑选", + "collections.detail.loading": "正在加载收藏列表…", + "collections.detail.share": "分享此收藏列表", "collections.edit_details": "编辑基本信息", "collections.edit_settings": "编辑设置", "collections.error_loading_collections": "加载你的收藏列表时发生错误。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index b5798703f1635d..cb9cb725d1f38e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -263,6 +263,11 @@ "collections.create_collection": "建立收藏名單", "collections.delete_collection": "刪除收藏名單", "collections.description_length_hint": "100 字限制", + "collections.detail.accounts_heading": "帳號", + "collections.detail.curated_by_author": "由 {author} 精選", + "collections.detail.curated_by_you": "由您精選", + "collections.detail.loading": "讀取收藏名單中...", + "collections.detail.share": "分享此收藏名單", "collections.edit_details": "編輯基本資料", "collections.edit_settings": "編輯設定", "collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。", diff --git a/config/locales/da.yml b/config/locales/da.yml index f917fdca53e153..2e355c23bc189a 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1499,7 +1499,7 @@ da: copy: Kopier delete: Slet deselect: Afmarkér alle - none: Intet + none: Ingen order_by: Sortér efter save_changes: Gem ændringer select_all_matching_items: @@ -1918,8 +1918,8 @@ da: account_suspension: Kontosuspendering (%{target_name}) domain_block: Serversuspendering (%{target_name}) user_domain_block: "%{target_name} blev blokeret" - lost_followers: Tabte følgere - lost_follows: Mistet følger + lost_followers: Mistet følgere + lost_follows: Mistet fulgte preamble: Du kan miste fulgte og følgere, når du blokerer et domæne, eller når dine moderatorer beslutter at suspendere en fjernserver. Når det sker, kan du downloade lister over afbrudte forhold til inspektion og eventuelt import til en anden server. purged: Oplysninger om denne server er blevet renset af serveradministratoreren. type: Begivenhed diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml index be075121b06e70..35f7c22f098dda 100644 --- a/config/locales/doorkeeper.es-AR.yml +++ b/config/locales/doorkeeper.es-AR.yml @@ -83,6 +83,10 @@ es-AR: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso fallaron debido a que "Doorkeeper.configure.resource_owner_from_credentials" está sin configurar. invalid_client: La autenticación del cliente falló debido a que es un cliente desconocido, o no está incluída la autenticación del cliente, o el método de autenticación no está soportado. + invalid_code_challenge_method: + one: El code_challenge_method debe ser %{challenge_methods}. + other: El code_challenge_method debe ser uno de %{challenge_methods}. + zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method. invalid_grant: La concesión de autorización ofrecida no es válida, venció, se revocó, no coincide con la dirección web de redireccionamiento usada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La dirección web de redireccionamiento incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml index 57b8078e44bed8..2c0a726c291544 100644 --- a/config/locales/doorkeeper.es.yml +++ b/config/locales/doorkeeper.es.yml @@ -83,6 +83,10 @@ es: access_denied: El propietario del recurso o servidor de autorización denegó la petición. credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar. invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado. + invalid_code_challenge_method: + one: El code_challenge_method debe ser %{challenge_methods}. + other: El code_challenge_method debe ser uno de %{challenge_methods}. + zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method. invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente. invalid_redirect_uri: La URI de redirección incluida no es válida. invalid_request: diff --git a/config/locales/doorkeeper.fr-CA.yml b/config/locales/doorkeeper.fr-CA.yml index a63e280a1990fa..40575f9a9d9ea2 100644 --- a/config/locales/doorkeeper.fr-CA.yml +++ b/config/locales/doorkeeper.fr-CA.yml @@ -83,8 +83,6 @@ fr-CA: access_denied: Le/la propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du/de la propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: - one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, révoquée, ne concorde pas avec l’URI de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URI de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml index fdc642d9cd83b8..4c7d067a0f2e80 100644 --- a/config/locales/doorkeeper.fr.yml +++ b/config/locales/doorkeeper.fr.yml @@ -83,8 +83,6 @@ fr: access_denied: Le propriétaire de la ressource ou le serveur d’autorisation a refusé la requête. credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n’est pas configuré. invalid_client: L’authentification du client a échoué à cause d’un client inconnu, d’aucune authentification de client incluse ou d’une méthode d’authentification non prise en charge. - invalid_code_challenge_method: - one: The code_challenge_method must be %{challenge_methods}. invalid_grant: L’autorisation accordée est invalide, expirée, annulée, ne concorde pas avec l’URL de redirection utilisée dans la requête d’autorisation, ou a été délivrée à un autre client. invalid_redirect_uri: L’URL de redirection n’est pas valide. invalid_request: diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml index 1d04e50f21ce65..dab7746b4f0e95 100644 --- a/config/locales/doorkeeper.nl.yml +++ b/config/locales/doorkeeper.nl.yml @@ -83,6 +83,10 @@ nl: access_denied: De resource-eigenaar of autorisatie-server weigerde het verzoek. credential_flow_not_configured: De wachtwoordgegevens-flow van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_from_credentials niet is ingesteld. invalid_client: Clientverificatie is mislukt door een onbekende client, ontbrekende client-authenticatie of een niet ondersteunde authenticatie-methode. + invalid_code_challenge_method: + one: De code_challenge_method moet %{challenge_methods} zijn. + other: De code_challenge_method moet een van %{challenge_methods} zijn. + zero: De autorisatieserver ondersteunt PKCE niet, aangezien er geen geaccepteerde code_challenge_method waarden zijn. invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect-URI die is opgegeven of werd uitgegeven aan een andere client. invalid_redirect_uri: De opgegeven redirect-URI is ongeldig. invalid_request: diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml index d663f025ac91a6..b51e49f32af69d 100644 --- a/config/locales/doorkeeper.tr.yml +++ b/config/locales/doorkeeper.tr.yml @@ -83,6 +83,10 @@ tr: access_denied: Kaynak sahibi veya yetkilendirme sunucusu isteği reddetti. credential_flow_not_configured: Kaynak Sahibi Parolası Kimlik Bilgileri akışı Doorkeeper.configure.resource_owner_from_credentials 'ın yapılandırılmamış olması nedeniyle başarısız oldu. invalid_client: İstemcinin kimlik doğrulaması bilinmeyen istemci, istemci kimlik doğrulamasının dahil olmaması veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle başarısız oldu. + invalid_code_challenge_method: + one: code_challenge_method %{challenge_methods} olmalıdır. + other: code_challenge_method %{challenge_methods} seçeneklerinden biri olmalıdır. + zero: Yetkilendirme sunucusu kabul edilen code_challenge_method değerleri olmadığı için PKCE'yi desteklemiyor. invalid_grant: Sağlanan yetkilendirme izni geçersiz, süresi dolmuş, iptal edilmiş, yetkilendirme isteğinde kullanılan yönlendirme URL'siyle eşleşmiyor veya başka bir istemciye verilmiş. invalid_redirect_uri: Dahil edilmiş yönlendirme uri'si geçersiz. invalid_request: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 2a5107952096d6..45cb6c367cf6f3 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1302,6 +1302,13 @@ ko: hint_html: "팁: 한 시간 동안 다시 암호를 묻지 않을 것입니다." invalid_password: 잘못된 암호 prompt: 계속하려면 암호를 확인하세요 + color_scheme: + auto: 자동 + dark: 어두움 + light: 밝음 + contrast: + auto: 자동 + high: 높음 crypto: errors: invalid_key: 유효하지 않은 Ed25519 또는 Curve25519 키 diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index fb5228a153cbc5..76a7deae73cbac 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -234,6 +234,8 @@ ko: setting_always_send_emails: 항상 이메일 알림 보내기 setting_auto_play_gif: 애니메이션 GIF를 자동 재생 setting_boost_modal: 부스트 공개범위 제어 + setting_color_scheme: 색상 구성 + setting_contrast: 대비 setting_default_language: 게시물 언어 setting_default_privacy: 게시물 공개 범위 setting_default_quote_policy: 인용할 수 있는 사람 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 97b09c97a9d01a..6d25b184c468ea 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -224,6 +224,7 @@ nl: email: E-mailadres expires_in: Vervalt na fields: Extra velden + filter_action: Filter-actie header: Omslagfoto honeypot: "%{label} (niet invullen)" inbox_url: Inbox-URL van de relayserver From f48a2990042f31d4dfc615a68b30f7b193373831 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 06:11:46 -0500 Subject: [PATCH 24/60] Use validation matchers for `UrlValidator` spec (#37911) --- spec/validators/url_validator_spec.rb | 64 +++++++++------------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb index 55c0347d18e8bb..56459b13fc4aa9 100644 --- a/spec/validators/url_validator_spec.rb +++ b/spec/validators/url_validator_spec.rb @@ -3,65 +3,45 @@ require 'rails_helper' RSpec.describe URLValidator do + subject { record_class.new } + let(:record_class) do Class.new do include ActiveModel::Validations + def self.name = 'Record' + attr_accessor :profile validates :profile, url: true end end - let(:record) { record_class.new } - - describe '#validate_each' do - context 'with a nil value' do - it 'adds errors' do - record.profile = nil - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + context 'with a nil value' do + it { is_expected.to_not allow_value(nil).for(:profile).with_message(:invalid) } + end - context 'with an invalid url scheme' do - it 'adds errors' do - record.profile = 'ftp://example.com/page' + context 'with an invalid url scheme' do + let(:invalid_scheme_url) { 'ftp://example.com/page' } - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + it { is_expected.to_not allow_value(invalid_scheme_url).for(:profile).with_message(:invalid) } + end - context 'without a hostname' do - it 'adds errors' do - record.profile = 'https:///page' + context 'without a hostname' do + let(:no_hostname_url) { 'https:///page' } - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + it { is_expected.to_not allow_value(no_hostname_url).for(:profile).with_message(:invalid) } + end - context 'with an unparseable value' do - it 'adds errors' do - record.profile = 'https://host:port/page' # non-numeric port string causes invalid uri error + context 'with an unparseable value' do + let(:non_numeric_port_url) { 'https://host:port/page' } - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:profile) - expect(record.errors.first.type).to eq(:invalid) - end - end + it { is_expected.to_not allow_value(non_numeric_port_url).for(:profile).with_message(:invalid) } + end - context 'with a valid url' do - it 'does not add errors' do - record.profile = 'https://example.com/page' + context 'with a valid url' do + let(:valid_url) { 'https://example.com/page' } - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + it { is_expected.to allow_value(valid_url).for(:profile) } end end From 238d0f8e1d04e14a5b6b4e7dfc5a11bddf73f2db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:12:08 +0100 Subject: [PATCH 25/60] Update dependency devise to v5.0.2 (#37903) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 31e331ea6e8e96..e08cc74ea4b3a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -187,7 +187,7 @@ GEM irb (~> 1.10) reline (>= 0.3.8) debug_inspector (1.2.0) - devise (5.0.1) + devise (5.0.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 7.0) From 6f859364fb3ca57898b200673c4c3bf1cc050ffa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:46:32 +0100 Subject: [PATCH 26/60] Update dependency rack to v3.2.5 (#37895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From e288bf6516d02f74b30271187a3ecae9994f2391 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 19 Feb 2026 13:46:38 +0100 Subject: [PATCH 27/60] Show reported collections in moderation interface (#37898) --- .../admin/collections_controller.rb | 22 ++++++ app/controllers/admin/reports_controller.rb | 4 +- app/models/collection.rb | 1 + app/views/admin/accounts/_account.html.haml | 3 +- app/views/admin/collections/show.html.haml | 21 ++++++ app/views/admin/reports/index.html.haml | 5 ++ app/views/admin/reports/show.html.haml | 21 +++++- app/views/admin/shared/_collection.html.haml | 22 ++++++ .../shared/_collection_batch_row.html.haml | 5 ++ config/locales/en.yml | 14 +++- config/routes/admin.rb | 2 + spec/requests/admin/collections_spec.rb | 20 ++++++ spec/requests/admin/reports_spec.rb | 68 +++++++++++++++++++ 13 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 app/controllers/admin/collections_controller.rb create mode 100644 app/views/admin/collections/show.html.haml create mode 100644 app/views/admin/shared/_collection.html.haml create mode 100644 app/views/admin/shared/_collection_batch_row.html.haml create mode 100644 spec/requests/admin/collections_spec.rb create mode 100644 spec/requests/admin/reports_spec.rb diff --git a/app/controllers/admin/collections_controller.rb b/app/controllers/admin/collections_controller.rb new file mode 100644 index 00000000000000..4701500f9f8691 --- /dev/null +++ b/app/controllers/admin/collections_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Admin + class CollectionsController < BaseController + before_action :set_account + before_action :set_collection, only: :show + + def show + authorize @collection, :show? + end + + private + + def set_account + @account = Account.find(params[:account_id]) + end + + def set_collection + @collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id]) + end + end +end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index aa877f1448c98e..44ee7206bf7bc7 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -50,7 +50,7 @@ def resolve private def filtered_reports - ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account) + ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account, :collections) end def filter_params @@ -58,7 +58,7 @@ def filter_params end def set_report - @report = Report.find(params[:id]) + @report = Report.includes(collections: :accepted_collection_items).find(params[:id]) end end end diff --git a/app/models/collection.rb b/app/models/collection.rb index e11cb7318849ac..d8386e43b44e76 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -26,6 +26,7 @@ class Collection < ApplicationRecord belongs_to :tag, optional: true has_many :collection_items, dependent: :delete_all + has_many :accepted_collection_items, -> { accepted }, class_name: 'CollectionItem', inverse_of: :collection # rubocop:disable Rails/HasManyOrHasOneDependent has_many :collection_reports, dependent: :delete_all validates :name, presence: true diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 6b5b5efbdc861a..74f84945620a6c 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -1,6 +1,7 @@ .batch-table__row{ class: [!account.unavailable? && account.user_pending? && 'batch-table__row--attention', (account.unavailable? || account.user_unconfirmed?) && 'batch-table__row--muted'] } %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox - = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id + - if local_assigns[:f].present? + = f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id .batch-table__row__content.batch-table__row__content--unpadded %table.accounts-table %tbody diff --git a/app/views/admin/collections/show.html.haml b/app/views/admin/collections/show.html.haml new file mode 100644 index 00000000000000..8f29b26309dc32 --- /dev/null +++ b/app/views/admin/collections/show.html.haml @@ -0,0 +1,21 @@ +- content_for :page_title do + = t('admin.collections.collection_title', name: @account.pretty_acct) + +- content_for :heading_actions do + = link_to t('admin.collections.open'), account_collection_path(@account, @collection), class: 'button', target: '_blank', rel: 'noopener' + +%h3= t('admin.collections.contents') + += render 'admin/shared/collection', collection: @collection + +%hr.spacer/ + +%h3= t('admin.collections.accounts') + +.batch-table + .batch-table__toolbar + .batch-table__body + - if @collection.accepted_collection_items.none? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'admin/accounts/account', collection: @collection.accepted_collection_items.map(&:account) diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index b910e1aab52ccd..1049cf733e2456 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -67,6 +67,11 @@ = material_symbol('photo_camera') = report.media_attachments_count + - if Mastodon::Feature.collections_enabled? + %span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') } + = material_symbol('groups-fill') + = report.collections.size + - if report.forwarded? · = t('admin.reports.forwarded_to', domain: target_account.domain) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index af92b05768e048..7ea690dc347282 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -32,7 +32,7 @@ %hr.spacer/ %h3 - = t 'admin.reports.statuses' + = t 'admin.reports.reported_content' %small.section-skip-link = link_to '#actions' do = material_symbol 'keyboard_double_arrow_down' @@ -41,6 +41,9 @@ %p = t 'admin.reports.statuses_description_html' +%h4 + = t 'admin.reports.statuses' + = form_with model: @form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id) do |f| .batch-table .batch-table__toolbar @@ -58,6 +61,22 @@ - else = render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f } +- if Mastodon::Feature.collections_enabled? + %h4 + = t 'admin.reports.collections' + + %form + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + -# = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + .batch-table__body + - if @report.collections.empty? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection + - if @report.unresolved? %hr.spacer/ diff --git a/app/views/admin/shared/_collection.html.haml b/app/views/admin/shared/_collection.html.haml new file mode 100644 index 00000000000000..e300a986ba6f25 --- /dev/null +++ b/app/views/admin/shared/_collection.html.haml @@ -0,0 +1,22 @@ +.status__card + - if collection.tag.present? + .status__prepend + = link_to collection.tag.formatted_name, admin_tag_path(collection.tag_id) + + .status__content + %h6= collection.name + + %p= collection.description + + .detailed-status__meta + = conditional_link_to can?(:show, collection), admin_account_collection_path(collection.account.id, collection), class: 'detailed-status__datetime' do + %time.formatted{ datetime: collection.created_at.iso8601, title: l(collection.created_at) }><= l(collection.created_at) + - if collection.sensitive? +  · + = material_symbol('visibility_off') + = t('stream_entries.sensitive_content') +  · + = t('admin.collections.number_of_accounts', count: collection.accepted_collection_items.size) +  · + = link_to account_collection_path(collection.account, collection), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do + = t('admin.collections.view_publicly') diff --git a/app/views/admin/shared/_collection_batch_row.html.haml b/app/views/admin/shared/_collection_batch_row.html.haml new file mode 100644 index 00000000000000..8bf7857e95a381 --- /dev/null +++ b/app/views/admin/shared/_collection_batch_row.html.haml @@ -0,0 +1,5 @@ +.batch-table__row + %label.batch-table__row__select.batch-checkbox + -# = f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id + .batch-table__row__content + = render partial: 'admin/shared/collection', object: collection diff --git a/config/locales/en.yml b/config/locales/en.yml index 2c777e72d3276c..71c559d7381c8c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -56,6 +56,7 @@ en: label: Change role no_role: No role title: Change role for %{username} + collections: Collections confirm: Confirm confirmed: Confirmed confirming: Confirming @@ -340,6 +341,15 @@ en: unpublish: Unpublish unpublished_msg: Announcement successfully unpublished! updated_msg: Announcement successfully updated! + collections: + accounts: Accounts + collection_title: Collection by %{name} + contents: Contents + number_of_accounts: + one: 1 account + other: "%{count} accounts" + open: Open + view_publicly: View publicly critical_update_pending: Critical update pending custom_emojis: assign_category: Assign category @@ -679,6 +689,7 @@ en: cancel: Cancel category: Category category_description_html: The reason this account and/or content was reported will be cited in communication with the reported account + collections: Collections comment: none: None comment_description_html: 'To provide more information, %{name} wrote:' @@ -708,12 +719,13 @@ en: report: 'Report #%{id}' reported_account: Reported account reported_by: Reported by + reported_content: Reported content reported_with_application: Reported with application resolved: Resolved resolved_msg: Report successfully resolved! skip_to_actions: Skip to actions status: Status - statuses: Reported content + statuses: Posts statuses_description_html: Offending content will be cited in communication with the reported account summary: action_preambles: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 97f84da44e7a64..84beea4611c55c 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -153,6 +153,8 @@ resource :reset, only: [:create] resource :action, only: [:new, :create], controller: 'account_actions' + resources :collections, only: [:show] + resources :statuses, only: [:index, :show] do collection do post :batch diff --git a/spec/requests/admin/collections_spec.rb b/spec/requests/admin/collections_spec.rb new file mode 100644 index 00000000000000..0e87e277ae88b7 --- /dev/null +++ b/spec/requests/admin/collections_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Collections' do + describe 'GET /admin/accounts/:account_id/collections/:id' do + let(:collection) { Fabricate(:collection) } + + before do + sign_in Fabricate(:admin_user) + end + + it 'returns success' do + get admin_account_collection_path(collection.account_id, collection) + + expect(response) + .to have_http_status(200) + end + end +end diff --git a/spec/requests/admin/reports_spec.rb b/spec/requests/admin/reports_spec.rb new file mode 100644 index 00000000000000..d44db63795371c --- /dev/null +++ b/spec/requests/admin/reports_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Reports' do + describe 'GET /admin/reports' do + before do + sign_in Fabricate(:admin_user) + + Fabricate.times(2, :report) + end + + it 'returns success' do + get admin_reports_path + + expect(response) + .to have_http_status(200) + end + end + + describe 'GET /admin/reports/:id' do + let(:report) { Fabricate(:report) } + + before do + sign_in Fabricate(:admin_user) + end + + shared_examples 'successful return' do + it 'returns success' do + get admin_report_path(report) + + expect(response) + .to have_http_status(200) + end + end + + context 'with a simple report' do + it_behaves_like 'successful return' + end + + context 'with a reported status' do + before do + status = Fabricate(:status, account: report.target_account) + report.update(status_ids: [status.id]) + end + + it_behaves_like 'successful return' + end + + context 'with a reported collection', feature: :collections do + before do + report.collections << Fabricate(:collection, account: report.target_account) + end + + it_behaves_like 'successful return' + end + + context 'with both status and collection', feature: :collections do + before do + status = Fabricate(:status, account: report.target_account) + report.update(status_ids: [status.id]) + report.collections << Fabricate(:collection, account: report.target_account) + end + + it_behaves_like 'successful return' + end + end +end From 40f92f3af8058d7bf7b98b344bf6d8b4e2f5ab28 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 07:50:09 -0500 Subject: [PATCH 28/60] Use validation matchers for `UnreservedUsernameValidator` spec (#37910) --- .../unreserved_username_validator_spec.rb | 126 ++++++------------ 1 file changed, 40 insertions(+), 86 deletions(-) diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index 55dca7db844afd..aa90ecb0f83b24 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe UnreservedUsernameValidator do + subject { record_class.new } + let(:record_class) do Class.new do include ActiveModel::Validations @@ -11,115 +13,67 @@ validates_with UnreservedUsernameValidator - def self.name - 'Foo' - end + def self.name = 'Record' end end - let(:record) { record_class.new } + context 'when username is nil' do + it { is_expected.to allow_value(nil).for(:username) } + end - describe '#validate' do - context 'when username is nil' do - it 'does not add errors' do - record.username = nil + context 'when PAM is enabled' do + before do + allow(Devise).to receive(:pam_authentication).and_return(true) + end - expect(record).to be_valid - expect(record.errors).to be_empty + context 'with a pam service available' do + let(:service) { double } + let(:pam_class) do + Class.new do + def self.account(service, username); end + end end - end - context 'when PAM is enabled' do before do - allow(Devise).to receive(:pam_authentication).and_return(true) + stub_const('Rpam2', pam_class) + allow(Devise).to receive(:pam_controlled_service).and_return(service) end - context 'with a pam service available' do - let(:service) { double } - let(:pam_class) do - Class.new do - def self.account(service, username); end - end - end - + context 'when the account exists' do before do - stub_const('Rpam2', pam_class) - allow(Devise).to receive(:pam_controlled_service).and_return(service) - end - - context 'when the account exists' do - before do - allow(Rpam2).to receive(:account).with(service, 'username').and_return(true) - end - - it 'adds errors to the record' do - record.username = 'username' - - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:username) - expect(record.errors.first.type).to eq(:reserved) - end + allow(Rpam2).to receive(:account).with(service, 'username').and_return(true) end - context 'when the account does not exist' do - before do - allow(Rpam2).to receive(:account).with(service, 'username').and_return(false) - end - - it 'does not add errors to the record' do - record.username = 'username' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + it { is_expected.to_not allow_value('username').for(:username).with_message(:reserved) } end - context 'without a pam service' do + context 'when the account does not exist' do before do - allow(Devise).to receive(:pam_controlled_service).and_return(false) + allow(Rpam2).to receive(:account).with(service, 'username').and_return(false) end - context 'when there are not any reserved usernames' do - before do - stub_reserved_usernames(nil) - end + it { is_expected.to allow_value('username').for(:username) } + end + end - it 'does not add errors to the record' do - record.username = 'username' + context 'without a pam service' do + before do + allow(Devise).to receive(:pam_controlled_service).and_return(false) + end - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + context 'when there are not any reserved usernames' do + it { is_expected.to allow_value('username').for(:username) } + end + + context 'when there are reserved usernames' do + before { %w(alice bob).each { |username| Fabricate(:username_block, exact: true, username:) } } - context 'when there are reserved usernames' do - before do - stub_reserved_usernames(%w(alice bob)) - end - - context 'when the username is reserved' do - it 'adds errors to the record' do - record.username = 'alice' - - expect(record).to_not be_valid - expect(record.errors.first.attribute).to eq(:username) - expect(record.errors.first.type).to eq(:reserved) - end - end - - context 'when the username is not reserved' do - it 'does not add errors to the record' do - record.username = 'chris' - - expect(record).to be_valid - expect(record.errors).to be_empty - end - end + context 'when the username is reserved' do + it { is_expected.to_not allow_values('alice', 'bob').for(:username).with_message(:reserved) } end - def stub_reserved_usernames(value) - value&.each { |str| Fabricate(:username_block, username: str, exact: true) } + context 'when the username is not reserved' do + it { is_expected.to allow_value('chris').for(:username) } end end end From 157583659a1cc899c0fed212a288927a3ed25597 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 19 Feb 2026 08:14:12 -0500 Subject: [PATCH 29/60] Use validation matchers for `UniqueUsernameValidator` spec (#37909) --- .../unique_username_validator_spec.rb | 95 +++++++++---------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/spec/validators/unique_username_validator_spec.rb b/spec/validators/unique_username_validator_spec.rb index 037ddadb9f70f7..12534714eade02 100644 --- a/spec/validators/unique_username_validator_spec.rb +++ b/spec/validators/unique_username_validator_spec.rb @@ -3,72 +3,63 @@ require 'rails_helper' RSpec.describe UniqueUsernameValidator do - describe '#validate' do - context 'when local account' do - it 'does not add errors if username is nil' do - account = instance_double(Account, username: nil, domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) - end - - it 'does not add errors when existing one is subject itself' do - account = Fabricate(:account, username: 'abcdef') - expect(account).to be_valid - end - - it 'adds an error when the username is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef') - account = instance_double(Account, username: 'abcDEF', domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) - end - - it 'does not add errors when same username remote account exists' do - Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = instance_double(Account, username: 'abcdef', domain: nil, persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) - end + subject { Fabricate.build :account, username: 'abcdef', domain: } + + context 'when local account' do + let(:domain) { nil } + + context 'when record is persisted and checking own name' do + before { subject.save } + + it { is_expected.to allow_value(subject.username).for(:username) } + end + + context 'when username case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef' } + + it { is_expected.to_not allow_value('abcDEF').for(:username).with_message(:taken) } + end + + context 'when username on remote account is in use' do + before { Fabricate :account, username: 'ABCdef', domain: 'host.example' } + + it { is_expected.to allow_value('abcDEF').for(:username) } end end context 'when remote account' do - it 'does not add errors if username is nil' do - account = instance_double(Account, username: nil, domain: 'example.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) - end + let(:domain) { 'host.example' } - it 'does not add errors when existing one is subject itself' do - account = Fabricate(:account, username: 'abcdef', domain: 'example.com') - expect(account).to be_valid + context 'when record is persisted and checking own name' do + before { subject.save } + + it { is_expected.to allow_value('abcdef').for(:username) } end - it 'adds an error when the username is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = instance_double(Account, username: 'abcDEF', domain: 'example.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) + context 'when username case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef', domain: 'host.example' } + + it { is_expected.to_not allow_value('abcDEF').for(:username) } end - it 'adds an error when the domain is already used with ignoring cases' do - Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = instance_double(Account, username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to have_received(:add) + context 'when domain case insensitive in use already' do + before { Fabricate :account, username: 'ABCdef', domain: 'HOST.EXAMPLE' } + + it { is_expected.to_not allow_value('abcDEF').for(:username) } end - it 'does not add errors when account with the same username and another domain exists' do - Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = instance_double(Account, username: 'abcdef', domain: 'example2.com', persisted?: false, errors: activemodel_errors) - subject.validate(account) - expect(account.errors).to_not have_received(:add) + context 'when same username on other domain is in use already' do + before { Fabricate :account, username: 'abcdef', domain: 'other.example' } + + it { is_expected.to allow_value('abcdef').for(:username) } end end - private + context 'when account has blank username' do + subject { described_class.new.validate(account) } + + let(:account) { Fabricate.build :account, username: nil } - def activemodel_errors - instance_double(ActiveModel::Errors, add: nil) + it { is_expected.to be_nil } end end From ed4787c1b1de1d7491719be0f45b7a7f16af8c0d Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 19 Feb 2026 14:53:29 +0100 Subject: [PATCH 30/60] Profile editing: Name and bio (#37907) --- .../mastodon/components/account_bio.tsx | 2 +- .../form_fields/text_area_field.stories.tsx | 7 ++ .../form_fields/text_area_field.tsx | 93 ++++++++------ .../account_edit/components/bio_modal.tsx | 94 ++++++++++++++ .../account_edit/components/char_counter.tsx | 27 ++++ .../account_edit/components/emoji_picker.tsx | 27 ++++ .../account_edit/components/name_modal.tsx | 87 +++++++++++++ .../account_edit/components/section.tsx | 62 ++++++++++ .../mastodon/features/account_edit/index.tsx | 117 +++++++++++++++++- .../features/account_edit/styles.module.scss | 100 +++++++++++++-- .../account_timeline/modals/note_modal.tsx | 2 +- .../mastodon/features/emoji/utils.ts | 18 +++ .../confirmation_modal.tsx | 16 ++- .../components/confirmation_modals/index.ts | 1 + .../quiet_post_quote_info.tsx | 4 +- .../features/ui/components/modal_root.jsx | 2 + app/javascript/mastodon/locales/en.json | 17 +++ 17 files changed, 617 insertions(+), 59 deletions(-) create mode 100644 app/javascript/mastodon/features/account_edit/components/bio_modal.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/char_counter.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/emoji_picker.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/name_modal.tsx create mode 100644 app/javascript/mastodon/features/account_edit/components/section.tsx diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index 6d4ab1ddd49b2c..75067530c946eb 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -6,7 +6,7 @@ import { EmojiHTML } from './emoji/html'; import { useElementHandledLink } from './status/handled_link'; interface AccountBioProps { - className: string; + className?: string; accountId: string; showDropdown?: boolean; } diff --git a/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx b/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx index 448af8a28ebe94..190239aee2a752 100644 --- a/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx +++ b/app/javascript/mastodon/components/form_fields/text_area_field.stories.tsx @@ -42,6 +42,13 @@ export const WithError: Story = { }, }; +export const AutoSize: Story = { + args: { + autoSize: true, + defaultValue: 'This textarea will grow as you type more lines.', + }, +}; + export const Plain: Story = { render(args) { return