From e2c9426c779641dcf0d3f54d65cc61b5006d18ed Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 25 Feb 2026 11:07:46 +0100 Subject: [PATCH 01/89] Split status batch and moderation actions (#37970) --- .../admin/reports/actions_controller.rb | 5 +- app/controllers/admin/statuses_controller.rb | 2 - app/models/admin/account_action.rb | 25 ------ app/models/admin/base_action.rb | 23 +++++ app/models/admin/moderation_action.rb | 84 +++++++++++++++++++ app/models/admin/status_batch_action.rb | 82 ------------------ spec/models/admin/moderation_action_spec.rb | 57 +++++++++++++ spec/models/admin/status_batch_action_spec.rb | 33 -------- 8 files changed, 166 insertions(+), 145 deletions(-) create mode 100644 app/models/admin/moderation_action.rb create mode 100644 spec/models/admin/moderation_action_spec.rb diff --git a/app/controllers/admin/reports/actions_controller.rb b/app/controllers/admin/reports/actions_controller.rb index fb7b6878baedfb..abfec42e75be41 100644 --- a/app/controllers/admin/reports/actions_controller.rb +++ b/app/controllers/admin/reports/actions_controller.rb @@ -13,7 +13,7 @@ def create case action_from_button when 'delete', 'mark_as_sensitive' - Admin::StatusBatchAction.new(status_batch_action_params).save! + Admin::ModerationAction.new(moderation_action_params).save! when 'silence', 'suspend' Admin::AccountAction.new(account_action_params).save! else @@ -25,9 +25,8 @@ def create private - def status_batch_action_params + def moderation_action_params shared_params - .merge(status_ids: @report.status_ids) end def account_action_params diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index aeadb35e7a6e77..7e75d841b5e1f8 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -78,8 +78,6 @@ def action_from_button 'report' elsif params[:remove_from_report] 'remove_from_report' - elsif params[:delete] - 'delete' end end end diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index 45c5987b8a36c5..eed17c9a87e5f3 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -66,20 +66,6 @@ def handle_type! end end - def process_strike! - @warning = target_account.strikes.create!( - account: current_account, - report: report, - action: type, - text: text_for_warning, - status_ids: status_ids - ) - - # A log entry is only interesting if the warning contains - # custom text from someone. Otherwise it's just noise. - log_action(:create, @warning) if @warning.text.present? && type == 'none' - end - def process_reports! # If we're doing "mark as resolved" on a single report, # then we want to keep other reports open in case they @@ -131,17 +117,6 @@ def process_queue! queue_suspension_worker! if type == 'suspend' end - def process_notification! - return unless warnable? - - UserMailer.warning(target_account.user, warning).deliver_later! - LocalNotificationWorker.perform_async(target_account.id, warning.id, 'AccountWarning', 'moderation_warning') - end - - def warnable? - send_email_notification? && target_account.local? - end - def status_ids report.status_ids if with_report? && include_statuses? end diff --git a/app/models/admin/base_action.rb b/app/models/admin/base_action.rb index 15c1acae424d13..d07e8464fd47c5 100644 --- a/app/models/admin/base_action.rb +++ b/app/models/admin/base_action.rb @@ -39,4 +39,27 @@ def report def with_report? !report.nil? end + + private + + def process_strike!(action = type) + @warning = target_account.strikes.create!( + account: current_account, + report: report, + action:, + text: text_for_warning, + status_ids: status_ids + ) + end + + def process_notification! + return unless warnable? + + UserMailer.warning(target_account.user, warning).deliver_later! + LocalNotificationWorker.perform_async(target_account.id, warning.id, 'AccountWarning', 'moderation_warning') + end + + def warnable? + send_email_notification? && target_account.local? + end end diff --git a/app/models/admin/moderation_action.rb b/app/models/admin/moderation_action.rb new file mode 100644 index 00000000000000..915306d0f5351d --- /dev/null +++ b/app/models/admin/moderation_action.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +class Admin::ModerationAction < Admin::BaseAction + TYPES = %w( + delete + mark_as_sensitive + ).freeze + + validates :report_id, presence: true + + private + + def status_ids + report.status_ids + end + + def statuses + @statuses ||= Status.with_discarded.where(id: status_ids).reorder(nil) + end + + def process_action! + case type + when 'delete' + handle_delete! + when 'mark_as_sensitive' + handle_mark_as_sensitive! + end + end + + def handle_delete! + statuses.each { |status| authorize([:admin, status], :destroy?) } + + ApplicationRecord.transaction do + statuses.each do |status| + status.discard_with_reblogs + log_action(:destroy, status) + end + + report.resolve!(current_account) + log_action(:resolve, report) + + process_strike!(:delete_statuses) + + statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? + end + + process_notification! + + RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, { 'preserve' => target_account.local?, 'immediate' => !target_account.local? }] } + end + + def handle_mark_as_sensitive! + representative_account = Account.representative + + # Can't use a transaction here because UpdateStatusService queues + # Sidekiq jobs + statuses.includes(:media_attachments, preview_cards_status: :preview_card).find_each do |status| + next if status.discarded? || !(status.with_media? || status.with_preview_card?) + + authorize([:admin, status], :update?) + + if target_account.local? + UpdateStatusService.new.call(status, representative_account.id, sensitive: true) + else + status.update(sensitive: true) + end + + log_action(:update, status) + + report.resolve!(current_account) + log_action(:resolve, report) + end + + process_strike!(:mark_statuses_as_sensitive) + + process_notification! + end + + def target_account + report.target_account + end + + def text_for_warning = text +end diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index d6dea02ed0f623..0485457a655a59 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -2,8 +2,6 @@ class Admin::StatusBatchAction < Admin::BaseAction TYPES = %w( - delete - mark_as_sensitive report remove_from_report ).freeze @@ -20,10 +18,6 @@ def process_action! return if status_ids.empty? case type - when 'delete' - handle_delete! - when 'mark_as_sensitive' - handle_mark_as_sensitive! when 'report' handle_report! when 'remove_from_report' @@ -31,71 +25,6 @@ def process_action! end end - def handle_delete! - statuses.each { |status| authorize([:admin, status], :destroy?) } - - ApplicationRecord.transaction do - statuses.each do |status| - status.discard_with_reblogs - log_action(:destroy, status) - end - - if with_report? - report.resolve!(current_account) - log_action(:resolve, report) - end - - @warning = target_account.strikes.create!( - action: :delete_statuses, - account: current_account, - report: report, - status_ids: status_ids, - text: text - ) - - statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? - end - - process_notification! - - RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, { 'preserve' => target_account.local?, 'immediate' => !target_account.local? }] } - end - - def handle_mark_as_sensitive! - representative_account = Account.representative - - # Can't use a transaction here because UpdateStatusService queues - # Sidekiq jobs - statuses.includes(:media_attachments, preview_cards_status: :preview_card).find_each do |status| - next if status.discarded? || !(status.with_media? || status.with_preview_card?) - - authorize([:admin, status], :update?) - - if target_account.local? - UpdateStatusService.new.call(status, representative_account.id, sensitive: true) - else - status.update(sensitive: true) - end - - log_action(:update, status) - - if with_report? - report.resolve!(current_account) - log_action(:resolve, report) - end - end - - @warning = target_account.strikes.create!( - action: :mark_statuses_as_sensitive, - account: current_account, - report: report, - status_ids: status_ids, - text: text - ) - - process_notification! - end - def handle_report! @report = Report.new(report_params) unless with_report? @report.status_ids = (@report.status_ids + allowed_status_ids).uniq @@ -111,17 +40,6 @@ def handle_remove_from_report! report.save! end - def process_notification! - return unless warnable? - - UserMailer.warning(target_account.user, @warning).deliver_later! - LocalNotificationWorker.perform_async(target_account.id, @warning.id, 'AccountWarning', 'moderation_warning') - end - - def warnable? - send_email_notification && target_account.local? - end - def target_account @target_account ||= statuses.first.account end diff --git a/spec/models/admin/moderation_action_spec.rb b/spec/models/admin/moderation_action_spec.rb new file mode 100644 index 00000000000000..b6c81d6c44d3da --- /dev/null +++ b/spec/models/admin/moderation_action_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::ModerationAction do + subject do + described_class.new( + current_account:, + type:, + report_id:, + text: + ) + end + + let(:current_account) { Fabricate(:admin_user).account } + let(:target_account) { Fabricate(:account) } + let(:statuses) { Fabricate.times(2, :status, account: target_account) } + let(:status_ids) { statuses.map(&:id) } + let(:report) { Fabricate(:report, target_account:, status_ids:) } + let(:report_id) { report.id } + let(:text) { 'test' } + + describe '#save!' do + context 'when `type` is `delete`' do + let(:type) { 'delete' } + + it 'discards the statuses' do + subject.save! + + statuses.each do |status| + expect(status.reload).to be_discarded + end + expect(report.reload).to be_action_taken + end + end + + context 'when `type` is `mark_as_sensitive`' do + let(:type) { 'mark_as_sensitive' } + + before do + preview_card = Fabricate(:preview_card) + statuses.each do |status| + PreviewCardsStatus.create!(status:, preview_card:) + end + end + + it 'marks the statuses as sensitive' do + subject.save! + + statuses.each do |status| + expect(status.reload).to be_sensitive + end + expect(report.reload).to be_action_taken + end + end + end +end diff --git a/spec/models/admin/status_batch_action_spec.rb b/spec/models/admin/status_batch_action_spec.rb index 4db1a985823acb..93db4665d26d5e 100644 --- a/spec/models/admin/status_batch_action_spec.rb +++ b/spec/models/admin/status_batch_action_spec.rb @@ -22,39 +22,6 @@ let(:text) { 'test' } describe '#save!' do - context 'when `type` is `delete`' do - let(:type) { 'delete' } - - it 'discards the statuses' do - subject.save! - - statuses.each do |status| - expect(status.reload).to be_discarded - end - expect(report.reload).to be_action_taken - end - end - - context 'when `type` is `mark_as_sensitive`' do - let(:type) { 'mark_as_sensitive' } - - before do - preview_card = Fabricate(:preview_card) - statuses.each do |status| - PreviewCardsStatus.create!(status:, preview_card:) - end - end - - it 'marks the statuses as sensitive' do - subject.save! - - statuses.each do |status| - expect(status.reload).to be_sensitive - end - expect(report.reload).to be_action_taken - end - end - context 'when `type` is `report`' do let(:report_id) { nil } let(:type) { 'report' } From 953472b40d540ba4ce8639c5f4ee120abaed44cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:14:08 +0100 Subject: [PATCH 02/89] New Crowdin Translations (automated) (#37968) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 7 ++++++- app/javascript/mastodon/locales/de.json | 23 +++++++++++++--------- app/javascript/mastodon/locales/el.json | 9 +++++++-- app/javascript/mastodon/locales/en-GB.json | 7 ++++++- app/javascript/mastodon/locales/es-AR.json | 7 ++++++- app/javascript/mastodon/locales/es-MX.json | 7 ++++++- app/javascript/mastodon/locales/es.json | 7 ++++++- app/javascript/mastodon/locales/fi.json | 7 ++++++- app/javascript/mastodon/locales/fo.json | 1 - app/javascript/mastodon/locales/fr-CA.json | 17 ++++++++++++++-- app/javascript/mastodon/locales/fr.json | 19 +++++++++++++++--- app/javascript/mastodon/locales/ga.json | 21 ++++++++++++++++++++ app/javascript/mastodon/locales/gl.json | 14 +++++++++++++ app/javascript/mastodon/locales/he.json | 7 ++++++- app/javascript/mastodon/locales/is.json | 7 ++++++- app/javascript/mastodon/locales/it.json | 7 ++++++- app/javascript/mastodon/locales/nl.json | 1 - app/javascript/mastodon/locales/nn.json | 9 +++++++++ app/javascript/mastodon/locales/pt-BR.json | 8 ++++---- app/javascript/mastodon/locales/sq.json | 7 ++++++- app/javascript/mastodon/locales/sv.json | 1 + app/javascript/mastodon/locales/tr.json | 9 +++++++++ app/javascript/mastodon/locales/vi.json | 7 ++++++- app/javascript/mastodon/locales/zh-CN.json | 7 ++++++- app/javascript/mastodon/locales/zh-TW.json | 9 +++++++-- config/locales/da.yml | 2 +- config/locales/doorkeeper.de.yml | 4 ++++ config/locales/fr-CA.yml | 2 ++ config/locales/fr.yml | 2 ++ config/locales/ga.yml | 2 ++ config/locales/gl.yml | 2 ++ config/locales/ko.yml | 2 +- config/locales/nn.yml | 5 +++++ config/locales/simple_form.zh-TW.yml | 4 ++-- config/locales/tr.yml | 2 ++ config/locales/zh-CN.yml | 4 ++-- config/locales/zh-TW.yml | 4 ++-- 37 files changed, 216 insertions(+), 44 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index c5f975be2c63d0..1ee5e74c395ff0 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Fremhævede hashtags hjælper brugere med at finde og interagere med din profil. De vises som filtre i aktivitetsvisningen på din profilside.", "account_edit_tags.search_placeholder": "Angiv et hashtag…", "account_edit_tags.suggestions": "Forslag:", - "account_edit_tags.tag_status_count": "{count} indlæg", + "account_edit_tags.tag_status_count": "{count, plural, one {# indlæg} other {# indlæg}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Ingen samlinger endnu.", "collections.old_last_post_note": "Seneste indlæg er fra over en uge siden", "collections.remove_account": "Fjern denne konto", + "collections.report_collection": "Anmeld denne samling", "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.sensitive": "Sensitivt", "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.view_other_collections_by_user": "Se andre samlinger af denne bruger", "collections.visibility_public": "Offentlig", "collections.visibility_public_hint": "Kan opdages i søgeresultater og andre områder, hvor anbefalinger vises.", "collections.visibility_title": "Synlighed", @@ -976,6 +978,7 @@ "report.category.title_account": "profil", "report.category.title_status": "indlæg", "report.close": "Udført", + "report.collection_comment": "Hvorfor vil du anmelde denne samling?", "report.comment.title": "Er der andet, som vi bør vide?", "report.forward": "Videresend til {target}", "report.forward_hint": "Kontoen er fra en anden server. Send også en anonymiseret kopi af anmeldelsen dertil?", @@ -997,6 +1000,8 @@ "report.rules.title": "Hvilke regler overtrædes?", "report.statuses.subtitle": "Vælg alle relevante", "report.statuses.title": "Er der indlæg, som kan bekræfte denne anmeldelse?", + "report.submission_error": "Anmeldelse kunne ikke indsendes", + "report.submission_error_details": "Tjek din netværksforbindelse eller prøv igen senere.", "report.submit": "Indsend", "report.target": "Anmelder {target}", "report.thanks.take_action": "Her er mulighederne for at styre, hvad du ser på Mastodon:", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index a9f57548de76bb..5bac1293cf018d 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -88,7 +88,7 @@ "account.menu.hide_reblogs": "Geteilte Beiträge in der Timeline ausblenden", "account.menu.mention": "Erwähnen", "account.menu.mute": "Konto stummschalten", - "account.menu.note.description": "Nur für Sie sichtbar", + "account.menu.note.description": "Nur für dich sichtbar", "account.menu.open_original_page": "Auf {domain} ansehen", "account.menu.remove_follower": "Follower entfernen", "account.menu.report": "Konto melden", @@ -104,11 +104,11 @@ "account.muted": "Stummgeschaltet", "account.muting": "Stummgeschaltet", "account.mutual": "Ihr folgt einander", - "account.name.help.domain": "{domain} ist der Server, auf dem das Profil und die Beiträge des Benutzers gespeichert sind.", - "account.name.help.domain_self": "{domain} ist Ihr Server, auf dem Ihr Profil und Ihre Beiträge gespeichert sind.", - "account.name.help.footer": "Genauso wie Sie E-Mails über verschiedene E-Mail-Anbieter versenden können, Sie können auch mit Personen auf anderen Mastodon Servers interagieren– und mit allen auf anderen sozialen Apps, die nach denselben Regeln wie Mastodon funktionieren (dem ActivityPub Protokoll).", + "account.name.help.domain": "{domain} ist der Server, auf dem das Profil registriert ist und die Beiträge verwaltet werden.", + "account.name.help.domain_self": "{domain} ist der Server, auf dem du registriert bist und deine Beiträge verwaltet werden.", + "account.name.help.footer": "So wie du E-Mails an andere trotz unterschiedlicher E-Mail-Clients senden kannst, so kannst du auch mit anderen Profilen auf unterschiedlichen Mastodon-Servern interagieren. Wenn andere soziale Apps die gleichen Kommunikationsregeln (das ActivityPub-Protokoll) wie Mastodon verwenden, dann funktioniert die Kommunikation auch dort.", "account.name.help.header": "Deine Adresse im Fediverse ist wie eine E-Mail-Adresse", - "account.name.help.username": "{username} ist der Profilname dieses Kontos auf diesem Server. Jemand auf einem anderen Server könnte denselben Profilnamen haben.", + "account.name.help.username": "{username} ist der Profilname auf deren Server. Es ist möglich, dass jemand auf einem anderen Server den gleichen Profilnamen hat.", "account.name.help.username_self": "{username} ist dein Profilname auf diesem Server. Es ist möglich, dass jemand auf einem anderen Server den gleichen Profilnamen hat.", "account.name_info": "Was bedeutet das?", "account.no_bio": "Keine Beschreibung verfügbar.", @@ -153,10 +153,10 @@ "account_edit.column_title": "Profil bearbeiten", "account_edit.custom_fields.placeholder": "Ergänze deine Pronomen, weiterführenden Links oder etwas anderes, das du teilen möchtest.", "account_edit.custom_fields.title": "Zusatzfelder", - "account_edit.display_name.placeholder": "Ihr Anzeigename ist der Name, der in Ihrem Profil und auf Ihrer \"Timeline\" angezeigt wird.", + "account_edit.display_name.placeholder": "Dein Anzeigename wird auf deinem Profil und in Timelines angezeigt.", "account_edit.display_name.title": "Anzeigename", "account_edit.featured_hashtags.item": "Hashtags", - "account_edit.featured_hashtags.placeholder": "Helfen Sie anderen dabei, Ihre Lieblingsthemen zu identifizieren und schnell darauf zuzugreifen.", + "account_edit.featured_hashtags.placeholder": "Präsentiere deine Lieblingsthemen und ermögliche anderen einen schnellen Zugriff darauf.", "account_edit.featured_hashtags.title": "Vorgestellte Hashtags", "account_edit.name_modal.add_title": "Anzeigenamen hinzufügen", "account_edit.name_modal.edit_title": "Anzeigenamen bearbeiten", @@ -164,10 +164,10 @@ "account_edit.profile_tab.title": "Profil-Tab-Einstellungen", "account_edit.save": "Speichern", "account_edit_tags.column_title": "Vorgestellte Hashtags bearbeiten", - "account_edit_tags.help_text": "Vorgestellte Hashtags können dabei helfen, dein Profil zu entdecken und Kontakt mit dir aufzunehmen. Diese erscheinen als Filter in der Aktivitätenübersicht deines Profils.", + "account_edit_tags.help_text": "Vorgestellte Hashtags können dabei helfen, dein Profil zu entdecken und besser mit dir zu interagieren. Sie erscheinen in der Aktivitätenübersicht deines Profils und dienen als Filter.", "account_edit_tags.search_placeholder": "Gib einen Hashtag ein …", "account_edit_tags.suggestions": "Vorschläge:", - "account_edit_tags.tag_status_count": "{count} Beiträge", + "account_edit_tags.tag_status_count": "{count, plural, one {# Beitrag} other {# Beiträge}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Bisher keine Sammlungen vorhanden.", "collections.old_last_post_note": "Neuester Beitrag mehr als eine Woche alt", "collections.remove_account": "Dieses Konto entfernen", + "collections.report_collection": "Sammlung melden", "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.sensitive": "Inhaltswarnung", "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.view_other_collections_by_user": "Andere Sammlungen dieses Kontos ansehen", "collections.visibility_public": "Öffentlich", "collections.visibility_public_hint": "Wird in den Suchergebnissen und anderen Bereichen mit Empfehlungen angezeigt.", "collections.visibility_title": "Sichtbarkeit", @@ -976,6 +978,7 @@ "report.category.title_account": "Profil", "report.category.title_status": "Beitrag", "report.close": "Fertig", + "report.collection_comment": "Weshalb möchtest du diese Sammlung melden?", "report.comment.title": "Gibt es noch etwas, das wir wissen sollten?", "report.forward": "Meldung auch an den externen Server {target} weiterleiten", "report.forward_hint": "Das gemeldete Konto befindet sich auf einem anderen Server. Soll zusätzlich eine anonymisierte Kopie deiner Meldung an diesen Server geschickt werden?", @@ -997,6 +1000,8 @@ "report.rules.title": "Gegen welche Regeln wurde verstoßen?", "report.statuses.subtitle": "Wähle alle zutreffenden Inhalte aus", "report.statuses.title": "Gibt es Beiträge, die diese Meldung stützen?", + "report.submission_error": "Meldung konnte nicht übermittelt werden", + "report.submission_error_details": "Bitte prüfe deine Internetverbindung und probiere es später erneut.", "report.submit": "Senden", "report.target": "{target} melden", "report.thanks.take_action": "Das sind deine Möglichkeiten zu bestimmen, was du auf Mastodon sehen möchtest:", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 4c897a41db0f7c..af7b21279570c8 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Οι αναδεδειγμένες ετικέτες βοηθούν τους χρήστες να ανακαλύψουν και να αλληλεπιδράσουν με το προφίλ σας. Εμφανίζονται ως φίλτρα στην προβολή Δραστηριότητας της σελίδας προφίλ σας.", "account_edit_tags.search_placeholder": "Εισάγετε μια ετικέτα…", "account_edit_tags.suggestions": "Προτάσεις:", - "account_edit_tags.tag_status_count": "{count} αναρτήσεις", + "account_edit_tags.tag_status_count": "{count, plural, one {# ανάρτηση} other {# αναρτήσεις}}", "account_note.placeholder": "Κάνε κλικ για να προσθέσεις σημείωση", "admin.dashboard.daily_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά ημέρα", "admin.dashboard.monthly_retention": "Ποσοστό χρηστών που παραμένουν μετά την εγγραφή, ανά μήνα", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Καμία συλλογή ακόμη.", "collections.old_last_post_note": "Τελευταία ανάρτηση πριν από μια εβδομάδα", "collections.remove_account": "Αφαίρεση λογαριασμού", + "collections.report_collection": "Αναφορά αυτής της συλλογής", "collections.search_accounts_label": "Αναζήτηση λογαριασμών για προσθήκη…", "collections.search_accounts_max_reached": "Έχετε προσθέσει τον μέγιστο αριθμό λογαριασμών", "collections.sensitive": "Ευαίσθητο", "collections.topic_hint": "Προσθέστε μια ετικέτα που βοηθά άλλους να κατανοήσουν το κύριο θέμα αυτής της συλλογής.", "collections.view_collection": "Προβολή συλλογής", + "collections.view_other_collections_by_user": "Δείτε άλλες συλλογές από αυτόν τον χρήστη", "collections.visibility_public": "Δημόσια", "collections.visibility_public_hint": "Ανιχνεύσιμη στα αποτελέσματα αναζήτησης και σε άλλα σημεία όπου εμφανίζονται προτάσεις.", "collections.visibility_title": "Ορατότητα", @@ -976,13 +978,14 @@ "report.category.title_account": "προφίλ", "report.category.title_status": "ανάρτηση", "report.close": "Τέλος", + "report.collection_comment": "Γιατί θέλετε να αναφέρετε αυτήν τη συλλογή;", "report.comment.title": "Υπάρχει κάτι άλλο που νομίζεις ότι θα πρέπει να γνωρίζουμε;", "report.forward": "Προώθηση προς {target}", "report.forward_hint": "Ο λογαριασμός είναι από διαφορετικό διακομιστή. Να σταλεί ανώνυμο αντίγραφο της αναφοράς και εκεί;", "report.mute": "Σίγαση", "report.mute_explanation": "Δεν θα βλέπεις τις αναρτήσεις τους. Εκείνοι μπορούν ακόμη να σε ακολουθούν και να βλέπουν τις αναρτήσεις σου χωρίς να γνωρίζουν ότι είναι σε σίγαση.", "report.next": "Επόμενο", - "report.placeholder": "Επιπλέον σχόλια", + "report.placeholder": "Επιπρόσθετα σχόλια", "report.reasons.dislike": "Δεν μου αρέσει", "report.reasons.dislike_description": "Δεν είναι κάτι που θα ήθελες να δεις", "report.reasons.legal": "Είναι παράνομο", @@ -997,6 +1000,8 @@ "report.rules.title": "Ποιοι κανόνες παραβιάζονται;", "report.statuses.subtitle": "Επίλεξε όλα όσα ισχύουν", "report.statuses.title": "Υπάρχουν αναρτήσεις που τεκμηριώνουν αυτή την αναφορά;", + "report.submission_error": "Δεν ήταν δυνατή η υποβολή της αναφοράς", + "report.submission_error_details": "Παρακαλούμε ελέγξτε τη σύνδεση δικτύου σας και προσπαθήστε ξανά αργότερα.", "report.submit": "Υποβολή", "report.target": "Αναφορά {target}", "report.thanks.take_action": "Αυτές είναι οι επιλογές σας για να ελέγχετε τι βλέπετε στο Mastodon:", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index a3bc3ec52b2240..828e62368c754f 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.", "account_edit_tags.search_placeholder": "Enter a hashtag…", "account_edit_tags.suggestions": "Suggestions:", - "account_edit_tags.tag_status_count": "{count} posts", + "account_edit_tags.tag_status_count": "{count, plural, one {# post} other {# posts}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "No collections yet.", "collections.old_last_post_note": "Last posted over a week ago", "collections.remove_account": "Remove this account", + "collections.report_collection": "Report this collection", "collections.search_accounts_label": "Search for accounts to add…", "collections.search_accounts_max_reached": "You have added the maximum number of accounts", "collections.sensitive": "Sensitive", "collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.", "collections.view_collection": "View collection", + "collections.view_other_collections_by_user": "View other collections by this user", "collections.visibility_public": "Public", "collections.visibility_public_hint": "Discoverable in search results and other areas where recommendations appear.", "collections.visibility_title": "Visibility", @@ -976,6 +978,7 @@ "report.category.title_account": "profile", "report.category.title_status": "post", "report.close": "Done", + "report.collection_comment": "Why do you want to report this collection?", "report.comment.title": "Is there anything else you think we should know?", "report.forward": "Forward to {target}", "report.forward_hint": "The account is from another server. Send an anonymised copy of the report there as well?", @@ -997,6 +1000,8 @@ "report.rules.title": "Which rules are being violated?", "report.statuses.subtitle": "Select all that apply", "report.statuses.title": "Are there any posts that back up this report?", + "report.submission_error": "Report could not be submitted", + "report.submission_error_details": "Please check your network connection and try again later.", "report.submit": "Submit", "report.target": "Reporting {target}", "report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 7d05e475293b9c..eb371f6013366b 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir e interactuar con tu perfil. Las etiquetas destacadas aparecen como filtros en la vista de actividad de la página de tu perfil.", "account_edit_tags.search_placeholder": "Ingresá una etiqueta…", "account_edit_tags.suggestions": "Sugerencias:", - "account_edit_tags.tag_status_count": "{count} mensajes", + "account_edit_tags.tag_status_count": "{count, plural, one {voto} other {votos}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "No hay colecciones aún.", "collections.old_last_post_note": "Último mensaje hace más de una semana", "collections.remove_account": "Eliminar esta cuenta", + "collections.report_collection": "Denunciar esta colección", "collections.search_accounts_label": "Buscar cuentas para agregar…", "collections.search_accounts_max_reached": "Agregaste el número máximo de cuentas", "collections.sensitive": "Sensible", "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.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", "collections.visibility_public_hint": "Puede ser descubierta en los resultados de búsqueda y en otras áreas donde aparezcan recomendaciones.", "collections.visibility_title": "Visibilidad", @@ -976,6 +978,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "mensaje", "report.close": "Listo", + "report.collection_comment": "¿Por qué querés denunciar esta colección?", "report.comment.title": "¿Hay algo más que creés que deberíamos saber?", "report.forward": "Reenviar a {target}", "report.forward_hint": "La cuenta es de otro servidor. ¿Querés enviar una copia anonimizada del informe también ahí?", @@ -997,6 +1000,8 @@ "report.rules.title": "¿Qué reglas se están violando?", "report.statuses.subtitle": "Seleccioná todo lo que corresponda", "report.statuses.title": "¿Hay algún mensaje que respalde esta denuncia?", + "report.submission_error": "No se pudo enviar la denuncia", + "report.submission_error_details": "Por favor, revisá tu conexión a Internet e intentá de nuevo más tarde.", "report.submit": "Enviar", "report.target": "Denunciando a {target}", "report.thanks.take_action": "Acá están tus opciones para controlar lo que ves en Mastodon:", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index f8184648c2aed5..fc01be8f8e098d 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir tu perfil e interactuar con él. Aparecen como filtros en la vista Actividad de tu página de perfil.", "account_edit_tags.search_placeholder": "Introduce una etiqueta…", "account_edit_tags.suggestions": "Sugerencias:", - "account_edit_tags.tag_status_count": "{count} publicaciones", + "account_edit_tags.tag_status_count": "{count, plural,one {# publicación} other {# publicaciones}}", "account_note.placeholder": "Haz clic para añadir 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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "No hay colecciones todavía.", "collections.old_last_post_note": "Última publicación hace más de una semana", "collections.remove_account": "Eliminar esta cuenta", + "collections.report_collection": "Reportar esta colección", "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.sensitive": "Sensible", "collections.topic_hint": "Añade una etiqueta que ayude a los demás a comprender el tema principal de esta colección.", "collections.view_collection": "Ver colección", + "collections.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", "collections.visibility_public_hint": "Visible en los resultados de búsqueda y otras áreas donde aparecen recomendaciones.", "collections.visibility_title": "Visibilidad", @@ -976,6 +978,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Realizado", + "report.collection_comment": "¿Por qué quieres reportar esta colección?", "report.comment.title": "¿Hay algo más que creas que deberíamos saber?", "report.forward": "Reenviar a {target}", "report.forward_hint": "La cuenta es de otro servidor. ¿Enviar también una copia anónima del informe allí?", @@ -997,6 +1000,8 @@ "report.rules.title": "¿Cuáles reglas se están infringiendo?", "report.statuses.subtitle": "Seleccione todas las que apliquen", "report.statuses.title": "¿Hay alguna publicación que respalde esta denuncia?", + "report.submission_error": "No se pudo enviar el informe", + "report.submission_error_details": "Por favor, comprueba tu conexión a internet y vuelve a intentarlo más tarde.", "report.submit": "Enviar", "report.target": "Denunciando a {target}", "report.thanks.take_action": "Aquí están tus opciones para controlar lo que ves en Mastodon:", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 4722bbac7ebee4..11b99095c0eb34 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir e interactuar con tu perfil. Aparecen como filtros en la vista de actividad de tu página de perfil.", "account_edit_tags.search_placeholder": "Introduce una etiqueta…", "account_edit_tags.suggestions": "Sugerencias:", - "account_edit_tags.tag_status_count": "{count} publicaciones", + "account_edit_tags.tag_status_count": "{count, plural, one {# publicación} other {# publicaciones}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Aún no hay colecciones.", "collections.old_last_post_note": "Última publicación hace más de una semana", "collections.remove_account": "Borrar esta cuenta", + "collections.report_collection": "Reportar esta colección", "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.sensitive": "Sensible", "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.view_other_collections_by_user": "Ver otras colecciones de este usuario", "collections.visibility_public": "Pública", "collections.visibility_public_hint": "Puede mostrarse en los resultados de búsqueda y en otros lugares donde aparezcan recomendaciones.", "collections.visibility_title": "Visibilidad", @@ -976,6 +978,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Hecho", + "report.collection_comment": "¿Por qué quieres reportar esta colección?", "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?", @@ -997,6 +1000,8 @@ "report.rules.title": "¿Qué normas se están violando?", "report.statuses.subtitle": "Selecciona todos los que correspondan", "report.statuses.title": "¿Hay alguna publicación que respalde este informe?", + "report.submission_error": "No se pudo enviar el reporte", + "report.submission_error_details": "Comprueba tu conexión de red e intenta volver a intentarlo más tarde.", "report.submit": "Enviar", "report.target": "Reportando {target}", "report.thanks.take_action": "Aquí están tus opciones para controlar lo que ves en Mastodon:", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index d376b8017c859f..6c99cae2f2e106 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Esiteltävät aihetunnisteet auttavat käyttäjiä löytämään profiilisi ja olemaan vuorovaikutuksessa sen kanssa. Ne näkyvät suodattimina profiilisivusi Toiminta-näkymässä.", "account_edit_tags.search_placeholder": "Syötä aihetunniste…", "account_edit_tags.suggestions": "Ehdotuksia:", - "account_edit_tags.tag_status_count": "{count} julkaisua", + "account_edit_tags.tag_status_count": "{count, plural, one {# julkaisu} other {# julkaisua}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Ei vielä kokoelmia.", "collections.old_last_post_note": "Julkaissut viimeksi yli viikko sitten", "collections.remove_account": "Poista tämä tili", + "collections.report_collection": "Raportoi tämä kokoelma", "collections.search_accounts_label": "Hae lisättäviä tilejä…", "collections.search_accounts_max_reached": "Olet lisännyt enimmäismäärän tilejä", "collections.sensitive": "Arkaluonteinen", "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.view_other_collections_by_user": "Näytä muut tämän käyttäjän kokoelmat", "collections.visibility_public": "Julkinen", "collections.visibility_public_hint": "Löydettävissä hakutuloksista ja muualta, jossa ilmenee suosituksia.", "collections.visibility_title": "Näkyvyys", @@ -976,6 +978,7 @@ "report.category.title_account": "profiili", "report.category.title_status": "julkaisu", "report.close": "Valmis", + "report.collection_comment": "Miksi haluat raportoida tämän kokoelman?", "report.comment.title": "Onko vielä jotain muuta, mitä meidän pitäisi tietää?", "report.forward": "Välitä palvelimelle {target}", "report.forward_hint": "Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?", @@ -997,6 +1000,8 @@ "report.rules.title": "Mitä sääntöjä rikotaan?", "report.statuses.subtitle": "Valitse kaikki sopivat", "report.statuses.title": "Onko julkaisuja, jotka tukevat tätä raporttia?", + "report.submission_error": "Raporttia ei voitu lähettää", + "report.submission_error_details": "Tarkista verkkoyhteytesi ja yritä uudelleen myöhemmin.", "report.submit": "Lähetä", "report.target": "Raportoidaan {target}", "report.thanks.take_action": "Tässä on vaihtoehtosi hallita näkemääsi Mastodonissa:", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 23f5c7cbe8f794..b8ded5d2969a3e 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -167,7 +167,6 @@ "account_edit_tags.help_text": "Sermerkt frámerki hjálpa brúkarum at varnast og virka saman við vanga tínum. Tey síggjast sum filtur á virksemisvísingini av vanga tínum.", "account_edit_tags.search_placeholder": "Áset eitt frámerki…", "account_edit_tags.suggestions": "Uppskot:", - "account_edit_tags.tag_status_count": "{count} postar", "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/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index c94da6c3df8c5f..3bf2dceabd01e5 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -15,7 +15,7 @@ "about.rules": "Règles du serveur", "account.about": "À propos", "account.account_note_header": "Note personnelle", - "account.activity": "Activités", + "account.activity": "Activité", "account.add_note": "Ajouter une note personnelle", "account.add_or_remove_from_list": "Ajouter ou enlever de listes", "account.badges.admin": "Admin", @@ -145,6 +145,9 @@ "account_edit.bio.title": "Présentation", "account_edit.bio_modal.add_title": "Ajouter une présentation", "account_edit.bio_modal.edit_title": "Modifier la présentation", + "account_edit.button.add": "Ajouter {item}", + "account_edit.button.delete": "Supprimer {item}", + "account_edit.button.edit": "Modifier {item}", "account_edit.char_counter": "{currentLength}/{maxLength} caractères", "account_edit.column_button": "Terminé", "account_edit.column_title": "Modifier le profil", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Champs personnalisés", "account_edit.display_name.placeholder": "Votre nom public est le nom qui apparaît sur votre profil et dans les fils d'actualités.", "account_edit.display_name.title": "Nom public", + "account_edit.featured_hashtags.item": "hashtags", "account_edit.featured_hashtags.placeholder": "Aider les autres à identifier et à accéder rapidement à vos sujets préférés.", "account_edit.featured_hashtags.title": "Hashtags mis en avant", "account_edit.name_modal.add_title": "Ajouter un nom public", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", + "account_edit_tags.column_title": "Modifier les hashtags mis en avant", + "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", + "account_edit_tags.search_placeholder": "Saisir un hashtag…", + "account_edit_tags.suggestions": "Suggestions :", + "account_edit_tags.tag_status_count": "{count, plural, one {# message} other {# messages}}", "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", @@ -297,6 +306,7 @@ "collections.no_collections_yet": "Aucune collection pour le moment.", "collections.old_last_post_note": "Dernière publication il y a plus d'une semaine", "collections.remove_account": "Supprimer ce compte", + "collections.report_collection": "Signaler cette collection", "collections.search_accounts_label": "Chercher des comptes à ajouter…", "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.sensitive": "Sensible", @@ -768,7 +778,7 @@ "not_signed_in_indicator.not_signed_in": "Vous devez vous connecter pour accéder à cette ressource.", "notification.admin.report": "{name} a signalé {target}", "notification.admin.report_account": "{name} a signalé {count, plural, one {un message} other {# messages}} de {target} pour {category}", - "notification.admin.report_account_other": "{name} a signalé {count, plural, one {un message} other {# messages}} depuis {target}", + "notification.admin.report_account_other": "{name} a signalé {count, plural, one {un message} other {# messages}} de {target}", "notification.admin.report_statuses": "{name} a signalé {target} pour {category}", "notification.admin.report_statuses_other": "{name} a signalé {target}", "notification.admin.sign_up": "{name} s'est inscrit·e", @@ -967,6 +977,7 @@ "report.category.title_account": "ce profil", "report.category.title_status": "ce message", "report.close": "Terminé", + "report.collection_comment": "Pourquoi souhaitez-vous signaler cette collection ?", "report.comment.title": "Y a-t-il autre chose que nous devrions savoir?", "report.forward": "Transférer à {target}", "report.forward_hint": "Le compte provient d’un autre serveur. Envoyer une copie anonyme du rapport là-bas également?", @@ -988,6 +999,8 @@ "report.rules.title": "Quelles règles sont enfreintes?", "report.statuses.subtitle": "Sélectionnez toutes les réponses appropriées", "report.statuses.title": "Existe-t-il des messages pour étayer ce rapport?", + "report.submission_error": "Le signalement n’a pas pu être envoyé", + "report.submission_error_details": "Veuillez vérifier votre connexion réseau et réessayer plus tard.", "report.submit": "Envoyer", "report.target": "Signalement de {target}", "report.thanks.take_action": "Voici les possibilités que vous avez pour contrôler ce que vous voyez sur Mastodon:", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index a825414eff2213..0169a86547226e 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -15,7 +15,7 @@ "about.rules": "Règles du serveur", "account.about": "À propos", "account.account_note_header": "Note personnelle", - "account.activity": "Activités", + "account.activity": "Activité", "account.add_note": "Ajouter une note personnelle", "account.add_or_remove_from_list": "Ajouter ou retirer des listes", "account.badges.admin": "Admin", @@ -145,6 +145,9 @@ "account_edit.bio.title": "Présentation", "account_edit.bio_modal.add_title": "Ajouter une présentation", "account_edit.bio_modal.edit_title": "Modifier la présentation", + "account_edit.button.add": "Ajouter {item}", + "account_edit.button.delete": "Supprimer {item}", + "account_edit.button.edit": "Modifier {item}", "account_edit.char_counter": "{currentLength}/{maxLength} caractères", "account_edit.column_button": "Terminé", "account_edit.column_title": "Modifier le profil", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Champs personnalisés", "account_edit.display_name.placeholder": "Votre nom public est le nom qui apparaît sur votre profil et dans les fils d'actualités.", "account_edit.display_name.title": "Nom public", + "account_edit.featured_hashtags.item": "hashtags", "account_edit.featured_hashtags.placeholder": "Aider les autres à identifier et à accéder rapidement à vos sujets préférés.", "account_edit.featured_hashtags.title": "Hashtags mis en avant", "account_edit.name_modal.add_title": "Ajouter un nom public", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", + "account_edit_tags.column_title": "Modifier les hashtags mis en avant", + "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", + "account_edit_tags.search_placeholder": "Saisir un hashtag…", + "account_edit_tags.suggestions": "Suggestions :", + "account_edit_tags.tag_status_count": "{count, plural, one {# message} other {# messages}}", "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", @@ -297,6 +306,7 @@ "collections.no_collections_yet": "Aucune collection pour le moment.", "collections.old_last_post_note": "Dernière publication il y a plus d'une semaine", "collections.remove_account": "Supprimer ce compte", + "collections.report_collection": "Signaler cette collection", "collections.search_accounts_label": "Chercher des comptes à ajouter…", "collections.search_accounts_max_reached": "Vous avez ajouté le nombre maximum de comptes", "collections.sensitive": "Sensible", @@ -768,7 +778,7 @@ "not_signed_in_indicator.not_signed_in": "Vous devez vous connecter pour accéder à cette ressource.", "notification.admin.report": "{name} a signalé {target}", "notification.admin.report_account": "{name} a signalé {count, plural, one {un message} other {# messages}} de {target} pour {category}", - "notification.admin.report_account_other": "{name} a signalé {count, plural, one {un message} other {# messages}} depuis {target}", + "notification.admin.report_account_other": "{name} a signalé {count, plural, one {un message} other {# messages}} de {target}", "notification.admin.report_statuses": "{name} a signalé {target} pour {category}", "notification.admin.report_statuses_other": "{name} a signalé {target}", "notification.admin.sign_up": "{name} s'est inscrit·e", @@ -967,6 +977,7 @@ "report.category.title_account": "ce profil", "report.category.title_status": "ce message", "report.close": "Terminé", + "report.collection_comment": "Pourquoi souhaitez-vous signaler cette collection ?", "report.comment.title": "Y a-t-il autre chose que nous devrions savoir ?", "report.forward": "Transférer à {target}", "report.forward_hint": "Le compte provient d’un autre serveur. Envoyer également une copie anonyme du rapport ?", @@ -988,6 +999,8 @@ "report.rules.title": "Quelles règles sont enfreintes ?", "report.statuses.subtitle": "Sélectionnez toutes les réponses appropriées", "report.statuses.title": "Existe-t-il des messages pour étayer ce rapport ?", + "report.submission_error": "Le signalement n’a pas pu être envoyé", + "report.submission_error_details": "Veuillez vérifier votre connexion réseau et réessayer plus tard.", "report.submit": "Envoyer", "report.target": "Signalement de {target}", "report.thanks.take_action": "Voici les possibilités que vous avez pour contrôler ce que vous voyez sur Mastodon :", @@ -996,7 +1009,7 @@ "report.thanks.title_actionable": "Merci pour votre signalement, nous allons investiguer.", "report.unfollow": "Ne plus suivre @{name}", "report.unfollow_explanation": "Vous êtes abonné à ce compte. Pour ne plus voir ses messages dans votre fil principal, retirez-le de votre liste d'abonnements.", - "report_notification.attached_statuses": "{count, plural, one {{count} message lié} other {{count} messages liés}}", + "report_notification.attached_statuses": "{count, plural, one {{count} message joint} other {{count} messages joints}}", "report_notification.categories.legal": "Légal", "report_notification.categories.legal_sentence": "contenu illégal", "report_notification.categories.other": "Autre", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 6d457b2f367b25..cd2ea68e363224 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -145,6 +145,9 @@ "account_edit.bio.title": "Beathaisnéis", "account_edit.bio_modal.add_title": "Cuir beathaisnéis leis", "account_edit.bio_modal.edit_title": "Cuir beathaisnéis in eagar", + "account_edit.button.add": "Cuir {item} leis", + "account_edit.button.delete": "Scrios {item}", + "account_edit.button.edit": "Cuir {item} in eagar", "account_edit.char_counter": "{currentLength}/{maxLength} carachtair", "account_edit.column_button": "Déanta", "account_edit.column_title": "Cuir Próifíl in Eagar", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Réimsí saincheaptha", "account_edit.display_name.placeholder": "Is é d’ainm taispeána an chaoi a bhfeictear d’ainm ar do phróifíl agus in amlínte.", "account_edit.display_name.title": "Ainm taispeána", + "account_edit.featured_hashtags.item": "haischlibeanna", "account_edit.featured_hashtags.placeholder": "Cabhraigh le daoine eile do thopaicí is fearr leat a aithint, agus rochtain thapa a bheith acu orthu.", "account_edit.featured_hashtags.title": "Haischlibeanna Réadmhaoine", "account_edit.name_modal.add_title": "Cuir ainm taispeána leis", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Saincheap na cluaisíní ar do phróifíl agus a bhfuil á thaispeáint iontu.", "account_edit.profile_tab.title": "Socruithe an chluaisín próifíle", "account_edit.save": "Sábháil", + "account_edit_tags.column_title": "Cuir haischlibeanna le feiceáil in eagar", + "account_edit_tags.help_text": "Cuidíonn haischlibeanna le húsáideoirí do phróifíl a aimsiú agus idirghníomhú léi. Feictear iad mar scagairí ar radharc Gníomhaíochta do leathanaigh Phróifíle.", + "account_edit_tags.search_placeholder": "Cuir isteach haischlib…", + "account_edit_tags.suggestions": "Moltaí:", + "account_edit_tags.tag_status_count": "{count, plural, one {# post} two {# poist} few {# poist} many {# poist} other {# poist}}", "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ú", @@ -283,6 +292,7 @@ "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í 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", @@ -291,13 +301,18 @@ "collections.manage_accounts": "Bainistigh cuntais", "collections.mark_as_sensitive": "Marcáil mar íogair", "collections.mark_as_sensitive_hint": "Folaíonn sé cur síos agus cuntais an bhailiúcháin taobh thiar de rabhadh ábhair. Beidh ainm an bhailiúcháin le feiceáil fós.", + "collections.name_length_hint": "Teorainn 40 carachtar", "collections.new_collection": "Bailiúchán nua", "collections.no_collections_yet": "Gan aon bhailiúcháin fós.", + "collections.old_last_post_note": "Postáilte go deireanach breis agus seachtain ó shin", "collections.remove_account": "Bain an cuntas seo", + "collections.report_collection": "Tuairiscigh an bailiúchán 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.sensitive": "Íogair", "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.view_other_collections_by_user": "Féach ar bhailiúcháin eile ón úsáideoir seo", "collections.visibility_public": "Poiblí", "collections.visibility_public_hint": "Infheicthe i dtorthaí cuardaigh agus i réimsí eile ina bhfuil moltaí le feiceáil.", "collections.visibility_title": "Infheictheacht", @@ -386,6 +401,9 @@ "confirmations.discard_draft.post.title": "An bhfuil tú ag iarraidh do dhréachtphost a chaitheamh amach?", "confirmations.discard_edit_media.confirm": "Faigh réidh de", "confirmations.discard_edit_media.message": "Tá athruithe neamhshlánaithe don tuarascáil gné nó réamhamharc agat, faigh réidh dóibh ar aon nós?", + "confirmations.follow_to_collection.confirm": "Lean agus cuir leis an mbailiúchán", + "confirmations.follow_to_collection.message": "Ní mór duit a bheith ag leanúint {name} le go gcuirfidh tú iad le bailiúchán.", + "confirmations.follow_to_collection.title": "Lean an cuntas?", "confirmations.follow_to_list.confirm": "Lean agus cuir leis an liosta", "confirmations.follow_to_list.message": "Ní mór duit {name} a leanúint chun iad a chur le liosta.", "confirmations.follow_to_list.title": "Lean an t-úsáideoir?", @@ -960,6 +978,7 @@ "report.category.title_account": "próifíl", "report.category.title_status": "postáil", "report.close": "Déanta", + "report.collection_comment": "Cén fáth ar mhaith leat an bailiúchán seo a thuairisciú?", "report.comment.title": "An bhfuil aon rud eile ba chóir dúinn a fhios agat, dar leat?", "report.forward": "Seol ar aghaidh chun {target}", "report.forward_hint": "Is ó fhreastalaí eile an cuntas. Cuir cóip gan ainm den tuarascáil ansin freisin?", @@ -981,6 +1000,8 @@ "report.rules.title": "Cén rialacha atá á sárú?", "report.statuses.subtitle": "Roghnaigh gach atá i bhfeidhm", "report.statuses.title": "An bhfuil aon phoist a thacaíonn leis an tuarascáil seo?", + "report.submission_error": "Níorbh fhéidir an tuarascáil a chur isteach", + "report.submission_error_details": "Seiceáil do nasc líonra agus déan iarracht arís ar ball.", "report.submit": "Cuir isteach", "report.target": "Ag tuairisciú {target}", "report.thanks.take_action": "Seo do roghanna chun an méid a fheiceann tú ar Mastodon a rialú:", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 15360f51c909ad..4ab4a38e0b8bef 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -145,6 +145,9 @@ "account_edit.bio.title": "Sobre ti", "account_edit.bio_modal.add_title": "Engadir biografía", "account_edit.bio_modal.edit_title": "Editar biografía", + "account_edit.button.add": "Engadir {item}", + "account_edit.button.delete": "Eliminar {item}", + "account_edit.button.edit": "Editar {item}", "account_edit.char_counter": "{currentLength}/{maxLength} caracteres", "account_edit.column_button": "Feito", "account_edit.column_title": "Editar perfil", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Campos personalizados", "account_edit.display_name.placeholder": "O nome público é o nome que aparece no perfil e nas cronoloxías.", "account_edit.display_name.title": "Nome público", + "account_edit.featured_hashtags.item": "cancelos", "account_edit.featured_hashtags.placeholder": "Facilita que te identifiquen, e da acceso rápido aos teus intereses favoritos.", "account_edit.featured_hashtags.title": "Cancelos destacados", "account_edit.name_modal.add_title": "Engadir nome público", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Personaliza as pestanas e o seu contido no teu perfil.", "account_edit.profile_tab.title": "Perfil e axustes das pestanas", "account_edit.save": "Gardar", + "account_edit_tags.column_title": "Editar cancelos destacados", + "account_edit_tags.help_text": "Os cancelos destacados axúdanlle ás usuarias a atopar e interactuar co teu perfil. Aparecen como filtros na túa páxina de perfil na vista Actividade.", + "account_edit_tags.search_placeholder": "Escribe un cancelo…", + "account_edit_tags.suggestions": "Suxestións:", + "account_edit_tags.tag_status_count": "{count, plural, one {# publicación} other {# publicacións}}", "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", @@ -297,11 +306,13 @@ "collections.no_collections_yet": "Aínda non tes coleccións.", "collections.old_last_post_note": "Hai máis dunha semana da última publicación", "collections.remove_account": "Retirar esta conta", + "collections.report_collection": "Denunciar esta colección", "collections.search_accounts_label": "Buscar contas para engadir…", "collections.search_accounts_max_reached": "Acadaches o máximo de contas permitidas", "collections.sensitive": "Sensible", "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.view_other_collections_by_user": "Ver outras coleccións desta usuaria", "collections.visibility_public": "Pública", "collections.visibility_public_hint": "Pódese atopar nos resultados das buscas e noutras áreas onde se mostran recomendacións.", "collections.visibility_title": "Visibilidade", @@ -967,6 +978,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Feito", + "report.collection_comment": "Por que queres denunciar esta colección?", "report.comment.title": "Hai algo máis que creas debamos saber?", "report.forward": "Reenviar a {target}", "report.forward_hint": "A conta é doutro servidor. Enviar unha copia anónima da denuncia aló tamén?", @@ -988,6 +1000,8 @@ "report.rules.title": "Que regras foron incumpridas?", "report.statuses.subtitle": "Elixe todo o que corresponda", "report.statuses.title": "Hai algunha publicación que apoie esta denuncia?", + "report.submission_error": "Non se puido enviar a denuncia", + "report.submission_error_details": "Comproba a conexión á rede e volve a intentalo máis tarde.", "report.submit": "Enviar", "report.target": "Denunciar a {target}", "report.thanks.take_action": "Aquí tes unhas opcións para controlar o que ves en Mastodon:", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 78f5ab7ab9dc38..1efc3427de2433 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "תגיות נבחרות עוזרות למשתמשים לגלות ולהשתמש בפרופיל שלך. הן יופיעו כסננים במבט הפעילויות על עמוד הפרופיל שלך.", "account_edit_tags.search_placeholder": "הזנת תגית…", "account_edit_tags.suggestions": "הצעות:", - "account_edit_tags.tag_status_count": "{count} הודעות", + "account_edit_tags.tag_status_count": "{count, plural, one {הודעה אחת} two {הודעותיים} other {# הודעות}}", "account_note.placeholder": "יש ללחוץ כדי להוסיף הערות", "admin.dashboard.daily_retention": "קצב שימור משתמשים יומי אחרי ההרשמה", "admin.dashboard.monthly_retention": "קצב שימור משתמשים (פר חודש) אחרי ההרשמה", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "עוד אין אוספים.", "collections.old_last_post_note": "פרסמו לאחרונה לפני יותר משבוע", "collections.remove_account": "הסר חשבון זה", + "collections.report_collection": "דיווח על אוסף זה", "collections.search_accounts_label": "לחפש חשבונות להוספה…", "collections.search_accounts_max_reached": "הגעת למספר החשבונות המירבי", "collections.sensitive": "רגיש", "collections.topic_hint": "הוספת תגית שמסייעת לאחרים להבין את הנושא הראשי של האוסף.", "collections.view_collection": "צפיה באוסף", + "collections.view_other_collections_by_user": "צפייה באוספים אחרים של משתמש.ת אלו", "collections.visibility_public": "פומבי", "collections.visibility_public_hint": "זמין לגילוי בתוצאות חיפוש ושאר אזורים בהם מופיעות המלצות.", "collections.visibility_title": "ניראות", @@ -976,6 +978,7 @@ "report.category.title_account": "פרופיל", "report.category.title_status": "הודעה", "report.close": "בוצע", + "report.collection_comment": "מדוע ברצונכם לדווח על האוסף הזה?", "report.comment.title": "האם יש דבר נוסף שלדעתך חשוב שנדע?", "report.forward": "קדם ל-{target}", "report.forward_hint": "חשבון זה הוא משרת אחר. האם לשלוח בנוסף עותק אנונימי לשם?", @@ -997,6 +1000,8 @@ "report.rules.title": "אילו חוקים מופרים?", "report.statuses.subtitle": "בחר/י את כל המתאימים", "report.statuses.title": "האם ישנן הודעות התומכות בדיווח זה?", + "report.submission_error": "לא ניתן לבצע את הדיווח", + "report.submission_error_details": "נא לבדוק את חיבור הרשת ולנסות שוב מאוחר יותר.", "report.submit": "שליחה", "report.target": "דיווח על {target}", "report.thanks.take_action": "הנה כמה אפשרויות לשליטה בתצוגת מסטודון:", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 065ee799291abf..a0288b838a7a13 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Myllumerki með aukið vægi hjálpa lesendum að finna og eiga við notandasíðuna þína. Þau birtast sem síur í virkniflipa notandasíðunnar þinnar.", "account_edit_tags.search_placeholder": "Settu inn myllumerki…", "account_edit_tags.suggestions": "Tillögur:", - "account_edit_tags.tag_status_count": "{count} færslur", + "account_edit_tags.tag_status_count": "{count, plural, one {# færsla} other {# færslur}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Engin söfn ennþá.", "collections.old_last_post_note": "Birti síðast fyrir meira en viku síðan", "collections.remove_account": "Fjarlægja þennan aðgang", + "collections.report_collection": "Kæra þetta safn", "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.sensitive": "Viðkvæmt", "collections.topic_hint": "Bættu við myllumerki sem hjálpar öðrum að skilja aðalefni þessa safns.", "collections.view_collection": "Skoða safn", + "collections.view_other_collections_by_user": "Skoða önnur söfn frá þessum notanda", "collections.visibility_public": "Opinbert", "collections.visibility_public_hint": "Hægt að finna í leitarniðurstöðum og öðrum þeim þáttum þar sem meðmæli birtast.", "collections.visibility_title": "Sýnileiki", @@ -976,6 +978,7 @@ "report.category.title_account": "notandasnið", "report.category.title_status": "færsla", "report.close": "Lokið", + "report.collection_comment": "Hvers vegna viltu kæra þetta safn?", "report.comment.title": "Er eitthvað annað sem þú heldur að við ættum að vita?", "report.forward": "Áframsenda til {target}", "report.forward_hint": "Notandaaðgangurinn er af öðrum vefþjóni. Á einnig að senda nafnlaust afrit af kærunni þangað?", @@ -997,6 +1000,8 @@ "report.rules.title": "Hvaða reglur eru brotnar?", "report.statuses.subtitle": "Veldu allt sem á við", "report.statuses.title": "Eru einhverjar færslur sem styðja þessa kæru?", + "report.submission_error": "Ekki var hægt að senda inn kæruna", + "report.submission_error_details": "Athugaðu nettenginguna þína og prófaðu aftur síðar.", "report.submit": "Senda inn", "report.target": "Kæri {target}", "report.thanks.take_action": "Hér eru nokkrir valkostir til að stýra hvað þú sérð á Mastodon:", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 6a0d7b731ea545..132e0633793af4 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Gli hashtag in evidenza aiutano gli utenti a scoprire e interagire con il tuo profilo. Appaiono come filtri nella visualizzazione Attività della tua pagina del profilo.", "account_edit_tags.search_placeholder": "Inserisci un hashtag…", "account_edit_tags.suggestions": "Suggerimenti:", - "account_edit_tags.tag_status_count": "{count} post", + "account_edit_tags.tag_status_count": "{count, plural, one {# post} other {# post}}", "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", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Nessuna collezione ancora.", "collections.old_last_post_note": "Ultimo post più di una settimana fa", "collections.remove_account": "Rimuovi questo account", + "collections.report_collection": "Segnala questa collezione", "collections.search_accounts_label": "Cerca account da aggiungere…", "collections.search_accounts_max_reached": "Hai aggiunto il numero massimo di account", "collections.sensitive": "Sensibile", "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.view_other_collections_by_user": "Visualizza altre collezioni da questo utente", "collections.visibility_public": "Pubblica", "collections.visibility_public_hint": "Scopribile nei risultati di ricerca e in altre aree in cui compaiono i suggerimenti.", "collections.visibility_title": "Visibilità", @@ -976,6 +978,7 @@ "report.category.title_account": "profilo", "report.category.title_status": "post", "report.close": "Fatto", + "report.collection_comment": "Perché vuoi segnalare questa collezione?", "report.comment.title": "C'è altro che pensi che dovremmo sapere?", "report.forward": "Inoltra a {target}", "report.forward_hint": "Il profilo proviene da un altro server. Inviare anche lì una copia anonima del rapporto?", @@ -997,6 +1000,8 @@ "report.rules.title": "Quali regole sono violate?", "report.statuses.subtitle": "Seleziona tutte le risposte pertinenti", "report.statuses.title": "Ci sono dei post a sostegno di questa segnalazione?", + "report.submission_error": "Non è stato possibile inviare la segnalazione", + "report.submission_error_details": "Si prega di controllare la tua connessione di rete e riprovare più tardi.", "report.submit": "Invia", "report.target": "Segnalando {target}", "report.thanks.take_action": "Ecco le tue opzioni per controllare cosa vedi su Mastodon:", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 3628849bedb401..625effdde28a4c 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -167,7 +167,6 @@ "account_edit_tags.help_text": "Aanbevolen hashtags helpen gebruikers je profiel te ontdekken en te communiceren. Ze verschijnen als filters op de activiteitenweergave van je pagina.", "account_edit_tags.search_placeholder": "Voer een hashtag in…", "account_edit_tags.suggestions": "Suggesties:", - "account_edit_tags.tag_status_count": "{count} berichten", "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", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 30db158915bb7c..de805231ede445 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -145,6 +145,9 @@ "account_edit.bio.title": "Om meg", "account_edit.bio_modal.add_title": "Skriv om deg sjølv", "account_edit.bio_modal.edit_title": "Endre bio", + "account_edit.button.add": "Legg til {item}", + "account_edit.button.delete": "Slett {item}", + "account_edit.button.edit": "Rediger {item}", "account_edit.char_counter": "{currentLength}/{maxLength} teikn", "account_edit.column_button": "Ferdig", "account_edit.column_title": "Rediger profil", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Eigne felt", "account_edit.display_name.placeholder": "Det synlege namnet ditt er det som syner på profilen din og i tidsliner.", "account_edit.display_name.title": "Synleg namn", + "account_edit.featured_hashtags.item": "emneknaggar", "account_edit.featured_hashtags.placeholder": "Hjelp andre å finna og få rask tilgang til favorittemna dine.", "account_edit.featured_hashtags.title": "Utvalde emneknaggar", "account_edit.name_modal.add_title": "Legg til synleg namn", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Tilpass fanene på profilen din og kva dei syner.", "account_edit.profile_tab.title": "Innstillingar for profilfane", "account_edit.save": "Lagre", + "account_edit_tags.column_title": "Rediger utvalde emneknaggar", + "account_edit_tags.help_text": "Utvalde emneknaggar hjelper folk å oppdaga og samhandla med profilen din. Dei blir viste som filter på aktivitetsoversikta på profilsida di.", + "account_edit_tags.search_placeholder": "Skriv ein emneknagg…", + "account_edit_tags.suggestions": "Framlegg:", + "account_edit_tags.tag_status_count": "{count, plural, one {# innlegg} other {# innlegg}}", "account_note.placeholder": "Klikk for å leggja til merknad", "admin.dashboard.daily_retention": "Mengda brukarar aktive ved dagar etter registrering", "admin.dashboard.monthly_retention": "Mengda brukarar aktive ved månader etter registrering", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 9523cf6e101e47..e86f8c72f9fcb7 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -106,9 +106,9 @@ "account.mutual": "Vocês se seguem", "account.name.help.domain": "{domain} é o servidor que hospeda o perfil e publicações do usuário.", "account.name.help.domain_self": "{domain} é o seu servidor que hospeda seu perfil e publicações.", - "account.name.help.footer": "Da mesma forma que você pode enviar emails para pessoas utilizando diferentes clientes de email, você pode interagir com pessoas em outros servidores do Mastodon – e com qualquer um em outros aplicativos sociais regidos pelo mesmo conjunto de regras que o Mastodon (chamadas de Protocolo ActivityPub).", - "account.name.help.header": "Um nome de usuário é como um endereço de email", - "account.name.help.username": "{username} é o nome de usuário desta conta no servidor dela. Alguém em outro servidor pode ter o mesmo nome de usuário.", + "account.name.help.footer": "Assim como pode enviar mensagens eletrônicas de serviços diferentes, você pode interagir com pessoas de outros servidores Mastodon — e qualquer pessoa em um aplicativo alimentado com as regras utilizadas pelo Mastodon (protocolo ActivityPub).", + "account.name.help.header": "Um identificador é como um endereço de endereço eletrônico", + "account.name.help.username": "{username} é o nome de usuário da conta neste servidor. Alguém em outro servidor pode ter o mesmo nome de usuário.", "account.name.help.username_self": "{username} é seu nome de usuário neste servidor. Alguém em outro servidor pode ter o mesmo nome de usuário.", "account.name_info": "O que isto significa?", "account.no_bio": "Nenhuma descrição fornecida.", @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Hashtags em destaque ajudam os usuários a descobrir e interagir com seu perfil. Elas aparecem como filtros na visualização de Atividade da sua página de Perfil.", "account_edit_tags.search_placeholder": "Insira uma hashtag…", "account_edit_tags.suggestions": "Sugestões:", - "account_edit_tags.tag_status_count": "{count} publicações", + "account_edit_tags.tag_status_count": "{count, plural, one {# publicação} other {# publicações}}", "account_note.placeholder": "Nota pessoal sobre este perfil aqui", "admin.dashboard.daily_retention": "Taxa de retenção de usuários por dia, após a inscrição", "admin.dashboard.monthly_retention": "Taxa de retenção de usuários por mês, após a inscrição", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 58e6847b4ff425..5c84ee3f2dd503 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Hashtag-ët e zgjedhur i ndihmojnë përdoruesit të zbulojnë dhe ndërveprojnë me profilin tuaj. Ata duken si filtra te pamja Veprimtari e faqes tuaj të Profilit.", "account_edit_tags.search_placeholder": "Jepni një hashtag…", "account_edit_tags.suggestions": "Sugjerime:", - "account_edit_tags.tag_status_count": "{count} postime", + "account_edit_tags.tag_status_count": "{count, plural, one {# postim} other {# postime}}", "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", @@ -303,11 +303,13 @@ "collections.no_collections_yet": "Ende pa koleksione.", "collections.old_last_post_note": "Të postuarat e fundit gjatë një jave më parë", "collections.remove_account": "Hiqe këtë llogari", + "collections.report_collection": "Raportojeni këtë koleksion", "collections.search_accounts_label": "Kërkoni për llogari për shtim…", "collections.search_accounts_max_reached": "Keni shtuar numrin maksimum të llogarive", "collections.sensitive": "Rezervat", "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.view_other_collections_by_user": "Shihni koleksione të tjera nga ky përdorues", "collections.visibility_public": "Publik", "collections.visibility_public_hint": "I zbulueshëm në përfundime kërkimi dhe fusha të tjera ku shfaqen rekomandime.", "collections.visibility_title": "Dukshmëri", @@ -973,6 +975,7 @@ "report.category.title_account": "profil", "report.category.title_status": "postim", "report.close": "U bë", + "report.collection_comment": "Pse doni ta raportoni këtë koleksion?", "report.comment.title": "Ka ndonjë gjë tjetër që do të duhej ta dinim?", "report.forward": "Përcillja {target}", "report.forward_hint": "Llogaria është nga një shërbyes tjetër. Të dërgohet edhe një kopje e anonimizuar e raportimit?", @@ -994,6 +997,8 @@ "report.rules.title": "Cilat rregulla po cenohen?", "report.statuses.subtitle": "Përzgjidhni gjithçka që ka vend", "report.statuses.title": "A ka postime që dëshmojnë problemet e këtij raporti?", + "report.submission_error": "Raportimi s’u parashtrua dot", + "report.submission_error_details": "Ju lutemi, kontrolloni lidhjen tuaj në rrjet dhe riprovoni më vonë.", "report.submit": "Parashtroje", "report.target": "Raportim i {target}", "report.thanks.take_action": "Ja mundësitë tuaja për të kontrolluar ç’shihni në Mastodon:", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 7d9b2d2024541a..f06613fc875637 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -874,6 +874,7 @@ "report.rules.title": "Vilka regler överträds?", "report.statuses.subtitle": "Välj alla som stämmer", "report.statuses.title": "Finns det några inlägg som stöder denna rapport?", + "report.submission_error_details": "Kontrollera din nätverksanslutning och försök igen senare.", "report.submit": "Skicka", "report.target": "Rapporterar {target}", "report.thanks.take_action": "Här är dina alternativ för att bestämma vad du ser på Mastodon:", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index bba44fa2056e60..640295ac6e9ca5 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -145,6 +145,9 @@ "account_edit.bio.title": "Kişisel bilgiler", "account_edit.bio_modal.add_title": "Kişisel bilgi ekle", "account_edit.bio_modal.edit_title": "Kişisel bilgiyi düzenle", + "account_edit.button.add": "{item} ekle", + "account_edit.button.delete": "{item} sil", + "account_edit.button.edit": "{item} düzenle", "account_edit.char_counter": "{currentLength}/{maxLength} karakter", "account_edit.column_button": "Tamamlandı", "account_edit.column_title": "Profili Düzenle", @@ -152,6 +155,7 @@ "account_edit.custom_fields.title": "Özel alanlar", "account_edit.display_name.placeholder": "Görünen adınız profilinizde ve zaman akışlarında adınızın nasıl göründüğüdür.", "account_edit.display_name.title": "Görünen ad", + "account_edit.featured_hashtags.item": "etiketler", "account_edit.featured_hashtags.placeholder": "Başkalarının favori konularınızı tanımlamasına ve bunlara hızlı bir şekilde erişmesine yardımcı olun.", "account_edit.featured_hashtags.title": "Öne çıkan etiketler", "account_edit.name_modal.add_title": "Görünen ad ekle", @@ -159,6 +163,11 @@ "account_edit.profile_tab.subtitle": "Profilinizdeki sekmeleri ve bunların görüntülediği bilgileri özelleştirin.", "account_edit.profile_tab.title": "Profil sekme ayarları", "account_edit.save": "Kaydet", + "account_edit_tags.column_title": "Öne çıkarılmış etiketleri düzenle", + "account_edit_tags.help_text": "Öne çıkan etiketler kullanıcıların profilinizi keşfetmesine ve etkileşim kurmasına yardımcı olur. Profil sayfanızın Etkinlik görünümünde filtreler olarak görünürler.", + "account_edit_tags.search_placeholder": "Bir etiket girin…", + "account_edit_tags.suggestions": "Öneriler:", + "account_edit_tags.tag_status_count": "{count, plural, one {# gönderi} other {# gönderi}}", "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 150cd68d9f8a36..0a0885f73dd5f3 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "Hashtag thường dùng giúp bạn mọi người khám phá và tương tác với hồ sơ của bạn. Chúng xuất hiện như những bộ lọc trên phần Hoạt động hồ sơ.", "account_edit_tags.search_placeholder": "Nhập một hashtag…", "account_edit_tags.suggestions": "Được đề xuất:", - "account_edit_tags.tag_status_count": "{count} tút", + "account_edit_tags.tag_status_count": "{count, plural, other {# tút}}", "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ý", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "Chưa có collection.", "collections.old_last_post_note": "Đăng lần cuối hơn một tuần trước", "collections.remove_account": "Gỡ tài khoản này", + "collections.report_collection": "Báo cáo collection 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.sensitive": "Nhạy cảm", "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.view_other_collections_by_user": "View những collection khác từ tài khoản này", "collections.visibility_public": "Công khai", "collections.visibility_public_hint": "Có thể tìm thấy trong kết quả tìm kiếm và các khu vực khác nơi xuất hiện đề xuất.", "collections.visibility_title": "Hiển thị", @@ -976,6 +978,7 @@ "report.category.title_account": "Người", "report.category.title_status": "Tút", "report.close": "Xong", + "report.collection_comment": "Vì sao bạn muốn báo cáo collection này?", "report.comment.title": "Có điều gì mà chúng tôi cần biết không?", "report.forward": "Chuyển đến {target}", "report.forward_hint": "Người này thuộc máy chủ khác. Gửi một báo cáo ẩn danh tới máy chủ đó?", @@ -997,6 +1000,8 @@ "report.rules.title": "Vi phạm nội quy nào?", "report.statuses.subtitle": "Chọn tất cả những gì phù hợp", "report.statuses.title": "Bạn muốn báo cáo tút nào?", + "report.submission_error": "Không thể gửi báo cáo", + "report.submission_error_details": "Kiểm tra kết nối mạng và thử lại sau.", "report.submit": "Gửi đi", "report.target": "Báo cáo {target}", "report.thanks.take_action": "Đây là cách kiểm soát những thứ mà bạn thấy:", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 157351a7c45ad4..60a89015dcbb51 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "精选话题标签可以帮助他人发现并与你的个人资料互动。这些标签会作为过滤器条件出现在你个人资料页面的活动视图中。", "account_edit_tags.search_placeholder": "输入话题标签…", "account_edit_tags.suggestions": "建议:", - "account_edit_tags.tag_status_count": "{count} 条嘟文", + "account_edit_tags.tag_status_count": "{count, plural, other {# 条嘟文}}", "account_note.placeholder": "点击添加备注", "admin.dashboard.daily_retention": "注册后用户留存率(按日计算)", "admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "尚无收藏列表。", "collections.old_last_post_note": "上次发言于一周多以前", "collections.remove_account": "移除此账号", + "collections.report_collection": "举报此收藏列表", "collections.search_accounts_label": "搜索要添加的账号…", "collections.search_accounts_max_reached": "你添加的账号数量已达上限", "collections.sensitive": "敏感内容", "collections.topic_hint": "添加话题标签,帮助他人了解此收藏列表的主题。", "collections.view_collection": "查看收藏列表", + "collections.view_other_collections_by_user": "查看此用户的其他收藏列表", "collections.visibility_public": "公开", "collections.visibility_public_hint": "可在搜索结果及其他推荐功能可用的区域被发现。", "collections.visibility_title": "可见性", @@ -976,6 +978,7 @@ "report.category.title_account": "账号", "report.category.title_status": "嘟文", "report.close": "完成", + "report.collection_comment": "举报此收藏列表的原因是什么?", "report.comment.title": "还有什么你认为我们应该知道的吗?", "report.forward": "转发举报至 {target}", "report.forward_hint": "这名用户来自另一个服务器。是否要向那个服务器发送一条匿名的举报?", @@ -997,6 +1000,8 @@ "report.rules.title": "违反了哪些规则?", "report.statuses.subtitle": "选择全部适用选项", "report.statuses.title": "是否有任何嘟文可以支持这一报告?", + "report.submission_error": "无法提交举报", + "report.submission_error_details": "请检查网络连接,然后再试一次。", "report.submit": "提交", "report.target": "举报 {target}", "report.thanks.take_action": "以下是你控制你在 Mastodon 上能看到哪些内容的选项:", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 43612f11720585..9f3befc52aa929 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -167,7 +167,7 @@ "account_edit_tags.help_text": "推薦主題標籤幫助其他人發現並與您的個人檔案互動。它們將作為過濾器出現於您個人檔案頁面之動態中。", "account_edit_tags.search_placeholder": "請輸入主題標籤…", "account_edit_tags.suggestions": "建議:", - "account_edit_tags.tag_status_count": "{count} 則嘟文", + "account_edit_tags.tag_status_count": "{count, plural, other {# 則嘟文}}", "account_note.placeholder": "點擊以新增備註", "admin.dashboard.daily_retention": "註冊後使用者存留率(日)", "admin.dashboard.monthly_retention": "註冊後使用者存留率(月)", @@ -306,11 +306,13 @@ "collections.no_collections_yet": "您沒有任何收藏名單。", "collections.old_last_post_note": "上次發表嘟文已超過一週", "collections.remove_account": "移除此帳號", + "collections.report_collection": "檢舉此收藏名單", "collections.search_accounts_label": "搜尋帳號以加入...", "collections.search_accounts_max_reached": "您新增之帳號數已達上限", "collections.sensitive": "敏感內容", "collections.topic_hint": "新增主題標籤以協助其他人瞭解此收藏名單之主題。", "collections.view_collection": "檢視收藏名單", + "collections.view_other_collections_by_user": "檢視此使用者之其他收藏名單", "collections.visibility_public": "公開", "collections.visibility_public_hint": "可於搜尋結果與其他推薦處可見。", "collections.visibility_title": "可見性", @@ -767,7 +769,7 @@ "navigation_bar.mutes": "已靜音的使用者", "navigation_bar.opened_in_classic_interface": "預設於經典網頁介面中開啟嘟文、帳號與其他特定頁面。", "navigation_bar.preferences": "偏好設定", - "navigation_bar.privacy_and_reach": "隱私權及觸及", + "navigation_bar.privacy_and_reach": "隱私權與觸及", "navigation_bar.search": "搜尋", "navigation_bar.search_trends": "搜尋 / 熱門趨勢", "navigation_panel.collapse_followed_tags": "收合已跟隨主題標籤選單", @@ -976,6 +978,7 @@ "report.category.title_account": "個人檔案", "report.category.title_status": "嘟文", "report.close": "已完成", + "report.collection_comment": "您檢舉此收藏名單的原因是?", "report.comment.title": "有什麼其他您想讓我們知道的嗎?", "report.forward": "轉寄到 {target}", "report.forward_hint": "這個帳號屬於其他伺服器。要向該伺服器發送匿名的檢舉訊息嗎?", @@ -997,6 +1000,8 @@ "report.rules.title": "違反了哪些規則?", "report.statuses.subtitle": "請選擇所有適用的選項", "report.statuses.title": "是否有能佐證這份檢舉之嘟文?", + "report.submission_error": "無法送出檢舉", + "report.submission_error_details": "請檢查您的網路連線並稍候重試。", "report.submit": "送出", "report.target": "檢舉 {target}", "report.thanks.take_action": "以下是控制您想於 Mastodon 看到什麼內容之選項:", diff --git a/config/locales/da.yml b/config/locales/da.yml index 0d6e6d812963d2..574aa708bb7526 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1952,7 +1952,7 @@ da: content_warnings: hide: Skjul indlæg show: Vis mere - default_language: Samme som UI-sproget + default_language: Samme som grænsefladesproget disallowed_hashtags: one: 'indeholdte et ikke tilladt hashtag: %{tags}' other: 'indeholdte de ikke tilladte etiketter: %{tags}' diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index e75ffb9d94283a..a499f6503ff2cd 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -83,6 +83,10 @@ 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: + one: Die code_challenge_method muss %{challenge_methods} sein. + other: Die code_challenge_method muss eine von %{challenge_methods} sein. + zero: Der Berechtigungsserver unterstützt PKCE nicht, da keine akzeptierten code_challenge_method Werte vorhanden sind. 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/fr-CA.yml b/config/locales/fr-CA.yml index 7d09a204679b43..751211e4705576 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -692,6 +692,7 @@ fr-CA: cancel: Annuler category: Catégorie category_description_html: La raison pour laquelle ce compte et/ou ce contenu a été signalé sera citée dans la communication avec le compte signalé + collections: Collections (%{count}) comment: none: Aucun comment_description_html: 'Pour fournir plus d''informations, %{name} a écrit :' @@ -727,6 +728,7 @@ fr-CA: resolved_msg: Signalement résolu avec succès ! skip_to_actions: Passer aux actions status: Statut + statuses: Messages (%{count}) statuses_description_html: Le contenu offensant sera cité dans la communication avec le compte signalé summary: action_preambles: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index d0dfa8e64bfdb1..5e0d57f820c74b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -692,6 +692,7 @@ fr: cancel: Annuler category: Catégorie category_description_html: La raison pour laquelle ce compte et/ou ce contenu a été signalé sera citée dans la communication avec le compte signalé + collections: Collections (%{count}) comment: none: Aucun comment_description_html: 'Pour fournir plus d''informations, %{name} a écrit :' @@ -727,6 +728,7 @@ fr: resolved_msg: Signalement résolu avec succès ! skip_to_actions: Passer aux actions status: Statut + statuses: Messages (%{count}) statuses_description_html: Le contenu offensant sera cité dans la communication avec le compte signalé summary: action_preambles: diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 41a64d06b13651..6148e6950c9121 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -731,6 +731,7 @@ ga: cancel: Cealaigh category: Catagóir category_description_html: Luafar an chúis ar tuairiscíodh an cuntas seo agus/nó an t-ábhar seo i gcumarsáid leis an gcuntas tuairiscithe + collections: Bailiúcháin (%{count}) comment: none: Dada comment_description_html: 'Chun tuilleadh eolais a sholáthar, scríobh %{name}:' @@ -766,6 +767,7 @@ ga: resolved_msg: D'éirigh le réiteach an tuairisc! skip_to_actions: Léim ar ghníomhartha status: Stádas + statuses: Poist (%{count}) statuses_description_html: Luafar ábhar ciontach i gcumarsáid leis an gcuntas tuairiscithe summary: action_preambles: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 4e40cf533a795c..0ac225d7286503 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -689,6 +689,7 @@ gl: cancel: Cancelar category: Categoría category_description_html: A razón para denunciar esta conta ou contido será citada na comunicación coa conta denunciada + collections: Coleccións (%{count}) comment: none: Ningún comment_description_html: 'Como información engadida, %{name} escribiu:' @@ -724,6 +725,7 @@ gl: resolved_msg: Resolveuse con éxito a denuncia! skip_to_actions: Ir a accións status: Estado + statuses: Publicacións (%{count}) statuses_description_html: O contido ofensivo será citado na comunicación coa conta denunciada summary: action_preambles: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index cfd255dd5f2b49..a5e90f9894b3f8 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -660,7 +660,7 @@ ko: add_to_report: 신고에 더 추가하기 already_suspended_badges: local: 이 서버에서 이미 정지되었습니다 - remote: 저 서버에서 이미 정지되었습니다 + remote: 이미 해당 서버에서 정지함 are_you_sure: 확실합니까? assign_to_self: 나에게 할당하기 assigned: 할당된 중재자 diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 0d7c0448e6dd4f..fa5204ab9732d5 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -345,6 +345,9 @@ nn: accounts: Kontoar collection_title: Samling av %{name} contents: Innhald + number_of_accounts: + one: 1 konto + other: "%{count} kontoar" open: Opna view_publicly: Vis offentleg critical_update_pending: Kritisk oppdatering ventar @@ -686,6 +689,7 @@ nn: cancel: Avbryt category: Kategori category_description_html: Årsaka til at kontoen og/eller innhaldet vart rapportert vil bli inkludert i kommunikasjonen med den rapporterte kontoen + collections: Samlingar (%{count}) comment: none: Ingen comment_description_html: 'For å gje meir informasjon, skreiv %{name}:' @@ -721,6 +725,7 @@ nn: resolved_msg: Rapporten er løyst! skip_to_actions: Gå til handlingar status: Status + statuses: Innlegg (%{count}) statuses_description_html: Støytande innhald vil bli inkludert i kommunikasjonen med den rapporterte kontoen summary: action_preambles: diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 08c75fb8e3673e..fcc6694b898a31 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -60,7 +60,7 @@ zh-TW: setting_boost_modal: 當啟用時,轉嘟前將先開啟確認對話框,您能於其變更轉嘟之可見性。 setting_default_quote_policy_private: Mastodon 上發佈之僅限跟隨者嘟文無法被其他使用者引用。 setting_default_quote_policy_unlisted: 當其他人引用您時,他們的嘟文也會自熱門時間軸隱藏。 - setting_default_sensitive: 敏感內容媒體預設隱藏,且按一下即可重新顯示 + setting_default_sensitive: 敏感內容媒體為預設隱藏,且按一下即可重新顯示 setting_display_media_default: 隱藏標為敏感內容的媒體 setting_display_media_hide_all: 總是隱藏所有媒體 setting_display_media_show_all: 總是顯示標為敏感內容的媒體 @@ -272,7 +272,7 @@ zh-TW: type: 匯入類型 username: 使用者名稱 username_or_email: 使用者名稱或電子郵件地址 - whole_word: 整個詞彙 + whole_word: 完整詞彙 email_domain_block: with_dns_records: 包括網域的 MX 記錄與 IP 位址 featured_tag: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 6c4befb91e7b0d..44ecabdc0f9530 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -689,6 +689,7 @@ tr: cancel: İptal et category: Kategori category_description_html: Bu hesap ve/veya içeriğin bildirilme gerekçesi, bildirilen hesapla iletişimde alıntılanacaktır + collections: Koleksiyonlar (%{count}) comment: none: Yok comment_description_html: 'Daha fazla bilgi vermek için %{name} şunu yazdı:' @@ -724,6 +725,7 @@ tr: resolved_msg: Şikayet başarıyla çözümlendi! skip_to_actions: İşlemlere atla status: Durum + statuses: Gönderiler (%{count}) statuses_description_html: İncitici içerik, bildirilen hesapla iletişimde alıntılanacaktır summary: action_preambles: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 99bf18c732aab2..782b3bcea9e3ab 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -675,7 +675,7 @@ zh-CN: cancel: 取消 category: 类别 category_description_html: 在与被举报账号的通信时,将引用该账号和/或内容被举报的原因 - collections: 收藏列表(%{count}) + collections: 收藏列表 (%{count}) comment: none: 没有 comment_description_html: "%{name} 补充道:" @@ -711,7 +711,7 @@ zh-CN: resolved_msg: 举报处理成功! skip_to_actions: 跳转到操作 status: 状态 - statuses: 嘟文(%{count}) + statuses: 嘟文 (%{count}) statuses_description_html: 在与该账号的通信中将引用违规内容 summary: action_preambles: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 9c0e621170d220..d1fe0c6c0e9b97 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1754,7 +1754,7 @@ zh-TW: too_many_options: 不能包含多於 %{max} 個項目 vote: 投票 posting_defaults: - explanation: 這些設定將作為您建立新嘟文時之預設值,但您能於編輯器中編輯個別嘟文之設定。 + explanation: 這些設定將作為您建立新嘟文時之預設值,但您能於嘟文編輯器中編輯個別嘟文之設定。 preferences: other: 其他 posting_defaults: 嘟文預設值 @@ -1767,7 +1767,7 @@ zh-TW: reach_hint_html: 控制您希望被新使用者探索或跟隨之方式。想使您的嘟文出現於探索頁面嗎?想使其他人透過他們的跟隨建議找到您嗎?想自動接受所有新跟隨者嗎?或是想逐一控制跟隨請求嗎? search: 搜尋 search_hint_html: 控制您希望如何被發現。您想透過您的公開嘟文被人們發現嗎?您想透過網頁搜尋被 Mastodon 以外的人找到您的個人檔案嗎?請注意,公開資訊可能無法全面地被所有搜尋引擎所排除。 - title: 隱私權及觸及 + title: 隱私權與觸及 privacy_policy: title: 隱私權政策 reactions: From 32873e63cf3b2ff67f2ca336048b5646f71c5187 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 25 Feb 2026 09:13:32 -0500 Subject: [PATCH 03/89] Remove references to removed prettier config files (#37962) --- .github/workflows/lint-css.yml | 2 -- .github/workflows/lint-js.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index 178342ed3441f7..60778f11a05c74 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -9,7 +9,6 @@ on: - 'package.json' - 'yarn.lock' - '.nvmrc' - - '.prettier*' - 'stylelint.config.js' - '**/*.css' - '**/*.scss' @@ -21,7 +20,6 @@ on: - 'package.json' - 'yarn.lock' - '.nvmrc' - - '.prettier*' - 'stylelint.config.js' - '**/*.css' - '**/*.scss' diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 7036c5e5698f1e..f16e01d4d2ef5c 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -10,7 +10,6 @@ on: - 'yarn.lock' - 'tsconfig.json' - '.nvmrc' - - '.prettier*' - 'eslint.config.mjs' - '**/*.js' - '**/*.jsx' @@ -24,7 +23,6 @@ on: - 'yarn.lock' - 'tsconfig.json' - '.nvmrc' - - '.prettier*' - 'eslint.config.mjs' - '**/*.js' - '**/*.jsx' From 440466c246a6ad372ce2c66eb56acc678ff600c4 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 25 Feb 2026 15:14:55 +0100 Subject: [PATCH 04/89] Display public collections on profile "Featured tab" (#37967) --- .../features/account_featured/index.tsx | 37 +++++++++++++++++++ .../detail/collection_list_item.module.scss | 10 +++-- .../detail/collection_list_item.tsx | 9 ++++- .../collections/detail/collection_menu.tsx | 31 ++++++++++++---- .../features/collections/detail/index.tsx | 35 +++++++++++++++--- .../mastodon/features/collections/index.tsx | 6 ++- app/javascript/mastodon/locales/en.json | 1 + .../mastodon/reducers/slices/collections.ts | 10 +++-- 8 files changed, 115 insertions(+), 24 deletions(-) diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx index 45f2ccb1d7f950..db01c5b272e2f4 100644 --- a/app/javascript/mastodon/features/account_featured/index.tsx +++ b/app/javascript/mastodon/features/account_featured/index.tsx @@ -17,8 +17,15 @@ import BundleColumnError from 'mastodon/features/ui/components/bundle_column_err import Column from 'mastodon/features/ui/components/column'; import { useAccountId } from 'mastodon/hooks/useAccountId'; import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility'; +import { + fetchAccountCollections, + selectAccountCollections, +} from 'mastodon/reducers/slices/collections'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; +import { CollectionListItem } from '../collections/detail/collection_list_item'; +import { areCollectionsEnabled } from '../collections/utils'; + import { EmptyMessage } from './components/empty_message'; import { FeaturedTag } from './components/featured_tag'; import type { TagMap } from './components/featured_tag'; @@ -42,6 +49,9 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ if (accountId) { void dispatch(fetchFeaturedTags({ accountId })); void dispatch(fetchEndorsedAccounts({ accountId })); + if (areCollectionsEnabled()) { + void dispatch(fetchAccountCollections({ accountId })); + } } }, [accountId, dispatch]); @@ -64,6 +74,14 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ ImmutableList(), ) as ImmutableList, ); + const { collections, status } = useAppSelector((state) => + selectAccountCollections(state, accountId ?? null), + ); + const publicCollections = collections.filter( + // This filter only applies when viewing your own profile, where the endpoint + // returns all collections, but we hide unlisted ones here to avoid confusion + (item) => item.discoverable, + ); if (accountId === null) { return ; @@ -101,6 +119,25 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ {accountId && ( )} + {publicCollections.length > 0 && status === 'idle' && ( + <> +

+ +

+
+ {publicCollections.map((item, index) => ( + + ))} +
+ + )} {!featuredTags.isEmpty() && ( <>

diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss index 74eac9f3e1e9c7..9e771dbaa03a7c 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss @@ -2,15 +2,17 @@ display: flex; align-items: center; gap: 16px; - margin-inline: 10px; - padding-inline-end: 5px; - border-bottom: 1px solid var(--color-border-primary); + padding-inline: 16px; + + &:not(.wrapperWithoutBorder) { + border-bottom: 1px solid var(--color-border-primary); + } } .content { position: relative; flex-grow: 1; - padding: 15px 5px; + padding-block: 15px; } .link { diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx index fe5aa500474137..1a7e18b5218379 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx @@ -67,13 +67,18 @@ export const CollectionMetaData: React.FC<{ export const CollectionListItem: React.FC<{ collection: ApiCollectionJSON; -}> = ({ collection }) => { + withoutBorder?: boolean; +}> = ({ collection, withoutBorder }) => { const { id, name } = collection; const linkId = useId(); return (
diff --git a/app/javascript/mastodon/features/collections/detail/collection_menu.tsx b/app/javascript/mastodon/features/collections/detail/collection_menu.tsx index ba17e8b1ec0276..b236c1cadb1234 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_menu.tsx +++ b/app/javascript/mastodon/features/collections/detail/collection_menu.tsx @@ -2,6 +2,8 @@ import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import { matchPath } from 'react-router'; + import { useAccount } from '@/mastodon/hooks/useAccount'; import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react'; import { openModal } from 'mastodon/actions/modal'; @@ -9,6 +11,7 @@ import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; import { Dropdown } from 'mastodon/components/dropdown_menu'; import { IconButton } from 'mastodon/components/icon_button'; import { me } from 'mastodon/initial_state'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { useAppDispatch } from 'mastodon/store'; import { messages as editorMessages } from '../editor'; @@ -70,7 +73,7 @@ export const CollectionMenu: React.FC<{ const menu = useMemo(() => { if (isOwnCollection) { - const commonItems = [ + const commonItems: MenuItem[] = [ { text: intl.formatMessage(editorMessages.manageAccounts), to: `/collections/${id}/edit`, @@ -97,17 +100,31 @@ export const CollectionMenu: React.FC<{ return commonItems; } } else if (ownerAccount) { - return [ - { - text: intl.formatMessage(messages.viewOtherCollections), - to: `/@${ownerAccount.acct}/featured`, - }, - null, + const items: MenuItem[] = [ { text: intl.formatMessage(messages.report), action: openReportModal, }, ]; + const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`; + // Don't show menu link to featured collections while on that very page + if ( + !matchPath(location.pathname, { + path: featuredCollectionsPath, + exact: true, + }) + ) { + items.unshift( + ...[ + { + text: intl.formatMessage(messages.viewOtherCollections), + to: featuredCollectionsPath, + }, + null, + ], + ); + } + return items; } else { return []; } diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx index 9ba4813cc8e52e..d5b14da8593097 100644 --- a/app/javascript/mastodon/features/collections/detail/index.tsx +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -5,6 +5,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Helmet } from 'react-helmet'; import { useParams } from 'react-router'; +import { useRelationship } from '@/mastodon/hooks/useRelationship'; 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'; @@ -123,6 +124,28 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); }; +const CollectionAccountItem: React.FC<{ + accountId: string | undefined; + collectionOwnerId: string; +}> = ({ accountId, collectionOwnerId }) => { + const relationship = useRelationship(accountId); + + if (!accountId) { + return null; + } + + // When viewing your own collection, only show the Follow button + // for accounts you're not following (anymore). + // Otherwise, always show the follow button in its various states. + const withoutButton = + accountId === me || + !relationship || + (collectionOwnerId === me && + (relationship.following || relationship.requested)); + + return ; +}; + export const CollectionDetailPage: React.FC<{ multiColumn?: boolean; }> = ({ multiColumn }) => { @@ -163,11 +186,13 @@ export const CollectionDetailPage: React.FC<{ collection ? : null } > - {collection?.items.map(({ account_id }) => - account_id ? ( - - ) : null, - )} + {collection?.items.map(({ account_id }) => ( + + ))} diff --git a/app/javascript/mastodon/features/collections/index.tsx b/app/javascript/mastodon/features/collections/index.tsx index 607d7fe4f3f04e..24819cf755f42d 100644 --- a/app/javascript/mastodon/features/collections/index.tsx +++ b/app/javascript/mastodon/features/collections/index.tsx @@ -14,7 +14,7 @@ import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; import { fetchAccountCollections, - selectMyCollections, + selectAccountCollections, } from 'mastodon/reducers/slices/collections'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -31,7 +31,9 @@ export const Collections: React.FC<{ const dispatch = useAppDispatch(); const intl = useIntl(); const me = useAppSelector((state) => state.meta.get('me') as string); - const { collections, status } = useAppSelector(selectMyCollections); + const { collections, status } = useAppSelector((state) => + selectAccountCollections(state, me), + ); useEffect(() => { void dispatch(fetchAccountCollections({ accountId: me })); diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 151c5d72a6606b..d3d9a2a4f39fc9 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -45,6 +45,7 @@ "account.familiar_followers_two": "Followed by {name1} and {name2}", "account.featured": "Featured", "account.featured.accounts": "Profiles", + "account.featured.collections": "Collections", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", diff --git a/app/javascript/mastodon/reducers/slices/collections.ts b/app/javascript/mastodon/reducers/slices/collections.ts index 77969ffcad66b0..c3bec4b1c6cd1e 100644 --- a/app/javascript/mastodon/reducers/slices/collections.ts +++ b/app/javascript/mastodon/reducers/slices/collections.ts @@ -229,14 +229,16 @@ interface AccountCollectionQuery { collections: ApiCollectionJSON[]; } -export const selectMyCollections = createAppSelector( +export const selectAccountCollections = createAppSelector( [ - (state) => state.meta.get('me') as string, + (_, accountId: string | null) => accountId, (state) => state.collections.accountCollections, (state) => state.collections.collections, ], - (me, collectionsByAccountId, collectionsMap) => { - const myCollectionsQuery = collectionsByAccountId[me]; + (accountId, collectionsByAccountId, collectionsMap) => { + const myCollectionsQuery = accountId + ? collectionsByAccountId[accountId] + : null; if (!myCollectionsQuery) { return { From ea34d35b32c41cedbddef7f57737da61d984a861 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 25 Feb 2026 15:30:01 +0100 Subject: [PATCH 05/89] Fix username availability check being wrongly applied on race conditions (#37975) --- app/javascript/entrypoints/public.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index b2a1473dfbc46c..f891410a3cdc48 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -182,15 +182,25 @@ function loaded() { ({ target }) => { if (!(target instanceof HTMLInputElement)) return; - if (target.value && target.value.length > 0) { + const checkedUsername = target.value; + if (checkedUsername && checkedUsername.length > 0) { axios - .get('/api/v1/accounts/lookup', { params: { acct: target.value } }) + .get('/api/v1/accounts/lookup', { + params: { acct: checkedUsername }, + }) .then(() => { - target.setCustomValidity(formatMessage(messages.usernameTaken)); + // Only update the validity if the result is for the currently-typed username + if (checkedUsername === target.value) { + target.setCustomValidity(formatMessage(messages.usernameTaken)); + } + return true; }) .catch(() => { - target.setCustomValidity(''); + // Only update the validity if the result is for the currently-typed username + if (checkedUsername === target.value) { + target.setCustomValidity(''); + } }); } else { target.setCustomValidity(''); From f9326efef637a4bffe1f068208cfc0f4ec27482c Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 25 Feb 2026 15:32:07 +0100 Subject: [PATCH 06/89] Add moderation consequences for collections (#37974) --- app/helpers/admin/action_logs_helper.rb | 2 +- app/models/admin/moderation_action.rb | 64 +++++++++++++++---- app/models/collection.rb | 8 +++ app/policies/admin/collection_policy.rb | 19 ++++++ app/views/admin/reports/_actions.html.haml | 6 +- config/locales/en.yml | 2 + spec/models/admin/moderation_action_spec.rb | 48 ++++++++++++++ spec/models/collection_spec.rb | 14 ++++ spec/policies/admin/collection_policy_spec.rb | 24 +++++++ 9 files changed, 169 insertions(+), 18 deletions(-) create mode 100644 app/policies/admin/collection_policy.rb create mode 100644 spec/policies/admin/collection_policy_spec.rb diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 4a55a36ecd1c40..76edb965a6aa38 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -19,7 +19,7 @@ def log_target(log) link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id) when 'Instance', 'DomainBlock', 'DomainAllow', 'UnavailableDomain' log.human_identifier.present? ? link_to(log.human_identifier, admin_instance_path(log.human_identifier)) : I18n.t('admin.action_logs.unavailable_instance') - when 'Status' + when 'Status', 'Collection' link_to log.human_identifier, log.permalink when 'AccountWarning' link_to log.human_identifier, disputes_strike_path(log.target_id) diff --git a/app/models/admin/moderation_action.rb b/app/models/admin/moderation_action.rb index 915306d0f5351d..5ce640ec27d947 100644 --- a/app/models/admin/moderation_action.rb +++ b/app/models/admin/moderation_action.rb @@ -18,6 +18,10 @@ def statuses @statuses ||= Status.with_discarded.where(id: status_ids).reorder(nil) end + def collections + report.collections + end + def process_action! case type when 'delete' @@ -29,19 +33,16 @@ def process_action! def handle_delete! statuses.each { |status| authorize([:admin, status], :destroy?) } + collections.each { |collection| authorize([:admin, collection], :destroy?) } ApplicationRecord.transaction do - statuses.each do |status| - status.discard_with_reblogs - log_action(:destroy, status) - end - - report.resolve!(current_account) - log_action(:resolve, report) + delete_statuses! + delete_collections! + resolve_report! process_strike!(:delete_statuses) - statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? + create_tombstones! unless target_account.local? end process_notification! @@ -50,10 +51,39 @@ def handle_delete! end def handle_mark_as_sensitive! - representative_account = Account.representative + collections.each { |collection| authorize([:admin, collection], :update?) } # Can't use a transaction here because UpdateStatusService queues # Sidekiq jobs + mark_statuses_as_sensitive! + mark_collections_as_sensitive! + + resolve_report! + process_strike!(:mark_statuses_as_sensitive) + process_notification! + end + + def delete_statuses! + statuses.each do |status| + status.discard_with_reblogs + log_action(:destroy, status) + end + end + + def delete_collections! + collections.each do |collection| + collection.destroy! + log_action(:destroy, collection) + end + end + + def create_tombstones! + (statuses + collections).each { |record| Tombstone.find_or_create_by(uri: record.uri, account: target_account, by_moderator: true) } + end + + def mark_statuses_as_sensitive! + representative_account = Account.representative + statuses.includes(:media_attachments, preview_cards_status: :preview_card).find_each do |status| next if status.discarded? || !(status.with_media? || status.with_preview_card?) @@ -66,14 +96,20 @@ def handle_mark_as_sensitive! end log_action(:update, status) - - report.resolve!(current_account) - log_action(:resolve, report) end + end - process_strike!(:mark_statuses_as_sensitive) + def mark_collections_as_sensitive! + collections.each do |collection| + UpdateCollectionService.new.call(collection, sensitive: true) - process_notification! + log_action(:update, collection) + end + end + + def resolve_report! + report.resolve!(current_account) + log_action(:resolve, report) end def target_account diff --git a/app/models/collection.rb b/app/models/collection.rb index d8386e43b44e76..c018dd9fa4272d 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -69,6 +69,14 @@ def object_type :featured_collection end + def to_log_human_identifier + account.acct + end + + def to_log_permalink + ActivityPub::TagManager.instance.uri_for(self) + end + private def tag_is_usable diff --git a/app/policies/admin/collection_policy.rb b/app/policies/admin/collection_policy.rb new file mode 100644 index 00000000000000..c8fba74ef6ace6 --- /dev/null +++ b/app/policies/admin/collection_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Admin::CollectionPolicy < ApplicationPolicy + def index? + role.can?(:manage_reports, :manage_users) + end + + def show? + role.can?(:manage_reports, :manage_users) + end + + def destroy? + role.can?(:manage_reports) + end + + def update? + role.can?(:manage_reports) + end +end diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml index ef016e949bddf0..071c46349795b0 100644 --- a/app/views/admin/reports/_actions.html.haml +++ b/app/views/admin/reports/_actions.html.haml @@ -5,7 +5,7 @@ = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), method: :post, class: 'button' .report-actions__item__description = t('admin.reports.actions.resolve_description_html') - - if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } + - if report.collections.any? || statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } .report-actions__item .report-actions__item__button = form.button t('admin.reports.mark_as_sensitive'), @@ -18,8 +18,8 @@ = form.button t('admin.reports.delete_and_resolve'), name: :delete, class: 'button button--destructive', - disabled: statuses.empty?, - title: statuses.empty? ? t('admin.reports.actions_no_posts') : '' + disabled: (report.collections + statuses).empty?, + title: (report.collections + statuses).empty? ? t('admin.reports.actions_no_posts') : '' .report-actions__item__description = t('admin.reports.actions.delete_description_html') .report-actions__item diff --git a/config/locales/en.yml b/config/locales/en.yml index 93b8d4088e7f81..aeb0051a112528 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -267,6 +267,7 @@ en: demote_user_html: "%{name} demoted user %{target}" destroy_announcement_html: "%{name} deleted announcement %{target}" destroy_canonical_email_block_html: "%{name} unblocked email with the hash %{target}" + destroy_collection_html: "%{name} removed collection by %{target}" destroy_custom_emoji_html: "%{name} deleted emoji %{target}" destroy_domain_allow_html: "%{name} disallowed federation with domain %{target}" destroy_domain_block_html: "%{name} unblocked domain %{target}" @@ -306,6 +307,7 @@ en: unsilence_account_html: "%{name} undid limit of %{target}'s account" unsuspend_account_html: "%{name} unsuspended %{target}'s account" update_announcement_html: "%{name} updated announcement %{target}" + update_collection_html: "%{name} updated collection by %{target}" update_custom_emoji_html: "%{name} updated emoji %{target}" update_domain_block_html: "%{name} updated domain block for %{target}" update_ip_block_html: "%{name} changed rule for IP %{target}" diff --git a/spec/models/admin/moderation_action_spec.rb b/spec/models/admin/moderation_action_spec.rb index b6c81d6c44d3da..fd2e7a4c4cb3eb 100644 --- a/spec/models/admin/moderation_action_spec.rb +++ b/spec/models/admin/moderation_action_spec.rb @@ -32,6 +32,36 @@ end expect(report.reload).to be_action_taken end + + context 'with attached collections', feature: :collections do + let(:status_ids) { [] } + let(:collections) { Fabricate.times(2, :collection, account: target_account) } + + before do + report.collections = collections + end + + it 'deletes the collections and creates an action log' do + expect { subject.save! }.to change(Collection, :count).by(-2) + .and change(Admin::ActionLog, :count).by(3) + end + end + + context 'with a remote collection', feature: :collections do + let(:status_ids) { [] } + let(:collection) { Fabricate(:remote_collection) } + let(:target_account) { collection.account } + + before do + report.collections << collection + end + + it 'creates a tombstone' do + expect { subject.save! }.to change(Tombstone, :count).by(1) + + expect(Tombstone.last.uri).to eq collection.uri + end + end end context 'when `type` is `mark_as_sensitive`' do @@ -52,6 +82,24 @@ end expect(report.reload).to be_action_taken end + + context 'with attached collections', feature: :collections do + let(:status_ids) { [] } + let(:collections) { Fabricate.times(2, :collection, account: target_account) } + + before do + report.collections = collections + end + + it 'marks the collections as sensitive' do + subject.save! + + collections.each do |collection| + expect(collection.reload).to be_sensitive + end + expect(report.reload).to be_action_taken + end + end end end end diff --git a/spec/models/collection_spec.rb b/spec/models/collection_spec.rb index b50969b68a9d6c..ba1819fa6cda2d 100644 --- a/spec/models/collection_spec.rb +++ b/spec/models/collection_spec.rb @@ -138,4 +138,18 @@ expect(subject.object_type).to eq :featured_collection end end + + describe '#to_log_human_identifier' do + subject { Fabricate(:collection) } + + it 'returns the account name' do + expect(subject.to_log_human_identifier).to eq subject.account.acct + end + end + + describe '#to_log_permalink' do + it 'includes the URI of the collection' do + expect(subject.to_log_permalink).to eq ActivityPub::TagManager.instance.uri_for(subject) + end + end end diff --git a/spec/policies/admin/collection_policy_spec.rb b/spec/policies/admin/collection_policy_spec.rb new file mode 100644 index 00000000000000..69f3cb5fdac4b2 --- /dev/null +++ b/spec/policies/admin/collection_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Admin::CollectionPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:admin_user).account } + let(:john) { Fabricate(:account) } + let(:collection) { Fabricate(:collection) } + + permissions :index?, :show?, :update?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Collection) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Collection) + end + end + end +end From dcbf7ab8dc74271eb24614369867c9cb430751cd Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 25 Feb 2026 17:59:18 +0100 Subject: [PATCH 07/89] Profile redesign: Account fields grid (#37976) --- .../mastodon/components/emoji/html.tsx | 20 +- .../mastodon/components/mini_card/index.tsx | 13 +- .../mastodon/features/account_about/index.tsx | 125 ------ .../features/account_about/styles.module.css | 7 - .../features/account_timeline/common.ts | 7 + .../components/account_header.tsx | 20 +- .../account_timeline/components/fields.tsx | 392 ++++++++++++------ .../components/redesign.module.scss | 90 ++-- .../account_timeline/components/tabs.tsx | 10 +- .../account_timeline/hooks/useFieldHtml.tsx | 38 ++ .../account_timeline/modals/field_modal.tsx | 44 ++ .../account_timeline/modals/note_modal.tsx | 2 +- .../{modals.module.css => styles.module.css} | 6 + .../features/ui/components/modal_root.jsx | 1 + app/javascript/mastodon/features/ui/index.jsx | 34 +- .../features/ui/util/async-components.js | 5 - app/javascript/mastodon/hooks/useObserver.ts | 29 ++ app/javascript/mastodon/hooks/useOverflow.ts | 51 +-- app/javascript/mastodon/locales/en.json | 4 +- config/routes.rb | 2 - 20 files changed, 488 insertions(+), 412 deletions(-) delete mode 100644 app/javascript/mastodon/features/account_about/index.tsx delete mode 100644 app/javascript/mastodon/features/account_about/styles.module.css create mode 100644 app/javascript/mastodon/features/account_timeline/hooks/useFieldHtml.tsx create mode 100644 app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx rename app/javascript/mastodon/features/account_timeline/modals/{modals.module.css => styles.module.css} (80%) create mode 100644 app/javascript/mastodon/hooks/useObserver.ts diff --git a/app/javascript/mastodon/components/emoji/html.tsx b/app/javascript/mastodon/components/emoji/html.tsx index bc3eda7a33238a..a9f2b64e337488 100644 --- a/app/javascript/mastodon/components/emoji/html.tsx +++ b/app/javascript/mastodon/components/emoji/html.tsx @@ -20,18 +20,7 @@ export interface EmojiHTMLProps { } export const EmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>( - ( - { - extraEmojis, - htmlString, - as: asProp = 'div', // Rename for syntax highlighting - className, - onElement, - onAttribute, - ...props - }, - ref, - ) => { + ({ extraEmojis, htmlString, onElement, onAttribute, ...props }, ref) => { const contents = useMemo( () => htmlStringToComponents(htmlString, { @@ -44,12 +33,7 @@ export const EmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>( return ( - + {contents} diff --git a/app/javascript/mastodon/components/mini_card/index.tsx b/app/javascript/mastodon/components/mini_card/index.tsx index 9ddb964d71609e..78e240b01e2316 100644 --- a/app/javascript/mastodon/components/mini_card/index.tsx +++ b/app/javascript/mastodon/components/mini_card/index.tsx @@ -23,7 +23,17 @@ export type MiniCardProps = OmitUnion< export const MiniCard = forwardRef( ( - { label, value, className, hidden, icon, iconId, iconClassName, ...props }, + { + label, + value, + className, + hidden, + icon, + iconId, + iconClassName, + children, + ...props + }, ref, ) => { if (!label) { @@ -50,6 +60,7 @@ export const MiniCard = forwardRef( )}
{label}
{value}
+ {children} ); }, diff --git a/app/javascript/mastodon/features/account_about/index.tsx b/app/javascript/mastodon/features/account_about/index.tsx deleted file mode 100644 index 28e97108865dc5..00000000000000 --- a/app/javascript/mastodon/features/account_about/index.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import type { FC } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { useParams } from 'react-router'; - -import { AccountBio } from '@/mastodon/components/account_bio'; -import { Column } from '@/mastodon/components/column'; -import { ColumnBackButton } from '@/mastodon/components/column_back_button'; -import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; -import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error'; -import type { AccountId } from '@/mastodon/hooks/useAccountId'; -import { useAccountId } from '@/mastodon/hooks/useAccountId'; -import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility'; -import { createAppSelector, useAppSelector } from '@/mastodon/store'; - -import { AccountHeader } from '../account_timeline/components/account_header'; -import { AccountHeaderFields } from '../account_timeline/components/fields'; -import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint'; - -import classes from './styles.module.css'; - -const selectIsProfileEmpty = createAppSelector( - [(state) => state.accounts, (_, accountId: AccountId) => accountId], - (accounts, accountId) => { - // Null means still loading, otherwise it's a boolean. - if (!accountId) { - return null; - } - const account = accounts.get(accountId); - if (!account) { - return null; - } - return !account.note && !account.fields.size; - }, -); - -export const AccountAbout: FC<{ multiColumn: boolean }> = ({ multiColumn }) => { - const accountId = useAccountId(); - const { blockedBy, hidden, suspended } = useAccountVisibility(accountId); - const forceEmptyState = blockedBy || hidden || suspended; - - const isProfileEmpty = useAppSelector((state) => - selectIsProfileEmpty(state, accountId), - ); - - if (accountId === null) { - return ; - } - - if (!accountId || isProfileEmpty === null) { - return ( - - - - ); - } - - const showEmptyMessage = forceEmptyState || isProfileEmpty; - - return ( - - -
- -
- {!showEmptyMessage ? ( - <> - - - - ) : ( -
- -
- )} -
-
-
- ); -}; - -const EmptyMessage: FC<{ accountId: string }> = ({ accountId }) => { - const { blockedBy, hidden, suspended } = useAccountVisibility(accountId); - const currentUserId = useAppSelector( - (state) => state.meta.get('me') as string | null, - ); - const { acct } = useParams<{ acct?: string }>(); - - if (suspended) { - return ( - - ); - } else if (hidden) { - return ; - } else if (blockedBy) { - return ( - - ); - } else if (accountId === currentUserId) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/app/javascript/mastodon/features/account_about/styles.module.css b/app/javascript/mastodon/features/account_about/styles.module.css deleted file mode 100644 index a0f5569b29c056..00000000000000 --- a/app/javascript/mastodon/features/account_about/styles.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.wrapper { - padding: 16px; -} - -.bio { - color: var(--color-text-primary); -} diff --git a/app/javascript/mastodon/features/account_timeline/common.ts b/app/javascript/mastodon/features/account_timeline/common.ts index 1bb62a422042a6..7a939bbec9d393 100644 --- a/app/javascript/mastodon/features/account_timeline/common.ts +++ b/app/javascript/mastodon/features/account_timeline/common.ts @@ -1,5 +1,12 @@ +import type { AccountFieldShape } from '@/mastodon/models/account'; import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; export function isRedesignEnabled() { return isServerFeatureEnabled('profile_redesign'); } + +export interface AccountField extends AccountFieldShape { + nameHasEmojis: boolean; + value_plain: string; + valueHasEmojis: boolean; +} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index e31e13b8dd660b..9731f4cc3340c2 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -210,18 +210,14 @@ export const AccountHeader: React.FC<{ ))} - {(!isRedesign || layout === 'single-column') && ( - <> - - - - )} + + diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index 539546759d547b..c2802c2c518c9a 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -1,25 +1,31 @@ -import { useCallback, useMemo, useState } from 'react'; -import type { FC, Key } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; +import type { FC } from 'react'; import { defineMessage, FormattedMessage, useIntl } from 'react-intl'; import classNames from 'classnames'; -import htmlConfig from '@/config/html-tags.json'; import IconVerified from '@/images/icons/icon_verified.svg?react'; +import { openModal } from '@/mastodon/actions/modal'; import { AccountFields } from '@/mastodon/components/account_fields'; import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; import type { EmojiHTMLProps } from '@/mastodon/components/emoji/html'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { FormattedDateWrapper } from '@/mastodon/components/formatted_date'; import { Icon } from '@/mastodon/components/icon'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { MiniCard } from '@/mastodon/components/mini_card'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useAccount } from '@/mastodon/hooks/useAccount'; -import type { Account, AccountFieldShape } from '@/mastodon/models/account'; -import type { OnElementHandler } from '@/mastodon/utils/html'; +import { useResizeObserver } from '@/mastodon/hooks/useObserver'; +import type { Account } from '@/mastodon/models/account'; +import { useAppDispatch } from '@/mastodon/store'; +import MoreIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { cleanExtraEmojis } from '../../emoji/normalize'; +import type { AccountField } from '../common'; import { isRedesignEnabled } from '../common'; +import { useFieldHtml } from '../hooks/useFieldHtml'; import classes from './redesign.module.scss'; @@ -74,172 +80,310 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => { () => cleanExtraEmojis(account.emojis), [account.emojis], ); - const textHasCustomEmoji = useCallback( - (text?: string | null) => { - if (!emojis || !text) { - return false; - } - for (const emoji of Object.keys(emojis)) { - if (text.includes(`:${emoji}:`)) { - return true; - } - } - return false; - }, - [emojis], - ); + const fields: AccountField[] = useMemo(() => { + const fields = account.fields.toJS(); + if (!emojis) { + return fields.map((field) => ({ + ...field, + nameHasEmojis: false, + value_plain: field.value_plain ?? '', + valueHasEmojis: false, + })); + } + + const shortcodes = Object.keys(emojis); + return fields.map((field) => ({ + ...field, + nameHasEmojis: shortcodes.some((code) => + field.name.includes(`:${code}:`), + ), + value_plain: field.value_plain ?? '', + valueHasEmojis: shortcodes.some((code) => + field.value_plain?.includes(`:${code}:`), + ), + })); + }, [account.fields, emojis]); + const htmlHandlers = useElementHandledLink({ hashtagAccountId: account.id, }); - if (account.fields.isEmpty()) { + const { wrapperRef } = useColumnWrap(); + + if (fields.length === 0) { return null; } return ( -
- {account.fields.map((field, key) => ( - +
+ {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 FieldRow: FC<{ + htmlHandlers: ReturnType; + field: AccountField; +}> = ({ htmlHandlers, field }) => { const intl = useIntl(); - const [showAll, setShowAll] = useState(false); - const handleClick = useCallback(() => { - setShowAll((prev) => !prev); - }, []); + const { + name, + name_emojified, + nameHasEmojis, + value_emojified, + value_plain, + valueHasEmojis, + verified_at, + } = field; + + const { wrapperRef, isLabelOverflowing, isValueOverflowing } = + useFieldOverflow(); + + const dispatch = useAppDispatch(); + const handleOverflowClick = useCallback(() => { + dispatch( + openModal({ + modalType: 'ACCOUNT_FIELD_OVERFLOW', + modalProps: { field }, + }), + ); + }, [dispatch, field]); return ( - /* eslint-disable -- This method of showing field contents is not very accessible, but it's what we've got for now */ -
- -
+ label={ + + } + value={ - - {verified_at && ( - - )} -
-
+ } + ref={wrapperRef} + > + {verified_at && ( + + )} + ); }; -const FieldHTML: FC< - { - as?: 'span' | 'dt'; - text: string; - textEmojified: string; - textHasCustomEmoji: boolean; - titleLength: number; - } & Omit -> = ({ - as, +type FieldHTMLProps = { + text: string; + textEmojified: string; + textHasCustomEmoji: boolean; + isOverflowing?: boolean; + onOverflowClick?: () => void; +} & Omit; + +const FieldHTML: FC = ({ className, extraEmojis, text, textEmojified, textHasCustomEmoji, - titleLength, + isOverflowing, + onOverflowClick, onElement, ...props }) => { - const handleElement: OnElementHandler = useCallback( - (element, props, children, extra) => { - if (element instanceof HTMLAnchorElement) { - // Don't allow custom emoji and links in the same field to prevent verification spoofing. - if (textHasCustomEmoji) { - return ( - - {children} - - ); - } - return onElement?.(element, props, children, extra); - } - return undefined; - }, - [onElement, textHasCustomEmoji], - ); + const intl = useIntl(); + const handleElement = useFieldHtml(textHasCustomEmoji, onElement); - return ( + const html = ( ); + if (!isOverflowing) { + return html; + } + + return ( + <> + {html} + + + ); }; -function filterAttributesForSpan(props: Record) { - const validAttributes: Record = {}; - for (const key of Object.keys(props)) { - if (key in htmlConfig.tags.span.attributes) { - validAttributes[key] = props[key]; +function useColumnWrap() { + const listRef = useRef(null); + + const handleRecalculate = useCallback(() => { + const listEle = listRef.current; + if (!listEle) { + return; } - } - return validAttributes; + + // Calculate dimensions from styles and element size to determine column spans. + const styles = getComputedStyle(listEle); + const gap = parseFloat(styles.columnGap || styles.gap || '0'); + const columnCount = parseInt(styles.getPropertyValue('--cols')) || 2; + const listWidth = listEle.offsetWidth; + const colWidth = (listWidth - gap * (columnCount - 1)) / columnCount; + + // Matrix to hold the grid layout. + const itemGrid: { ele: HTMLElement; span: number }[][] = []; + + // First, determine the column span for each item and populate the grid matrix. + let currentRow = 0; + for (const child of listEle.children) { + if (!(child instanceof HTMLElement)) { + continue; + } + + // This uses a data attribute to detect which elements to measure that overflow. + const contents = child.querySelectorAll('[data-contents]'); + + const childStyles = getComputedStyle(child); + const padding = + parseFloat(childStyles.paddingLeft) + + parseFloat(childStyles.paddingRight); + + const contentWidth = + Math.max( + ...Array.from(contents).map((content) => content.scrollWidth), + ) + padding; + + const contentSpan = Math.ceil(contentWidth / colWidth); + const maxColSpan = Math.min(contentSpan, columnCount); + + const curRow = itemGrid[currentRow] ?? []; + const availableCols = + columnCount - curRow.reduce((carry, curr) => carry + curr.span, 0); + // Move to next row if current item doesn't fit. + if (maxColSpan > availableCols) { + currentRow++; + } + + itemGrid[currentRow] = (itemGrid[currentRow] ?? []).concat({ + ele: child, + span: maxColSpan, + }); + } + + // Next, iterate through the grid matrix and set the column spans and row breaks. + for (const row of itemGrid) { + let remainingRowSpan = columnCount; + for (let i = 0; i < row.length; i++) { + const item = row[i]; + if (!item) { + break; + } + const { ele, span } = item; + if (i < row.length - 1) { + ele.dataset.cols = span.toString(); + remainingRowSpan -= span; + } else { + // Last item in the row takes up remaining space to fill the row. + ele.dataset.cols = remainingRowSpan.toString(); + break; + } + } + } + }, []); + + const observer = useResizeObserver(handleRecalculate); + + const wrapperRefCallback = useCallback( + (element: HTMLDListElement | null) => { + if (element) { + listRef.current = element; + observer.observe(element); + } + }, + [observer], + ); + + return { wrapperRef: wrapperRefCallback }; } -function showTitleOnLength(value: string | null, maxLength: number) { - if (value && value.length > maxLength) { - return value; - } - return undefined; +function useFieldOverflow() { + const [isLabelOverflowing, setIsLabelOverflowing] = useState(false); + const [isValueOverflowing, setIsValueOverflowing] = useState(false); + + const wrapperRef = useRef(null); + + const handleRecalculate = useCallback(() => { + const wrapperEle = wrapperRef.current; + if (!wrapperEle) return; + + const wrapperStyles = getComputedStyle(wrapperEle); + const maxWidth = + wrapperEle.offsetWidth - + (parseFloat(wrapperStyles.paddingLeft) + + parseFloat(wrapperStyles.paddingRight)); + + const label = wrapperEle.querySelector( + 'dt > [data-contents]', + ); + const value = wrapperEle.querySelector( + 'dd > [data-contents]', + ); + + setIsLabelOverflowing(label ? label.scrollWidth > maxWidth : false); + setIsValueOverflowing(value ? value.scrollWidth > maxWidth : false); + }, []); + + const observer = useResizeObserver(handleRecalculate); + + const wrapperRefCallback = useCallback( + (element: HTMLElement | null) => { + if (element) { + wrapperRef.current = element; + observer.observe(element); + } + }, + [observer], + ); + + return { + isLabelOverflowing, + isValueOverflowing, + wrapperRef: wrapperRefCallback, + }; } 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 5d7a5fa65008c9..1d52b1f5ab35d7 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -214,64 +214,90 @@ svg.badgeIcon { } .fieldList { + --cols: 4; + + position: relative; display: grid; - grid-template-columns: 160px 1fr; - column-gap: 12px; + grid-template-columns: repeat(var(--cols), 1fr); + gap: 12px; margin: 16px 0; - border-top: 0.5px solid var(--color-border-primary); @container (width < 420px) { - grid-template-columns: 100px 1fr; + --cols: 2; } } -.fieldRow { - display: grid; - grid-column: 1 / -1; - align-items: start; - grid-template-columns: subgrid; - padding: 8px; - border-bottom: 0.5px solid var(--color-border-primary); - - > :is(dt, dd) { - &:not(.fieldShowAll) { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - line-clamp: 2; - overflow: hidden; - text-overflow: ellipsis; +.fieldItem { + --col-span: 1; + + grid-column: span var(--col-span); + position: relative; + + @for $col from 2 through 4 { + &[data-cols='#{$col}'] { + --col-span: #{$col}; } } - > dt { - color: var(--color-text-secondary); + dt { + font-weight: normal; } - > dd { - display: flex; - align-items: center; - gap: 4px; + dd { + font-weight: 500; } - a { - color: inherit; - text-decoration: none; + :is(dt, dd) { + text-overflow: initial; - &:hover, - &:focus { - text-decoration: underline; + // Override the MiniCard link styles + a { + color: inherit; + font-weight: inherit; + + &:hover, + &:focus { + color: inherit; + text-decoration: underline; + } } } } .fieldVerified { background-color: var(--color-bg-success-softer); + + dt { + padding-right: 24px; + } } .fieldVerifiedIcon { width: 16px; height: 16px; + position: absolute; + top: 8px; + right: 8px; +} + +.fieldOverflowButton { + --default-bg-color: var(--color-bg-secondary-solid); + --hover-bg-color: color-mix( + in oklab, + var(--color-bg-brand-base), + var(--default-bg-color) var(--overlay-strength-brand) + ); + + position: absolute; + right: 8px; + padding: 0 2px; + transition: background-color 0.2s ease-in-out; + border: 2px solid var(--color-bg-primary); + + > svg { + width: 16px; + height: 12px; + } } .fieldNumbersWrapper { diff --git a/app/javascript/mastodon/features/account_timeline/components/tabs.tsx b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx index b718839ed89048..eeb48c1c532d98 100644 --- a/app/javascript/mastodon/features/account_timeline/components/tabs.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/tabs.tsx @@ -5,23 +5,15 @@ import { FormattedMessage } from 'react-intl'; import type { NavLinkProps } from 'react-router-dom'; import { NavLink } from 'react-router-dom'; -import { useLayout } from '@/mastodon/hooks/useLayout'; - import { isRedesignEnabled } from '../common'; import classes from './redesign.module.scss'; export const AccountTabs: FC<{ acct: string }> = ({ acct }) => { - const { layout } = useLayout(); if (isRedesignEnabled()) { return (
- {layout !== 'single-column' && ( - - - - )} - + diff --git a/app/javascript/mastodon/features/account_timeline/hooks/useFieldHtml.tsx b/app/javascript/mastodon/features/account_timeline/hooks/useFieldHtml.tsx new file mode 100644 index 00000000000000..8ab991e85beaa1 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/hooks/useFieldHtml.tsx @@ -0,0 +1,38 @@ +import type { Key } from 'react'; +import { useCallback } from 'react'; + +import htmlConfig from '@/config/html-tags.json'; +import type { OnElementHandler } from '@/mastodon/utils/html'; + +export function useFieldHtml( + hasCustomEmoji: boolean, + onElement?: OnElementHandler, +): OnElementHandler { + return useCallback( + (element, props, children, extra) => { + if (element instanceof HTMLAnchorElement) { + // Don't allow custom emoji and links in the same field to prevent verification spoofing. + if (hasCustomEmoji) { + return ( + + {children} + + ); + } + return onElement?.(element, props, children, extra); + } + return undefined; + }, + [onElement, hasCustomEmoji], + ); +} + +function filterAttributesForSpan(props: Record) { + const validAttributes: Record = {}; + for (const key of Object.keys(props)) { + if (key in htmlConfig.tags.span.attributes) { + validAttributes[key] = props[key]; + } + } + return validAttributes; +} diff --git a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx new file mode 100644 index 00000000000000..33e2e228918dd3 --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx @@ -0,0 +1,44 @@ +import type { FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { EmojiHTML } from '@/mastodon/components/emoji/html'; + +import type { AccountField } from '../common'; +import { useFieldHtml } from '../hooks/useFieldHtml'; + +import classes from './styles.module.css'; + +export const AccountFieldModal: FC<{ + onClose: () => void; + field: AccountField; +}> = ({ onClose, field }) => { + const handleLabelElement = useFieldHtml(field.nameHasEmojis); + const handleValueElement = useFieldHtml(field.valueHasEmojis); + return ( +
+
+
+ + +
+
+
+
+ +
+
+
+ ); +}; diff --git a/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx index 0d736b3467aba5..45fe4d7105d59e 100644 --- a/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/modals/note_modal.tsx @@ -13,7 +13,7 @@ import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import { ConfirmationModal } from '../../ui/components/confirmation_modals'; -import classes from './modals.module.css'; +import classes from './styles.module.css'; const messages = defineMessages({ newTitle: { diff --git a/app/javascript/mastodon/features/account_timeline/modals/modals.module.css b/app/javascript/mastodon/features/account_timeline/modals/styles.module.css similarity index 80% rename from app/javascript/mastodon/features/account_timeline/modals/modals.module.css rename to app/javascript/mastodon/features/account_timeline/modals/styles.module.css index cee0bc498a95e6..4740a42cb9fae6 100644 --- a/app/javascript/mastodon/features/account_timeline/modals/modals.module.css +++ b/app/javascript/mastodon/features/account_timeline/modals/styles.module.css @@ -19,3 +19,9 @@ outline: var(--outline-focus-default); outline-offset: 2px; } + +.fieldValue { + color: var(--color-text-primary); + font-weight: 600; + margin-top: 4px; +} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 163a04fdd61da0..7cacfab8003312 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -92,6 +92,7 @@ export const MODAL_COMPONENTS = { 'ANNUAL_REPORT': AnnualReportModal, 'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }), 'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })), + 'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })), 'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })), 'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })), }; diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 9e61158f149df9..17822929a78d4c 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 { isClientFeatureEnabled, isServerFeatureEnabled } from '@/mastodon/utils/environment'; +import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { clearHeight } from '../../actions/height_cache'; @@ -80,7 +80,6 @@ import { PrivacyPolicy, TermsOfService, AccountFeatured, - AccountAbout, AccountEdit, AccountEditFeaturedTags, Quotes, @@ -166,36 +165,6 @@ class SwitchingColumnsArea extends PureComponent { } const profileRedesignRoutes = []; - if (isServerFeatureEnabled('profile_redesign')) { - profileRedesignRoutes.push( - , - ); - // Check if we're in single-column mode. Confusingly, the singleColumn prop includes mobile. - if (this.props.layout === 'single-column') { - // When in single column mode (desktop w/o advanced view), redirect both the root and about to the posts tab. - profileRedesignRoutes.push( - , - , - , - , - ); - } else { - // Otherwise, provide and redirect to the /about page. - profileRedesignRoutes.push( - , - , - - ); - } - } else { - profileRedesignRoutes.push( - , - // If the redesign is not enabled but someone shares an /about link, redirect to the root. - , - - ); - } - if (isClientFeatureEnabled('profile_editing')) { profileRedesignRoutes.push( , @@ -257,6 +226,7 @@ class SwitchingColumnsArea extends PureComponent { {...profileRedesignRoutes} + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index ef9125d06f783d..d6c0f70c704db3 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -93,11 +93,6 @@ export function AccountFeatured() { return import('../../account_featured'); } -export function AccountAbout() { - return import('../../account_about') - .then((module) => ({ default: module.AccountAbout })); -} - export function AccountEdit() { return import('../../account_edit') .then((module) => ({ default: module.AccountEdit })); diff --git a/app/javascript/mastodon/hooks/useObserver.ts b/app/javascript/mastodon/hooks/useObserver.ts new file mode 100644 index 00000000000000..4b979f6d329b72 --- /dev/null +++ b/app/javascript/mastodon/hooks/useObserver.ts @@ -0,0 +1,29 @@ +import { useEffect, useRef } from 'react'; + +export function useResizeObserver(callback: ResizeObserverCallback) { + const observerRef = useRef(null); + observerRef.current ??= new ResizeObserver(callback); + + useEffect(() => { + const observer = observerRef.current; + return () => { + observer?.disconnect(); + }; + }, []); + + return observerRef.current; +} + +export function useMutationObserver(callback: MutationCallback) { + const observerRef = useRef(null); + observerRef.current ??= new MutationObserver(callback); + + useEffect(() => { + const observer = observerRef.current; + return () => { + observer?.disconnect(); + }; + }, []); + + return observerRef.current; +} diff --git a/app/javascript/mastodon/hooks/useOverflow.ts b/app/javascript/mastodon/hooks/useOverflow.ts index b306fb4871498c..b85222cf56136a 100644 --- a/app/javascript/mastodon/hooks/useOverflow.ts +++ b/app/javascript/mastodon/hooks/useOverflow.ts @@ -1,6 +1,8 @@ import type { MutableRefObject, RefCallback } from 'react'; import { useState, useRef, useCallback, useEffect } from 'react'; +import { useMutationObserver, useResizeObserver } from './useObserver'; + /** * Hook to manage overflow of items in a container with a "more" button. * @@ -182,48 +184,30 @@ export function useOverflowObservers({ // This is the item container element. const listRef = useRef(null); - // Set up observers to watch for size and content changes. - const resizeObserverRef = useRef(null); - const mutationObserverRef = useRef(null); - - // Helper to get or create the resize observer. - const resizeObserver = useCallback(() => { - const observer = (resizeObserverRef.current ??= new ResizeObserver( - onRecalculate, - )); - return observer; - }, [onRecalculate]); + const resizeObserver = useResizeObserver(onRecalculate); // Iterate through children and observe them for size changes. const handleChildrenChange = useCallback(() => { const listEle = listRef.current; - const observer = resizeObserver(); - if (listEle) { for (const child of listEle.children) { if (child instanceof HTMLElement) { - observer.observe(child); + resizeObserver.observe(child); } } } onRecalculate(); }, [onRecalculate, resizeObserver]); - // Helper to get or create the mutation observer. - const mutationObserver = useCallback(() => { - const observer = (mutationObserverRef.current ??= new MutationObserver( - handleChildrenChange, - )); - return observer; - }, [handleChildrenChange]); + const mutationObserver = useMutationObserver(handleChildrenChange); // Set up observers. const handleObserve = useCallback(() => { if (wrapperRef.current) { - resizeObserver().observe(wrapperRef.current); + resizeObserver.observe(wrapperRef.current); } if (listRef.current) { - mutationObserver().observe(listRef.current, { childList: true }); + mutationObserver.observe(listRef.current, { childList: true }); handleChildrenChange(); } }, [handleChildrenChange, mutationObserver, resizeObserver]); @@ -233,12 +217,12 @@ export function useOverflowObservers({ const wrapperRefCallback = useCallback( (node: HTMLElement | null) => { if (node) { - wrapperRef.current = node; + wrapperRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955 handleObserve(); if (typeof onWrapperRef === 'function') { onWrapperRef(node); } else if (onWrapperRef && 'current' in onWrapperRef) { - onWrapperRef.current = node; + onWrapperRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955 } } }, @@ -254,28 +238,13 @@ export function useOverflowObservers({ if (typeof onListRef === 'function') { onListRef(node); } else if (onListRef && 'current' in onListRef) { - onListRef.current = node; + onListRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955 } } }, [handleObserve, onListRef], ); - useEffect(() => { - handleObserve(); - - return () => { - if (resizeObserverRef.current) { - resizeObserverRef.current.disconnect(); - resizeObserverRef.current = null; - } - if (mutationObserverRef.current) { - mutationObserverRef.current.disconnect(); - mutationObserverRef.current = null; - } - }; - }, [handleObserve]); - return { wrapperRefCallback, listRefCallback, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d3d9a2a4f39fc9..e95ac604208c89 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -13,7 +13,6 @@ "about.not_available": "This information has not been made available on this server.", "about.powered_by": "Decentralized social media powered by {mastodon}", "about.rules": "Server rules", - "account.about": "About", "account.account_note_header": "Personal note", "account.activity": "Activity", "account.add_note": "Add a personal note", @@ -49,6 +48,7 @@ "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", + "account.field_overflow": "Show full content", "account.filters.all": "All activity", "account.filters.boosts_toggle": "Show boosts", "account.filters.posts_boosts": "Posts and boosts", @@ -499,8 +499,6 @@ "emoji_button.search_results": "Search results", "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", - "empty_column.account_about.me": "You have not added any information about yourself yet.", - "empty_column.account_about.other": "{acct} has not added any information about themselves yet.", "empty_column.account_featured.me": "You have not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured_other.unknown": "This account has not featured anything yet.", diff --git a/config/routes.rb b/config/routes.rb index 7aeece5d001584..b516a48866f1bd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -155,9 +155,7 @@ def redirect_with_vary(path) constraints(username: %r{[^@/.]+}) do with_options to: 'accounts#show' do get '/@:username', as: :short_account - get '/@:username/posts' get '/@:username/featured' - get '/@:username/about' get '/@:username/with_replies', as: :short_account_with_replies get '/@:username/media', as: :short_account_media get '/@:username/tagged/:tag', as: :short_account_tag From 4d2a148ccbedc818c98fd712a0b44869c1019321 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 25 Feb 2026 18:45:51 +0100 Subject: [PATCH 08/89] Fix profile redesign profile fields `gap` (#37979) --- .../features/account_timeline/components/redesign.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1d52b1f5ab35d7..aad78f1e084ecb 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -219,7 +219,7 @@ svg.badgeIcon { position: relative; display: grid; grid-template-columns: repeat(var(--cols), 1fr); - gap: 12px; + gap: 4px; margin: 16px 0; @container (width < 420px) { From 970ac04be7ca841249e618757b920de638fd827f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:07:33 +0100 Subject: [PATCH 09/89] New Crowdin Translations (automated) (#37985) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 50 ++++++++++++++++++--- app/javascript/mastodon/locales/ca.json | 1 - app/javascript/mastodon/locales/cs.json | 3 -- app/javascript/mastodon/locales/cy.json | 3 -- app/javascript/mastodon/locales/da.json | 5 +-- 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/et.json | 3 -- app/javascript/mastodon/locales/fi.json | 5 +-- app/javascript/mastodon/locales/fo.json | 11 +++-- app/javascript/mastodon/locales/fr-CA.json | 7 ++- app/javascript/mastodon/locales/fr.json | 7 ++- app/javascript/mastodon/locales/ga.json | 5 +-- app/javascript/mastodon/locales/gl.json | 5 +-- app/javascript/mastodon/locales/he.json | 5 +-- app/javascript/mastodon/locales/hu.json | 3 -- app/javascript/mastodon/locales/is.json | 5 +-- app/javascript/mastodon/locales/it.json | 3 -- app/javascript/mastodon/locales/ko.json | 1 - app/javascript/mastodon/locales/nan-TW.json | 3 -- app/javascript/mastodon/locales/nl.json | 10 +++-- app/javascript/mastodon/locales/nn.json | 5 +-- app/javascript/mastodon/locales/no.json | 2 +- app/javascript/mastodon/locales/pt-BR.json | 3 -- app/javascript/mastodon/locales/pt-PT.json | 19 ++++++-- app/javascript/mastodon/locales/sq.json | 5 +-- app/javascript/mastodon/locales/sv.json | 3 -- app/javascript/mastodon/locales/tr.json | 8 ++-- app/javascript/mastodon/locales/vi.json | 5 +-- app/javascript/mastodon/locales/zh-CN.json | 4 +- app/javascript/mastodon/locales/zh-TW.json | 5 +-- config/locales/be.yml | 17 +++++++ config/locales/da.yml | 2 + config/locales/de.yml | 2 + config/locales/el.yml | 2 + config/locales/en-GB.yml | 2 + config/locales/es-AR.yml | 2 + config/locales/es-MX.yml | 2 + config/locales/es.yml | 5 +++ config/locales/fi.yml | 2 + config/locales/fo.yml | 8 ++++ config/locales/fr-CA.yml | 2 + config/locales/fr.yml | 2 + config/locales/ga.yml | 2 + config/locales/gl.yml | 2 + config/locales/he.yml | 2 + config/locales/is.yml | 2 + config/locales/nl.yml | 2 + config/locales/no.yml | 2 +- config/locales/sq.yml | 2 + config/locales/vi.yml | 2 + config/locales/zh-CN.yml | 2 + config/locales/zh-TW.yml | 2 + 57 files changed, 186 insertions(+), 108 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 45ac2b50fc70a3..5e20ae1b087f49 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -13,7 +13,6 @@ "about.not_available": "Дадзеная інфармацыя не дасяжная на гэтым серверы.", "about.powered_by": "Дэцэнтралізаваная сацыяльная сетка, створаная {mastodon}", "about.rules": "Правілы сервера", - "account.about": "Пра сябе", "account.account_note_header": "Асабістая нататка", "account.activity": "Актыўнасць", "account.add_note": "Дадаць асабістую нататку", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Мае сярод падпісчыкаў {name1} і {name2}", "account.featured": "Рэкамендаванае", "account.featured.accounts": "Профілі", + "account.featured.collections": "Калекцыі", "account.featured.hashtags": "Хэштэгі", "account.featured_tags.last_status_at": "Апошні допіс ад {date}", "account.featured_tags.last_status_never": "Няма допісаў", + "account.field_overflow": "Паказаць усё змесціва", "account.filters.all": "Уся актыўнасць", "account.filters.boosts_toggle": "Паказваць пашырэнні", "account.filters.posts_boosts": "Допісы і пашырэнні", @@ -141,8 +142,33 @@ "account.unmute": "Не ігнараваць @{name}", "account.unmute_notifications_short": "Апавяшчаць", "account.unmute_short": "Не ігнараваць", + "account_edit.bio.placeholder": "Коратка апішыце сябе, каб дапамагчы іншым пазнаць Вас.", + "account_edit.bio.title": "Хто я", + "account_edit.bio_modal.add_title": "Апісаць сябе", + "account_edit.bio_modal.edit_title": "Змяніць апісанне", + "account_edit.button.add": "Дадаць {item}", + "account_edit.button.delete": "Выдаліць {item}", + "account_edit.button.edit": "Змяніць {item}", + "account_edit.char_counter": "{currentLength}/{maxLength} сімвалаў", "account_edit.column_button": "Гатова", "account_edit.column_title": "Рэдагаваць профіль", + "account_edit.custom_fields.placeholder": "Дадайце свае займеннікі, знешнія спасылкі ці нешта іншае, чым Вы хацелі б падзяліцца.", + "account_edit.custom_fields.title": "Карыстальніцкія палі", + "account_edit.display_name.placeholder": "Вашае бачнае імя — гэта імя, якое іншыя людзі бачаць у Вашым профілі і ў стужках.", + "account_edit.display_name.title": "Бачнае імя", + "account_edit.featured_hashtags.item": "хэштэгі", + "account_edit.featured_hashtags.placeholder": "Дапамажыце іншым зразумець, якія тэмы Вас цікавяць, і атрымаць доступ да іх.", + "account_edit.featured_hashtags.title": "Выбраныя хэштэгі", + "account_edit.name_modal.add_title": "Дадаць бачнае імя", + "account_edit.name_modal.edit_title": "Змяніць бачнае імя", + "account_edit.profile_tab.subtitle": "Змяняйце на свой густ укладкі свайго профілю і тое, што яны паказваюць.", + "account_edit.profile_tab.title": "Налады ўкладкі профілю", + "account_edit.save": "Захаваць", + "account_edit_tags.column_title": "Змяніць выбраныя хэштэгі", + "account_edit_tags.help_text": "Выбраныя хэштэгі дапамагаюць карыстальнікам знаходзіць Ваш профіль і ўзаемадзейнічаць з ім. Яны дзейнічаюць як фільтры пры праглядзе актыўнасці на Вашай старонцы.", + "account_edit_tags.search_placeholder": "Увядзіце хэштэг…", + "account_edit_tags.suggestions": "Прапановы:", + "account_edit_tags.tag_status_count": "{count, plural, one {допіс} few {допісы} many {допісаў} other {допісаў}}", "account_note.placeholder": "Націсніце, каб дадаць нататку", "admin.dashboard.daily_retention": "Штодзённы паказчык утрымання карыстальнікаў пасля рэгістрацыі", "admin.dashboard.monthly_retention": "Штомесячны паказчык утрымання карыстальнікаў пасля рэгістрацыі", @@ -267,21 +293,27 @@ "collections.detail.curated_by_you": "Курыруеце Вы", "collections.detail.loading": "Загружаецца калекцыя…", "collections.detail.share": "Падзяліцца гэтай калекцыяй", + "collections.edit_details": "Рэдагаваць падрабязнасці", "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.mark_as_sensitive": "Пазначыць як далікатную", + "collections.mark_as_sensitive": "Пазначыць як адчувальную", "collections.mark_as_sensitive_hint": "Схаваць апісанне калекцыі і ўліковыя запісы за банерам з папярэджаннем. Назва калекцыі застанецца бачнай.", + "collections.name_length_hint": "Максімум 40 сімвалаў", "collections.new_collection": "Новая калекцыя", "collections.no_collections_yet": "Пакуль няма калекцый.", + "collections.old_last_post_note": "Апошні допіс быў больш за тыдзень таму", "collections.remove_account": "Прыбраць гэты ўліковы запіс", + "collections.report_collection": "Паскардзіцца на гэту калекцыю", "collections.search_accounts_label": "Шукайце ўліковыя запісы, каб дадаць іх сюды…", "collections.search_accounts_max_reached": "Вы дадалі максімальную колькасць уліковых запісаў", + "collections.sensitive": "Адчувальная", "collections.topic_hint": "Дадайце хэштэг, які дапаможа іншым зразумець галоўную тэму гэтай калекцыі.", "collections.view_collection": "Глядзець калекцыю", + "collections.view_other_collections_by_user": "Паглядзець іншыя калекцыі гэтага карыстальніка", "collections.visibility_public": "Публічная", "collections.visibility_public_hint": "Можна знайсці ў пошуку і іншых месцах, дзе з'яўляюцца рэкамендацыі.", "collections.visibility_title": "Бачнасць", @@ -370,6 +402,9 @@ "confirmations.discard_draft.post.title": "Скасаваць чарнавік?", "confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", + "confirmations.follow_to_collection.confirm": "Падпісацца і дадаць у калекцыю", + "confirmations.follow_to_collection.message": "Вам трэба падпісацца на {name}, каб дадаць яго/яе ў калекцыю.", + "confirmations.follow_to_collection.title": "Падпісацца на ўліковы запіс?", "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", "confirmations.follow_to_list.message": "Вам трэба падпісацца на {name}, каб дадаць яго/яе ў спіс.", "confirmations.follow_to_list.title": "Падпісацца на карыстальніка?", @@ -464,8 +499,6 @@ "emoji_button.search_results": "Вынікі пошуку", "emoji_button.symbols": "Сімвалы", "emoji_button.travel": "Падарожжы і месцы", - "empty_column.account_about.me": "Вы пакуль не дадалі аніякай інфармацыі пра сябе.", - "empty_column.account_about.other": "{acct} пакуль не дадаў(-ла) аніякай інфармацыі пра сябе.", "empty_column.account_featured.me": "Вы яшчэ нічога не паказалі. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якімі найбольш карыстаецеся, і нават профілі сваіх сяброў?", "empty_column.account_featured.other": "{acct} яшчэ нічога не паказаў. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якімі найбольш карыстаецеся, і нават профілі сваіх сяброў?", "empty_column.account_featured_other.unknown": "Гэты ўліковы запіс яшчэ нічога не паказаў.", @@ -771,9 +804,9 @@ "notification.moderation_warning": "Вы атрымалі папярэджанне ад мадэратараў", "notification.moderation_warning.action_delete_statuses": "Некаторыя Вашыя допісы былі выдаленыя.", "notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.", - "notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як адчувальныя.", "notification.moderation_warning.action_none": "Ваш уліковы запіс атрымаў папярэджанне ад мадэратараў.", - "notification.moderation_warning.action_sensitive": "З гэтага моманту вашыя допісы будуць пазначаныя як далікатныя.", + "notification.moderation_warning.action_sensitive": "З гэтага моманту вашыя допісы будуць пазначаныя як адчувальныя.", "notification.moderation_warning.action_silence": "Ваш уліковы запіс быў абмежаваны.", "notification.moderation_warning.action_suspend": "Ваш уліковы запіс быў заблакіраваны.", "notification.own_poll": "Ваша апытанне скончылася", @@ -944,6 +977,7 @@ "report.category.title_account": "профіль", "report.category.title_status": "допіс", "report.close": "Гатова", + "report.collection_comment": "Чаму Вы хочаце паскардзіцца на гэту калекцыю?", "report.comment.title": "Што-небудзь яшчэ, што нам неабходна ведаць?", "report.forward": "Пераслаць на {target}", "report.forward_hint": "Гэты ўліковы запіс з іншага сервера. Даслаць ананімную копію скаргі і туды?", @@ -965,6 +999,8 @@ "report.rules.title": "Якія правілы былі парушаны?", "report.statuses.subtitle": "Абярыце ўсе пункты, што падыходзяць", "report.statuses.title": "Ці ёсць допісы, каб падмацаваць гэтую скаргу?", + "report.submission_error": "Немагчыма адправіць скаргу", + "report.submission_error_details": "Калі ласка, праверце сваё сеткавае злучэнне і паспрабуйце зноў пазней.", "report.submit": "Адправіць", "report.target": "Скарга на {target}", "report.thanks.take_action": "Вось Вашыя варыянты кантролю над тым, што Вы бачыце в Mastodon:", @@ -1100,7 +1136,7 @@ "status.report": "Паскардзіцца на @{name}", "status.request_quote": "Даслаць запыт на цытаванне", "status.revoke_quote": "Выдаліць мой допіс з допісу @{name}", - "status.sensitive_warning": "Уражвальны змест", + "status.sensitive_warning": "Адчувальнае змесціва", "status.share": "Абагуліць", "status.show_less_all": "Згарнуць усё", "status.show_more_all": "Разгарнуць усё", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 88bbd4cab85a7a..496cb0d0c96006 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -13,7 +13,6 @@ "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/cs.json b/app/javascript/mastodon/locales/cs.json index cea7ae2b92b677..e79fafa0761974 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -13,7 +13,6 @@ "about.not_available": "Tato informace nebyla zpřístupněna na tomto serveru.", "about.powered_by": "Decentralizovaná sociální média poháněná {mastodon}", "about.rules": "Pravidla serveru", - "account.about": "O účtu", "account.account_note_header": "Osobní poznámka", "account.activity": "Aktivita", "account.add_note": "Přidat vlastní poznámku", @@ -446,8 +445,6 @@ "emoji_button.search_results": "Výsledky hledání", "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestování a místa", - "empty_column.account_about.me": "Zatím jste o sobě nepřidali žádné informace.", - "empty_column.account_about.other": "{acct} zatím o sobě nepřidali žádné informace.", "empty_column.account_featured.me": "Zatím jste nic nezvýraznili. Věděli jste, že na svém profilu můžete zvýraznit hashtagy, které používáte nejvíce, a dokonce účty vašich přátel?", "empty_column.account_featured.other": "{acct} zatím nic nezvýraznili. Věděli jste, že na svém profilu můžete zvýraznit hashtagy, které používáte nejvíce, a dokonce účty vašich přátel?", "empty_column.account_featured_other.unknown": "Tento účet zatím nemá nic zvýrazněného.", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index a54878a80e2fb9..de9b235878de89 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -13,7 +13,6 @@ "about.not_available": "Dyw'r wybodaeth yma heb ei wneud ar gael ar y gweinydd hwn.", "about.powered_by": "Cyfrwng cymdeithasol datganoledig wedi ei yrru gan {mastodon}", "about.rules": "Rheolau'r gweinydd", - "account.about": "Ynghylch", "account.account_note_header": "Nodyn personol", "account.activity": "Gweithgaredd", "account.add_note": "Ychwanegu nodyn personol", @@ -486,8 +485,6 @@ "emoji_button.search_results": "Canlyniadau chwilio", "emoji_button.symbols": "Symbolau", "emoji_button.travel": "Teithio a Llefydd", - "empty_column.account_about.me": "Dydych chi heb ychwanegu unrhyw wybodaeth amdanoch chi'ch hun eto.", - "empty_column.account_about.other": "Dyw {acct} ddim wedi ychwanegu unrhyw wybodaeth amdanyn nhw eu hunain eto.", "empty_column.account_featured.me": "Dydych chi ddim wedi cynnwys unrhyw beth eto. Oeddech chi'n gwybod y gallwch chi gynnwys yr hashnodau rydych chi'n eu defnyddio fwyaf, a hyd yn oed cyfrifon eich ffrindiau ar eich proffil?", "empty_column.account_featured.other": "Dyw {acct} heb gynnwys unrhyw beth eto. Oeddech chi'n gwybod y gallwch chi gynnwys yr hashnodau rydych chi'n eu defnyddio fwyaf, a hyd yn oed cyfrifon eich ffrindiau ar eich proffil?", "empty_column.account_featured_other.unknown": "Dyw'r cyfrif hwn heb gynnwys dim eto.", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 1ee5e74c395ff0..401dfc29488559 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -13,7 +13,6 @@ "about.not_available": "Denne information er ikke blevet gjort tilgængelig på denne server.", "about.powered_by": "Decentraliserede sociale medier drevet af {mastodon}", "about.rules": "Serverregler", - "account.about": "Om", "account.account_note_header": "Personligt notat", "account.activity": "Aktivitet", "account.add_note": "Tilføj en personlig note", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Følges af {name1} og {name2}", "account.featured": "Fremhævet", "account.featured.accounts": "Profiler", + "account.featured.collections": "Samlinger", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Seneste indlæg {date}", "account.featured_tags.last_status_never": "Ingen indlæg", + "account.field_overflow": "Vis fuldt indhold", "account.filters.all": "Al aktivitet", "account.filters.boosts_toggle": "Vis fremhævelser", "account.filters.posts_boosts": "Indlæg og fremhævelser", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Søgeresultater", "emoji_button.symbols": "Symboler", "emoji_button.travel": "Rejser og steder", - "empty_column.account_about.me": "Du har endnu ikke tilføjet nogen information om dig selv.", - "empty_column.account_about.other": "{acct} har endnu ikke tilføjet nogen oplysninger om sig selv.", "empty_column.account_featured.me": "Intet fremhævet endnu. Vidste du, at du kan fremhæve dine mest brugte hashtags og endda din vens konti på din profil?", "empty_column.account_featured.other": "{acct} har ikke fremhævet noget endnu. Vidste du, at du kan fremhæve dine mest brugte hashtags og endda din vens konti på din profil?", "empty_column.account_featured_other.unknown": "Denne konto har ikke fremhævet noget endnu.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 5bac1293cf018d..c4e62d65bc28a1 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -13,7 +13,6 @@ "about.not_available": "Diese Informationen sind auf diesem Server nicht verfügbar.", "about.powered_by": "Ein dezentralisiertes soziales Netzwerk, ermöglicht durch {mastodon}", "about.rules": "Serverregeln", - "account.about": "Über", "account.account_note_header": "Persönliche Notiz", "account.activity": "Aktivitäten", "account.add_note": "Persönliche Notiz hinzufügen", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Gefolgt von {name1} und {name2}", "account.featured": "Vorgestellt", "account.featured.accounts": "Profile", + "account.featured.collections": "Sammlungen", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Neuester Beitrag vom {date}", "account.featured_tags.last_status_never": "Keine Beiträge", + "account.field_overflow": "Vollständigen Inhalt anzeigen", "account.filters.all": "Alle Aktivitäten", "account.filters.boosts_toggle": "Geteilte Beiträge anzeigen", "account.filters.posts_boosts": "Beiträge & geteilte Beiträge", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Suchergebnisse", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen & Orte", - "empty_column.account_about.me": "Du hast noch keine Informationen über dich hinzugefügt.", - "empty_column.account_about.other": "{acct} hat noch keine Informationen über sich hinzugefügt.", "empty_column.account_featured.me": "Du hast bisher noch nichts vorgestellt. Wusstest du, dass du deine häufig verwendeten Hashtags und sogar Profile von Freund*innen vorstellen kannst?", "empty_column.account_featured.other": "{acct} hat bisher noch nichts vorgestellt. Wusstest du, dass du deine häufig verwendeten Hashtags und sogar Profile von Freund*innen vorstellen kannst?", "empty_column.account_featured_other.unknown": "Dieses Profil hat bisher noch nichts vorgestellt.", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index af7b21279570c8..8a38e7c0ce2022 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -13,7 +13,6 @@ "about.not_available": "Αυτές οι πληροφορίες δεν έχουν είναι διαθέσιμες σε αυτόν τον διακομιστή.", "about.powered_by": "Αποκεντρωμένο μέσο κοινωνικής δικτύωσης που βασίζεται στο {mastodon}", "about.rules": "Κανόνες διακομιστή", - "account.about": "Σχετικά", "account.account_note_header": "Προσωπική σημείωση", "account.activity": "Δραστηριότητα", "account.add_note": "Προσθέστε μια προσωπική σημείωση", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Ακολουθείται από {name1} και {name2}", "account.featured": "Αναδεδειγμένα", "account.featured.accounts": "Προφίλ", + "account.featured.collections": "Συλλογές", "account.featured.hashtags": "Ετικέτες", "account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}", "account.featured_tags.last_status_never": "Καμία ανάρτηση", + "account.field_overflow": "Εμφάνιση πλήρους περιεχομένου", "account.filters.all": "Όλη η δραστηριότητα", "account.filters.boosts_toggle": "Εμφάνιση ενισχύσεων", "account.filters.posts_boosts": "Αναρτήσεις και ενισχύσεις", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Αποτελέσματα αναζήτησης", "emoji_button.symbols": "Σύμβολα", "emoji_button.travel": "Ταξίδια & Τοποθεσίες", - "empty_column.account_about.me": "Δεν έχετε προσθέσει ακόμη καμία πληροφορία για τον εαυτό σας.", - "empty_column.account_about.other": "Ο/Η {acct} δεν έχει προσθέσει ακόμη καμία πληροφορία για τον εαυτό του/της.", "empty_column.account_featured.me": "Δεν έχεις αναδείξει τίποτα ακόμη. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", "empty_column.account_featured.other": "Ο/Η {acct} δεν έχει αναδείξει τίποτα ακόμη. Γνώριζες ότι μπορείς να αναδείξεις τις ετικέτες που χρησιμοποιείς περισσότερο, ακόμη και τους λογαριασμούς των φίλων σου στο προφίλ σου;", "empty_column.account_featured_other.unknown": "Αυτός ο λογαριασμός δεν έχει αναδείξει τίποτα ακόμη.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 828e62368c754f..d5b1e24ef41ede 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -13,7 +13,6 @@ "about.not_available": "This information has not been made available on this server.", "about.powered_by": "Decentralised social media powered by {mastodon}", "about.rules": "Server rules", - "account.about": "About", "account.account_note_header": "Personal note", "account.activity": "Activity", "account.add_note": "Add a personal note", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Followed by {name1} and {name2}", "account.featured": "Featured", "account.featured.accounts": "Profiles", + "account.featured.collections": "Collections", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", + "account.field_overflow": "Show full content", "account.filters.all": "All activity", "account.filters.boosts_toggle": "Show boosts", "account.filters.posts_boosts": "Posts and boosts", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Search results", "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", - "empty_column.account_about.me": "You have not added any information about yourself yet.", - "empty_column.account_about.other": "{acct} has not added any information about themselves yet.", "empty_column.account_featured.me": "You have not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?", "empty_column.account_featured_other.unknown": "This account has not featured anything yet.", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index eb371f6013366b..53676366cc77da 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -13,7 +13,6 @@ "about.not_available": "Esta información no está disponible en este servidor.", "about.powered_by": "Redes sociales descentralizadas con tecnología de {mastodon}", "about.rules": "Reglas del servidor", - "account.about": "Información", "account.account_note_header": "Nota personal", "account.activity": "Actividad", "account.add_note": "Agregar una nota personal", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Seguido por {name1} y {name2}", "account.featured": "Destacados", "account.featured.accounts": "Perfiles", + "account.featured.collections": "Colecciones", "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Último mensaje: {date}", "account.featured_tags.last_status_never": "Sin mensajes", + "account.field_overflow": "Mostrar contenido completo", "account.filters.all": "Toda la actividad", "account.filters.boosts_toggle": "Mostrar adhesiones", "account.filters.posts_boosts": "Mensajes y adhesiones", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", - "empty_column.account_about.me": "Todavía no has agregaste ninguna información sobre vos.", - "empty_column.account_about.other": "{acct} todavía no agregó ninguna información sobre esta cuenta.", "empty_column.account_featured.me": "Todavía no destacaste nada. ¿Sabías que en tu perfil podés destacar tus etiquetas que más usás e incluso las cuentas de tus contactos?", "empty_column.account_featured.other": "{acct} todavía no destacó nada. ¿Sabías que en tu perfil podés destacar tus etiquetas que más usás e incluso las cuentas de tus contactos?", "empty_column.account_featured_other.unknown": "Esta cuenta todavía no destacó nada.", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index fc01be8f8e098d..9983265adf5d95 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -13,7 +13,6 @@ "about.not_available": "Esta información no está disponible en este servidor.", "about.powered_by": "Medio social descentralizado con tecnología de {mastodon}", "about.rules": "Reglas del servidor", - "account.about": "Acerca de", "account.account_note_header": "Nota personal", "account.activity": "Actividad", "account.add_note": "Añadir una nota personal", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Seguid por {name1} y {name2}", "account.featured": "Destacado", "account.featured.accounts": "Perfiles", + "account.featured.collections": "Colecciones", "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", + "account.field_overflow": "Mostrar contenido completo", "account.filters.all": "Toda la actividad", "account.filters.boosts_toggle": "Mostrar impulsos", "account.filters.posts_boosts": "Publicaciones e impulsos", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Resultados de búsqueda", "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_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} no ha destacado nada todavía. ¿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 no ha destacado nada todavía.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 11b99095c0eb34..22102bae45c882 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -13,7 +13,6 @@ "about.not_available": "Esta información no está disponible en este servidor.", "about.powered_by": "Redes sociales descentralizadas con tecnología de {mastodon}", "about.rules": "Reglas del servidor", - "account.about": "Acerca de", "account.account_note_header": "Nota personal", "account.activity": "Actividad", "account.add_note": "Añadir una nota personal", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Seguido por {name1} y {name2}", "account.featured": "Destacado", "account.featured.accounts": "Perfiles", + "account.featured.collections": "Colecciones", "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", + "account.field_overflow": "Mostrar contenido completo", "account.filters.all": "Toda la actividad", "account.filters.boosts_toggle": "Mostrar impulsos", "account.filters.posts_boosts": "Publicaciones e impulsos", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Resultados de búsqueda", "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/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.", @@ -1001,7 +1000,7 @@ "report.statuses.subtitle": "Selecciona todos los que correspondan", "report.statuses.title": "¿Hay alguna publicación que respalde este informe?", "report.submission_error": "No se pudo enviar el reporte", - "report.submission_error_details": "Comprueba tu conexión de red e intenta volver a intentarlo más tarde.", + "report.submission_error_details": "Comprueba tu conexión de red e inténtalo más tarde.", "report.submit": "Enviar", "report.target": "Reportando {target}", "report.thanks.take_action": "Aquí están tus opciones para controlar lo que ves en Mastodon:", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 8f3c82027176f6..51a9abb35c3202 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -13,7 +13,6 @@ "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", @@ -448,8 +447,6 @@ "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.", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 6c99cae2f2e106..d9c7ee97a1ed75 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -13,7 +13,6 @@ "about.not_available": "Näitä tietoja ei ole julkaistu tällä palvelimella.", "about.powered_by": "Hajautetun sosiaalisen median tarjoaa {mastodon}", "about.rules": "Palvelimen säännöt", - "account.about": "Tietoja", "account.account_note_header": "Henkilökohtainen muistiinpano", "account.activity": "Toiminta", "account.add_note": "Lisää henkilökohtainen muistiinpano", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Seuraajina {name1} ja {name2}", "account.featured": "Esittelyssä", "account.featured.accounts": "Profiilit", + "account.featured.collections": "Kokoelmat", "account.featured.hashtags": "Aihetunnisteet", "account.featured_tags.last_status_at": "Viimeisin julkaisu {date}", "account.featured_tags.last_status_never": "Ei julkaisuja", + "account.field_overflow": "Näytä koko sisältö", "account.filters.all": "Kaikki toiminta", "account.filters.boosts_toggle": "Näytä tehostukset", "account.filters.posts_boosts": "Julkaisut ja tehostukset", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Hakutulokset", "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu ja paikat", - "empty_column.account_about.me": "Et ole vielä lisännyt tietoja itsestäsi.", - "empty_column.account_about.other": "{acct} ei ole vielä lisännyt tietoja itsestään.", "empty_column.account_featured.me": "Et esittele vielä mitään. Tiesitkö, että voit esitellä profiilissasi eniten käyttämiäsi aihetunnisteita ja jopa ystäviesi tilejä?", "empty_column.account_featured.other": "{acct} ei esittele vielä mitään. Tiesitkö, että voit esitellä profiilissasi eniten käyttämiäsi aihetunnisteita ja jopa ystäviesi tilejä?", "empty_column.account_featured_other.unknown": "Tämä tili ei esittele vielä mitään.", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index b8ded5d2969a3e..9467d2802adfda 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -13,7 +13,6 @@ "about.not_available": "Hetta er ikki tøkt á føroyska servaranum enn.", "about.powered_by": "Miðfirra almennur miðil koyrandi á {mastodon}", "about.rules": "Ambætarareglur", - "account.about": "Um", "account.account_note_header": "Persónlig viðmerking", "account.activity": "Virksemi", "account.add_note": "Legg persónliga notu afturat", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "{name1} og {name2} fylgja", "account.featured": "Tikin fram", "account.featured.accounts": "Vangar", + "account.featured.collections": "Søvn", "account.featured.hashtags": "Frámerki", "account.featured_tags.last_status_at": "Seinasta strongur skrivaður {date}", "account.featured_tags.last_status_never": "Einki uppslag", + "account.field_overflow": "Vís alt innihaldið", "account.filters.all": "Alt virksemi", "account.filters.boosts_toggle": "Vís stimbranir", "account.filters.posts_boosts": "Postar og stimbranir", @@ -167,6 +168,7 @@ "account_edit_tags.help_text": "Sermerkt frámerki hjálpa brúkarum at varnast og virka saman við vanga tínum. Tey síggjast sum filtur á virksemisvísingini av vanga tínum.", "account_edit_tags.search_placeholder": "Áset eitt frámerki…", "account_edit_tags.suggestions": "Uppskot:", + "account_edit_tags.tag_status_count": "{count, plural, one {# postur} other {# postar}}", "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", @@ -305,11 +307,13 @@ "collections.no_collections_yet": "Eingi søvn enn.", "collections.old_last_post_note": "Postaði seinast fyri meira enn einari viku síðani", "collections.remove_account": "Strika hesa kontuna", + "collections.report_collection": "Melda hetta savnið", "collections.search_accounts_label": "Leita eftir kontum at leggja afturat…", "collections.search_accounts_max_reached": "Tú hevur lagt afturat mesta talið av kontum", "collections.sensitive": "Viðkvæmt", "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.view_other_collections_by_user": "Vís hini søvnini hjá hesum brúkaranum", "collections.visibility_public": "Alment", "collections.visibility_public_hint": "Kann uppdagast í leitiúrslitum og øðrum økjum, har viðmæli síggjast.", "collections.visibility_title": "Sýni", @@ -495,8 +499,6 @@ "emoji_button.search_results": "Leitiúrslit", "emoji_button.symbols": "Ímyndir", "emoji_button.travel": "Ferðing og støð", - "empty_column.account_about.me": "Tú hevur ikki lagt nakrar upplýsingar um teg sjálva/n inn enn.", - "empty_column.account_about.other": "{acct} hevur ikki lagt nakrar upplýsingar um seg sjálva/n inn enn.", "empty_column.account_featured.me": "Tú hevur ikki tikið nakað fram enn. Visti tú, at tú kanst taka fram tey frámerki, tú brúkar mest, og sjálvt kontur hjá vinum tínum á vangan hjá tær?", "empty_column.account_featured.other": "{acct} hevur ikki tikið nakað fram enn. Visti tú, at tú kanst taka fram tey frámerki, tú brúkar mest, og sjálvt kontur hjá vinum tínum á vangan hjá tær?", "empty_column.account_featured_other.unknown": "Hendan kontan hevur ikki tikið nakað fram enn.", @@ -975,6 +977,7 @@ "report.category.title_account": "vangi", "report.category.title_status": "postinum", "report.close": "Liðugt", + "report.collection_comment": "Hví vilt tú melda hetta savnið?", "report.comment.title": "Er nakað annað, sum tú heldur, at vit áttu at vitað?", "report.forward": "Víðarisend til {target}", "report.forward_hint": "Kontan er frá einum øðrum ambætara. Send eitt dulnevnt avrit av meldingini hagar eisini?", @@ -996,6 +999,8 @@ "report.rules.title": "Hvørjar reglur verða brotnar?", "report.statuses.subtitle": "Vel alt viðkomandi", "report.statuses.title": "Eru nakrir postar, sum stuðla uppundir hesa meldingina?", + "report.submission_error": "Meldingin kundi ikki fráboðast", + "report.submission_error_details": "Vinarliga eftirkanna netsambandið og royn aftur seinni.", "report.submit": "Send inn", "report.target": "Meldi {target}", "report.thanks.take_action": "Her eru tínir møguleikar fyri at stýra, hvat tú sær á Mastodon:", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 3bf2dceabd01e5..4d3421be058a4b 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -13,7 +13,6 @@ "about.not_available": "Cette information n'a pas été rendue disponible sur ce serveur.", "about.powered_by": "Réseau social décentralisé propulsé par {mastodon}", "about.rules": "Règles du serveur", - "account.about": "À propos", "account.account_note_header": "Note personnelle", "account.activity": "Activité", "account.add_note": "Ajouter une note personnelle", @@ -45,6 +44,7 @@ "account.familiar_followers_two": "Suivi·e par {name1} et {name2}", "account.featured": "En vedette", "account.featured.accounts": "Profils", + "account.featured.collections": "Collections", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernière publication {date}", "account.featured_tags.last_status_never": "Aucune publication", @@ -312,6 +312,7 @@ "collections.sensitive": "Sensible", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.view_collection": "Voir la collection", + "collections.view_other_collections_by_user": "Voir les autres collections par ce compte", "collections.visibility_public": "Publique", "collections.visibility_public_hint": "Visible dans les résultats de recherche et les recommandations.", "collections.visibility_title": "Visibilité", @@ -497,8 +498,6 @@ "emoji_button.search_results": "Résultats", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", - "empty_column.account_about.me": "Vous n'avez pas encore ajouté d'informations sur vous.", - "empty_column.account_about.other": "{acct} n'a pas encore ajouté d'informations sur lui-même.", "empty_column.account_featured.me": "Vous n'avez pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?", "empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?", "empty_column.account_featured_other.unknown": "Ce compte n'a mis aucun contenu en avant pour l'instant.", @@ -977,7 +976,7 @@ "report.category.title_account": "ce profil", "report.category.title_status": "ce message", "report.close": "Terminé", - "report.collection_comment": "Pourquoi souhaitez-vous signaler cette collection ?", + "report.collection_comment": "Pourquoi voulez-vous signaler cette collection ?", "report.comment.title": "Y a-t-il autre chose que nous devrions savoir?", "report.forward": "Transférer à {target}", "report.forward_hint": "Le compte provient d’un autre serveur. Envoyer une copie anonyme du rapport là-bas également?", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 0169a86547226e..dcbfb5af3484a2 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -13,7 +13,6 @@ "about.not_available": "Cette information n'a pas été rendue disponible sur ce serveur.", "about.powered_by": "Réseau social décentralisé propulsé par {mastodon}", "about.rules": "Règles du serveur", - "account.about": "À propos", "account.account_note_header": "Note personnelle", "account.activity": "Activité", "account.add_note": "Ajouter une note personnelle", @@ -45,6 +44,7 @@ "account.familiar_followers_two": "Suivi·e par {name1} et {name2}", "account.featured": "En vedette", "account.featured.accounts": "Profils", + "account.featured.collections": "Collections", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernier message le {date}", "account.featured_tags.last_status_never": "Aucun message", @@ -312,6 +312,7 @@ "collections.sensitive": "Sensible", "collections.topic_hint": "Ajouter un hashtag pour aider les autres personnes à comprendre le sujet de la collection.", "collections.view_collection": "Voir la collection", + "collections.view_other_collections_by_user": "Voir les autres collections par ce compte", "collections.visibility_public": "Publique", "collections.visibility_public_hint": "Visible dans les résultats de recherche et les recommandations.", "collections.visibility_title": "Visibilité", @@ -497,8 +498,6 @@ "emoji_button.search_results": "Résultats de la recherche", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", - "empty_column.account_about.me": "Vous n'avez pas encore ajouté d'informations sur vous.", - "empty_column.account_about.other": "{acct} n'a pas encore ajouté d'informations sur lui-même.", "empty_column.account_featured.me": "Vous n'avez pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?", "empty_column.account_featured.other": "{acct} n'a pas encore mis de contenu en avant. Saviez-vous que vous pouviez mettre en avant les hashtags que vous utilisez le plus, et même les comptes de vos amis sur votre profil ?", "empty_column.account_featured_other.unknown": "Ce compte n'a mis aucun contenu en avant pour l'instant.", @@ -977,7 +976,7 @@ "report.category.title_account": "ce profil", "report.category.title_status": "ce message", "report.close": "Terminé", - "report.collection_comment": "Pourquoi souhaitez-vous signaler cette collection ?", + "report.collection_comment": "Pourquoi voulez-vous signaler cette collection ?", "report.comment.title": "Y a-t-il autre chose que nous devrions savoir ?", "report.forward": "Transférer à {target}", "report.forward_hint": "Le compte provient d’un autre serveur. Envoyer également une copie anonyme du rapport ?", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index cd2ea68e363224..5bc25e66ccb536 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -13,7 +13,6 @@ "about.not_available": "Níor cuireadh an t-eolas seo ar fáil ar an bhfreastalaí seo.", "about.powered_by": "Meáin shóisialta díláraithe faoi chumhacht {mastodon}", "about.rules": "Rialacha an fhreastalaí", - "account.about": "Maidir", "account.account_note_header": "Nóta pearsanta", "account.activity": "Gníomhaíocht", "account.add_note": "Cuir nóta pearsanta leis", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Ina dhiaidh sin tá {name1} agus {name2}", "account.featured": "Faoi thrácht", "account.featured.accounts": "Próifílí", + "account.featured.collections": "Bailiúcháin", "account.featured.hashtags": "Haischlibeanna", "account.featured_tags.last_status_at": "Postáil is déanaí ar {date}", "account.featured_tags.last_status_never": "Gan aon phoist", + "account.field_overflow": "Taispeáin an t-ábhar iomlán", "account.filters.all": "Gach gníomhaíocht", "account.filters.boosts_toggle": "Taispeáin borradh", "account.filters.posts_boosts": "Poist agus borradh", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Torthaí cuardaigh", "emoji_button.symbols": "Comharthaí", "emoji_button.travel": "Taisteal ⁊ Áiteanna", - "empty_column.account_about.me": "Níl aon fhaisnéis fút féin curtha leis agat go fóill.", - "empty_column.account_about.other": "Níl aon fhaisnéis fúthu féin curtha leis ag {acct} go fóill.", "empty_column.account_featured.me": "Níl aon rud curtha i láthair agat go fóill. An raibh a fhios agat gur féidir leat na haischlibeanna is mó a úsáideann tú, agus fiú cuntais do chairde, a chur i láthair ar do phróifíl?", "empty_column.account_featured.other": "Níl aon rud feicthe ag {acct} go fóill. An raibh a fhios agat gur féidir leat na hashtags is mó a úsáideann tú, agus fiú cuntais do chairde, a chur ar do phróifíl?", "empty_column.account_featured_other.unknown": "Níl aon rud le feiceáil sa chuntas seo go fóill.", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 4ab4a38e0b8bef..1a49f6ba36dd46 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -13,7 +13,6 @@ "about.not_available": "Esta información non está dispoñible neste servidor.", "about.powered_by": "Comunicación social descentralizada grazas a {mastodon}", "about.rules": "Regras do servidor", - "account.about": "Sobre", "account.account_note_header": "Nota persoal", "account.activity": "Actividade", "account.add_note": "Engadir nota persoal", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Seguida por {name1} e {name2}", "account.featured": "Destacado", "account.featured.accounts": "Perfís", + "account.featured.collections": "Coleccións", "account.featured.hashtags": "Cancelos", "account.featured_tags.last_status_at": "Última publicación o {date}", "account.featured_tags.last_status_never": "Sen publicacións", + "account.field_overflow": "Mostrar contido completo", "account.filters.all": "Toda actividade", "account.filters.boosts_toggle": "Mostrar promocións", "account.filters.posts_boosts": "Publicacións e promocións", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Resultados da procura", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viaxes e Lugares", - "empty_column.account_about.me": "Aínda non engadiches ningunha información sobre ti.", - "empty_column.account_about.other": "{acct} aínda non engadiu ningunha información sobre a súa conta.", "empty_column.account_featured.me": "Aínda non destacaches nada. Sabías que podes facer destacar no teu perfil os cancelos que máis usas, incluso as contas das túas amizades?", "empty_column.account_featured.other": "{acct} aínda non escolleu nada para destacar. Sabías que podes facer destacatar no teu perfil os cancelos que máis usas, incluso os perfís das túas amizades?", "empty_column.account_featured_other.unknown": "Esta conta aínda non destacou nada.", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 1efc3427de2433..abe3687f897d7e 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -13,7 +13,6 @@ "about.not_available": "המידע אינו זמין על שרת זה.", "about.powered_by": "רשת חברתית מבוזרת המופעלת על ידי {mastodon}", "about.rules": "כללי השרת", - "account.about": "אודות", "account.account_note_header": "הערה אישית", "account.activity": "פעילות", "account.add_note": "הוספת הערה פרטית", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "החשבון נעקב על ידי {name1} ו־{name2}", "account.featured": "מומלץ", "account.featured.accounts": "פרופילים", + "account.featured.collections": "אוספים", "account.featured.hashtags": "תגיות", "account.featured_tags.last_status_at": "חצרוץ אחרון בתאריך {date}", "account.featured_tags.last_status_never": "אין חצרוצים", + "account.field_overflow": "הצג תוכן מלא", "account.filters.all": "כל הפעילות", "account.filters.boosts_toggle": "הצגת הדהודים", "account.filters.posts_boosts": "הודעות והדהודים", @@ -498,8 +499,6 @@ "emoji_button.search_results": "תוצאות חיפוש", "emoji_button.symbols": "סמלים", "emoji_button.travel": "טיולים ואתרים", - "empty_column.account_about.me": "עוד לא הוספת מידע על עצמך.", - "empty_column.account_about.other": "{acct} עוד לא הוסיפו מידע עצמי.", "empty_column.account_featured.me": "עוד לא קידמת תכנים. הידעת שניתן לקדם תגיות שבשימושך התדיר או אפילו את החשבונות של חבריםות בפרופיל שלך?", "empty_column.account_featured.other": "{acct} עוד לא קידם תכנים. הידעת שניתן לקדם תגיות שבשימושך התדיר או אפילו את החשבונות של חבריםות בפרופיל שלך?", "empty_column.account_featured_other.unknown": "חשבון זה עוד לא קידם תכנים.", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index ffb3bb05e1b897..6e2b30eb510a58 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -13,7 +13,6 @@ "about.not_available": "Ez az információ nem lett közzétéve ezen a kiszolgálón.", "about.powered_by": "Decentralizált közösségi média a {mastodon} segítségével", "about.rules": "Kiszolgáló szabályai", - "account.about": "Névjegy", "account.account_note_header": "Személyes megjegyzés", "account.activity": "Tevékenység", "account.add_note": "Személyes megjegyzés hozzáadása", @@ -477,8 +476,6 @@ "emoji_button.search_results": "Keresési találatok", "emoji_button.symbols": "Szimbólumok", "emoji_button.travel": "Utazás és helyek", - "empty_column.account_about.me": "Még nem adtál meg semmilyen információt magadról.", - "empty_column.account_about.other": "{acct} nem adott meg semmilyen információt magáról.", "empty_column.account_featured.me": "Még semmit sem emeltél ki. Tudtad, hogy kiemelheted a profilodon a legtöbbet használt hashtageidet, és még a barátaid fiókját is?", "empty_column.account_featured.other": "{acct} még semmit sem emelt ki. Tudtad, hogy kiemelheted a profilodon a legtöbbet használt hashtageidet, és még a barátaid fiókját is?", "empty_column.account_featured_other.unknown": "Ez a fiók még semmit sem emelt ki.", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index a0288b838a7a13..3bbde7a51bd670 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -13,7 +13,6 @@ "about.not_available": "Þessar upplýsingar hafa ekki verið gerðar aðgengilegar á þessum netþjóni.", "about.powered_by": "Dreifhýstur samskiptamiðill keyrður með {mastodon}", "about.rules": "Reglur netþjónsins", - "account.about": "Um aðganginn", "account.account_note_header": "Einkaminnispunktur", "account.activity": "Virkni", "account.add_note": "Bæta við einkaminnispunkti", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Fylgt af {name1} og {name2}", "account.featured": "Með aukið vægi", "account.featured.accounts": "Notendasnið", + "account.featured.collections": "Söfn", "account.featured.hashtags": "Myllumerki", "account.featured_tags.last_status_at": "Síðasta færsla þann {date}", "account.featured_tags.last_status_never": "Engar færslur", + "account.field_overflow": "Birta allt efnið", "account.filters.all": "Öll virkni", "account.filters.boosts_toggle": "Sýna endurbirtingar", "account.filters.posts_boosts": "Færslur og endurbirtingar", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Leitarniðurstöður", "emoji_button.symbols": "Tákn", "emoji_button.travel": "Ferðalög og staðir", - "empty_column.account_about.me": "Þú hefur ekki enn bætt við neinum upplýsingum um þig.", - "empty_column.account_about.other": "{acct} hefur ekki enn bætt við neinum upplýsingum um sig.", "empty_column.account_featured.me": "Þú hefur enn ekki sett neitt sem áberandi. Vissirðu að þú getur gefið meira vægi á notandasniðinu þínu ýmsum myllumerkjum sem þú notar oft og jafnvel aðgöngum vina þinna?", "empty_column.account_featured.other": "{acct} hefur enn ekki sett neitt sem áberandi. Vissirðu að þú getur gefið meira vægi á notandasniðinu þínu ýmsum myllumerkjum sem þú notar oft og jafnvel aðgöngum vina þinna?", "empty_column.account_featured_other.unknown": "Þessi notandi hefur enn ekki sett neitt sem áberandi.", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 132e0633793af4..8802c730b8eab9 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -13,7 +13,6 @@ "about.not_available": "Queste informazioni non sono state rese disponibili su questo server.", "about.powered_by": "Social media decentralizzato basato su {mastodon}", "about.rules": "Regole del server", - "account.about": "Info", "account.account_note_header": "Note personali", "account.activity": "Attività", "account.add_note": "Aggiungi una nota personale", @@ -498,8 +497,6 @@ "emoji_button.search_results": "Risultati della ricerca", "emoji_button.symbols": "Simboli", "emoji_button.travel": "Viaggi & Luoghi", - "empty_column.account_about.me": "Non hai ancora aggiunto alcuna informazione su di te.", - "empty_column.account_about.other": "{acct} non ha ancora aggiunto alcuna informazione su sé stesso/a.", "empty_column.account_featured.me": "Non hai ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?", "empty_column.account_featured.other": "{acct} non ha ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?", "empty_column.account_featured_other.unknown": "Questo account non ha ancora pubblicato nulla.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index b18c147e9eba3b..b08775be977262 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -13,7 +13,6 @@ "about.not_available": "이 정보는 이 서버에서 사용할 수 없습니다.", "about.powered_by": "{mastodon}으로 구동되는 분산 소셜 미디어", "about.rules": "서버 규칙", - "account.about": "정보", "account.account_note_header": "개인 메모", "account.activity": "활동", "account.add_note": "개인 메모 추가", diff --git a/app/javascript/mastodon/locales/nan-TW.json b/app/javascript/mastodon/locales/nan-TW.json index ab51b182a6243f..60639f9756a1cc 100644 --- a/app/javascript/mastodon/locales/nan-TW.json +++ b/app/javascript/mastodon/locales/nan-TW.json @@ -13,7 +13,6 @@ "about.not_available": "Tsit ê資訊bē-tàng tī tsit ê服侍器使用。", "about.powered_by": "由 {mastodon} 提供ê非中心化社群媒體", "about.rules": "服侍器ê規則", - "account.about": "概要", "account.account_note_header": "個人ê註解", "account.activity": "活動", "account.add_note": "加私人ê註解", @@ -487,8 +486,6 @@ "emoji_button.search_results": "Tshiau-tshuē ê結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅行kap地點", - "empty_column.account_about.me": "Lí iáu bē加任何關係lí ê資訊。", - "empty_column.account_about.other": "{acct} iáu bē加任何關係伊ê資訊。", "empty_column.account_featured.me": "Lí iáu無任何ê特色內容。Lí kám知影lí ē當kā lí tsia̍p-tsia̍p用ê hashtag,甚至是朋友ê口座揀做特色ê內容,khǹg佇lí ê個人資料內底?", "empty_column.account_featured.other": "{acct} iáu無任何ê特色內容。Lí kám知影lí ē當kā lí tsia̍p-tsia̍p用ê hashtag,甚至是朋友ê口座揀做特色ê內容,khǹg佇lí ê個人資料內底?", "empty_column.account_featured_other.unknown": "Tsit ê口座iáu無任何ê特色內容。", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 625effdde28a4c..084cff2c85c2c6 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -13,7 +13,6 @@ "about.not_available": "Deze informatie is niet beschikbaar gemaakt op deze server.", "about.powered_by": "Gedecentraliseerde sociale media mogelijk gemaakt door {mastodon}", "about.rules": "Serverregels", - "account.about": "Over", "account.account_note_header": "Persoonlijke opmerking", "account.activity": "Activiteit", "account.add_note": "Een persoonlijke opmerking toevoegen", @@ -45,6 +44,7 @@ "account.familiar_followers_two": "Gevolgd door {name1} en {name2}", "account.featured": "Uitgelicht", "account.featured.accounts": "Profielen", + "account.featured.collections": "Verzamelingen", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Laatste bericht op {date}", "account.featured_tags.last_status_never": "Geen berichten", @@ -167,6 +167,7 @@ "account_edit_tags.help_text": "Aanbevolen hashtags helpen gebruikers je profiel te ontdekken en te communiceren. Ze verschijnen als filters op de activiteitenweergave van je pagina.", "account_edit_tags.search_placeholder": "Voer een hashtag in…", "account_edit_tags.suggestions": "Suggesties:", + "account_edit_tags.tag_status_count": "{count, plural, one {# bericht} other {# berichten}}", "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", @@ -305,11 +306,13 @@ "collections.no_collections_yet": "Nog geen verzamelingen.", "collections.old_last_post_note": "Laatst gepost over een week geleden", "collections.remove_account": "Deze account verwijderen", + "collections.report_collection": "Deze verzameling rapporteren", "collections.search_accounts_label": "Zoek naar accounts om toe te voegen…", "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.sensitive": "Gevoelig", "collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze verzameling te begrijpen.", "collections.view_collection": "Verzameling bekijken", + "collections.view_other_collections_by_user": "Bekijk andere verzamelingen van deze gebruiker", "collections.visibility_public": "Openbaar", "collections.visibility_public_hint": "Te zien onder zoekresultaten en in andere gebieden waar aanbevelingen verschijnen.", "collections.visibility_title": "Zichtbaarheid", @@ -495,8 +498,6 @@ "emoji_button.search_results": "Zoekresultaten", "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en locaties", - "empty_column.account_about.me": "Je hebt nog een enkele informatie over jezelf toegevoegd.", - "empty_column.account_about.other": "{acct} heeft nog geen enkele informatie over zichzelf toegevoegd.", "empty_column.account_featured.me": "Je hebt nog niets uitgelicht. Wist je dat je een aantal van jouw berichten, jouw meest gebruikte hashtags en zelfs accounts van je vrienden op je profiel kunt uitlichten?", "empty_column.account_featured.other": "{acct} heeft nog niets uitgelicht. Wist je dat je een aantal van jouw berichten, jouw meest gebruikte hashtags en zelfs accounts van je vrienden op je profiel kunt uitlichten?", "empty_column.account_featured_other.unknown": "Dit account heeft nog niets uitgelicht.", @@ -975,6 +976,7 @@ "report.category.title_account": "account", "report.category.title_status": "bericht", "report.close": "Klaar", + "report.collection_comment": "Waarom wil je deze verzameling rapporteren?", "report.comment.title": "Zijn er nog andere dingen waarvan je denkt dat wij dat moeten weten?", "report.forward": "Naar {target} doorsturen", "report.forward_hint": "Het account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van deze rapportage naar toe sturen?", @@ -996,6 +998,8 @@ "report.rules.title": "Welke regels worden geschonden?", "report.statuses.subtitle": "Selecteer wat van toepassing is", "report.statuses.title": "Zijn er berichten die deze rapportage ondersteunen?", + "report.submission_error": "Rapportering kon niet worden ingediend", + "report.submission_error_details": "Controleer de netwerkverbinding en probeer het later opnieuw.", "report.submit": "Verzenden", "report.target": "{target} rapporteren", "report.thanks.take_action": "Hier zijn jouw opties waarmee je kunt bepalen wat je in Mastodon wilt zien:", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index de805231ede445..3313efd115ad15 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -13,7 +13,6 @@ "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", @@ -496,8 +495,6 @@ "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.", @@ -1165,7 +1162,7 @@ "ui.beforeunload": "Kladden din forsvinn om du forlèt Mastodon no.", "units.short.billion": "{count}m.ard", "units.short.million": "{count}mill", - "units.short.thousand": "{count}T", + "units.short.thousand": "{count}k", "upload_area.title": "Dra & slepp for å lasta opp", "upload_button.label": "Legg til medium", "upload_error.limit": "Du har gått over opplastingsgrensa.", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 87d10df9bb85d4..a6ab9f30e1614f 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -843,7 +843,7 @@ "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.", "units.short.billion": "{count}m.ard", "units.short.million": "{count}mill", - "units.short.thousand": "{count}T", + "units.short.thousand": "{count}k", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", "upload_error.limit": "Filopplastingsgrensen er oversteget.", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index e86f8c72f9fcb7..d6084366cd568e 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -13,7 +13,6 @@ "about.not_available": "Esta informação não foi disponibilizada neste servidor.", "about.powered_by": "Rede social descentralizada baseada no {mastodon}", "about.rules": "Regras do servidor", - "account.about": "Sobre", "account.account_note_header": "Nota pessoal", "account.activity": "Atividade", "account.add_note": "Adicionar nota pessoal", @@ -496,8 +495,6 @@ "emoji_button.search_results": "Resultado da pesquisa", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagem e Lugares", - "empty_column.account_about.me": "Você ainda não inseriu nenhuma informação sobre si.", - "empty_column.account_about.other": "{acct} ainda não adicionou nenhuma informação sobre si.", "empty_column.account_featured.me": "Você ainda não destacou nada. Você sabia que pode destacar seus posts, hashtags que você mais usa e até mesmo contas de seus amigos no seu perfil?", "empty_column.account_featured.other": "{acct} Ainda não destacou nada. Você sabia que pode destacar suas publicações, hashtags que você mais usa e até mesmo contas de seus amigos no seu perfil?", "empty_column.account_featured_other.unknown": "Esta conta ainda não destacou nada.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 930e69f8460fb9..74147e6c59290c 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -13,7 +13,6 @@ "about.not_available": "Esta informação não foi disponibilizada neste servidor.", "about.powered_by": "Rede social descentralizada baseada no {mastodon}", "about.rules": "Regras do servidor", - "account.about": "Sobre", "account.account_note_header": "Nota pessoal", "account.activity": "Atividade", "account.add_note": "Adicionar uma nota pessoal", @@ -45,6 +44,7 @@ "account.familiar_followers_two": "Seguido por {name1} e {name2}", "account.featured": "Destaques", "account.featured.accounts": "Perfis", + "account.featured.collections": "Coleções", "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", @@ -145,6 +145,9 @@ "account_edit.bio.title": "Bio", "account_edit.bio_modal.add_title": "Adicionar biografia", "account_edit.bio_modal.edit_title": "Editar biografia", + "account_edit.button.add": "Adicionar {item}", + "account_edit.button.delete": "Eliminar \"{item}", + "account_edit.button.edit": "Editar {item}", "account_edit.char_counter": "{currentLength}/{maxLength} caracteres", "account_edit.column_button": "Concluído", "account_edit.column_title": "Editar Perfil", @@ -152,10 +155,15 @@ "account_edit.custom_fields.title": "Campos personalizados", "account_edit.display_name.placeholder": "Como o seu nome vai aparecer no seu perfil e nas linhas do tempo.", "account_edit.display_name.title": "Nome a mostrar", + "account_edit.featured_hashtags.item": "etiquetas", "account_edit.featured_hashtags.placeholder": "Ajude à sua identificação por outros e tenha acesso rápido aos seus tópicos favoritos.", "account_edit.featured_hashtags.title": "Etiquetas em destaque", "account_edit.name_modal.add_title": "Adicionar nome a mostrar", "account_edit.name_modal.edit_title": "Editar o nome a mostrar", + "account_edit.save": "Guardar", + "account_edit_tags.column_title": "Editar etiquetas em destaque", + "account_edit_tags.search_placeholder": "Insira uma etiqueta…", + "account_edit_tags.suggestions": "Sugestões:", "account_note.placeholder": "Clicar para adicionar nota", "admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição", "admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição", @@ -259,9 +267,11 @@ "closed_registrations_modal.preamble": "O Mastodon é descentralizado, por isso não importa onde a tua conta é criada, pois continuarás a poder acompanhar e interagir com qualquer um neste servidor. Podes até alojar o teu próprio servidor!", "closed_registrations_modal.title": "Criar uma conta no Mastodon", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", + "collections.accounts.empty_title": "Esta coleção está vazia", "collections.collection_description": "Descrição", "collections.collection_name": "Nome", "collections.collection_topic": "Tópico", + "collections.confirm_account_removal": "Tem a certeza que quer remover esta conta desta coleção?", "collections.content_warning": "Aviso de conteúdo", "collections.continue": "Continuar", "collections.create.accounts_subtitle": "Apenas as contas que segue e que optaram por ser descobertas podem ser adicionadas.", @@ -272,7 +282,12 @@ "collections.create_collection": "Criar coleção", "collections.delete_collection": "Eliminar coleção", "collections.description_length_hint": "Limite de 100 caracteres", + "collections.detail.accounts_heading": "Contas", + "collections.detail.loading": "A carregar a coleção…", + "collections.detail.share": "Partilhar esta coleção", + "collections.edit_details": "Editar detalhes", "collections.error_loading_collections": "Ocorreu um erro ao tentar carregar as suas coleções.", + "collections.hints.add_more_accounts": "Adicione pelo menos {count, plural, one {# conta} other {# contas}} para continuar", "collections.last_updated_at": "Última atualização: {date}", "collections.manage_accounts": "Gerir contas", "collections.mark_as_sensitive": "Marcar como sensível", @@ -463,8 +478,6 @@ "emoji_button.search_results": "Resultados da pesquisa", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens e lugares", - "empty_column.account_about.me": "Ainda não adicionou nenhuma informação sobre si.", - "empty_column.account_about.other": "{acct} ainda não adicionou nenhuma informação sobre si.", "empty_column.account_featured.me": "Ainda não colocou nada em destaque. Sabia que pode destacar as etiquetas que mais utiliza e até as contas dos seus amigos no seu perfil?", "empty_column.account_featured.other": "{acct} ainda não colocou nada em destaque. Sabia que pode destacar as etiquetas que mais utiliza e até as contas dos seus amigos no seu perfil?", "empty_column.account_featured_other.unknown": "Esta conta ainda não colocou nada em destaque.", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 5c84ee3f2dd503..7e3c421ccec9cd 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -13,7 +13,6 @@ "about.not_available": "Ky informacion, në këtë shërbyes, nuk jepet.", "about.powered_by": "Media shoqërore e decentralizuar, bazuar në {mastodon}", "about.rules": "Rregulla shërbyesi", - "account.about": "Mbi", "account.account_note_header": "Shënim personal", "account.activity": "Veprimtari", "account.add_note": "Shtoni një shënim personal", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Ndjekur nga {name1} dhe {name2}", "account.featured": "Të zgjedhur", "account.featured.accounts": "Profile", + "account.featured.collections": "Koleksione", "account.featured.hashtags": "Hashtag-ë", "account.featured_tags.last_status_at": "Postimi i fundit më {date}", "account.featured_tags.last_status_never": "Pa postime", + "account.field_overflow": "Shfaq lëndë të plotë", "account.filters.all": "Krejt veprimtarinë", "account.filters.boosts_toggle": "Shfaq përforcime", "account.filters.posts_boosts": "Postime dhe përforcime", @@ -495,8 +496,6 @@ "emoji_button.search_results": "Përfundime kërkimi", "emoji_button.symbols": "Simbole", "emoji_button.travel": "Udhëtime & Vende", - "empty_column.account_about.me": "S’keni shtuar ende ndonjë hollësi rreth vetes.", - "empty_column.account_about.other": "{acct} s’ka shtuar ende ndonjë hollësi rreth vetes.", "empty_column.account_featured.me": "S’keni ende të zgjedhur diçka. E dini se në profilin tuaj mund të shfaqni si të zgjedhura hashtag-ët që përdorni më tepër dhe madje edhe llogaritë e shokëve tuaj?", "empty_column.account_featured.other": "{acct} s’ka të zgjedhur ende ndonjë gjë. E dini se në profilin tuaj mund të shfaqni si të zgjedhura hashtag-ët që përdorni më tepër dhe madje edhe llogaritë e shokëve tuaj?", "empty_column.account_featured_other.unknown": "Kjo llogari s’ka ende gjë të zgjedhur.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index f06613fc875637..17f254a1e0f524 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -13,7 +13,6 @@ "about.not_available": "Denna information har inte gjorts tillgänglig på denna server.", "about.powered_by": "En decentraliserad plattform for sociala medier, drivet av {mastodon}", "about.rules": "Serverregler", - "account.about": "Om", "account.account_note_header": "Personlig anteckning", "account.add_or_remove_from_list": "Lägg till i eller ta bort från listor", "account.badges.bot": "Bot", @@ -377,8 +376,6 @@ "emoji_button.search_results": "Sökresultat", "emoji_button.symbols": "Symboler", "emoji_button.travel": "Resor & platser", - "empty_column.account_about.me": "Du har inte lagt till någon information om dig själv än.", - "empty_column.account_about.other": "{acct} har inte lagt till någon information om sig själv än.", "empty_column.account_featured.me": "Du har inte presenterat något ännu. Visste du att du kan markera de fyrkantstaggar du använder mest och även din väns konton på din profil?", "empty_column.account_featured.other": "{acct} har inte presenterat något ännu. Visste du att du kan markera de fyrkantstaggar som du använder mest och även din väns konton på din profil?", "empty_column.account_featured_other.unknown": "Detta konto har inte presenterat något ännu.", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 640295ac6e9ca5..ec7906da578a7e 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -13,7 +13,6 @@ "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", @@ -306,11 +305,13 @@ "collections.no_collections_yet": "Henüz hiçbir koleksiyon yok.", "collections.old_last_post_note": "Son gönderi bir haftadan önce", "collections.remove_account": "Bu hesabı çıkar", + "collections.report_collection": "Bu koleksiyonu bildir", "collections.search_accounts_label": "Eklemek için hesap arayın…", "collections.search_accounts_max_reached": "Maksimum hesabı eklediniz", "collections.sensitive": "Hassas", "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.view_other_collections_by_user": "Bu kullanıcının diğer koleksiyonlarını görüntüle", "collections.visibility_public": "Herkese açık", "collections.visibility_public_hint": "Arama sonuçlarında ve önerilerin görüntülendiği diğer alanlarda keşfedilebilir.", "collections.visibility_title": "Görünürlük", @@ -496,8 +497,6 @@ "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ı.", @@ -976,6 +975,7 @@ "report.category.title_account": "profil", "report.category.title_status": "gönderi", "report.close": "Tamam", + "report.collection_comment": "Bu koleksiyonu neden bildirmek istiyorsunuz?", "report.comment.title": "Bilmemizi istediğiniz başka bir şey var mı?", "report.forward": "{target} ilet", "report.forward_hint": "Hesap başka bir sunucudan. Raporun anonim bir kopyası da oraya gönderilsin mi?", @@ -997,6 +997,8 @@ "report.rules.title": "Hangi kurallar ihlal ediliyor?", "report.statuses.subtitle": "Geçerli olanların hepsini seçin", "report.statuses.title": "Bu bildirimi destekleyecek herhangi bir gönderi var mı?", + "report.submission_error": "Bildirim gönderilemiyor", + "report.submission_error_details": "Lütfen ağ durumunu kontrol edin ve daha sonra tekrar deneyin.", "report.submit": "Gönder", "report.target": "{target} Bildiriliyor", "report.thanks.take_action": "Mastodon'da ne görebileceğinizi denetlemeye ilişkin seçenekler şunlardır:", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 0a0885f73dd5f3..ad14df1a1a92e3 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -13,7 +13,6 @@ "about.not_available": "Máy chủ này chưa cung cấp thông tin.", "about.powered_by": "Mạng xã hội liên hợp {mastodon}", "about.rules": "Nội quy máy chủ", - "account.about": "Giới thiệu", "account.account_note_header": "Ghi chú", "account.activity": "Hoạt động", "account.add_note": "Thêm ghi chú", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "Theo dõi bởi {name1} và {name2}", "account.featured": "Nêu bật", "account.featured.accounts": "Tài khoản", + "account.featured.collections": "Collection", "account.featured.hashtags": "Hashtag thường dùng", "account.featured_tags.last_status_at": "Tút gần nhất {date}", "account.featured_tags.last_status_never": "Chưa có tút", + "account.field_overflow": "Hiện đầy đủ nội dung", "account.filters.all": "Tất cả hoạt động", "account.filters.boosts_toggle": "Hiện những lượt đăng lại", "account.filters.posts_boosts": "Tút và lượt đăng lại", @@ -498,8 +499,6 @@ "emoji_button.search_results": "Kết quả tìm kiếm", "emoji_button.symbols": "Biểu tượng", "emoji_button.travel": "Du lịch", - "empty_column.account_about.me": "Bạn chưa thêm thông tin gì về bản thân.", - "empty_column.account_about.other": "{acct} chưa thêm thông tin gì về họ.", "empty_column.account_featured.me": "Bạn chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng và hồ sơ của bạn bè trên trang cá nhân của mình không?", "empty_column.account_featured.other": "{acct} chưa nêu bật gì. Bạn có biết rằng, bạn có thể giới thiệu hashtag thường dùng và hồ sơ của bạn bè trên trang cá nhân của mình không?", "empty_column.account_featured_other.unknown": "Tài khoản này chưa nêu bật gì.", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 60a89015dcbb51..3d00966ea49beb 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -13,7 +13,6 @@ "about.not_available": "此信息在当前服务器尚不可用。", "about.powered_by": "由 {mastodon} 驱动的去中心化社交媒体", "about.rules": "站点规则", - "account.about": "关于", "account.account_note_header": "个人备注", "account.activity": "活动", "account.add_note": "添加个人备注", @@ -45,6 +44,7 @@ "account.familiar_followers_two": "{name1} 和 {name2} 关注了此账号", "account.featured": "精选", "account.featured.accounts": "个人资料", + "account.featured.collections": "收藏列表", "account.featured.hashtags": "话题", "account.featured_tags.last_status_at": "上次发言于 {date}", "account.featured_tags.last_status_never": "暂无嘟文", @@ -498,8 +498,6 @@ "emoji_button.search_results": "搜索结果", "emoji_button.symbols": "符号", "emoji_button.travel": "旅行与地点", - "empty_column.account_about.me": "你尚未添加有关你自己的任何信息。", - "empty_column.account_about.other": "{acct} 尚未添加有关自己的任何信息。", "empty_column.account_featured.me": "你尚未设置任何精选。你知道吗?你可以将自己最常使用的话题标签,甚至是好友的账号,在你的个人主页上设为精选。", "empty_column.account_featured.other": "{acct} 尚未设置任何精选。你知道吗?你可以将自己最常使用的话题标签,甚至是好友的账号,在你的个人主页上设为精选。", "empty_column.account_featured_other.unknown": "此账号尚未设置任何精选。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 9f3befc52aa929..186dbb055010e5 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -13,7 +13,6 @@ "about.not_available": "無法於本伺服器上使用此資訊。", "about.powered_by": "由 {mastodon} 提供之去中心化社群媒體", "about.rules": "伺服器規則", - "account.about": "關於", "account.account_note_header": "個人備註", "account.activity": "活動", "account.add_note": "新增個人備註", @@ -45,9 +44,11 @@ "account.familiar_followers_two": "被 {name1} 與 {name2} 跟隨", "account.featured": "精選內容", "account.featured.accounts": "個人檔案", + "account.featured.collections": "收藏名單", "account.featured.hashtags": "主題標籤", "account.featured_tags.last_status_at": "上次發嘟於 {date}", "account.featured_tags.last_status_never": "沒有嘟文", + "account.field_overflow": "顯示完整內容", "account.filters.all": "所有活動", "account.filters.boosts_toggle": "顯示轉嘟", "account.filters.posts_boosts": "嘟文與轉嘟", @@ -498,8 +499,6 @@ "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", - "empty_column.account_about.me": "您尚未新增任何關於您的資訊。", - "empty_column.account_about.other": "{acct} 尚未新增任何關於他們的資訊。", "empty_column.account_featured.me": "您尚未有任何精選內容。您知道您可以將您的常用主題標籤、甚至您朋友們的帳號作為您個人檔案上之精選內容嗎?", "empty_column.account_featured.other": "{acct} 尚未有任何精選內容。您知道您可以將您的常用主題標籤、甚至您朋友們的帳號作為您個人檔案上之精選內容嗎?", "empty_column.account_featured_other.unknown": "此帳號尚未有任何精選內容。", diff --git a/config/locales/be.yml b/config/locales/be.yml index 431219415f841a..bcd568fa54faf3 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -62,6 +62,7 @@ be: label: Змяніць ролю no_role: Няма ролі title: Змяніць ролю для %{username} + collections: Калекцыі confirm: Пацвердзіць confirmed: Пацверджаны confirming: Ідзе пацвярджэнне @@ -274,6 +275,7 @@ be: demote_user_html: "%{name} прыбраў карыстальніка %{target}" destroy_announcement_html: "%{name} выдаліў аб'яву %{target}" destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}" + destroy_collection_html: "%{name} выдаліў(-ла) калекцыю %{target}" destroy_custom_emoji_html: "%{name} выдаліў(-ла) эмодзі %{target}" destroy_domain_allow_html: "%{name} зняў(-ла) дазвол на аб’яднанне з даменам %{target}" destroy_domain_block_html: "%{name} разблакаваў дамен %{target}" @@ -313,6 +315,7 @@ be: unsilence_account_html: "%{name} зняў ліміт з уліковага запісу %{target}" unsuspend_account_html: Уліковы запіс %{target} адноўлены %{name} update_announcement_html: "%{name} абнавіў(-ла) аб’яву %{target}" + update_collection_html: "%{name} абнавіў(-ла) калекцыю %{target}" update_custom_emoji_html: "%{name} абнавіў эмодзі %{target}" update_domain_block_html: "%{name} абнавіў блакіроўку дамена для %{target}" update_ip_block_html: "%{name} змяніў правіла для IP %{target}" @@ -348,6 +351,17 @@ be: unpublish: Зняць з публікацыі unpublished_msg: Аб’ява схавана! updated_msg: Аб’ява абноўлена! + collections: + accounts: Уліковыя запісы + collection_title: Калекцыя %{name} + contents: Змесціва + number_of_accounts: + few: "%{count} уліковыя запісы" + many: "%{count} уліковых запісаў" + one: 1 уліковы запіс + other: "%{count} уліковых запісаў" + open: Адкрыць + view_publicly: Глядзець публічна critical_update_pending: Чакаецца абнаўленне custom_emojis: assign_category: Прызначыць катэгорыю @@ -705,6 +719,7 @@ be: cancel: Скасаваць category: Катэгорыя category_description_html: Прычына паведамлення аб гэтым уліковым запісе і/або кантэнце будзе згадана ў сувязі з уліковым запісам, на які пададзена скарга + collections: Калекцыі (%{count}) comment: none: Пуста comment_description_html: 'Каб даць больш інфармацыі, %{name} напісаў:' @@ -734,11 +749,13 @@ be: report: 'Скарга #%{id}' reported_account: Уліковы запіс парушальніка reported_by: Адпраўнік скаргі + reported_content: Змесціва, на якое паскардзіліся reported_with_application: Паведамлена праз праграму resolved: Вырашана resolved_msg: Скарга была паспяхова вырашана! skip_to_actions: Прапусціць дзеянні status: Стан + statuses: Допісы (%{count}) statuses_description_html: Крыўднае змесціва будзе згадвацца ў зносінах з уліковым запісам, на які пададзена скарга summary: action_preambles: diff --git a/config/locales/da.yml b/config/locales/da.yml index 574aa708bb7526..2df10f12b11289 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -267,6 +267,7 @@ da: demote_user_html: "%{name} degraderede brugeren %{target}" destroy_announcement_html: "%{name} slettede bekendtgørelsen %{target}" destroy_canonical_email_block_html: "%{name} afblokerede e-mailen med hash'et %{target}" + destroy_collection_html: "%{name} fjernede samling af %{target}" destroy_custom_emoji_html: "%{name} slettede emojien %{target}" destroy_domain_allow_html: "%{name} fjernede federeringstilladelsen med domænet %{target}" destroy_domain_block_html: "%{name} afblokerede domænet %{target}" @@ -306,6 +307,7 @@ da: unsilence_account_html: "%{name} fjernede begrænsningen af %{target}s konto" unsuspend_account_html: "%{name} fjernede suspenderingen af %{target}s konto" update_announcement_html: "%{name} opdaterede bekendtgørelsen %{target}" + update_collection_html: "%{name} opdaterede samling af %{target}" update_custom_emoji_html: "%{name} opdaterede emoji %{target}" update_domain_block_html: "%{name} opdaterede domæneblokeringen for %{target}" update_ip_block_html: "%{name} ændrede reglen for IP'en %{target}" diff --git a/config/locales/de.yml b/config/locales/de.yml index c94bef60e68f88..2315be3f8d7986 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -267,6 +267,7 @@ de: demote_user_html: "%{name} stufte %{target} herunter" destroy_announcement_html: "%{name} löschte die Ankündigung %{target}" destroy_canonical_email_block_html: "%{name} entsperrte die E-Mail mit dem Hash %{target}" + destroy_collection_html: "%{name} entfernte Sammlung von %{target}" destroy_custom_emoji_html: "%{name} löschte das Emoji %{target}" destroy_domain_allow_html: "%{name} verwehrte die Föderation mit der Domain %{target}" destroy_domain_block_html: "%{name} entsperrte die Domain %{target}" @@ -306,6 +307,7 @@ de: unsilence_account_html: "%{name} hob die Stummschaltung von %{target} auf" unsuspend_account_html: "%{name} entsperrte das Konto von %{target}" update_announcement_html: "%{name} überarbeitete die Ankündigung %{target}" + update_collection_html: "%{name} überarbeitete Sammlung von %{target}" update_custom_emoji_html: "%{name} bearbeitete das Emoji %{target}" update_domain_block_html: "%{name} aktualisierte die Domain-Sperre für %{target}" update_ip_block_html: "%{name} änderte eine IP-Regel für %{target}" diff --git a/config/locales/el.yml b/config/locales/el.yml index b606f6787705d9..d806558a6d116c 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -267,6 +267,7 @@ el: demote_user_html: Ο/Η %{name} υποβίβασε τον χρήστη %{target} destroy_announcement_html: Ο/Η %{name} διέγραψε την ανακοίνωση %{target} destroy_canonical_email_block_html: Ο χρήστης %{name} έκανε άρση αποκλεισμού email με το hash %{target} + destroy_collection_html: Ο/Η %{name} αφαίρεσε τη συλλογή του/της %{target} destroy_custom_emoji_html: Ο/Η %{name} διέγραψε το emoji %{target} destroy_domain_allow_html: Ο/Η %{name} αφαίρεσε τον τομέα %{target} από τη λίστα εγκρίσεων destroy_domain_block_html: Ο/Η %{name} επέτρεψε τον τομέα %{target} @@ -306,6 +307,7 @@ el: unsilence_account_html: Ο/Η %{name} αφαίρεσε το περιορισμό του λογαριασμού του/της %{target} unsuspend_account_html: Ο/Η %{name} επανέφερε τον λογαριασμό του/της %{target} update_announcement_html: Ο/Η %{name} ενημέρωσε την ανακοίνωση %{target} + update_collection_html: Ο/Η %{name} ενημέρωσε τη συλλογή του/της %{target} update_custom_emoji_html: Ο/Η %{name} ενημέρωσε το emoji %{target} update_domain_block_html: Ο/Η %{name} ενημέρωσε τον αποκλεισμό τομέα για %{target} update_ip_block_html: Ο/Η %{name} άλλαξε τον κανόνα για την IP %{target} diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index abef3aa441aa0c..815378fbdd6602 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -267,6 +267,7 @@ en-GB: demote_user_html: "%{name} demoted user %{target}" destroy_announcement_html: "%{name} deleted announcement %{target}" destroy_canonical_email_block_html: "%{name} unblocked email with the hash %{target}" + destroy_collection_html: "%{name} removed collection by %{target}" destroy_custom_emoji_html: "%{name} deleted emoji %{target}" destroy_domain_allow_html: "%{name} disallowed federation with domain %{target}" destroy_domain_block_html: "%{name} unblocked domain %{target}" @@ -306,6 +307,7 @@ en-GB: unsilence_account_html: "%{name} undid limit of %{target}'s account" unsuspend_account_html: "%{name} unsuspended %{target}'s account" update_announcement_html: "%{name} updated announcement %{target}" + update_collection_html: "%{name} updated collection by %{target}" update_custom_emoji_html: "%{name} updated emoji %{target}" update_domain_block_html: "%{name} updated domain block for %{target}" update_ip_block_html: "%{name} changed rule for IP %{target}" diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index f19ba9971cdf42..bbc1fd78e3ba1d 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -267,6 +267,7 @@ es-AR: demote_user_html: "%{name} bajó de nivel al usuario %{target}" destroy_announcement_html: "%{name} eliminó el anuncio %{target}" destroy_canonical_email_block_html: "%{name} desbloqueó el correo electrónico con el hash %{target}" + destroy_collection_html: "%{name} eliminó la colección de %{target}" destroy_custom_emoji_html: "%{name} eliminó el emoji %{target}" destroy_domain_allow_html: "%{name} no permitió la federación con el dominio %{target}" destroy_domain_block_html: "%{name} desbloqueó el dominio %{target}" @@ -306,6 +307,7 @@ es-AR: unsilence_account_html: "%{name} quitó el límite de la cuenta de %{target}" unsuspend_account_html: "%{name} quitó la suspensión de la cuenta de %{target}" update_announcement_html: "%{name} actualizó el anuncio %{target}" + update_collection_html: "%{name} actualizó la colección de %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}" update_ip_block_html: "%{name} cambió la regla para la dirección IP %{target}" diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 3a19094e382a7e..bf05a6d4053ebd 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -267,6 +267,7 @@ es-MX: demote_user_html: "%{name} degradó al usuario %{target}" destroy_announcement_html: "%{name} eliminó el anuncio %{target}" destroy_canonical_email_block_html: "%{name} ha desbloqueado el correo electrónico con el hash %{target}" + destroy_collection_html: "%{name} eliminó la colección de %{target}" destroy_custom_emoji_html: "%{name} eliminó el emoji %{target}" destroy_domain_allow_html: "%{name} bloqueó la federación con el dominio %{target}" destroy_domain_block_html: "%{name} desbloqueó el dominio %{target}" @@ -306,6 +307,7 @@ es-MX: unsilence_account_html: "%{name} desilenció la cuenta de %{target}" unsuspend_account_html: "%{name} reactivó la cuenta de %{target}" update_announcement_html: "%{name} actualizó el anuncio %{target}" + update_collection_html: "%{name} actualizó la colección de %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}" update_ip_block_html: "%{name} cambió la regla para la IP %{target}" diff --git a/config/locales/es.yml b/config/locales/es.yml index d18d0e3cc43013..b5014dba7e42cd 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -12,6 +12,9 @@ es: followers: one: Seguidor other: Seguidores + following: + one: Siguiendo + other: Siguiendo instance_actor_flash: Esta cuenta es un actor virtual utilizado para representar al propio servidor y no a ningún usuario individual. Se utiliza con fines de federación y no debe suspenderse. last_active: última conexión link_verified_on: La propiedad de este vínculo fue verificada el %{date} @@ -264,6 +267,7 @@ es: demote_user_html: "%{name} degradó al usuario %{target}" destroy_announcement_html: "%{name} eliminó el anuncio %{target}" destroy_canonical_email_block_html: "%{name} desbloqueó el correo electrónico con el hash %{target}" + destroy_collection_html: "%{name} eliminó la colección de %{target}" destroy_custom_emoji_html: "%{name} eliminó el emoji %{target}" destroy_domain_allow_html: "%{name} bloqueó la federación con el dominio %{target}" destroy_domain_block_html: "%{name} desbloqueó el dominio %{target}" @@ -303,6 +307,7 @@ es: unsilence_account_html: "%{name} desilenció la cuenta de %{target}" unsuspend_account_html: "%{name} reactivó la cuenta de %{target}" update_announcement_html: "%{name} actualizó el anuncio %{target}" + update_collection_html: "%{name} actualizó la colección de %{target}" update_custom_emoji_html: "%{name} actualizó el emoji %{target}" update_domain_block_html: "%{name} actualizó el bloqueo de dominio para %{target}" update_ip_block_html: "%{name} cambió la regla para la IP %{target}" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index d48ded9e222b80..94c4793bdc01c8 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -267,6 +267,7 @@ fi: demote_user_html: "%{name} alensi käyttäjän %{target}" destroy_announcement_html: "%{name} poisti tiedotteen %{target}" destroy_canonical_email_block_html: "%{name} kumosi eston tiivistettä %{target} vastaavalta sähköpostiosoitteelta" + destroy_collection_html: "%{name} poisti käyttäjän %{target} kokoelman" destroy_custom_emoji_html: "%{name} poisti emojin %{target}" destroy_domain_allow_html: "%{name} kielsi federoinnin verkkotunnuksen %{target} kanssa" destroy_domain_block_html: "%{name} kumosi verkkotunnuksen %{target} eston" @@ -306,6 +307,7 @@ fi: unsilence_account_html: "%{name} kumosi käyttäjän %{target} tilin rajoituksen" unsuspend_account_html: "%{name} kumosi käyttäjän %{target} tilin jäädytyksen" update_announcement_html: "%{name} päivitti tiedotteen %{target}" + update_collection_html: "%{name} päivitti käyttäjän %{target} kokoelman" update_custom_emoji_html: "%{name} päivitti emojin %{target}" update_domain_block_html: "%{name} päivitti verkkotunnuksen %{target} eston" update_ip_block_html: "%{name} muutti IP-⁠osoitteen %{target} sääntöä" diff --git a/config/locales/fo.yml b/config/locales/fo.yml index de2fcf7ae852c7..36b2dc5c684334 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -12,6 +12,9 @@ fo: followers: one: Fylgjari other: Fylgjarar + following: + one: Fylgi + other: Fylgi instance_actor_flash: Hendan kontan er ein tykisligur aktørur, sum verður brúktur til at umboða ambætaran sjálvan og ikki nakran ávísan brúkara. Hon verður brúkt til sameind endamál og eigur ikki at vera tikin úr gildi. last_active: virkin seinast link_verified_on: Eigaraskapur av hesum leinki var eftirkannaður tann %{date} @@ -264,6 +267,7 @@ fo: demote_user_html: "%{name} lækkaði tignina hjá brúkaranum %{target}" destroy_announcement_html: "%{name} strikaðar fráboðanir %{target}" destroy_canonical_email_block_html: "%{name} strikaði blokeringina av teldupostin við hashkodu %{target}" + destroy_collection_html: "%{name} slettaði savnið hjá %{target}" destroy_custom_emoji_html: "%{name} strikaði kensluteknið %{target}" destroy_domain_allow_html: "%{name} havnaði sameining við navnaøkið %{target}" destroy_domain_block_html: "%{name} strikaði blokering av navnaøkinum %{target}" @@ -303,6 +307,7 @@ fo: unsilence_account_html: "%{name} strikaði avmarkingina av kontuni hjá %{target}" unsuspend_account_html: "%{name} setti kontuna hjá %{target} í gildi aftur" update_announcement_html: "%{name} dagførdi kunngerðina %{target}" + update_collection_html: "%{name} dagførdi savnið hjá %{target}" update_custom_emoji_html: "%{name} dagførdi kensluteknið %{target}" update_domain_block_html: "%{name} dagførdi navnaøkisblokeringina hjá %{target}" update_ip_block_html: "%{name} broytti IP-reglurnar %{target}" @@ -342,6 +347,9 @@ fo: accounts: Kontur collection_title: Savn hjá %{name} contents: Innihald + number_of_accounts: + one: 1 konta + other: "%{count} kontur" open: Opin view_publicly: Vís fyri øllum critical_update_pending: Kritisk dagføring bíðar diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 751211e4705576..5ccda09b4bf3f9 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -267,6 +267,7 @@ fr-CA: demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}" destroy_announcement_html: "%{name} a supprimé l'annonce %{target}" destroy_canonical_email_block_html: "%{name} a débloqué l'adresse email avec le hachage %{target}" + destroy_collection_html: "%{name} a supprimé la collection de %{target}" destroy_custom_emoji_html: "%{name} a supprimé l'émoji %{target}" destroy_domain_allow_html: "%{name} a rejeté la fédération avec le domaine %{target}" destroy_domain_block_html: "%{name} a débloqué le domaine %{target}" @@ -306,6 +307,7 @@ fr-CA: unsilence_account_html: "%{name} a annulé la limitation du compte de %{target}" unsuspend_account_html: "%{name} a réactivé le compte de %{target}" update_announcement_html: "%{name} a mis à jour l'annonce %{target}" + update_collection_html: "%{name} a mis à jour la collections de %{target}" update_custom_emoji_html: "%{name} a mis à jour l'émoji %{target}" update_domain_block_html: "%{name} a mis à jour le blocage de domaine pour %{target}" update_ip_block_html: "%{name} a modifié la règle pour l'IP %{target}" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5e0d57f820c74b..f330733b082b5b 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -267,6 +267,7 @@ fr: demote_user_html: "%{name} a rétrogradé l'utilisateur·rice %{target}" destroy_announcement_html: "%{name} a supprimé l'annonce %{target}" destroy_canonical_email_block_html: "%{name} a débloqué l'adresse email avec le hachage %{target}" + destroy_collection_html: "%{name} a supprimé la collection de %{target}" destroy_custom_emoji_html: "%{name} a supprimé l'émoji %{target}" destroy_domain_allow_html: "%{name} a rejeté la fédération avec le domaine %{target}" destroy_domain_block_html: "%{name} a débloqué le domaine %{target}" @@ -306,6 +307,7 @@ fr: unsilence_account_html: "%{name} a annulé la limitation du compte de %{target}" unsuspend_account_html: "%{name} a réactivé le compte de %{target}" update_announcement_html: "%{name} a mis à jour l'annonce %{target}" + update_collection_html: "%{name} a mis à jour la collections de %{target}" update_custom_emoji_html: "%{name} a mis à jour l'émoji %{target}" update_domain_block_html: "%{name} a mis à jour le blocage de domaine pour %{target}" update_ip_block_html: "%{name} a modifié la règle pour l'IP %{target}" diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 6148e6950c9121..1d6f2c68e483f7 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -279,6 +279,7 @@ ga: demote_user_html: "%{name} úsáideoir scriosta %{target}" destroy_announcement_html: "%{name} fógra scriosta %{target}" destroy_canonical_email_block_html: "%{name} ríomhphost díchoiscthe leis an hash %{target}" + destroy_collection_html: Bhain %{name} bailiúchán le %{target} destroy_custom_emoji_html: Scriosadh %{name} emoji %{target} destroy_domain_allow_html: Dhiúltaigh %{name} cónaidhm le fearann ​​%{target} destroy_domain_block_html: "%{name} fearann ​​%{target} bainte de" @@ -318,6 +319,7 @@ ga: unsilence_account_html: Chealaigh %{name} teorainn chuntas %{target} unsuspend_account_html: Níor chuir %{name} cuntas %{target} ar fionraí update_announcement_html: "%{name} fógra nuashonraithe %{target}" + update_collection_html: Nuashonraigh %{name} bailiúchán le %{target} update_custom_emoji_html: "%{name} emoji nuashonraithe %{target}" update_domain_block_html: "%{name} nuashonraithe bloc fearainn le haghaidh %{target}" update_ip_block_html: D'athraigh %{name} riail an IP %{target} diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 0ac225d7286503..7c59072d8f98e3 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -267,6 +267,7 @@ gl: demote_user_html: "%{name} degradou a usuaria %{target}" destroy_announcement_html: "%{name} eliminou o anuncio %{target}" destroy_canonical_email_block_html: "%{name} desbloqueou o correo con suma de comprobación %{target}" + destroy_collection_html: "%{name} eliminou a colección de %{target}" destroy_custom_emoji_html: "%{name} eliminou o emoji %{target}" destroy_domain_allow_html: "%{name} retirou a federación co dominio %{target}" destroy_domain_block_html: "%{name} desbloqueou o dominio %{target}" @@ -306,6 +307,7 @@ gl: unsilence_account_html: "%{name} reactivou a conta de %{target}" unsuspend_account_html: "%{name} retiroulle a suspensión á conta de %{target}" update_announcement_html: "%{name} actualizou o anuncio %{target}" + update_collection_html: "%{name} actualizou a colección de %{target}" update_custom_emoji_html: "%{name} actualizou o emoji %{target}" update_domain_block_html: "%{name} actualizou o bloqueo do dominio para %{target}" update_ip_block_html: "%{name} cambiou a regra para IP %{target}" diff --git a/config/locales/he.yml b/config/locales/he.yml index 6c3dda16bd8ee2..62bbecccd1a690 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -275,6 +275,7 @@ he: demote_user_html: "%{name} הוריד/ה בדרגה את המשתמש %{target}" destroy_announcement_html: "%{name} מחק/ה את ההכרזה %{target}" destroy_canonical_email_block_html: "%{name} הסירו חסימה מדואל %{target}" + destroy_collection_html: האוסף של %{target} הוסר ע"י %{name} destroy_custom_emoji_html: "%{name} מחק אמוג'י של %{target}" destroy_domain_allow_html: "%{name} לא התיר/ה פדרציה עם הדומיין %{target}" destroy_domain_block_html: החסימה על מתחם %{target} הוסרה ע"י %{name} @@ -314,6 +315,7 @@ he: unsilence_account_html: "%{name} ביטל/ה ההגבלה מהחשבון של %{target}" unsuspend_account_html: "%{name} ביטל/ה את ההשעיה של החשבון של %{target}" update_announcement_html: "%{name} עדכן/ה הכרזה %{target}" + update_collection_html: האוסף של %{target} עודכן ע"י %{name} update_custom_emoji_html: "%{name} עדכן/ה אמוג'י %{target}" update_domain_block_html: "%{name} עדכן/ה חסימת דומיין עבור %{target}" update_ip_block_html: "%{name} שינה כלל עבור IP %{target}" diff --git a/config/locales/is.yml b/config/locales/is.yml index 4bcda2ceeef11d..d173cc31c78951 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -267,6 +267,7 @@ is: demote_user_html: "%{name} lækkaði notandann %{target} í tign" destroy_announcement_html: "%{name} eyddi tilkynninguni %{target}" destroy_canonical_email_block_html: "%{name} tók af útilokun á tölvupósti með tætigildið %{target}" + destroy_collection_html: "%{name} fjarlægði safn frá %{target}" destroy_custom_emoji_html: "%{name} eyddi emoji-tákni %{target}" destroy_domain_allow_html: "%{name} bannaði skýjasamband með léninu %{target}" destroy_domain_block_html: "%{name} aflétti útilokun af léninu %{target}" @@ -306,6 +307,7 @@ is: unsilence_account_html: "%{name} hætti að hylja notandaaðganginn %{target}" unsuspend_account_html: "%{name} tók notandaaðganginn %{target} úr frysti" update_announcement_html: "%{name} uppfærði tilkynningu %{target}" + update_collection_html: "%{name} uppfærði safn frá %{target}" update_custom_emoji_html: "%{name} uppfærði lyndistáknið %{target}" update_domain_block_html: "%{name} uppfærði útilokun lénsins %{target}" update_ip_block_html: "%{name} breytti reglu fyrir IP-vistfangið %{target}" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 5eb860f7a67354..6f52b8ec97de8e 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -267,6 +267,7 @@ nl: demote_user_html: Gebruiker %{target} is door %{name} gedegradeerd destroy_announcement_html: "%{name} heeft de mededeling %{target} verwijderd" destroy_canonical_email_block_html: "%{name} deblokkeerde e-mail met de hash %{target}" + destroy_collection_html: "%{name} heeft de verzameling van %{target} verwijderd" destroy_custom_emoji_html: "%{name} verwijderde de emoji %{target}" destroy_domain_allow_html: "%{name} heeft de federatie met het domein %{target} afgekeurd" destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd @@ -306,6 +307,7 @@ nl: unsilence_account_html: Beperking van account %{target} is door %{name} opgeheven unsuspend_account_html: Opschorten van account %{target} is door %{name} opgeheven update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt" + update_collection_html: "%{name} heeft de verzameling van %{target} bijgewerkt" update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}" update_ip_block_html: "%{name} wijzigde de IP-regel voor %{target}" diff --git a/config/locales/no.yml b/config/locales/no.yml index a7abdf8ab1b969..a143940462a2a0 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1436,7 +1436,7 @@ billion: Mrd million: Mln quadrillion: Kvd - thousand: T + thousand: k trillion: Trl otp_authentication: code_hint: Skriv inn koden generert av autentiseringsappen din for å bekrefte diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 06733756387b80..9daddea9c42c1d 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -267,6 +267,7 @@ sq: demote_user_html: "%{name} zhgradoi përdoruesin %{target}" destroy_announcement_html: "%{name} fshiu lajmërimin për %{target}" destroy_canonical_email_block_html: "%{name} zhbllokoi email me hashin %{target}" + destroy_collection_html: "%{name} hoqi koleksion nga %{target}" destroy_custom_emoji_html: "%{name} fshiu emoji-n %{target}" destroy_domain_allow_html: "%{name} hoqi lejimin për federim me %{target}" destroy_domain_block_html: "%{name} zhbllokoi përkatësinë %{target}" @@ -306,6 +307,7 @@ sq: unsilence_account_html: "%{name} hoqi heshtimin për llogarinë %{target}" unsuspend_account_html: "%{name} hoqi pezullimin për llogarinë e %{target}" update_announcement_html: "%{name} përditësoi lajmërimin %{target}" + update_collection_html: "%{name} përditësoi koleksion nga %{target}" update_custom_emoji_html: "%{name} përditësoi emoxhin %{target}" update_domain_block_html: "%{name} përditësoi bllokim përkatësish për %{target}" update_ip_block_html: "%{name} ndryshoi rregull për IP-në %{target}" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 42af0b660366f5..c68c7f755edf9e 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -263,6 +263,7 @@ vi: demote_user_html: "%{name} đã hạ vai trò của %{target}" destroy_announcement_html: "%{name} đã xóa thông báo %{target}" destroy_canonical_email_block_html: "%{name} đã bỏ chặn địa chỉ email biến thể %{target}" + destroy_collection_html: "%{name} đã gỡ collection của %{target}" destroy_custom_emoji_html: "%{name} đã xóa emoji %{target}" destroy_domain_allow_html: "%{name} đã ngừng liên hợp với %{target}" destroy_domain_block_html: "%{name} đã bỏ chặn máy chủ %{target}" @@ -302,6 +303,7 @@ vi: unsilence_account_html: "%{name} đã bỏ ẩn %{target}" unsuspend_account_html: "%{name} đã bỏ vô hiệu hóa %{target}" update_announcement_html: "%{name} đã cập nhật thông báo %{target}" + update_collection_html: "%{name} đã cập nhật collection của %{target}" update_custom_emoji_html: "%{name} đã cập nhật emoji %{target}" update_domain_block_html: "%{name} đã cập nhật chặn máy chủ %{target}" update_ip_block_html: "%{name} đã cập nhật chặn IP %{target}" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 782b3bcea9e3ab..87ada1c7ad223c 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -263,6 +263,7 @@ zh-CN: demote_user_html: "%{name} 撤销了用户 %{target} 的管理权限" destroy_announcement_html: "%{name} 删除了公告 %{target}" destroy_canonical_email_block_html: "%{name} 解封了 hash 为 %{target} 的邮箱地址" + destroy_collection_html: "%{name} 移除了 %{target} 的收藏列表" destroy_custom_emoji_html: "%{name} 删除了自定义表情 %{target}" destroy_domain_allow_html: "%{name} 拒绝了与站点 %{target} 的联合" destroy_domain_block_html: "%{name} 解除了对站点 %{target} 的屏蔽" @@ -302,6 +303,7 @@ zh-CN: unsilence_account_html: "%{name} 解除了用户 %{target} 的隐藏状态" unsuspend_account_html: "%{name} 解封了用户 %{target}" update_announcement_html: "%{name} 更新了公告 %{target}" + update_collection_html: "%{name} 更新了 %{target} 的收藏列表" update_custom_emoji_html: "%{name} 更新了自定义表情 %{target}" update_domain_block_html: "%{name} 更新了对 %{target} 的域名屏蔽" update_ip_block_html: "%{name} 修改了对 IP %{target} 的规则" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index d1fe0c6c0e9b97..d9582685f9115d 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -263,6 +263,7 @@ zh-TW: demote_user_html: "%{name} 將使用者 %{target} 降級" destroy_announcement_html: "%{name} 已刪除公告 %{target}" destroy_canonical_email_block_html: "%{name} 已解除封鎖 hash 為 %{target} 之電子郵件" + destroy_collection_html: "%{name} 已刪除 %{target} 的收藏名單" destroy_custom_emoji_html: "%{name} 已刪除 emoji 表情符號 %{target}" destroy_domain_allow_html: "%{name} 不允許與網域 %{target} 加入聯邦宇宙" destroy_domain_block_html: "%{name} 已解除封鎖網域 %{target}" @@ -302,6 +303,7 @@ zh-TW: unsilence_account_html: "%{name} 已取消使用者 %{target} 的靜音狀態" unsuspend_account_html: "%{name} 已取消停權 %{target} 的帳號" update_announcement_html: "%{name} 已更新公告 %{target}" + update_collection_html: "%{name} 已更新 %{target} 的收藏名單" update_custom_emoji_html: "%{name} 已更新自訂 emoji 表情符號 %{target}" update_domain_block_html: "%{name} 已更新 %{target} 之網域封鎖" update_ip_block_html: "%{name} 已變更 IP %{target} 之規則" From 51b81b3ce9762c9f27260268460cca5142c26296 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 26 Feb 2026 14:31:23 +0100 Subject: [PATCH 10/89] Allow remote accounts in Collections (#37989) --- app/models/account.rb | 7 +++++-- app/policies/account_policy.rb | 2 +- spec/models/account_spec.rb | 30 +++++++++++++++++++++++----- spec/policies/account_policy_spec.rb | 2 +- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index fd99dc3b186595..0e80b99d8d466e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -469,8 +469,11 @@ def ensure_keys! save! end - def featureable? - local? && discoverable? + def featureable_by?(other_account) + return discoverable? if local? + return false unless Mastodon::Feature.collections_federation_enabled? + + feature_policy_for_account(other_account).in?(%i(automatic manual)) end private diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb index 1fef35714cda37..c46eb080348e78 100644 --- a/app/policies/account_policy.rb +++ b/app/policies/account_policy.rb @@ -66,7 +66,7 @@ def review? end def feature? - record.featureable? && !current_account.blocking?(record) && !current_account.blocked_by?(record) + record.featureable_by?(current_account) && !current_account.blocking?(record) && !current_account.blocked_by?(record) end def index_collections? diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index ca85b0fbfcdf8a..2fd32d13614431 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -790,17 +790,20 @@ def fields_empty_name end end - describe '#featureable?' do - subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:) } + describe '#featureable_by?' do + subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:, feature_approval_policy:) } + + let(:local_account) { Fabricate(:account) } context 'when account is local' do let(:local) { true } + let(:feature_approval_policy) { nil } context 'when account is discoverable' do let(:discoverable) { true } it 'returns `true`' do - expect(subject.featureable?).to be true + expect(subject.featureable_by?(local_account)).to be true end end @@ -808,7 +811,7 @@ def fields_empty_name let(:discoverable) { false } it 'returns `false`' do - expect(subject.featureable?).to be false + expect(subject.featureable_by?(local_account)).to be false end end end @@ -816,9 +819,26 @@ def fields_empty_name context 'when account is remote' do let(:local) { false } let(:discoverable) { true } + let(:feature_approval_policy) { (0b10 << 16) | 0 } it 'returns `false`' do - expect(subject.featureable?).to be false + expect(subject.featureable_by?(local_account)).to be false + end + + context 'when collections federation is enabled', feature: :collections_federation do + context 'when the policy allows it' do + it 'returns `true`' do + expect(subject.featureable_by?(local_account)).to be true + end + end + + context 'when the policy forbids it' do + let(:feature_approval_policy) { 0 } + + it 'returns `false`' do + expect(subject.featureable_by?(local_account)).to be false + end + end end end end diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb index 96fcbdb4d82ba1..2f350bc0923fbd 100644 --- a/spec/policies/account_policy_spec.rb +++ b/spec/policies/account_policy_spec.rb @@ -165,7 +165,7 @@ end context 'when account is not featureable' do - before { allow(alice).to receive(:featureable?).and_return(false) } + before { allow(alice).to receive(:featureable_by?).and_return(false) } it 'denies' do expect(subject).to_not permit(john, alice) From d18a47b6a704ea599e7ab0abe1fdd7a3a791a334 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 26 Feb 2026 14:55:10 +0100 Subject: [PATCH 11/89] Profile editing: Utilize new API (#37990) --- app/javascript/mastodon/api.ts | 7 + app/javascript/mastodon/api/accounts.ts | 17 +- app/javascript/mastodon/api_types/profile.ts | 42 +++++ .../account_edit/components/bio_modal.tsx | 29 ++-- .../account_edit/components/name_modal.tsx | 22 ++- .../features/account_edit/featured_tags.tsx | 21 ++- .../mastodon/features/account_edit/index.tsx | 145 ++++++++++-------- .../mastodon/reducers/custom_emojis.js | 1 + .../mastodon/reducers/slices/profile_edit.ts | 102 ++++++++++-- app/javascript/mastodon/utils/types.ts | 5 + app/serializers/rest/profile_serializer.rb | 1 + 11 files changed, 287 insertions(+), 105 deletions(-) create mode 100644 app/javascript/mastodon/api_types/profile.ts diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index 2af29c783e08b8..39617d82fe04f2 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -179,3 +179,10 @@ export async function apiRequestDelete< >(url: ApiUrl, params?: RequestParamsOrData) { return apiRequest('DELETE', url, { params }); } + +export async function apiRequestPatch( + url: ApiUrl, + data?: RequestParamsOrData, +) { + return apiRequest('PATCH', url, { data }); +} diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts index 9c35d619a4cb9f..da4b0e94f8637c 100644 --- a/app/javascript/mastodon/api/accounts.ts +++ b/app/javascript/mastodon/api/accounts.ts @@ -1,4 +1,9 @@ -import { apiRequestPost, apiRequestGet, apiRequestDelete } from 'mastodon/api'; +import { + apiRequestPost, + apiRequestGet, + apiRequestDelete, + apiRequestPatch, +} from 'mastodon/api'; import type { ApiAccountJSON, ApiFamiliarFollowersJSON, @@ -9,6 +14,11 @@ import type { ApiHashtagJSON, } from 'mastodon/api_types/tags'; +import type { + ApiProfileJSON, + ApiProfileUpdateParams, +} from '../api_types/profile'; + export const apiSubmitAccountNote = (id: string, value: string) => apiRequestPost(`v1/accounts/${id}/note`, { comment: value, @@ -54,3 +64,8 @@ export const apiGetFamiliarFollowers = (id: string) => apiRequestGet('v1/accounts/familiar_followers', { id, }); + +export const apiGetProfile = () => apiRequestGet('v1/profile'); + +export const apiPatchProfile = (params: ApiProfileUpdateParams) => + apiRequestPatch('v1/profile', params); diff --git a/app/javascript/mastodon/api_types/profile.ts b/app/javascript/mastodon/api_types/profile.ts new file mode 100644 index 00000000000000..7968f008ed377f --- /dev/null +++ b/app/javascript/mastodon/api_types/profile.ts @@ -0,0 +1,42 @@ +import type { ApiAccountFieldJSON } from './accounts'; + +export interface ApiProfileJSON { + id: string; + display_name: string; + note: string; + fields: ApiAccountFieldJSON[]; + avatar: string; + avatar_static: string; + avatar_description: string; + header: string; + header_static: string; + header_description: string; + locked: boolean; + bot: boolean; + hide_collections: boolean; + discoverable: boolean; + indexable: boolean; + show_media: boolean; + show_media_replies: boolean; + show_featured: boolean; + attribution_domains: string[]; +} + +export type ApiProfileUpdateParams = Partial< + Pick< + ApiProfileJSON, + | 'display_name' + | 'note' + | 'locked' + | 'bot' + | 'hide_collections' + | 'discoverable' + | 'indexable' + | 'show_media' + | 'show_media_replies' + | 'show_featured' + > +> & { + attribution_domains?: string[]; + fields_attributes?: Pick[]; +}; diff --git a/app/javascript/mastodon/features/account_edit/components/bio_modal.tsx b/app/javascript/mastodon/features/account_edit/components/bio_modal.tsx index fbc74e769b052f..3391bd7290255d 100644 --- a/app/javascript/mastodon/features/account_edit/components/bio_modal.tsx +++ b/app/javascript/mastodon/features/account_edit/components/bio_modal.tsx @@ -4,12 +4,11 @@ import type { ChangeEventHandler, FC } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { TextArea } from '@/mastodon/components/form_fields'; -import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils'; import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals'; import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals'; -import { useAccount } from '@/mastodon/hooks/useAccount'; -import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId'; +import { patchProfile } from '@/mastodon/reducers/slices/profile_edit'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import classes from '../styles.module.scss'; @@ -38,10 +37,11 @@ export const BioModal: FC = ({ onClose }) => { const titleId = useId(); const counterId = useId(); const textAreaRef = useRef(null); - const accountId = useCurrentAccountId(); - const account = useAccount(accountId); - const [newBio, setNewBio] = useState(account?.note_plain ?? ''); + const { profile: { bio } = {}, isPending } = useAppSelector( + (state) => state.profileEdit, + ); + const [newBio, setNewBio] = useState(bio ?? ''); const handleChange: ChangeEventHandler = useCallback( (event) => { setNewBio(event.currentTarget.value); @@ -55,19 +55,22 @@ export const BioModal: FC = ({ onClose }) => { }); }, []); - if (!account) { - return ; - } + const dispatch = useAppDispatch(); + const handleSave = useCallback(() => { + if (!isPending) { + void dispatch(patchProfile({ note: newBio })).then(onClose); + } + }, [dispatch, isPending, newBio, onClose]); return ( MAX_BIO_LENGTH} noFocusButton >
diff --git a/app/javascript/mastodon/features/account_edit/components/name_modal.tsx b/app/javascript/mastodon/features/account_edit/components/name_modal.tsx index 0b38419b6f19a0..c7f533bba26cb7 100644 --- a/app/javascript/mastodon/features/account_edit/components/name_modal.tsx +++ b/app/javascript/mastodon/features/account_edit/components/name_modal.tsx @@ -7,8 +7,8 @@ import { TextInput } from '@/mastodon/components/form_fields'; import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils'; import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals'; import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals'; -import { useAccount } from '@/mastodon/hooks/useAccount'; -import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId'; +import { patchProfile } from '@/mastodon/reducers/slices/profile_edit'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import classes from '../styles.module.scss'; @@ -37,10 +37,11 @@ export const NameModal: FC = ({ onClose }) => { const titleId = useId(); const counterId = useId(); const inputRef = useRef(null); - const accountId = useCurrentAccountId(); - const account = useAccount(accountId); - const [newName, setNewName] = useState(account?.display_name ?? ''); + const { profile: { displayName } = {}, isPending } = useAppSelector( + (state) => state.profileEdit, + ); + const [newName, setNewName] = useState(displayName ?? ''); const handleChange: ChangeEventHandler = useCallback( (event) => { setNewName(event.currentTarget.value); @@ -54,13 +55,22 @@ export const NameModal: FC = ({ onClose }) => { }); }, []); + const dispatch = useAppDispatch(); + const handleSave = useCallback(() => { + if (!isPending) { + void dispatch(patchProfile({ display_name: newName })).then(onClose); + } + }, [dispatch, isPending, newName, onClose]); + return ( MAX_NAME_LENGTH} noCloseOnConfirm noFocusButton > diff --git a/app/javascript/mastodon/features/account_edit/featured_tags.tsx b/app/javascript/mastodon/features/account_edit/featured_tags.tsx index eaea7a220597a2..4095707a263432 100644 --- a/app/javascript/mastodon/features/account_edit/featured_tags.tsx +++ b/app/javascript/mastodon/features/account_edit/featured_tags.tsx @@ -14,7 +14,11 @@ import { fetchFeaturedTags, fetchSuggestedTags, } from '@/mastodon/reducers/slices/profile_edit'; -import { useAppDispatch, useAppSelector } from '@/mastodon/store'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/mastodon/store'; import { AccountEditColumn, AccountEditEmptyColumn } from './components/column'; import { AccountEditItemList } from './components/item_list'; @@ -28,14 +32,23 @@ const messages = defineMessages({ }, }); +const selectTags = createAppSelector( + [(state) => state.profileEdit], + (profileEdit) => ({ + tags: profileEdit.tags ?? [], + tagSuggestions: profileEdit.tagSuggestions ?? [], + isLoading: !profileEdit.tags || !profileEdit.tagSuggestions, + isPending: profileEdit.isPending, + }), +); + export const AccountEditFeaturedTags: FC = () => { const accountId = useCurrentAccountId(); const account = useAccount(accountId); const intl = useIntl(); - const { tags, tagSuggestions, isLoading, isPending } = useAppSelector( - (state) => state.profileEdit, - ); + const { tags, tagSuggestions, isLoading, isPending } = + useAppSelector(selectTags); const dispatch = useAppDispatch(); useEffect(() => { diff --git a/app/javascript/mastodon/features/account_edit/index.tsx b/app/javascript/mastodon/features/account_edit/index.tsx index 2673c5363f5665..c8bc93f15f4a87 100644 --- a/app/javascript/mastodon/features/account_edit/index.tsx +++ b/app/javascript/mastodon/features/account_edit/index.tsx @@ -7,13 +7,17 @@ import { useHistory } from 'react-router-dom'; import type { ModalType } from '@/mastodon/actions/modal'; import { openModal } from '@/mastodon/actions/modal'; -import { AccountBio } from '@/mastodon/components/account_bio'; import { Avatar } from '@/mastodon/components/avatar'; -import { DisplayNameSimple } from '@/mastodon/components/display_name/simple'; +import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; +import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; import { useAccount } from '@/mastodon/hooks/useAccount'; import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId'; import { autoPlayGif } from '@/mastodon/initial_state'; -import { fetchFeaturedTags } from '@/mastodon/reducers/slices/profile_edit'; +import { + fetchFeaturedTags, + fetchProfile, +} from '@/mastodon/reducers/slices/profile_edit'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; import { AccountEditColumn, AccountEditEmptyColumn } from './components/column'; @@ -82,11 +86,10 @@ export const AccountEdit: FC = () => { const dispatch = useAppDispatch(); - const { tags: featuredTags, isLoading: isTagsLoading } = useAppSelector( - (state) => state.profileEdit, - ); + const { profile, tags = [] } = useAppSelector((state) => state.profileEdit); useEffect(() => { void dispatch(fetchFeaturedTags()); + void dispatch(fetchProfile()); }, [dispatch]); const handleOpenModal = useCallback( @@ -107,14 +110,20 @@ export const AccountEdit: FC = () => { history.push('/profile/featured_tags'); }, [history]); - if (!accountId || !account) { + // Normally we would use the account emoji, but we want all custom emojis to be available to render after editing. + const emojis = useAppSelector((state) => state.custom_emojis); + const htmlHandlers = useElementHandledLink({ + hashtagAccountId: profile?.id, + }); + + if (!accountId || !account || !profile) { return ; } - const headerSrc = autoPlayGif ? account.header : account.header_static; - const hasName = !!account.display_name; - const hasBio = !!account.note_plain; - const hasTags = !isTagsLoading && featuredTags.length > 0; + const headerSrc = autoPlayGif ? profile.header : profile.headerStatic; + const hasName = !!profile.displayName; + const hasBio = !!profile.bio; + const hasTags = tags.length > 0; return ( { - - } - > - - - - - } - > - - - - - - - } - > - {featuredTags.map((tag) => `#${tag.name}`).join(', ')} - - - + + + } + > + + + + + } + > + + + + + + + } + > + {tags.map((tag) => `#${tag.name}`).join(', ')} + + + + ); }; diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js index 56ec80f2ffce22..47aa3edbbb6032 100644 --- a/app/javascript/mastodon/reducers/custom_emojis.js +++ b/app/javascript/mastodon/reducers/custom_emojis.js @@ -4,6 +4,7 @@ import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom_emojis'; import { buildCustomEmojis } from '../features/emoji/emoji'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; +/** @type {ImmutableList} */ const initialState = ImmutableList([]); export default function custom_emojis(state = initialState, action) { diff --git a/app/javascript/mastodon/reducers/slices/profile_edit.ts b/app/javascript/mastodon/reducers/slices/profile_edit.ts index c9663252039282..4f5bd6a4c8b516 100644 --- a/app/javascript/mastodon/reducers/slices/profile_edit.ts +++ b/app/javascript/mastodon/reducers/slices/profile_edit.ts @@ -6,10 +6,16 @@ import { debounce } from 'lodash'; import { apiDeleteFeaturedTag, apiGetCurrentFeaturedTags, + apiGetProfile, apiGetTagSuggestions, + apiPatchProfile, apiPostFeaturedTag, } from '@/mastodon/api/accounts'; import { apiGetSearch } from '@/mastodon/api/search'; +import type { + ApiProfileJSON, + ApiProfileUpdateParams, +} from '@/mastodon/api_types/profile'; import { hashtagToFeaturedTag } from '@/mastodon/api_types/tags'; import type { ApiFeaturedTagJSON } from '@/mastodon/api_types/tags'; import type { AppDispatch } from '@/mastodon/store'; @@ -17,11 +23,21 @@ import { createAppAsyncThunk, createDataLoadingThunk, } from '@/mastodon/store/typed_functions'; +import type { SnakeToCamelCase } from '@/mastodon/utils/types'; + +type ProfileData = { + [Key in keyof Omit< + ApiProfileJSON, + 'note' + > as SnakeToCamelCase]: ApiProfileJSON[Key]; +} & { + bio: ApiProfileJSON['note']; +}; -interface ProfileEditState { - tags: ApiFeaturedTagJSON[]; - tagSuggestions: ApiFeaturedTagJSON[]; - isLoading: boolean; +export interface ProfileEditState { + profile?: ProfileData; + tags?: ApiFeaturedTagJSON[]; + tagSuggestions?: ApiFeaturedTagJSON[]; isPending: boolean; search: { query: string; @@ -31,9 +47,6 @@ interface ProfileEditState { } const initialState: ProfileEditState = { - tags: [], - tagSuggestions: [], - isLoading: true, isPending: false, search: { query: '', @@ -49,6 +62,7 @@ const profileEditSlice = createSlice({ if (state.search.query === action.payload) { return; } + state.search.query = action.payload; state.search.isLoading = false; state.search.results = undefined; @@ -60,13 +74,25 @@ const profileEditSlice = createSlice({ }, }, extraReducers(builder) { + builder.addCase(fetchProfile.fulfilled, (state, action) => { + state.profile = action.payload; + }); builder.addCase(fetchSuggestedTags.fulfilled, (state, action) => { state.tagSuggestions = action.payload.map(hashtagToFeaturedTag); - state.isLoading = false; }); builder.addCase(fetchFeaturedTags.fulfilled, (state, action) => { state.tags = action.payload; - state.isLoading = false; + }); + + builder.addCase(patchProfile.pending, (state) => { + state.isPending = true; + }); + builder.addCase(patchProfile.rejected, (state) => { + state.isPending = false; + }); + builder.addCase(patchProfile.fulfilled, (state, action) => { + state.profile = action.payload; + state.isPending = false; }); builder.addCase(addFeaturedTag.pending, (state) => { @@ -76,12 +102,18 @@ const profileEditSlice = createSlice({ state.isPending = false; }); builder.addCase(addFeaturedTag.fulfilled, (state, action) => { + if (!state.tags) { + return; + } + state.tags = [...state.tags, action.payload].toSorted( (a, b) => b.statuses_count - a.statuses_count, ); - state.tagSuggestions = state.tagSuggestions.filter( - (tag) => tag.name !== action.meta.arg.name, - ); + if (state.tagSuggestions) { + state.tagSuggestions = state.tagSuggestions.filter( + (tag) => tag.name !== action.meta.arg.name, + ); + } state.isPending = false; }); @@ -92,6 +124,10 @@ const profileEditSlice = createSlice({ state.isPending = false; }); builder.addCase(deleteFeaturedTag.fulfilled, (state, action) => { + if (!state.tags) { + return; + } + state.tags = state.tags.filter((tag) => tag.id !== action.meta.arg.tagId); state.isPending = false; }); @@ -106,7 +142,7 @@ const profileEditSlice = createSlice({ builder.addCase(fetchSearchResults.fulfilled, (state, action) => { state.search.isLoading = false; const searchResults: ApiFeaturedTagJSON[] = []; - const currentTags = new Set(state.tags.map((tag) => tag.name)); + const currentTags = new Set((state.tags ?? []).map((tag) => tag.name)); for (const tag of action.payload) { if (currentTags.has(tag.name)) { @@ -125,6 +161,41 @@ const profileEditSlice = createSlice({ export const profileEdit = profileEditSlice.reducer; export const { clearSearch } = profileEditSlice.actions; +const transformProfile = (result: ApiProfileJSON): ProfileData => ({ + id: result.id, + displayName: result.display_name, + bio: result.note, + fields: result.fields, + avatar: result.avatar, + avatarStatic: result.avatar_static, + avatarDescription: result.avatar_description, + header: result.header, + headerStatic: result.header_static, + headerDescription: result.header_description, + locked: result.locked, + bot: result.bot, + hideCollections: result.hide_collections, + discoverable: result.discoverable, + indexable: result.indexable, + showMedia: result.show_media, + showMediaReplies: result.show_media_replies, + showFeatured: result.show_featured, + attributionDomains: result.attribution_domains, +}); + +export const fetchProfile = createDataLoadingThunk( + `${profileEditSlice.name}/fetchProfile`, + apiGetProfile, + transformProfile, +); + +export const patchProfile = createDataLoadingThunk( + `${profileEditSlice.name}/patchProfile`, + (params: Partial) => apiPatchProfile(params), + transformProfile, + { useLoadingBar: false }, +); + export const fetchFeaturedTags = createDataLoadingThunk( `${profileEditSlice.name}/fetchFeaturedTags`, apiGetCurrentFeaturedTags, @@ -143,7 +214,10 @@ export const addFeaturedTag = createDataLoadingThunk( { condition(arg, { getState }) { const state = getState(); - return !state.profileEdit.tags.some((tag) => tag.name === arg.name); + return ( + !!state.profileEdit.tags && + !state.profileEdit.tags.some((tag) => tag.name === arg.name) + ); }, }, ); diff --git a/app/javascript/mastodon/utils/types.ts b/app/javascript/mastodon/utils/types.ts index f51b3ad8b31d34..2383dbc50db1aa 100644 --- a/app/javascript/mastodon/utils/types.ts +++ b/app/javascript/mastodon/utils/types.ts @@ -24,3 +24,8 @@ export type OmitValueType = { export type AnyFunction = (...args: never) => unknown; export type OmitUnion = TBase & Omit; + +export type SnakeToCamelCase = + S extends `${infer T}_${infer U}` + ? `${T}${Capitalize>}` + : S; diff --git a/app/serializers/rest/profile_serializer.rb b/app/serializers/rest/profile_serializer.rb index d535e3776d68e2..fb4ffab989da57 100644 --- a/app/serializers/rest/profile_serializer.rb +++ b/app/serializers/rest/profile_serializer.rb @@ -3,6 +3,7 @@ class REST::ProfileSerializer < ActiveModel::Serializer include RoutingHelper + # Please update app/javascript/api_types/profile.ts when making changes to the attributes attributes :id, :display_name, :note, :fields, :avatar, :avatar_static, :avatar_description, :header, :header_static, :header_description, :locked, :bot, From 951a42f4919875670c7169004b5cb37b7f7fcb23 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 26 Feb 2026 15:58:07 +0100 Subject: [PATCH 12/89] Add `max_note_length` and `max_display_name_length` to `configuration.accounts` in `Instance` entity (#37991) --- app/serializers/rest/instance_serializer.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 1900330475c463..625cd3ad8c2502 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -69,6 +69,8 @@ def configuration }, accounts: { + max_display_name_length: Account::DISPLAY_NAME_LENGTH_LIMIT, + max_note_length: Account::NOTE_LENGTH_LIMIT, max_featured_tags: FeaturedTag::LIMIT, max_pinned_statuses: StatusPinValidator::PIN_LIMIT, max_profile_fields: Account::DEFAULT_FIELDS_SIZE, From b09e63da875439928eeb1db1c17f52f36f89dfc4 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 26 Feb 2026 16:11:02 +0100 Subject: [PATCH 13/89] Federate activity when remote account is added to a Collection (#37992) --- app/models/collection_item.rb | 5 ++++ .../activitypub/feature_request_serializer.rb | 22 ++++++++++++++ .../add_account_to_collection_service.rb | 13 ++++++-- app/services/create_collection_service.rb | 11 ++++++- .../activitypub/feature_request_worker.rb | 22 ++++++++++++++ spec/models/collection_item_spec.rb | 14 ++++----- .../feature_request_serializer_spec.rb | 26 ++++++++++++++++ .../add_account_to_collection_service_spec.rb | 18 +++++++++-- .../create_collection_service_spec.rb | 10 +++++++ .../feature_request_worker_spec.rb | 30 +++++++++++++++++++ 10 files changed, 156 insertions(+), 15 deletions(-) create mode 100644 app/serializers/activitypub/feature_request_serializer.rb create mode 100644 app/workers/activitypub/feature_request_worker.rb create mode 100644 spec/serializers/activitypub/feature_request_serializer_spec.rb create mode 100644 spec/workers/activitypub/feature_request_worker_spec.rb diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index d05d12df605552..78b5f6a6e26c01 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -35,6 +35,7 @@ class CollectionItem < ApplicationRecord validates :uri, presence: true, if: :remote? before_validation :set_position, on: :create + before_validation :set_activity_uri, only: :create, if: :local_item_with_remote_account? scope :ordered, -> { order(position: :asc) } scope :with_accounts, -> { includes(account: [:account_stat, :user]) } @@ -55,4 +56,8 @@ def set_position self.position = self.class.where(collection_id:).maximum(:position).to_i + 1 end + + def set_activity_uri + self.activity_uri = [ActivityPub::TagManager.instance.uri_for(collection.account), '/feature_requests/', SecureRandom.uuid].join + end end diff --git a/app/serializers/activitypub/feature_request_serializer.rb b/app/serializers/activitypub/feature_request_serializer.rb new file mode 100644 index 00000000000000..a7a22f4ea0f9e4 --- /dev/null +++ b/app/serializers/activitypub/feature_request_serializer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::FeatureRequestSerializer < ActivityPub::Serializer + attributes :id, :type, :instrument + attribute :virtual_object, key: :object + + def id + object.activity_uri + end + + def type + 'FeatureRequest' + end + + def virtual_object + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def instrument + ActivityPub::TagManager.instance.uri_for(object.collection) + end +end diff --git a/app/services/add_account_to_collection_service.rb b/app/services/add_account_to_collection_service.rb index 2109baf67e6054..e53c67b57fe434 100644 --- a/app/services/add_account_to_collection_service.rb +++ b/app/services/add_account_to_collection_service.rb @@ -11,7 +11,10 @@ def call(collection, account) @collection_item = create_collection_item - distribute_add_activity if @account.local? && Mastodon::Feature.collections_federation_enabled? + if Mastodon::Feature.collections_federation_enabled? + distribute_add_activity if @account.local? + distribute_feature_request_activity if @account.remote? + end @collection_item end @@ -26,10 +29,14 @@ def create_collection_item end def distribute_add_activity - ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @collection.account_id) + ActivityPub::AccountRawDistributionWorker.perform_async(add_activity_json, @collection.account_id) end - def activity_json + def distribute_feature_request_activity + ActivityPub::FeatureRequestWorker.perform_async(@collection_item.id) + end + + def add_activity_json ActiveModelSerializers::SerializableResource.new(@collection_item, serializer: ActivityPub::AddFeaturedItemSerializer, adapter: ActivityPub::Adapter).to_json end end diff --git a/app/services/create_collection_service.rb b/app/services/create_collection_service.rb index bcc68d01cd3d37..b0d291d7c3190f 100644 --- a/app/services/create_collection_service.rb +++ b/app/services/create_collection_service.rb @@ -9,7 +9,10 @@ def call(params, account) @collection.save! - distribute_add_activity if Mastodon::Feature.collections_federation_enabled? + if Mastodon::Feature.collections_federation_enabled? + distribute_add_activity + distribute_feature_request_activities + end @collection end @@ -20,6 +23,12 @@ def distribute_add_activity ActivityPub::AccountRawDistributionWorker.perform_async(activity_json, @account.id) end + def distribute_feature_request_activities + @collection.collection_items.select(&:local_item_with_remote_account?).each do |collection_item| + ActivityPub::FeatureRequestWorker.perform_async(collection_item.id) + end + end + def build_items return if @accounts_to_add.empty? diff --git a/app/workers/activitypub/feature_request_worker.rb b/app/workers/activitypub/feature_request_worker.rb new file mode 100644 index 00000000000000..fa895a546d772c --- /dev/null +++ b/app/workers/activitypub/feature_request_worker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::FeatureRequestWorker < ActivityPub::RawDistributionWorker + def perform(collection_item_id) + @collection_item = CollectionItem.find(collection_item_id) + @account = @collection_item.collection.account + + distribute! + rescue ActiveRecord::RecordNotFound + true + end + + protected + + def inboxes + @inboxes ||= [@collection_item.account.inbox_url] + end + + def payload + @payload ||= Oj.dump(serialize_payload(@collection_item, ActivityPub::FeatureRequestSerializer, signer: @account)) + end +end diff --git a/spec/models/collection_item_spec.rb b/spec/models/collection_item_spec.rb index f5497d90416615..e4905535cfc263 100644 --- a/spec/models/collection_item_spec.rb +++ b/spec/models/collection_item_spec.rb @@ -16,14 +16,6 @@ it { is_expected.to validate_presence_of(:account) } end - context 'when item is local and account is remote' do - subject { Fabricate.build(:collection_item, account: remote_account) } - - let(:remote_account) { Fabricate.build(:remote_account) } - - it { is_expected.to validate_presence_of(:activity_uri) } - end - context 'when item is not local' do subject { Fabricate.build(:collection_item, collection: remote_collection) } @@ -58,5 +50,11 @@ expect(unrelated_item.position).to eq 1 expect(custom_item.position).to eq 7 end + + it 'automatically sets `activity_uri` when account is remote' do + item = collection.collection_items.create(account: Fabricate(:remote_account)) + + expect(item.activity_uri).to be_present + end end end diff --git a/spec/serializers/activitypub/feature_request_serializer_spec.rb b/spec/serializers/activitypub/feature_request_serializer_spec.rb new file mode 100644 index 00000000000000..45040698f83848 --- /dev/null +++ b/spec/serializers/activitypub/feature_request_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::FeatureRequestSerializer do + subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) } + + let(:tag_manager) { ActivityPub::TagManager.instance } + let(:target_account) { Fabricate(:remote_account) } + let(:collection) { Fabricate(:collection) } + let(:collection_item) { Fabricate(:collection_item, collection:, account: target_account) } + + it 'serializes to the expected json' do + expect(subject).to include({ + 'id' => collection_item.activity_uri, + 'type' => 'FeatureRequest', + 'instrument' => tag_manager.uri_for(collection_item.collection), + 'object' => tag_manager.uri_for(target_account), + }) + + expect(subject).to_not have_key('published') + expect(subject).to_not have_key('to') + expect(subject).to_not have_key('cc') + expect(subject).to_not have_key('target') + end +end diff --git a/spec/services/add_account_to_collection_service_spec.rb b/spec/services/add_account_to_collection_service_spec.rb index 3085c597a7d9a7..35c7432ebe92f5 100644 --- a/spec/services/add_account_to_collection_service_spec.rb +++ b/spec/services/add_account_to_collection_service_spec.rb @@ -21,10 +21,22 @@ expect(new_item.account).to eq account end - it 'federates an `Add` activity', feature: :collections_federation do - subject.call(collection, account) + context 'when the account is local' do + it 'federates an `Add` activity', feature: :collections_federation do + subject.call(collection, account) + + expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job + end + end + + context 'when the account is remote', feature: :collections_federation do + let(:account) { Fabricate(:remote_account, feature_approval_policy: (0b10 << 16)) } + + it 'federates a `FeatureRequest` activity' do + subject.call(collection, account) - expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job + expect(ActivityPub::FeatureRequestWorker).to have_enqueued_sidekiq_job + end end end diff --git a/spec/services/create_collection_service_spec.rb b/spec/services/create_collection_service_spec.rb index 8189b01fbe480a..0d71117e732cd8 100644 --- a/spec/services/create_collection_service_spec.rb +++ b/spec/services/create_collection_service_spec.rb @@ -61,6 +61,16 @@ end.to raise_error(Mastodon::NotPermittedError) end end + + context 'when some accounts are remote' do + let(:accounts) { Fabricate.times(2, :remote_account, feature_approval_policy: (0b10 << 16)) } + + it 'federates `FeatureRequest` activities', feature: :collections_federation do + subject.call(params, author) + + expect(ActivityPub::FeatureRequestWorker).to have_enqueued_sidekiq_job.exactly(2).times + end + end end context 'when given a tag' do diff --git a/spec/workers/activitypub/feature_request_worker_spec.rb b/spec/workers/activitypub/feature_request_worker_spec.rb new file mode 100644 index 00000000000000..23e0524f41890d --- /dev/null +++ b/spec/workers/activitypub/feature_request_worker_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::FeatureRequestWorker do + subject { described_class.new } + + let(:account) { Fabricate(:account, inbox_url: 'http://example.com', domain: 'example.com') } + let(:collection_owner) { Fabricate(:account) } + let(:collection) { Fabricate(:collection, account: collection_owner) } + let(:collection_item) { Fabricate(:collection_item, collection:, account:) } + + describe '#perform' do + it 'sends the expected `FeatureRequest` activity' do + subject.perform(collection_item.id) + + expect(ActivityPub::DeliveryWorker) + .to have_enqueued_sidekiq_job(expected_json, collection_owner.id, 'http://example.com', {}) + end + + def expected_json + match_json_values( + id: a_string_matching(/^http/), + type: 'FeatureRequest', + object: ActivityPub::TagManager.instance.uri_for(account), + instrument: ActivityPub::TagManager.instance.uri_for(collection_item.collection) + ) + end + end +end From 5026bf6ac71581cd65b098a10174628051ed0b65 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 26 Feb 2026 10:56:16 -0500 Subject: [PATCH 14/89] Use validation matchers for `DisallowedHashtagValidator` spec (#37636) --- .../disallowed_hashtags_validator_spec.rb | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/spec/validators/disallowed_hashtags_validator_spec.rb b/spec/validators/disallowed_hashtags_validator_spec.rb index 570ddb31c20d30..b725e26a81bb32 100644 --- a/spec/validators/disallowed_hashtags_validator_spec.rb +++ b/spec/validators/disallowed_hashtags_validator_spec.rb @@ -3,46 +3,47 @@ require 'rails_helper' RSpec.describe DisallowedHashtagsValidator do - let(:disallowed_tags) { [] } + subject { Fabricate.build :status } - describe '#validate' do - before do - disallowed_tags.each { |name| Fabricate(:tag, name: name, usable: false) } - described_class.new.validate(status) - end + let(:tag_string) { 'ok #a #b #c then' } - let(:status) { instance_double(Status, errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| "##{x}" }.join(' ')) } - let(:errors) { instance_double(ActiveModel::Errors, add: nil) } + context 'when local' do + before { subject.local = true } - context 'with a remote reblog' do - let(:local) { false } - let(:reblog) { true } + context 'when reblog? is true' do + before { subject.reblog = Fabricate(:status) } - it 'does not add errors' do - expect(errors).to_not have_received(:add).with(:text, any_args) - end + it { is_expected.to allow_values(nil, tag_string).for(:text) } end - context 'with a local original status' do - let(:local) { true } - let(:reblog) { false } + context 'when reblog? is false' do + context 'when text does not contain unusable tags' do + it { is_expected.to allow_values('text', tag_string).for(:text) } + end + + context 'when text contains unusable tags' do + before { Fabricate :tag, name: 'a', usable: false } - context 'when does not contain any disallowed hashtags' do - let(:disallowed_tags) { [] } + it { is_expected.to_not allow_values(tag_string).for(:text).with_message(disallow_message) } - it 'does not add errors' do - expect(errors).to_not have_received(:add).with(:text, any_args) + def disallow_message + I18n.t('statuses.disallowed_hashtags', tags: 'a', count: 1) end end + end + end - context 'when contains disallowed hashtags' do - let(:disallowed_tags) { %w(a b c) } + context 'when remote' do + before { subject.local = false } - it 'adds an error' do - expect(errors).to have_received(:add) - .with(:text, I18n.t('statuses.disallowed_hashtags', tags: disallowed_tags.join(', '), count: disallowed_tags.size)) - end - end + context 'when reblog? is true' do + before { subject.reblog = Fabricate(:status) } + + it { is_expected.to allow_values(nil, tag_string).for(:text) } + end + + context 'when reblog? is false' do + it { is_expected.to allow_values('text', tag_string).for(:text) } end end end From 9c4d11f9276c46155245a03dbd0f2df48750a141 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 26 Feb 2026 17:18:42 +0100 Subject: [PATCH 15/89] Add `featured_tags` to `GET /api/v1/profile` (#37932) --- app/serializers/rest/profile_serializer.rb | 2 ++ spec/requests/api/v1/profiles_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/serializers/rest/profile_serializer.rb b/app/serializers/rest/profile_serializer.rb index fb4ffab989da57..b96daf87d46324 100644 --- a/app/serializers/rest/profile_serializer.rb +++ b/app/serializers/rest/profile_serializer.rb @@ -11,6 +11,8 @@ class REST::ProfileSerializer < ActiveModel::Serializer :show_media, :show_media_replies, :show_featured, :attribution_domains + has_many :featured_tags, serializer: REST::FeaturedTagSerializer + def id object.id.to_s end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index fa4e8d9a57891d..faff16bcc1cf2e 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -48,7 +48,8 @@ 'note' => account.note, 'show_featured' => account.show_featured, 'show_media' => account.show_media, - 'show_media_replies' => account.show_media_replies + 'show_media_replies' => account.show_media_replies, + 'featured_tags' => [] ) end end From bca57020a04c552a3c91912a0e0b69ddafaad06a Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 26 Feb 2026 18:04:46 +0100 Subject: [PATCH 16/89] Profile redesign: Fix fields in Chromium (#37996) --- .../account_timeline/components/redesign.module.scss | 5 +++++ 1 file changed, 5 insertions(+) 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 aad78f1e084ecb..d4ea2d7a7d412d 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -262,6 +262,11 @@ svg.badgeIcon { } } } + + // See: https://stackoverflow.com/questions/13226296/is-scrollwidth-property-of-a-span-not-working-on-chrome + [data-contents] { + display: inline-block; + } } .fieldVerified { From 7970eb392abea1a452bccfc04b673fc55e117578 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 26 Feb 2026 18:45:09 +0100 Subject: [PATCH 17/89] Add share dialog for collections (#37986) --- app/javascript/mastodon/components/avatar.tsx | 9 ++ .../mastodon/components/copy_icon_button.tsx | 8 +- .../form_fields/copy_link_field.module.scss | 14 ++ .../form_fields/copy_link_field.tsx | 81 ++++++++++ .../mastodon/components/form_fields/index.ts | 2 + .../mastodon/components/modal_shell/index.tsx | 56 +++++++ .../detail/collection_list_item.module.scss | 1 + .../features/collections/detail/index.tsx | 66 +++++--- .../detail/share_modal.module.scss | 75 ++++++++++ .../collections/detail/share_modal.tsx | 141 ++++++++++++++++++ .../collections/detail/styles.module.scss | 4 + .../features/collections/editor/details.tsx | 4 +- .../confirmation_modal.tsx | 85 +++++------ .../features/ui/components/modal_root.jsx | 2 + .../features/ui/util/async-components.js | 6 + app/javascript/mastodon/locales/en.json | 8 + .../styles/mastodon/components.scss | 8 +- 17 files changed, 498 insertions(+), 72 deletions(-) create mode 100644 app/javascript/mastodon/components/form_fields/copy_link_field.module.scss create mode 100644 app/javascript/mastodon/components/form_fields/copy_link_field.tsx create mode 100644 app/javascript/mastodon/components/modal_shell/index.tsx create mode 100644 app/javascript/mastodon/features/collections/detail/share_modal.module.scss create mode 100644 app/javascript/mastodon/features/collections/detail/share_modal.tsx diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx index 6e1c5dbfd4ca98..b086ef42259dcc 100644 --- a/app/javascript/mastodon/components/avatar.tsx +++ b/app/javascript/mastodon/components/avatar.tsx @@ -7,6 +7,8 @@ import { useHovering } from 'mastodon/hooks/useHovering'; import { autoPlayGif } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; +import { useAccount } from '../hooks/useAccount'; + interface Props { account: | Pick @@ -91,3 +93,10 @@ export const Avatar: React.FC = ({ return avatar; }; + +export const AvatarById: React.FC< + { accountId: string } & Omit +> = ({ accountId, ...otherProps }) => { + const account = useAccount(accountId); + return ; +}; diff --git a/app/javascript/mastodon/components/copy_icon_button.tsx b/app/javascript/mastodon/components/copy_icon_button.tsx index 29f5f344302b46..51cffe6292e16f 100644 --- a/app/javascript/mastodon/components/copy_icon_button.tsx +++ b/app/javascript/mastodon/components/copy_icon_button.tsx @@ -19,8 +19,9 @@ const messages = defineMessages({ export const CopyIconButton: React.FC<{ title: string; value: string; - className: string; -}> = ({ title, value, className }) => { + className?: string; + 'aria-describedby'?: string; +}> = ({ title, value, className, 'aria-describedby': ariaDescribedBy }) => { const [copied, setCopied] = useState(false); const dispatch = useAppDispatch(); @@ -38,8 +39,9 @@ export const CopyIconButton: React.FC<{ className={classNames(className, copied ? 'copied' : 'copyable')} title={title} onClick={handleClick} - icon='' + icon='copy-icon' iconComponent={ContentCopyIcon} + aria-describedby={ariaDescribedBy} /> ); }; diff --git a/app/javascript/mastodon/components/form_fields/copy_link_field.module.scss b/app/javascript/mastodon/components/form_fields/copy_link_field.module.scss new file mode 100644 index 00000000000000..06834e9d91f2fb --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/copy_link_field.module.scss @@ -0,0 +1,14 @@ +.wrapper { + position: relative; +} + +.input { + padding-inline-end: 45px; +} + +.copyButton { + position: absolute; + inset-inline-end: 0; + top: 0; + padding: 9px; +} diff --git a/app/javascript/mastodon/components/form_fields/copy_link_field.tsx b/app/javascript/mastodon/components/form_fields/copy_link_field.tsx new file mode 100644 index 00000000000000..ad93e3a065d66f --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/copy_link_field.tsx @@ -0,0 +1,81 @@ +import { forwardRef, useCallback, useRef } from 'react'; + +import { useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { CopyIconButton } from 'mastodon/components/copy_icon_button'; + +import classes from './copy_link_field.module.scss'; +import { FormFieldWrapper } from './form_field_wrapper'; +import type { CommonFieldWrapperProps } from './form_field_wrapper'; +import { TextInput } from './text_input_field'; +import type { TextInputProps } from './text_input_field'; + +interface CopyLinkFieldProps extends CommonFieldWrapperProps, TextInputProps { + value: string; +} + +/** + * A read-only text field with a button for copying the field value + */ + +export const CopyLinkField = forwardRef( + ( + { id, label, hint, hasError, value, required, className, ...otherProps }, + ref, + ) => { + const intl = useIntl(); + const inputRef = useRef(); + const handleFocus = useCallback(() => { + inputRef.current?.select(); + }, []); + + const mergeRefs = useCallback( + (element: HTMLInputElement | null) => { + inputRef.current = element; + if (typeof ref === 'function') { + ref(element); + } else if (ref) { + ref.current = element; + } + }, + [ref], + ); + + return ( + + {(inputProps) => ( +
+ + +
+ )} +
+ ); + }, +); + +CopyLinkField.displayName = 'CopyLinkField'; diff --git a/app/javascript/mastodon/components/form_fields/index.ts b/app/javascript/mastodon/components/form_fields/index.ts index fca366106fb0b1..b44ceb63f8a8af 100644 --- a/app/javascript/mastodon/components/form_fields/index.ts +++ b/app/javascript/mastodon/components/form_fields/index.ts @@ -1,3 +1,4 @@ +export { FormFieldWrapper } from './form_field_wrapper'; export { FormStack } from './form_stack'; export { Fieldset } from './fieldset'; export { TextInputField, TextInput } from './text_input_field'; @@ -8,6 +9,7 @@ export { Combobox, type ComboboxItemState, } from './combobox_field'; +export { CopyLinkField } from './copy_link_field'; export { RadioButtonField, RadioButton } from './radio_button_field'; export { ToggleField, Toggle } from './toggle_field'; export { SelectField, Select } from './select_field'; diff --git a/app/javascript/mastodon/components/modal_shell/index.tsx b/app/javascript/mastodon/components/modal_shell/index.tsx new file mode 100644 index 00000000000000..8b6fdcc6ad279c --- /dev/null +++ b/app/javascript/mastodon/components/modal_shell/index.tsx @@ -0,0 +1,56 @@ +import classNames from 'classnames'; + +interface SimpleComponentProps { + className?: string; + children?: React.ReactNode; +} + +interface ModalShellComponent extends React.FC { + Body: React.FC; + Actions: React.FC; +} + +export const ModalShell: ModalShellComponent = ({ children, className }) => { + return ( +
+ {children} +
+ ); +}; + +const ModalShellBody: ModalShellComponent['Body'] = ({ + children, + className, +}) => { + return ( +
+
+ {children} +
+
+ ); +}; + +const ModalShellActions: ModalShellComponent['Actions'] = ({ + children, + className, +}) => { + return ( +
+
+ {children} +
+
+ ); +}; + +ModalShell.Body = ModalShellBody; +ModalShell.Actions = ModalShellActions; diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss index 9e771dbaa03a7c..3c71e90f484fc0 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss @@ -44,6 +44,7 @@ --gap: 0.75ch; display: flex; + flex-wrap: wrap; gap: var(--gap); & > li:not(:last-child)::after { diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx index d5b14da8593097..d2317e716f85ad 100644 --- a/app/javascript/mastodon/features/collections/detail/index.tsx +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -3,18 +3,21 @@ import { useCallback, useEffect } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Helmet } from 'react-helmet'; -import { useParams } from 'react-router'; +import { useLocation, useParams } from 'react-router'; +import { openModal } from '@/mastodon/actions/modal'; import { useRelationship } from '@/mastodon/hooks/useRelationship'; 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 { + DisplayName, + 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'; @@ -46,32 +49,40 @@ const messages = defineMessages({ }, }); -const AuthorNote: React.FC<{ id: string }> = ({ id }) => { +export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({ + id, + // When previewMode is enabled, your own display name + // will not be replaced with "you" + previewMode = false, +}) => { const account = useAccount(id); const author = ( - + {previewMode ? ( + + ) : ( + + )} ); - if (id === me) { - return ( -

+ const displayAsYou = id === me && !previewMode; + + return ( +

+ {displayAsYou ? ( -

- ); - } - return ( -

- + ) : ( + + )}

); }; @@ -84,8 +95,23 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ const dispatch = useAppDispatch(); const handleShare = useCallback(() => { - dispatch(showAlert({ message: 'Collection sharing not yet implemented' })); - }, [dispatch]); + dispatch( + openModal({ + modalType: 'SHARE_COLLECTION', + modalProps: { + collection, + }, + }), + ); + }, [collection, dispatch]); + + const location = useLocation<{ newCollection?: boolean }>(); + const wasJustCreated = location.state.newCollection; + useEffect(() => { + if (wasJustCreated) { + handleShare(); + } + }, [handleShare, wasJustCreated]); return (
diff --git a/app/javascript/mastodon/features/collections/detail/share_modal.module.scss b/app/javascript/mastodon/features/collections/detail/share_modal.module.scss new file mode 100644 index 00000000000000..2344ea519eb0ce --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/share_modal.module.scss @@ -0,0 +1,75 @@ +.heading { + font-size: 28px; + line-height: 1.3; + margin-bottom: 16px; +} + +.preview { + display: flex; + flex-wrap: wrap-reverse; + align-items: start; + justify-content: space-between; + gap: 8px; + padding: 16px; + margin-bottom: 16px; + border-radius: 8px; + color: var(--color-text-primary); + background: linear-gradient( + 145deg, + var(--color-bg-brand-soft), + var(--color-bg-primary) + ); + border: 1px solid var(--color-bg-brand-base); +} + +.previewHeading { + font-size: 22px; + line-height: 1.3; + margin-bottom: 4px; +} + +.actions { + display: flex; + flex-direction: column; + justify-content: center; +} + +$bottomsheet-breakpoint: 630px; + +.shareButtonWrapper { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + width: 100%; + + & > button { + flex: 1; + min-width: 220px; + white-space: normal; + + @media (width > $bottomsheet-breakpoint) { + max-width: 50%; + } + } +} + +.closeButtonDesktop { + position: absolute; + top: 4px; + inset-inline-end: 4px; + padding: 8px; + + @media (width <= $bottomsheet-breakpoint) { + display: none; + } +} + +.closeButtonMobile { + margin-top: 16px; + margin-bottom: -18px; + + @media (width > $bottomsheet-breakpoint) { + display: none; + } +} diff --git a/app/javascript/mastodon/features/collections/detail/share_modal.tsx b/app/javascript/mastodon/features/collections/detail/share_modal.tsx new file mode 100644 index 00000000000000..3bff066ee6db65 --- /dev/null +++ b/app/javascript/mastodon/features/collections/detail/share_modal.tsx @@ -0,0 +1,141 @@ +import { useCallback } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { useLocation } from 'react-router'; + +import { me } from '@/mastodon/initial_state'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import { changeCompose, focusCompose } from 'mastodon/actions/compose'; +import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; +import { AvatarById } from 'mastodon/components/avatar'; +import { AvatarGroup } from 'mastodon/components/avatar_group'; +import { Button } from 'mastodon/components/button'; +import { CopyLinkField } from 'mastodon/components/form_fields'; +import { IconButton } from 'mastodon/components/icon_button'; +import { ModalShell } from 'mastodon/components/modal_shell'; +import { useAppDispatch } from 'mastodon/store'; + +import { AuthorNote } from '.'; +import classes from './share_modal.module.scss'; + +const messages = defineMessages({ + shareTextOwn: { + id: 'collection.share_template_own', + defaultMessage: 'Check out my new collection: {link}', + }, + shareTextOther: { + id: 'collection.share_template_other', + defaultMessage: 'Check out this cool collection: {link}', + }, +}); + +export const CollectionShareModal: React.FC<{ + collection: ApiCollectionJSON; + onClose: () => void; +}> = ({ collection, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const location = useLocation<{ newCollection?: boolean }>(); + const isNew = !!location.state.newCollection; + const isOwnCollection = collection.account_id === me; + + const collectionLink = `${window.location.origin}/collections/${collection.id}`; + + const handleShareOnDevice = useCallback(() => { + void navigator.share({ + url: collectionLink, + }); + }, [collectionLink]); + + const handleShareViaPost = useCallback(() => { + const shareMessage = isOwnCollection + ? intl.formatMessage(messages.shareTextOwn, { + link: collectionLink, + }) + : intl.formatMessage(messages.shareTextOther, { + link: collectionLink, + }); + + onClose(); + dispatch(changeCompose(shareMessage)); + dispatch(focusCompose()); + }, [collectionLink, dispatch, intl, isOwnCollection, onClose]); + + return ( + + +

+ {isNew ? ( + + ) : ( + + )} +

+ + + +
+
+

{collection.name}

+ +
+ + {collection.items.slice(0, 5).map(({ account_id }) => { + if (!account_id) return; + return ( + + ); + })} + +
+ + +
+ + +
+ + {'share' in navigator && ( + + )} +
+ + +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/collections/detail/styles.module.scss b/app/javascript/mastodon/features/collections/detail/styles.module.scss index cb94f2894c8d56..690ec29f71643d 100644 --- a/app/javascript/mastodon/features/collections/detail/styles.module.scss +++ b/app/javascript/mastodon/features/collections/detail/styles.module.scss @@ -48,6 +48,10 @@ color: var(--color-text-secondary); } +.previewAuthorNote { + font-size: 13px; +} + .metaData { margin-top: 16px; font-size: 15px; diff --git a/app/javascript/mastodon/features/collections/editor/details.tsx b/app/javascript/mastodon/features/collections/editor/details.tsx index e8d99df4ddb6c1..6234bca5142713 100644 --- a/app/javascript/mastodon/features/collections/editor/details.tsx +++ b/app/javascript/mastodon/features/collections/editor/details.tsx @@ -127,7 +127,9 @@ export const CollectionDetails: React.FC<{ history.replace( `/collections/${result.payload.collection.id}/edit/details`, ); - history.push(`/collections/${result.payload.collection.id}`); + history.push(`/collections/${result.payload.collection.id}`, { + newCollection: true, + }); } }); } diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 2ea413208e169a..385ec6a794191c 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import { Button } from 'mastodon/components/button'; +import { ModalShell } from 'mastodon/components/modal_shell'; export interface BaseConfirmationModalProps { onClose: () => void; @@ -56,53 +57,49 @@ export const ConfirmationModal: React.FC< }, [onClose, onSecondary]); return ( -
-
-
-

{title}

- {message &&

{message}

} + + +

{title}

+ {message &&

{message}

} - {extraContent ?? children} -
-
+ {extraContent ?? children} + -
-
- - - {secondary && ( - <> -
- - + + + + {secondary && ( + <> +
+ + + )} - {/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */} - - {/* eslint-enable */} -
-
-
+ {/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */} + + {/* eslint-enable */} + + ); }; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 7cacfab8003312..3e2751c7a34891 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -11,6 +11,7 @@ import { DomainBlockModal, ReportModal, ReportCollectionModal, + ShareCollectionModal, EmbedModal, ListAdder, CompareHistoryModal, @@ -79,6 +80,7 @@ export const MODAL_COMPONENTS = { 'DOMAIN_BLOCK': DomainBlockModal, 'REPORT': ReportModal, 'REPORT_COLLECTION': ReportCollectionModal, + 'SHARE_COLLECTION': ShareCollectionModal, 'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'EMBED': EmbedModal, 'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }), diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index d6c0f70c704db3..099be340d2245a 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -62,6 +62,12 @@ export function CollectionsEditor() { ); } +export function ShareCollectionModal() { + return import('../../collections/detail/share_modal').then( + module => ({default: module.CollectionShareModal}) + ); +} + export function Status () { return import('../../status'); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index e95ac604208c89..32088474eb837d 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Find another server", "closed_registrations_modal.preamble": "Mastodon is decentralized, 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", + "collection.share_modal.share_link_label": "Invite share link", + "collection.share_modal.share_via_post": "Post on Mastodon", + "collection.share_modal.share_via_system": "Share to…", + "collection.share_modal.title": "Share collection", + "collection.share_modal.title_new": "Share your new collection!", + "collection.share_template_other": "Check out this cool collection: {link}", + "collection.share_template_own": "Check out my new collection: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "View conversation", "conversation.with": "With {names}", "copy_icon_button.copied": "Copied to clipboard", + "copy_icon_button.copy_this_text": "Copy link to clipboard", "copypaste.copied": "Copied", "copypaste.copy_to_clipboard": "Copy to clipboard", "directory.federated": "From known fediverse", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 5a906c93a25119..1e29fcb2b3e74d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6408,15 +6408,15 @@ a.status-card { line-height: 20px; color: var(--color-text-secondary); - h1 { + :where(h1) { font-size: 16px; line-height: 24px; color: var(--color-text-primary); font-weight: 500; + } - &:not(:only-child) { - margin-bottom: 8px; - } + :where(h1:not(:only-child)) { + margin-bottom: 8px; } strong { From 1dbb258d53022693461c9b0e36666fbc7852d0c1 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 02:50:35 -0500 Subject: [PATCH 18/89] Update rails to version 8.1.2 (#36505) --- Gemfile | 2 +- Gemfile.lock | 113 +++++++++--------- app/lib/admin/metrics/measure/base_measure.rb | 2 +- config/application.rb | 2 +- config/environments/development.rb | 11 +- config/environments/production.rb | 12 +- config/initializers/open_redirects.rb | 15 ++- config/routes.rb | 2 +- 8 files changed, 90 insertions(+), 69 deletions(-) diff --git a/Gemfile b/Gemfile index 9a0e1d609408fd..a294fb7206ee82 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ ruby '>= 3.2.0', '< 3.5.0' gem 'propshaft' gem 'puma', '~> 7.0' -gem 'rails', '~> 8.0' +gem 'rails', '~> 8.1.0' gem 'thor', '~> 1.2' gem 'dotenv' diff --git a/Gemfile.lock b/Gemfile.lock index 8feb1490d03436..d71fa8360c52f1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,29 +10,31 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.3) - actionpack (= 8.0.3) - activesupport (= 8.0.3) + action_text-trix (2.1.16) + railties + actioncable (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.3) - actionpack (= 8.0.3) - activejob (= 8.0.3) - activerecord (= 8.0.3) - activestorage (= 8.0.3) - activesupport (= 8.0.3) + actionmailbox (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) - actionmailer (8.0.3) - actionpack (= 8.0.3) - actionview (= 8.0.3) - activejob (= 8.0.3) - activesupport (= 8.0.3) + actionmailer (8.1.2) + actionpack (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activesupport (= 8.1.2) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.3) - actionview (= 8.0.3) - activesupport (= 8.0.3) + actionpack (8.1.2) + actionview (= 8.1.2) + activesupport (= 8.1.2) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,15 +42,16 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.3) - actionpack (= 8.0.3) - activerecord (= 8.0.3) - activestorage (= 8.0.3) - activesupport (= 8.0.3) + actiontext (8.1.2) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.3) - activesupport (= 8.0.3) + actionview (8.1.2) + activesupport (= 8.1.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -58,29 +61,29 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.0.3) - activesupport (= 8.0.3) + activejob (8.1.2) + activesupport (= 8.1.2) globalid (>= 0.3.6) - activemodel (8.0.3) - activesupport (= 8.0.3) - activerecord (8.0.3) - activemodel (= 8.0.3) - activesupport (= 8.0.3) + activemodel (8.1.2) + activesupport (= 8.1.2) + activerecord (8.1.2) + activemodel (= 8.1.2) + activesupport (= 8.1.2) timeout (>= 0.4.0) - activestorage (8.0.3) - actionpack (= 8.0.3) - activejob (= 8.0.3) - activerecord (= 8.0.3) - activesupport (= 8.0.3) + activestorage (8.1.2) + actionpack (= 8.1.2) + activejob (= 8.1.2) + activerecord (= 8.1.2) + activesupport (= 8.1.2) marcel (~> 1.0) - activesupport (8.0.3) + activesupport (8.1.2) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) @@ -657,20 +660,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.0.3) - actioncable (= 8.0.3) - actionmailbox (= 8.0.3) - actionmailer (= 8.0.3) - actionpack (= 8.0.3) - actiontext (= 8.0.3) - actionview (= 8.0.3) - activejob (= 8.0.3) - activemodel (= 8.0.3) - activerecord (= 8.0.3) - activestorage (= 8.0.3) - activesupport (= 8.0.3) + rails (8.1.2) + actioncable (= 8.1.2) + actionmailbox (= 8.1.2) + actionmailer (= 8.1.2) + actionpack (= 8.1.2) + actiontext (= 8.1.2) + actionview (= 8.1.2) + activejob (= 8.1.2) + activemodel (= 8.1.2) + activerecord (= 8.1.2) + activestorage (= 8.1.2) + activesupport (= 8.1.2) bundler (>= 1.15.0) - railties (= 8.0.3) + railties (= 8.1.2) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -681,9 +684,9 @@ GEM rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.0.3) - actionpack (= 8.0.3) - activesupport (= 8.0.3) + railties (8.1.2) + actionpack (= 8.1.2) + activesupport (= 8.1.2) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -1050,7 +1053,7 @@ DEPENDENCIES rack-attack (~> 6.6) rack-cors rack-test (~> 2.1) - rails (~> 8.0) + rails (~> 8.1.0) rails-i18n (~> 8.0) rdf-normalize (~> 0.5) redcarpet (~> 3.6) diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb index eabbe0890b67d0..88a7cb09a0abd4 100644 --- a/app/lib/admin/metrics/measure/base_measure.rb +++ b/app/lib/admin/metrics/measure/base_measure.rb @@ -94,7 +94,7 @@ def previous_time_period end def length_of_period - @length_of_period ||= @end_at - @start_at + @length_of_period ||= @end_at.to_date - @start_at.to_date end def params diff --git a/config/application.rb b/config/application.rb index 4e58bd9f6c213e..09fe38065e3ed0 100644 --- a/config/application.rb +++ b/config/application.rb @@ -58,7 +58,7 @@ module Mastodon class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 8.0 + config.load_defaults 8.1 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/environments/development.rb b/config/environments/development.rb index 4f3e076e7c5d78..1d9ba5a8fcd61a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -28,7 +28,7 @@ config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.to_i}", + 'cache-control' => "public, max-age=#{2.days.to_i}", } else config.action_controller.perform_caching = false @@ -67,9 +67,18 @@ # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true + # Append comments with runtime information tags to SQL queries in logs. + config.active_record.query_log_tags_enabled = true + # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true + # Highlight code that triggered redirect in logs. + config.action_dispatch.verbose_redirect_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/config/environments/production.rb b/config/environments/production.rb index 993e4186915809..5f655e01f1fb06 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -11,8 +11,10 @@ # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). config.eager_load = true - # Full error reports are disabled and caching is turned on. + # Full error reports are disabled. config.consider_all_requests_local = false + + # Turn on fragment caching in view templates. config.action_controller.perform_caching = true # Do not fallback to assets pipeline if a precompiled asset is missed. @@ -55,9 +57,8 @@ # Use a different cache store in production. config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache - # Disable caching for Action Mailer templates even if Action Controller - # caching is enabled. - config.action_mailer.perform_caching = false + # Prevent health checks from clogging up the logs. + config.silence_healthcheck_path = '/health' # Don't log any deprecations. config.active_support.report_deprecations = false @@ -104,6 +105,9 @@ 'Referrer-Policy' => 'same-origin', } + # Only use :id for inspections in production. + config.active_record.attributes_for_inspect = [:id] + # Enable DNS rebinding protection and other `Host` header attacks. # config.hosts = [ # "example.com", # Allow requests from example.com diff --git a/config/initializers/open_redirects.rb b/config/initializers/open_redirects.rb index 1c5a66e07dc4b7..821fde784d4b46 100644 --- a/config/initializers/open_redirects.rb +++ b/config/initializers/open_redirects.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true -# TODO: Starting with Rails 7.0, the framework default is true for this setting. -# This location in devise redirects and we can't hook in or override: -# https://github.com/heartcombo/devise/blob/v4.9.3/app/controllers/devise/confirmations_controller.rb#L28 -# When solution is found, this setting can go back to default. -Rails.application.config.action_controller.raise_on_open_redirects = false +# In the Devise confirmations#show action, a redirect_to is called: +# https://github.com/heartcombo/devise/blob/v5.0.0/app/controllers/devise/confirmations_controller.rb#L28 +# +# We override the `after_confirmation_path_for` method in a way which sometimes +# returns raw URLs to external hosts, as part of the auth workflow. +# Discussion: https://github.com/mastodon/mastodon/pull/36505#discussion_r2782876831 + +Rails.application.reloader.to_prepare do + ActionController::Base.action_on_open_redirect = :log +end diff --git a/config/routes.rb b/config/routes.rb index b516a48866f1bd..3ddd3494d31bb8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,7 +12,7 @@ def build_response(req) end def redirect_with_vary(path) - RedirectWithVary.new(301, path) + RedirectWithVary.new(301, path, caller(1..1).first) end Rails.application.routes.draw do From dd1623e9889126ca86414b27c6aa7e03bcf2f52f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:46:48 +0100 Subject: [PATCH 19/89] Update dependency storybook to v10.2.10 [SECURITY] (#37998) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 367136406742aa..e09e2aa1cbec4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3923,7 +3923,7 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^2.0.0": +"@storybook/icons@npm:^2.0.1": version: 2.0.1 resolution: "@storybook/icons@npm:2.0.1" peerDependencies: @@ -12630,7 +12630,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.3": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.1, semver@npm:^7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -13085,11 +13085,11 @@ __metadata: linkType: hard "storybook@npm:^10.0.5": - version: 10.1.10 - resolution: "storybook@npm:10.1.10" + version: 10.2.13 + resolution: "storybook@npm:10.2.13" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^2.0.0" + "@storybook/icons": "npm:^2.0.1" "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" @@ -13097,7 +13097,7 @@ __metadata: esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" open: "npm:^10.2.0" recast: "npm:^0.23.5" - semver: "npm:^7.6.2" + semver: "npm:^7.7.3" use-sync-external-store: "npm:^1.5.0" ws: "npm:^8.18.0" peerDependencies: @@ -13107,7 +13107,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10c0/beff5472ee86a995cbde2789b2aabd941f823e31ca6957bb4434cb8ee3d3703cf1248e44f4b4d402416a52bfee94677e74f233cc906487901e831e8ab610defa + checksum: 10c0/5ca338b707c3e7e94c16ecdcb00ca3c450157dceec758c15c416649e346e628a0e034d2265656650fc4fee4680631de7cc588e1a244e42cbb41af9416281a998 languageName: node linkType: hard From 1bc5cc4fcf91a99774c1ee46522df98befb61e3c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 04:14:46 -0500 Subject: [PATCH 20/89] Use bundler version 4.0.7 (#37995) --- Gemfile.lock | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d71fa8360c52f1..1d94c3ec050a82 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,7 +99,7 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1213.0) + aws-partitions (1.1220.0) aws-sdk-core (3.242.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -108,7 +108,7 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.121.0) + aws-sdk-kms (1.122.0) aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) aws-sdk-s3 (1.213.0) @@ -132,7 +132,7 @@ GEM binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) blurhash (0.1.8) - bootsnap (1.22.0) + bootsnap (1.23.0) msgpack (~> 1.2) brakeman (8.0.2) racc @@ -230,7 +230,7 @@ GEM mail (~> 2.7) email_validator (2.2.4) activemodel - erb (6.0.1) + erb (6.0.2) erubi (1.13.1) et-orbi (1.4.0) tzinfo @@ -294,7 +294,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.69.0) + haml_lint (0.71.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -450,17 +450,18 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2026.0203) + mime-types-data (3.2026.0224) mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (6.0.1) + minitest (6.0.2) + drb (~> 2.0) prism (~> 1.5) msgpack (1.8.0) multi_json (1.19.1) mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.6.2) + net-imap (0.6.3) date net-protocol net-ldap (0.20.0) @@ -593,7 +594,7 @@ GEM ox (2.14.23) bigdecimal (>= 3.0) parallel (1.27.0) - parser (3.3.10.1) + parser (3.3.10.2) ast (~> 2.4.1) racc parslet (2.0.0) @@ -678,8 +679,8 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (8.1.0) i18n (>= 0.7, < 2) @@ -704,7 +705,7 @@ GEM readline (~> 0.0) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (7.1.0) + rdoc (7.2.0) erb psych (>= 4.0.0) tsort @@ -795,8 +796,9 @@ GEM lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) - ruby-prof (1.7.2) + ruby-prof (2.0.2) base64 + ostruct ruby-progressbar (1.13.0) ruby-saml (1.18.1) nokogiri (>= 1.13.10) @@ -906,7 +908,7 @@ GEM vite_rails (3.0.20) railties (>= 5.1, < 9) vite_ruby (~> 3.0, >= 3.2.2) - vite_ruby (3.9.2) + vite_ruby (3.9.3) dry-cli (>= 0.7, < 2) logger (~> 1.6) mutex_m @@ -939,7 +941,7 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.7.4) + zeitwerk (2.7.5) PLATFORMS ruby @@ -1103,4 +1105,4 @@ RUBY VERSION ruby 3.4.8 BUNDLED WITH - 4.0.6 + 4.0.7 From fc1f57b5f1d6b59ed5d108cc9e1db45102f59fba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:29:07 +0100 Subject: [PATCH 21/89] New Crowdin Translations (automated) (#38001) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 8 ++++++++ app/javascript/mastodon/locales/da.json | 7 +++++++ app/javascript/mastodon/locales/de.json | 8 ++++++++ app/javascript/mastodon/locales/el.json | 8 ++++++++ app/javascript/mastodon/locales/en-GB.json | 8 ++++++++ app/javascript/mastodon/locales/es-AR.json | 8 ++++++++ app/javascript/mastodon/locales/es-MX.json | 8 ++++++++ app/javascript/mastodon/locales/es.json | 14 +++++++++++--- app/javascript/mastodon/locales/fo.json | 8 ++++++++ app/javascript/mastodon/locales/fr-CA.json | 1 + app/javascript/mastodon/locales/fr.json | 1 + app/javascript/mastodon/locales/ga.json | 8 ++++++++ app/javascript/mastodon/locales/is.json | 8 ++++++++ app/javascript/mastodon/locales/it.json | 10 ++++++++++ app/javascript/mastodon/locales/nl.json | 8 ++++++++ app/javascript/mastodon/locales/pt-PT.json | 20 ++++++++++++++++++++ app/javascript/mastodon/locales/sq.json | 7 +++++++ app/javascript/mastodon/locales/tr.json | 10 ++++++++++ app/javascript/mastodon/locales/zh-CN.json | 9 +++++++++ app/javascript/mastodon/locales/zh-TW.json | 8 ++++++++ config/locales/activerecord.fr.yml | 2 +- config/locales/it.yml | 2 ++ config/locales/pt-PT.yml | 4 ++++ config/locales/tr.yml | 2 ++ 24 files changed, 173 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 5e20ae1b087f49..c3e879c7c2193e 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Знайсці іншы сервер", "closed_registrations_modal.preamble": "Mastodon дэцэнтралізаваны, так што дзе б вы ні стварылі ўліковы запіс, вы зможаце падпісвацца і камунікаваць з кім хочаце на гэтым серверы. Вы нават можаце стварыць свой!", "closed_registrations_modal.title": "Рэгістрацыя ў Mastodon", + "collection.share_modal.share_link_label": "Падзяліцца спасылкай", + "collection.share_modal.share_via_post": "Апублікаваць у Mastodon", + "collection.share_modal.share_via_system": "Падзяліцца ў…", + "collection.share_modal.title": "Падзяліцца калекцыяй", + "collection.share_modal.title_new": "Падзяліцеся сваёй калекцыяй!", + "collection.share_template_other": "Глядзі, якая класная калекцыя: {link}", + "collection.share_template_own": "Глядзі, у мяне новая калекцыя: {link}", "collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}", "collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя", "collections.accounts.empty_title": "Гэтая калекцыя пустая", @@ -448,6 +455,7 @@ "conversation.open": "Прагледзець размову", "conversation.with": "З {names}", "copy_icon_button.copied": "Скапіявана ў буфер абмену", + "copy_icon_button.copy_this_text": "Скапіяваць спасылку ў буфер абмену", "copypaste.copied": "Скапіравана", "copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену", "directory.federated": "З вядомага федэральнага сусвету", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 401dfc29488559..4fcb4cc5e2b4d4 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -271,6 +271,12 @@ "closed_registrations_modal.find_another_server": "Find en anden server", "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", + "collection.share_modal.share_link_label": "Invitationlink til deling", + "collection.share_modal.share_via_system": "Del med…", + "collection.share_modal.title": "Del samling", + "collection.share_modal.title_new": "Del din nye samling!", + "collection.share_template_other": "Tjek denne seje samling: {link}", + "collection.share_template_own": "Tjek min nye samling: {link}", "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", @@ -448,6 +454,7 @@ "conversation.open": "Vis samtale", "conversation.with": "Med {names}", "copy_icon_button.copied": "Kopieret til udklipsholderen", + "copy_icon_button.copy_this_text": "Kopiér link til udklipsholderen", "copypaste.copied": "Kopieret", "copypaste.copy_to_clipboard": "Kopiér til udklipsholder", "directory.federated": "Fra kendt fediverse", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index c4e62d65bc28a1..ce49025b5a7b1c 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Anderen Server suchen", "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", + "collection.share_modal.share_link_label": "Link zum Teilen", + "collection.share_modal.share_via_post": "Auf Mastodon veröffentlichen", + "collection.share_modal.share_via_system": "Teilen …", + "collection.share_modal.title": "Sammlung teilen", + "collection.share_modal.title_new": "Teile deine neue Sammlung!", + "collection.share_template_other": "Seht euch diese coole Sammlung an: {link}", + "collection.share_template_own": "Seht euch meine neue Sammlung an: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Unterhaltung anzeigen", "conversation.with": "Mit {names}", "copy_icon_button.copied": "In die Zwischenablage kopiert", + "copy_icon_button.copy_this_text": "Link in die Zwischenablage kopieren", "copypaste.copied": "Kopiert", "copypaste.copy_to_clipboard": "In die Zwischenablage kopieren", "directory.federated": "Aus bekanntem Fediverse", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 8a38e7c0ce2022..68de7881495060 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Βρες άλλον διακομιστή", "closed_registrations_modal.preamble": "Το Mastodon είναι αποκεντρωμένο, οπότε ανεξάρτητα από το πού θα δημιουργήσεις τον λογαριασμό σου, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με οποιονδήποτε σε αυτόν τον διακομιστή. Μπορείς ακόμη και να κάνεις τον δικό σου!", "closed_registrations_modal.title": "Εγγραφή στο Mastodon", + "collection.share_modal.share_link_label": "Σύνδεσμος κοινοποίησης", + "collection.share_modal.share_via_post": "Ανάρτηση στο Mastodon", + "collection.share_modal.share_via_system": "Κοινοποίηση σε…", + "collection.share_modal.title": "Κοινοποίηση συλλογής", + "collection.share_modal.title_new": "Μοιραστείτε τη νέα σας συλλογή!", + "collection.share_template_other": "Δείτε αυτή την ωραία συλλογή: {link}", + "collection.share_template_own": "Δείτε τη νέα μου συλλογή: {link}", "collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}", "collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε", "collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή", @@ -448,6 +455,7 @@ "conversation.open": "Προβολή συνομιλίας", "conversation.with": "Με {names}", "copy_icon_button.copied": "Αντιγράφηκε στο πρόχειρο", + "copy_icon_button.copy_this_text": "Αντιγραφή συνδέσμου στο πρόχειρο", "copypaste.copied": "Αντιγράφηκε", "copypaste.copy_to_clipboard": "Αντιγραφή στο πρόχειρο", "directory.federated": "Από το γνωστό fediverse", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index d5b1e24ef41ede..cac4cf72e095f5 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Find another server", "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", + "collection.share_modal.share_link_label": "Invite share link", + "collection.share_modal.share_via_post": "Post on Mastodon", + "collection.share_modal.share_via_system": "Share to…", + "collection.share_modal.title": "Share collection", + "collection.share_modal.title_new": "Share your new collection!", + "collection.share_template_other": "Check out this cool collection: {link}", + "collection.share_template_own": "Check out my new collection: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "View conversation", "conversation.with": "With {names}", "copy_icon_button.copied": "Copied to clipboard", + "copy_icon_button.copy_this_text": "Copy link to clipboard", "copypaste.copied": "Copied", "copypaste.copy_to_clipboard": "Copy to clipboard", "directory.federated": "From known fediverse", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 53676366cc77da..3e81941bc83697 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Buscar otro servidor", "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", + "collection.share_modal.share_link_label": "Enlace para compartir", + "collection.share_modal.share_via_post": "Enviar a Mastodon", + "collection.share_modal.share_via_system": "Compartir en…", + "collection.share_modal.title": "Compartir colección", + "collection.share_modal.title_new": "¡Compartí tu nueva colección!", + "collection.share_template_other": "¡Mirá qué copada está esta colección! {link}", + "collection.share_template_own": "Mirá mi nueva colección: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Ver conversación", "conversation.with": "Con {names}", "copy_icon_button.copied": "Copiado en el portapapeles", + "copy_icon_button.copy_this_text": "Copiar enlace al portapapeles", "copypaste.copied": "Copiado", "copypaste.copy_to_clipboard": "Copiar al portapapeles", "directory.federated": "Desde fediverso conocido", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 9983265adf5d95..51c98822612207 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Buscar otro servidor", "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", + "collection.share_modal.share_link_label": "Enlace para compartir", + "collection.share_modal.share_via_post": "Publicar en Mastodon", + "collection.share_modal.share_via_system": "Compartir con…", + "collection.share_modal.title": "Compartir la colección", + "collection.share_modal.title_new": "¡Comparte tu nueva colección!", + "collection.share_template_other": "Echa un vistazo a esta increíble colección: {link}", + "collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Ver conversación", "conversation.with": "Con {names}", "copy_icon_button.copied": "Copiado al portapapeles", + "copy_icon_button.copy_this_text": "Copiar enlace al portapapeles", "copypaste.copied": "Copiado", "copypaste.copy_to_clipboard": "Copiar al portapapeles", "directory.federated": "Desde el fediverso conocido", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 22102bae45c882..62abafc4bc99d5 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Buscar otro servidor", "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", + "collection.share_modal.share_link_label": "Enlace para compartir", + "collection.share_modal.share_via_post": "Publicar en Mastodon", + "collection.share_modal.share_via_system": "Compartir con…", + "collection.share_modal.title": "Compartir la colección", + "collection.share_modal.title_new": "¡Comparte tu nueva colección!", + "collection.share_template_other": "Echa un vistazo a esta fantástica colección: {link}", + "collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}", "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", @@ -307,7 +314,7 @@ "collections.no_collections_yet": "Aún no hay colecciones.", "collections.old_last_post_note": "Última publicación hace más de una semana", "collections.remove_account": "Borrar esta cuenta", - "collections.report_collection": "Reportar esta colección", + "collections.report_collection": "Informar de esta colección", "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.sensitive": "Sensible", @@ -448,6 +455,7 @@ "conversation.open": "Ver conversación", "conversation.with": "Con {names}", "copy_icon_button.copied": "Copiado al portapapeles", + "copy_icon_button.copy_this_text": "Copiar enlace al portapapeles", "copypaste.copied": "Copiado", "copypaste.copy_to_clipboard": "Copiar al portapapeles", "directory.federated": "Desde el fediverso conocido", @@ -977,7 +985,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicación", "report.close": "Hecho", - "report.collection_comment": "¿Por qué quieres reportar esta colección?", + "report.collection_comment": "¿Por qué quieres informar de esta colección?", "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?", @@ -999,7 +1007,7 @@ "report.rules.title": "¿Qué normas se están violando?", "report.statuses.subtitle": "Selecciona todos los que correspondan", "report.statuses.title": "¿Hay alguna publicación que respalde este informe?", - "report.submission_error": "No se pudo enviar el reporte", + "report.submission_error": "No se pudo enviar el informe", "report.submission_error_details": "Comprueba tu conexión de red e inténtalo más tarde.", "report.submit": "Enviar", "report.target": "Reportando {target}", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 9467d2802adfda..417947a25b22f8 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Finn ein annan ambætara", "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", + "collection.share_modal.share_link_label": "Innbjóðingarleinki at deila", + "collection.share_modal.share_via_post": "Posta á Mastodon", + "collection.share_modal.share_via_system": "Deil til…", + "collection.share_modal.title": "Deil savn", + "collection.share_modal.title_new": "Deil títt nýggja savn!", + "collection.share_template_other": "Hygg at hesum kula savninum: {link}", + "collection.share_template_own": "Hygg at mínum nýggja savni: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Vís samrøðu", "conversation.with": "Við {names}", "copy_icon_button.copied": "Avritað til setiborðið", + "copy_icon_button.copy_this_text": "Avrita leinki til setiborðið", "copypaste.copied": "Avritað", "copypaste.copy_to_clipboard": "Avrita til setiborðið", "directory.federated": "Frá tí kenda fediversinum", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 4d3421be058a4b..54036c953fc8b2 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -48,6 +48,7 @@ "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernière publication {date}", "account.featured_tags.last_status_never": "Aucune publication", + "account.field_overflow": "Voir tout", "account.filters.all": "Toutes les activités", "account.filters.boosts_toggle": "Afficher les partages", "account.filters.posts_boosts": "Messages et partages", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index dcbfb5af3484a2..18daca78930efa 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -48,6 +48,7 @@ "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Dernier message le {date}", "account.featured_tags.last_status_never": "Aucun message", + "account.field_overflow": "Voir tout", "account.filters.all": "Toutes les activités", "account.filters.boosts_toggle": "Afficher les partages", "account.filters.posts_boosts": "Messages et partages", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 5bc25e66ccb536..5ae8d75fce4150 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Faigh freastalaí eile", "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", + "collection.share_modal.share_link_label": "Nasc roinnte cuireadh", + "collection.share_modal.share_via_post": "Postáil ar Mastodon", + "collection.share_modal.share_via_system": "Comhroinn le…", + "collection.share_modal.title": "Comhroinn bailiúchán", + "collection.share_modal.title_new": "Roinn do bhailiúchán nua!", + "collection.share_template_other": "Féach ar an mbailiúchán fionnuar seo: {link}", + "collection.share_template_own": "Féach ar mo bhailiúchán nua: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Féach ar comhrá", "conversation.with": "Le {names}", "copy_icon_button.copied": "Cóipeáladh chuig an ngearrthaisce", + "copy_icon_button.copy_this_text": "Cóipeáil nasc chuig an ghearrthaisce", "copypaste.copied": "Cóipeáilte", "copypaste.copy_to_clipboard": "Cóipeáil chuig an ngearrthaisce", "directory.federated": "Ó chomhchruinne aitheanta", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 3bbde7a51bd670..ee31fcac5d207e 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Finna annan netþjón", "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", + "collection.share_modal.share_link_label": "Tengill til að deila", + "collection.share_modal.share_via_post": "Birta á Mastodon", + "collection.share_modal.share_via_system": "Deila með…", + "collection.share_modal.title": "Deila safni", + "collection.share_modal.title_new": "Deildu nýja safninu þínu!", + "collection.share_template_other": "Kíktu á þetta áhugaverða safn: {link}", + "collection.share_template_own": "Kíktu á nýja safnið mitt: {link}", "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", @@ -448,6 +455,7 @@ "conversation.open": "Skoða samtal", "conversation.with": "Við {names}", "copy_icon_button.copied": "Afritað á klippispjald", + "copy_icon_button.copy_this_text": "Afrita tengil á klippispjald", "copypaste.copied": "Afritað", "copypaste.copy_to_clipboard": "Afrita á klippispjald", "directory.federated": "Frá samtengdum vefþjónum", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 8802c730b8eab9..97461539613ac5 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "Seguito da {name1} e {name2}", "account.featured": "In primo piano", "account.featured.accounts": "Profili", + "account.featured.collections": "Collezioni", "account.featured.hashtags": "Hashtag", "account.featured_tags.last_status_at": "Ultimo post il {date}", "account.featured_tags.last_status_never": "Nessun post", + "account.field_overflow": "Mostra il contenuto completo", "account.filters.all": "Tutte le attività", "account.filters.boosts_toggle": "Mostra le condivisioni", "account.filters.posts_boosts": "Post e condivisioni", @@ -269,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Trova un altro server", "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", + "collection.share_modal.share_link_label": "Condividi il collegamento d'invito", + "collection.share_modal.share_via_post": "Pubblica su Mastodon", + "collection.share_modal.share_via_system": "Condividi con…", + "collection.share_modal.title": "Condividi la collezione", + "collection.share_modal.title_new": "Condividi la tua nuova collezione!", + "collection.share_template_other": "Dai un'occhiata a questa fantastica collezione: {link}", + "collection.share_template_own": "Dai un'occhiata alla mia collezione: {link}", "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", @@ -446,6 +455,7 @@ "conversation.open": "Visualizza conversazione", "conversation.with": "Con {names}", "copy_icon_button.copied": "Copiato negli appunti", + "copy_icon_button.copy_this_text": "Copia il link negli appunti", "copypaste.copied": "Copiato", "copypaste.copy_to_clipboard": "Copia negli Appunti", "directory.federated": "Da un fediverse noto", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 084cff2c85c2c6..3bdec56300f1c8 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -48,6 +48,7 @@ "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Laatste bericht op {date}", "account.featured_tags.last_status_never": "Geen berichten", + "account.field_overflow": "Volledige inhoud tonen", "account.filters.all": "Alle activiteit", "account.filters.boosts_toggle": "Boosts tonen", "account.filters.posts_boosts": "Berichten en boosts", @@ -270,6 +271,12 @@ "closed_registrations_modal.find_another_server": "Een andere server zoeken", "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", + "collection.share_modal.share_via_post": "Bericht op Mastodon", + "collection.share_modal.share_via_system": "Delen met…", + "collection.share_modal.title": "Verzameling delen", + "collection.share_modal.title_new": "Deel je nieuwe verzameling!", + "collection.share_template_other": "Bekijk deze coole verzameling: {link}", + "collection.share_template_own": "Bekijk mijn nieuwe verzameling: {link}", "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", @@ -447,6 +454,7 @@ "conversation.open": "Gesprek tonen", "conversation.with": "Met {names}", "copy_icon_button.copied": "Gekopieerd naar klembord", + "copy_icon_button.copy_this_text": "Link kopiëren naar klembord", "copypaste.copied": "Gekopieerd", "copypaste.copy_to_clipboard": "Naar klembord kopiëren", "directory.federated": "Fediverse (wat bekend is)", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 74147e6c59290c..56f3149fdb0d66 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -48,6 +48,7 @@ "account.featured.hashtags": "Etiquetas", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", + "account.field_overflow": "Mostrar todo o conteúdo", "account.filters.all": "Toda a atividade", "account.filters.boosts_toggle": "Mostrar partilhas", "account.filters.posts_boosts": "Publicações e partilhas", @@ -162,8 +163,10 @@ "account_edit.name_modal.edit_title": "Editar o nome a mostrar", "account_edit.save": "Guardar", "account_edit_tags.column_title": "Editar etiquetas em destaque", + "account_edit_tags.help_text": "As etiquetas destacadas ajudam os utilizadores a descobrir e interagir com o seu perfil. Aparecem como filtros na vista de atividade da sua página de perfil.", "account_edit_tags.search_placeholder": "Insira uma etiqueta…", "account_edit_tags.suggestions": "Sugestões:", + "account_edit_tags.tag_status_count": "{count, plural, one {# publicação} other {# publicações}}", "account_note.placeholder": "Clicar para adicionar nota", "admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição", "admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição", @@ -267,6 +270,7 @@ "closed_registrations_modal.preamble": "O Mastodon é descentralizado, por isso não importa onde a tua conta é criada, pois continuarás a poder acompanhar e interagir com qualquer um neste servidor. Podes até alojar o teu próprio servidor!", "closed_registrations_modal.title": "Criar uma conta no Mastodon", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", + "collections.accounts.empty_description": "Adicione até {count} contas que segue", "collections.accounts.empty_title": "Esta coleção está vazia", "collections.collection_description": "Descrição", "collections.collection_name": "Nome", @@ -287,15 +291,25 @@ "collections.detail.share": "Partilhar esta coleção", "collections.edit_details": "Editar detalhes", "collections.error_loading_collections": "Ocorreu um erro ao tentar carregar as suas coleções.", + "collections.hints.accounts_counter": "{count} / {max} contas", "collections.hints.add_more_accounts": "Adicione pelo menos {count, plural, one {# conta} other {# contas}} para continuar", + "collections.hints.can_not_remove_more_accounts": "As coleções devem conter pelo menos {count, plural, one {# conta} other {# contas}}. Não é possível remover mais contas.", "collections.last_updated_at": "Última atualização: {date}", "collections.manage_accounts": "Gerir contas", "collections.mark_as_sensitive": "Marcar como sensível", "collections.mark_as_sensitive_hint": "Oculta a descrição e as contas da coleção por trás de um aviso de conteúdo. O nome da coleção ainda estará visível.", + "collections.name_length_hint": "Limite de 40 carateres", "collections.new_collection": "Nova coleção", "collections.no_collections_yet": "Ainda não existem coleções.", + "collections.old_last_post_note": "Última publicação há mais de uma semana", + "collections.remove_account": "Remover esta conta", + "collections.report_collection": "Denunciar esta coleção", + "collections.search_accounts_label": "Procurar contas para adicionar…", + "collections.search_accounts_max_reached": "Já adicionou o máximo de contas", + "collections.sensitive": "Sensível", "collections.topic_hint": "Adicione uma etiqueta para ajudar outros a entender o tópico principal desta coleção.", "collections.view_collection": "Ver coleções", + "collections.view_other_collections_by_user": "Ver outras coleções deste utilizador", "collections.visibility_public": "Pública", "collections.visibility_public_hint": "Visível nos resultados de pesquisa e outras áreas onde aparecem recomendações.", "collections.visibility_title": "Visibilidade", @@ -384,6 +398,9 @@ "confirmations.discard_draft.post.title": "Descartar o rascunho da publicação?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Tens alterações por guardar na descrição da multimédia ou pré-visualização do conteúdo. Descartar mesmo assim?", + "confirmations.follow_to_collection.confirm": "Seguir e adicionar à coleção", + "confirmations.follow_to_collection.message": "Precisa de seguir {name} para o/a adicionar a uma coleção.", + "confirmations.follow_to_collection.title": "Seguir conta?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", "confirmations.follow_to_list.message": "Tens de seguir {name} para o adicionares a uma lista.", "confirmations.follow_to_list.title": "Seguir utilizador?", @@ -956,6 +973,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicação", "report.close": "Concluído", + "report.collection_comment": "Porque quer denunciar esta coleção?", "report.comment.title": "Há mais alguma coisa que devamos saber?", "report.forward": "Reencaminhar para {target}", "report.forward_hint": "A conta pertence a outro servidor. Enviar uma cópia anónima da denúncia para esse servidor também?", @@ -977,6 +995,8 @@ "report.rules.title": "Que regras estão a ser violadas?", "report.statuses.subtitle": "Seleciona tudo o que se aplicar", "report.statuses.title": "Existe alguma publicação que suporte esta denúncia?", + "report.submission_error": "A denúncia não pôde ser enviada", + "report.submission_error_details": "Por favor, verifique a sua ligação à rede e tente novamente mais tarde.", "report.submit": "Enviar", "report.target": "A denunciar {target}", "report.thanks.take_action": "Aqui estão as suas opções para controlar o que vê no Mastodon:", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 7e3c421ccec9cd..4fd73e3cea5c5f 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -269,6 +269,12 @@ "closed_registrations_modal.find_another_server": "Gjeni shërbyes tjetër", "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", + "collection.share_modal.share_via_post": "Postoje në Mastodon", + "collection.share_modal.share_via_system": "Ndajeni me të tjerë në…", + "collection.share_modal.title": "Ndani koleksionin me të tjerë", + "collection.share_modal.title_new": "Ndani me të tjerë koleksionin tuaj të ri!", + "collection.share_template_other": "Shihni këtë koleksion të hijshëm: {link}", + "collection.share_template_own": "Shihni koleksionin tim të ri: {link}", "collections.account_count": "{count, plural, one {# llogari} other {# llogari}}", "collections.accounts.empty_title": "Ky koleksion është i zbrazët", "collections.collection_description": "Përshkrim", @@ -445,6 +451,7 @@ "conversation.open": "Shfaq bisedën", "conversation.with": "Me {names}", "copy_icon_button.copied": "U kopjua në të papastër", + "copy_icon_button.copy_this_text": "Kopjoje lidhjen në të papastër", "copypaste.copied": "U kopjua", "copypaste.copy_to_clipboard": "Kopjoje në të papastër", "directory.federated": "Nga fedivers i njohur", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index ec7906da578a7e..270f130cade5b2 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "{name1} ve {name2} tarafından takip ediliyor", "account.featured": "Öne çıkan", "account.featured.accounts": "Profiller", + "account.featured.collections": "Koleksiyonlar", "account.featured.hashtags": "Etiketler", "account.featured_tags.last_status_at": "Son gönderinin tarihi {date}", "account.featured_tags.last_status_never": "Gönderi yok", + "account.field_overflow": "Tüm içeriği göster", "account.filters.all": "Tüm aktiviteler", "account.filters.boosts_toggle": "Yeniden paylaşımları göster", "account.filters.posts_boosts": "Gönderiler ve yeniden paylaşımlar", @@ -269,6 +271,13 @@ "closed_registrations_modal.find_another_server": "Başka sunucu bul", "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", + "collection.share_modal.share_link_label": "Davet bağlantısı", + "collection.share_modal.share_via_post": "Mastodon'da paylaş", + "collection.share_modal.share_via_system": "Paylaş…", + "collection.share_modal.title": "Koleksiyonu paylaş", + "collection.share_modal.title_new": "Yeni koleksiyonunuzu paylaşın!", + "collection.share_template_other": "Bu harika koleksiyona göz atın: {link}", + "collection.share_template_own": "Yeni koleksiyonuma göz atın: {link}", "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ş", @@ -446,6 +455,7 @@ "conversation.open": "Sohbeti görüntüle", "conversation.with": "{names} ile", "copy_icon_button.copied": "Panoya kopyalandı", + "copy_icon_button.copy_this_text": "Bağlantıyı panoya kopyala", "copypaste.copied": "Kopyalandı", "copypaste.copy_to_clipboard": "Panoya kopyala", "directory.federated": "Bilinen fediverse'lerden", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 3d00966ea49beb..6295ca9b50ec0a 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -48,6 +48,7 @@ "account.featured.hashtags": "话题", "account.featured_tags.last_status_at": "上次发言于 {date}", "account.featured_tags.last_status_never": "暂无嘟文", + "account.field_overflow": "显示完整内容", "account.filters.all": "所有活动", "account.filters.boosts_toggle": "显示转嘟", "account.filters.posts_boosts": "嘟文与转嘟", @@ -270,6 +271,13 @@ "closed_registrations_modal.find_another_server": "查找其他服务器", "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以无论在哪个实例创建账号,都可以关注本服务器上的账号并与之交流。 或者你还可以自己搭建实例!", "closed_registrations_modal.title": "注册 Mastodon 账号", + "collection.share_modal.share_link_label": "分享链接", + "collection.share_modal.share_via_post": "在 Mastodon 上发布", + "collection.share_modal.share_via_system": "分享到…", + "collection.share_modal.title": "分享收藏列表", + "collection.share_modal.title_new": "分享你的新收藏列表!", + "collection.share_template_other": "发现了个收藏列表,来看看:{link}", + "collection.share_template_own": "我的新收藏列表,来看看:{link}", "collections.account_count": "{count, plural, other {# 个账号}}", "collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个", "collections.accounts.empty_title": "收藏列表为空", @@ -447,6 +455,7 @@ "conversation.open": "查看对话", "conversation.with": "与 {names}", "copy_icon_button.copied": "已复制到剪贴板", + "copy_icon_button.copy_this_text": "复制链接到剪贴板", "copypaste.copied": "已复制", "copypaste.copy_to_clipboard": "复制到剪贴板", "directory.federated": "来自已知联邦宇宙", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 186dbb055010e5..31443295bb4cd7 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -271,6 +271,13 @@ "closed_registrations_modal.find_another_server": "尋找另一個伺服器", "closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架設自己的伺服器!", "closed_registrations_modal.title": "註冊 Mastodon", + "collection.share_modal.share_link_label": "分享連結", + "collection.share_modal.share_via_post": "於 Mastodon 上發嘟文", + "collection.share_modal.share_via_system": "分享至...", + "collection.share_modal.title": "分享收藏名單", + "collection.share_modal.title_new": "分享您的新收藏名單!", + "collection.share_template_other": "來看看這個酷酷的收藏名單:{link}", + "collection.share_template_own": "來看看我的新收藏名單:{link}", "collections.account_count": "{count, plural, other {# 個帳號}}", "collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號", "collections.accounts.empty_title": "此收藏名單是空的", @@ -448,6 +455,7 @@ "conversation.open": "檢視對話", "conversation.with": "與 {names}", "copy_icon_button.copied": "已複製到剪貼簿", + "copy_icon_button.copy_this_text": "複製連結至剪貼簿", "copypaste.copied": "已複製", "copypaste.copy_to_clipboard": "複製到剪貼簿", "directory.federated": "來自已知聯邦宇宙", diff --git a/config/locales/activerecord.fr.yml b/config/locales/activerecord.fr.yml index 25bd8f7fae5475..bc616b22abb1ba 100644 --- a/config/locales/activerecord.fr.yml +++ b/config/locales/activerecord.fr.yml @@ -3,7 +3,7 @@ fr: activerecord: attributes: poll: - expires_at: Date d'expiration + expires_at: Date d'échéance options: Choix user: agreement: Contrat de service diff --git a/config/locales/it.yml b/config/locales/it.yml index a9e489f1a598e7..09dfb6a3493397 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -267,6 +267,7 @@ it: demote_user_html: "%{name} ha retrocesso l'utente %{target}" destroy_announcement_html: "%{name} ha eliminato l'annuncio %{target}" destroy_canonical_email_block_html: "%{name} ha sbloccato l'e-mail con l'hash %{target}" + destroy_collection_html: "%{name} ha rimosso la collezione di %{target}" destroy_custom_emoji_html: "%{name} ha eliminato l'emoji %{target}" destroy_domain_allow_html: "%{name} non ha consentito la federazione con il dominio %{target}" destroy_domain_block_html: "%{name} ha sbloccato il dominio %{target}" @@ -306,6 +307,7 @@ it: unsilence_account_html: "%{name} ha riattivato l'account di %{target}" unsuspend_account_html: "%{name} ha annullato la sospensione dell'account di %{target}" update_announcement_html: "%{name} ha aggiornato l'annuncio %{target}" + update_collection_html: "%{name} ha aggiornato la collezione di %{target}" update_custom_emoji_html: "%{name} ha aggiornato emoji %{target}" update_domain_block_html: "%{name} ha aggiornato il blocco dominio per %{target}" update_ip_block_html: "%{name} ha cambiato la regola per l'IP %{target}" diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 6d7f710f0f11ae..b85b5fbd1829e8 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -267,6 +267,7 @@ pt-PT: demote_user_html: "%{name} despromoveu o utilizador %{target}" destroy_announcement_html: "%{name} eliminou a mensagem de manutenção %{target}" destroy_canonical_email_block_html: "%{name} desbloqueou o e-mail com a hash %{target}" + destroy_collection_html: "%{name} removeu a coleção de %{target}" destroy_custom_emoji_html: "%{name} eliminou o emoji %{target}" destroy_domain_allow_html: "%{name} bloqueou a federação com o domínio %{target}" destroy_domain_block_html: "%{name} desbloqueou o domínio %{target}" @@ -306,6 +307,7 @@ pt-PT: unsilence_account_html: "%{name} deixou de limitar a conta de %{target}" unsuspend_account_html: "%{name} desativou a suspensão de %{target}" update_announcement_html: "%{name} atualizou a mensagem de manutenção %{target}" + update_collection_html: "%{name} atualizou a coleção de %{target}" update_custom_emoji_html: "%{name} atualizou o emoji %{target}" update_domain_block_html: "%{name} atualizou o bloqueio de domínio para %{target}" update_ip_block_html: "%{name} alterou regra para o IP %{target}" @@ -686,6 +688,7 @@ pt-PT: cancel: Cancelar category: Categoria category_description_html: A razão pela qual esta conta e/ou conteúdo foi denunciado será citada na comunicação com a conta denunciada + collections: Coleções (%{count}) comment: none: Nenhum comment_description_html: 'Para fornecer mais informações, %{name} escreveu:' @@ -721,6 +724,7 @@ pt-PT: resolved_msg: Denúncia resolvida com sucesso! skip_to_actions: Passar para as ações status: Estado + statuses: Publicações (%{count}) statuses_description_html: O conteúdo ofensivo será citado na comunicação com a conta denunciada summary: action_preambles: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 44ecabdc0f9530..4744417181741f 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -267,6 +267,7 @@ tr: demote_user_html: "%{name}, %{target} kullanıcısını düşürdü" destroy_announcement_html: "%{name}, %{target} duyurusunu sildi" destroy_canonical_email_block_html: "%{name}, %{target} karmasıyla e-posta engelini kaldırdı" + destroy_collection_html: "%{name}, %{target} kullanıcısının koleksiyonunu kaldırdı" destroy_custom_emoji_html: "%{name}, %{target} ifadesini sildi" destroy_domain_allow_html: "%{name}, %{target} alan adıyla birlik iznini kaldırdı" destroy_domain_block_html: "%{name}, %{target} alan adı engelini kaldırdı" @@ -306,6 +307,7 @@ tr: unsilence_account_html: "%{name}, %{target} kullanıcısının hesabının sessizliğini kaldırdı" unsuspend_account_html: "%{name}, %{target} kullanıcısının hesabının askı durumunu kaldırdı" update_announcement_html: "%{name}, %{target} duyurusunu güncelledi" + update_collection_html: "%{name}, %{target} kullanıcısının koleksiyonunu güncelledi" update_custom_emoji_html: "%{name}, %{target} emojisini güncelledi" update_domain_block_html: "%{name}, %{target} alan adının engelini güncelledi" update_ip_block_html: "%{name}, %{target} IP adresi için kuralı güncelledi" From 3b7c33e7632c0360d8489f35633f7741e682ffa9 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 27 Feb 2026 10:34:12 +0100 Subject: [PATCH 22/89] Add `exclude_direct` flag to `/api/v1/accounts/:id/statuses` to exclude direct messages (#37763) --- app/lib/account_statuses_filter.rb | 16 ++++++-- lib/mastodon/version.rb | 2 +- spec/lib/account_statuses_filter_spec.rb | 51 ++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/app/lib/account_statuses_filter.rb b/app/lib/account_statuses_filter.rb index 92196b6da408ec..5fa9a2ca419a22 100644 --- a/app/lib/account_statuses_filter.rb +++ b/app/lib/account_statuses_filter.rb @@ -37,7 +37,7 @@ def initial_scope if anonymous? account.statuses.distributable_visibility elsif author? - account.statuses.all # NOTE: #merge! does not work without the #all + exclude_direct? ? account.statuses.where(visibility: %i(public unlisted private)) : account.statuses.all # NOTE: #merge! does not work without the #all elsif blocked? Status.none else @@ -46,9 +46,15 @@ def initial_scope end def filtered_scope - scope = account.statuses.left_outer_joins(:mentions) + scope = account.statuses + + if exclude_direct? + scope = scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)) + else + scope = account.statuses.left_outer_joins(:mentions) + scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id])) + end - scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id])) scope.merge!(filtered_reblogs_scope) if reblogs_may_occur? scope @@ -123,6 +129,10 @@ def only_media? truthy_param?(:only_media) end + def exclude_direct? + truthy_param?(:exclude_direct) + end + def exclude_replies? truthy_param?(:exclude_replies) end diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index e83fa6b998f996..76c849e8f6da59 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -45,7 +45,7 @@ def gem_version def api_versions { - mastodon: 7, + mastodon: 8, } end diff --git a/spec/lib/account_statuses_filter_spec.rb b/spec/lib/account_statuses_filter_spec.rb index 351d3dae574052..ecb75d55d0dde6 100644 --- a/spec/lib/account_statuses_filter_spec.rb +++ b/spec/lib/account_statuses_filter_spec.rb @@ -64,6 +64,9 @@ def status_with_media_attachment!(visibility) expect(results_for(exclude_reblogs: true)) .to all(satisfy { |status| !status.reblog? }) + + expect(results_for(exclude_direct: true)) + .to all(satisfy { |status| !status.direct_visibility? }) end def results_for(params) @@ -77,6 +80,18 @@ def results_for(params) let(:current_account) { nil } let(:direct_status) { nil } + context 'when rejecting direct messages' do + let(:params) { { exclude_direct: true } } + + it 'returns only public statuses, public replies, and public reblogs' do + expect(results_unique_visibilities).to match_array %w(unlisted public) + + expect(results_in_reply_to_ids).to_not be_empty + + expect(results_reblog_of_ids).to_not be_empty + end + end + it 'returns only public statuses, public replies, and public reblogs' do expect(results_unique_visibilities).to match_array %w(unlisted public) @@ -95,6 +110,14 @@ def results_for(params) account.block!(current_account) end + context 'when rejecting direct messages' do + let(:params) { { exclude_direct: true } } + + it 'returns nothing' do + expect(subject.to_a).to be_empty + end + end + it 'returns nothing' do expect(subject.to_a).to be_empty end @@ -121,6 +144,18 @@ def results_for(params) current_account.follow!(account) end + context 'when rejecting direct messages' do + let(:params) { { exclude_direct: true } } + + it 'returns private statuses, replies, and reblogs' do + expect(results_unique_visibilities).to match_array %w(private unlisted public) + + expect(results_in_reply_to_ids).to_not be_empty + + expect(results_reblog_of_ids).to_not be_empty + end + end + it 'returns private statuses, replies, and reblogs' do expect(results_unique_visibilities).to match_array %w(private unlisted public) @@ -135,6 +170,8 @@ def results_for(params) it 'returns the direct status' do expect(results_ids).to include(direct_status.id) end + + it_behaves_like 'filter params' end it_behaves_like 'filter params' @@ -143,6 +180,18 @@ def results_for(params) context 'when accessed by a non-follower' do let(:current_account) { Fabricate(:account) } + context 'when rejecting direct messages' do + let(:params) { { exclude_direct: true } } + + it 'returns private statuses, replies, and reblogs' do + expect(results_unique_visibilities).to match_array %w(unlisted public) + + expect(results_in_reply_to_ids).to_not be_empty + + expect(results_reblog_of_ids).to_not be_empty + end + end + it 'returns only public statuses, replies, and reblogs' do expect(results_unique_visibilities).to match_array %w(unlisted public) @@ -157,6 +206,8 @@ def results_for(params) it 'returns the private status' do expect(results_ids).to include(private_status.id) end + + it_behaves_like 'filter params' end context 'when blocking a reblogged account' do From 6b88dd492378a6d454365c0f870fe9264a7a499c Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 27 Feb 2026 12:00:21 +0100 Subject: [PATCH 23/89] Issue `FeatureAuthorization`s (#38004) --- .../feature_authorizations_controller.rb | 29 +++++++++++ app/models/collection.rb | 1 + app/models/collection_item.rb | 1 + .../feature_authorization_serializer.rb | 23 +++++++++ .../activitypub/featured_item_serializer.rb | 13 ++++- config/routes.rb | 1 + .../feature_authorizations_spec.rb | 49 +++++++++++++++++++ .../feature_authorization_serializer_spec.rb | 24 +++++++++ .../featured_collection_serializer_spec.rb | 4 ++ .../featured_item_serializer_spec.rb | 35 ++++++++++--- 10 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 app/controllers/activitypub/feature_authorizations_controller.rb create mode 100644 app/serializers/activitypub/feature_authorization_serializer.rb create mode 100644 spec/requests/activitypub/feature_authorizations_spec.rb create mode 100644 spec/serializers/activitypub/feature_authorization_serializer_spec.rb diff --git a/app/controllers/activitypub/feature_authorizations_controller.rb b/app/controllers/activitypub/feature_authorizations_controller.rb new file mode 100644 index 00000000000000..ef9f458bf78b9d --- /dev/null +++ b/app/controllers/activitypub/feature_authorizations_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class ActivityPub::FeatureAuthorizationsController < ActivityPub::BaseController + include Authorization + + vary_by -> { 'Signature' if authorized_fetch_mode? } + + before_action :require_account_signature!, if: :authorized_fetch_mode? + before_action :set_collection_item + + def show + expires_in 30.seconds, public: true if public_fetch_mode? + render json: @collection_item, serializer: ActivityPub::FeatureAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' + end + + private + + def pundit_user + signed_request_account + end + + def set_collection_item + @collection_item = @account.collection_items.accepted.find(params[:id]) + + authorize @collection_item.collection, :show? + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + not_found + end +end diff --git a/app/models/collection.rb b/app/models/collection.rb index c018dd9fa4272d..3b8ee82a3cfe10 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -46,6 +46,7 @@ class Collection < ApplicationRecord scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) } scope :with_tag, -> { includes(:tag) } scope :discoverable, -> { where(discoverable: true) } + scope :local, -> { where(local: true) } def remote? !local? diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index 78b5f6a6e26c01..c5c9ebc16e91fd 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -40,6 +40,7 @@ class CollectionItem < ApplicationRecord scope :ordered, -> { order(position: :asc) } scope :with_accounts, -> { includes(account: [:account_stat, :user]) } scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) } + scope :local, -> { joins(:collection).merge(Collection.local) } def local_item_with_remote_account? local? && account&.remote? diff --git a/app/serializers/activitypub/feature_authorization_serializer.rb b/app/serializers/activitypub/feature_authorization_serializer.rb new file mode 100644 index 00000000000000..4181025802d07e --- /dev/null +++ b/app/serializers/activitypub/feature_authorization_serializer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ActivityPub::FeatureAuthorizationSerializer < ActivityPub::Serializer + include RoutingHelper + + attributes :id, :type, :interacting_object, :interaction_target + + def id + ap_account_feature_authorization_url(object.account_id, object) + end + + def type + 'FeatureAuthorization' + end + + def interaction_target + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def interacting_object + ActivityPub::TagManager.instance.uri_for(object.collection) + end +end diff --git a/app/serializers/activitypub/featured_item_serializer.rb b/app/serializers/activitypub/featured_item_serializer.rb index a524d6c25ff6ca..56c0b4390fac0a 100644 --- a/app/serializers/activitypub/featured_item_serializer.rb +++ b/app/serializers/activitypub/featured_item_serializer.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer - attributes :id, :type, :featured_object, :featured_object_type + include RoutingHelper + + attributes :id, :type, :featured_object, :featured_object_type, + :feature_authorization def id ActivityPub::TagManager.instance.uri_for(object) @@ -18,4 +21,12 @@ def featured_object def featured_object_type object.account.actor_type || 'Person' end + + def feature_authorization + if object.account.local? + ap_account_feature_authorization_url(object.account_id, object) + else + object.approval_uri + end + end end diff --git a/config/routes.rb b/config/routes.rb index 3ddd3494d31bb8..57ca5923c620c2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,6 +125,7 @@ def redirect_with_vary(path) scope path: 'ap', as: 'ap' do resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do resources :collection_items, only: [:show] + resources :feature_authorizations, only: [:show], module: :activitypub resources :featured_collections, only: [:index], module: :activitypub resources :statuses, only: [:show] do diff --git a/spec/requests/activitypub/feature_authorizations_spec.rb b/spec/requests/activitypub/feature_authorizations_spec.rb new file mode 100644 index 00000000000000..ee4cc0579a0004 --- /dev/null +++ b/spec/requests/activitypub/feature_authorizations_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'ActivityPub FeatureAuthorization endpoint' do + describe 'GET /ap/accounts/:account_id/feature_authorizations/:collection_item_id' do + let(:account) { Fabricate(:account) } + let(:collection) { Fabricate(:collection) } + let(:collection_item) { Fabricate(:collection_item, collection:, account:, state:) } + + context 'with an accepted collection item' do + let(:state) { :accepted } + + it 'returns http success and activity json' do + get ap_account_feature_authorization_path(account.id, collection_item) + + expect(response) + .to have_http_status(200) + expect(response.media_type) + .to eq 'application/activity+json' + + expect(response.parsed_body) + .to include(type: 'FeatureAuthorization') + end + end + + shared_examples 'not found' do + it 'returns http not found' do + get ap_account_feature_authorization_path(collection.account_id, collection_item) + + expect(response) + .to have_http_status(404) + end + end + + context 'with a revoked collection item' do + let(:state) { :revoked } + + it_behaves_like 'not found' + end + + context 'with a collection item featuring a remote account' do + let(:account) { Fabricate(:remote_account) } + let(:state) { :accepted } + + it_behaves_like 'not found' + end + end +end diff --git a/spec/serializers/activitypub/feature_authorization_serializer_spec.rb b/spec/serializers/activitypub/feature_authorization_serializer_spec.rb new file mode 100644 index 00000000000000..30fd0e46408e6f --- /dev/null +++ b/spec/serializers/activitypub/feature_authorization_serializer_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::FeatureAuthorizationSerializer do + include RoutingHelper + + subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) } + + describe 'serializing an object' do + let(:collection_item) { Fabricate(:collection_item) } + let(:tag_manager) { ActivityPub::TagManager.instance } + + it 'returns the expected json structure' do + expect(subject) + .to include( + 'type' => 'FeatureAuthorization', + 'id' => ap_account_feature_authorization_url(collection_item.account_id, collection_item), + 'interactionTarget' => tag_manager.uri_for(collection_item.account), + 'interactingObject' => tag_manager.uri_for(collection_item.collection) + ) + end + end +end diff --git a/spec/serializers/activitypub/featured_collection_serializer_spec.rb b/spec/serializers/activitypub/featured_collection_serializer_spec.rb index 24dd0654809211..c0ae43abb923cd 100644 --- a/spec/serializers/activitypub/featured_collection_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_collection_serializer_spec.rb @@ -3,6 +3,8 @@ require 'rails_helper' RSpec.describe ActivityPub::FeaturedCollectionSerializer do + include RoutingHelper + subject { serialized_record_json(collection, described_class, adapter: ActivityPub::Adapter) } let(:collection) do @@ -35,12 +37,14 @@ 'type' => 'FeaturedItem', 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.first.account), 'featuredObjectType' => 'Person', + 'featureAuthorization' => ap_account_feature_authorization_url(collection_items.first.account_id, collection_items.first), }, { 'id' => ActivityPub::TagManager.instance.uri_for(collection_items.last), 'type' => 'FeaturedItem', 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.last.account), 'featuredObjectType' => 'Person', + 'featureAuthorization' => ap_account_feature_authorization_url(collection_items.last.account_id, collection_items.last), }, ], 'published' => match_api_datetime_format, diff --git a/spec/serializers/activitypub/featured_item_serializer_spec.rb b/spec/serializers/activitypub/featured_item_serializer_spec.rb index f17faf24100da0..7aca086192a295 100644 --- a/spec/serializers/activitypub/featured_item_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_item_serializer_spec.rb @@ -3,16 +3,37 @@ require 'rails_helper' RSpec.describe ActivityPub::FeaturedItemSerializer do + include RoutingHelper + subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) } let(:collection_item) { Fabricate(:collection_item) } - it 'serializes to the expected structure' do - expect(subject).to include({ - 'type' => 'FeaturedItem', - 'id' => ActivityPub::TagManager.instance.uri_for(collection_item), - 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account), - 'featuredObjectType' => 'Person', - }) + context 'when a local account is featured' do + it 'serializes to the expected structure' do + expect(subject).to include({ + 'type' => 'FeaturedItem', + 'id' => ActivityPub::TagManager.instance.uri_for(collection_item), + 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account), + 'featuredObjectType' => 'Person', + 'featureAuthorization' => ap_account_feature_authorization_url(collection_item.account_id, collection_item), + }) + end + end + + context 'when a remote account is featured' do + let(:collection) { Fabricate(:collection) } + let(:account) { Fabricate(:remote_account) } + let(:collection_item) { Fabricate(:collection_item, collection:, account:, approval_uri: 'https://example.com/auth/1') } + + it 'serializes to the expected structure' do + expect(subject).to include({ + 'type' => 'FeaturedItem', + 'id' => ActivityPub::TagManager.instance.uri_for(collection_item), + 'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account), + 'featuredObjectType' => 'Person', + 'featureAuthorization' => 'https://example.com/auth/1', + }) + end end end From d69d7c0507f4e0453e032c897cb3343065798381 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 27 Feb 2026 14:36:19 +0100 Subject: [PATCH 24/89] Profile editing: Tab display controls (#37994) --- .../components/profile_display_modal.tsx | 122 ++++++++++++++++++ .../mastodon/features/account_edit/index.tsx | 19 ++- .../features/account_edit/styles.module.scss | 10 ++ .../features/ui/components/dialog_modal.tsx | 94 ++++++++++++++ .../features/ui/components/modal_root.jsx | 1 + app/javascript/mastodon/locales/en.json | 9 ++ 6 files changed, 253 insertions(+), 2 deletions(-) create mode 100644 app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx create mode 100644 app/javascript/mastodon/features/ui/components/dialog_modal.tsx diff --git a/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx b/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx new file mode 100644 index 00000000000000..edf98e977c6406 --- /dev/null +++ b/app/javascript/mastodon/features/account_edit/components/profile_display_modal.tsx @@ -0,0 +1,122 @@ +import type { ChangeEventHandler, FC } from 'react'; +import { useCallback } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import { Callout } from '@/mastodon/components/callout'; +import { ToggleField } from '@/mastodon/components/form_fields'; +import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; +import { patchProfile } from '@/mastodon/reducers/slices/profile_edit'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; + +import type { DialogModalProps } from '../../ui/components/dialog_modal'; +import { DialogModal } from '../../ui/components/dialog_modal'; +import { messages } from '../index'; +import classes from '../styles.module.scss'; + +export const ProfileDisplayModal: FC = ({ onClose }) => { + const intl = useIntl(); + + const { profile, isPending } = useAppSelector((state) => state.profileEdit); + const serverName = useAppSelector( + (state) => state.meta.get('domain') as string, + ); + + const dispatch = useAppDispatch(); + const handleToggleChange: ChangeEventHandler = useCallback( + (event) => { + const { name, checked } = event.target; + void dispatch(patchProfile({ [name]: checked })); + }, + [dispatch], + ); + + if (!profile) { + return ; + } + + return ( + +
+ + } + hint={ + + } + /> + + + } + hint={ + + } + /> + + + } + hint={ + + } + /> +
+ + + } + icon={false} + > + + +
+ ); +}; diff --git a/app/javascript/mastodon/features/account_edit/index.tsx b/app/javascript/mastodon/features/account_edit/index.tsx index c8bc93f15f4a87..da58088e89998d 100644 --- a/app/javascript/mastodon/features/account_edit/index.tsx +++ b/app/javascript/mastodon/features/account_edit/index.tsx @@ -1,13 +1,14 @@ import { useCallback, useEffect } from 'react'; import type { FC } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import type { ModalType } from '@/mastodon/actions/modal'; import { openModal } from '@/mastodon/actions/modal'; import { Avatar } from '@/mastodon/components/avatar'; +import { Button } from '@/mastodon/components/button'; import { CustomEmojiProvider } from '@/mastodon/components/emoji/context'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; import { useElementHandledLink } from '@/mastodon/components/status/handled_link'; @@ -25,7 +26,7 @@ import { EditButton } from './components/edit_button'; import { AccountEditSection } from './components/section'; import classes from './styles.module.scss'; -const messages = defineMessages({ +export const messages = defineMessages({ columnTitle: { id: 'account_edit.column_title', defaultMessage: 'Edit Profile', @@ -104,6 +105,9 @@ export const AccountEdit: FC = () => { const handleBioEdit = useCallback(() => { handleOpenModal('ACCOUNT_EDIT_BIO'); }, [handleOpenModal]); + const handleProfileDisplayEdit = useCallback(() => { + handleOpenModal('ACCOUNT_EDIT_PROFILE_DISPLAY'); + }, [handleOpenModal]); const history = useHistory(); const handleFeaturedTagsEdit = useCallback(() => { @@ -193,6 +197,17 @@ export const AccountEdit: FC = () => { title={messages.profileTabTitle} description={messages.profileTabSubtitle} showDescription + buttons={ + + } /> diff --git a/app/javascript/mastodon/features/account_edit/styles.module.scss b/app/javascript/mastodon/features/account_edit/styles.module.scss index ee8603cc4f2477..29daddbe3f852e 100644 --- a/app/javascript/mastodon/features/account_edit/styles.module.scss +++ b/app/javascript/mastodon/features/account_edit/styles.module.scss @@ -90,6 +90,16 @@ textarea.inputText { } } +.toggleInputWrapper { + > div { + padding: 12px 0; + + &:not(:first-child) { + border-top: 1px solid var(--color-border-primary); + } + } +} + // Column component .column { diff --git a/app/javascript/mastodon/features/ui/components/dialog_modal.tsx b/app/javascript/mastodon/features/ui/components/dialog_modal.tsx new file mode 100644 index 00000000000000..8c850fa0d36656 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/dialog_modal.tsx @@ -0,0 +1,94 @@ +import type { FC, ReactNode } from 'react'; + +import { FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { Button } from '@/mastodon/components/button'; +import { IconButton } from '@/mastodon/components/icon_button'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; + +export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal'; + +interface DialogModalProps { + className?: string; + title: ReactNode; + onClose: () => void; + description?: ReactNode; + formClassName?: string; + children?: ReactNode; + noCancelButton?: boolean; + onSave?: () => void; + saveLabel?: ReactNode; +} + +export const DialogModal: FC = ({ + className, + title, + onClose, + description, + formClassName, + children, + noCancelButton = false, + onSave, + saveLabel, +}) => { + const intl = useIntl(); + + const showButtons = !noCancelButton || onSave; + + return ( +
+
+ + +

{title}

+
+ +
+ {description && ( +
+ {description} +
+ )} +
+ {children} +
+
+ + {showButtons && ( +
+ {!noCancelButton && ( + + )} + {onSave && ( + + )} +
+ )} +
+ ); +}; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 3e2751c7a34891..6086858e3d1f83 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -97,6 +97,7 @@ export const MODAL_COMPONENTS = { 'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })), 'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })), 'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })), + 'ACCOUNT_EDIT_PROFILE_DISPLAY': () => import('@/mastodon/features/account_edit/components/profile_display_modal').then(module => ({ default: module.ProfileDisplayModal })), }; export default class ModalRoot extends PureComponent { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 32088474eb837d..03b7a5d2c49791 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Featured hashtags", "account_edit.name_modal.add_title": "Add display name", "account_edit.name_modal.edit_title": "Edit display name", + "account_edit.profile_tab.button_label": "Customize", + "account_edit.profile_tab.hint.description": "These settings customize what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.", + "account_edit.profile_tab.hint.title": "Displays still vary", + "account_edit.profile_tab.show_featured.description": "‘Featured’ is an optional tab where you can showcase other accounts.", + "account_edit.profile_tab.show_featured.title": "Show ‘Featured’ tab", + "account_edit.profile_tab.show_media.description": "‘Media’ is an optional tab that shows your posts containing images or videos.", + "account_edit.profile_tab.show_media.title": "Show ‘Media’ tab", + "account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.", + "account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab", "account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.", "account_edit.profile_tab.title": "Profile tab settings", "account_edit.save": "Save", From 1e5cad072e5a3785551d70d5af00282a51271c42 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 27 Feb 2026 14:54:08 +0100 Subject: [PATCH 25/89] Profile redesign: Profile fields feedback (#38005) --- .../account_timeline/components/fields.tsx | 15 +++--- .../components/redesign.module.scss | 8 +++- .../account_timeline/modals/field_modal.tsx | 46 +++++++++---------- .../account_timeline/v2/styles.module.scss | 5 ++ 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/app/javascript/mastodon/features/account_timeline/components/fields.tsx b/app/javascript/mastodon/features/account_timeline/components/fields.tsx index c2802c2c518c9a..5bad90aaaebb95 100644 --- a/app/javascript/mastodon/features/account_timeline/components/fields.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/fields.tsx @@ -118,14 +118,14 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
{fields.map((field, key) => ( - + ))}
); }; -const FieldRow: FC<{ +const FieldCard: FC<{ htmlHandlers: ReturnType; field: AccountField; }> = ({ htmlHandlers, field }) => { @@ -183,15 +183,14 @@ const FieldRow: FC<{ ref={wrapperRef} > {verified_at && ( - + > + + )} ); 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 d4ea2d7a7d412d..51a7962c762f2c 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -278,11 +278,17 @@ svg.badgeIcon { } .fieldVerifiedIcon { + display: block; + position: absolute; width: 16px; height: 16px; - position: absolute; top: 8px; right: 8px; + + > svg { + width: 100%; + height: 100%; + } } .fieldOverflowButton { diff --git a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx index 33e2e228918dd3..f7251f7b4181e9 100644 --- a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx @@ -2,7 +2,9 @@ import type { FC } from 'react'; import { FormattedMessage } from 'react-intl'; +import { Button } from '@/mastodon/components/button'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; +import { ModalShell } from '@/mastodon/components/modal_shell'; import type { AccountField } from '../common'; import { useFieldHtml } from '../hooks/useFieldHtml'; @@ -16,29 +18,25 @@ export const AccountFieldModal: FC<{ const handleLabelElement = useFieldHtml(field.nameHasEmojis); const handleValueElement = useFieldHtml(field.valueHasEmojis); return ( -
-
-
- - -
-
-
-
- -
-
-
+ + + + + + + + + ); }; 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 a57a5e738a0f93..2ef62a7d258a24 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss +++ b/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss @@ -7,6 +7,7 @@ border: none; background: none; padding: 8px 0; + font-size: 15px; font-weight: 500; display: flex; align-items: center; @@ -41,6 +42,10 @@ align-items: center; font-size: 15px; } + + [data-color-scheme='dark'] & { + border: 1px solid var(--color-border-primary); + } } .tagsWrapper { From 3c8e37907d2e34bf51a889488cc28dbffb822a41 Mon Sep 17 00:00:00 2001 From: Dock Date: Fri, 27 Feb 2026 10:22:19 -0500 Subject: [PATCH 26/89] Fix avatar alt-text running into other elements on image load failure. (#38000) --- .../avatar_overlay-test.jsx.snap | 2 ++ .../mastodon/components/avatar_overlay.tsx | 24 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap index 94d5402b20404d..de4ccd659429dc 100644 --- a/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap +++ b/app/javascript/mastodon/components/__tests__/__snapshots__/avatar_overlay-test.jsx.snap @@ -26,6 +26,7 @@ exports[` renders a overlay avatar 1`] = ` > alice
@@ -44,6 +45,7 @@ exports[` renders a overlay avatar 1`] = ` > eve@blackhat.lair
diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx index 0bd33fea6937b6..e7fc1252c1ba4a 100644 --- a/app/javascript/mastodon/components/avatar_overlay.tsx +++ b/app/javascript/mastodon/components/avatar_overlay.tsx @@ -10,6 +10,14 @@ interface Props { overlaySize?: number; } +const handleImgLoadError = (error: { currentTarget: HTMLElement }) => { + // + // When the img tag fails to load the image, set the img tag to display: none. This prevents the + // alt-text from overrunning the containing div. + // + error.currentTarget.style.display = 'none'; +}; + export const AvatarOverlay: React.FC = ({ account, friend, @@ -38,7 +46,13 @@ export const AvatarOverlay: React.FC = ({ className='account__avatar' style={{ width: `${baseSize}px`, height: `${baseSize}px` }} > - {accountSrc && {account?.get('acct')}} + {accountSrc && ( + {account?.get('acct')} + )}
@@ -46,7 +60,13 @@ export const AvatarOverlay: React.FC = ({ className='account__avatar' style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }} > - {friendSrc && {friend?.get('acct')}} + {friendSrc && ( + {friend?.get('acct')} + )}
From e92b7beb7555aad2c10c840d951b4dcf5d3900b6 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 10:23:48 -0500 Subject: [PATCH 27/89] Refactor tests for `FeaturedTag#display_name` (#38007) --- spec/models/featured_tag_spec.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/spec/models/featured_tag_spec.rb b/spec/models/featured_tag_spec.rb index 1197776b02ca06..b0a994aaf1c0dc 100644 --- a/spec/models/featured_tag_spec.rb +++ b/spec/models/featured_tag_spec.rb @@ -93,24 +93,22 @@ end describe '#display_name' do - subject { Fabricate.build :featured_tag, name: name, tag: tag } + subject { featured_tag.display_name } - context 'with a name value present' do - let(:name) { 'Test' } + let(:featured_tag) { Fabricate.build :featured_tag, name: name, tag: tag } + + context 'with a name value present on the featured tag' do + let(:name) { 'FeaturedTagName' } let(:tag) { nil } - it 'uses name value' do - expect(subject.display_name).to eq('Test') - end + it { is_expected.to eq('FeaturedTagName') } end - context 'with a missing name value but a present tag' do + context 'with a missing name value but a present linked tag' do let(:name) { nil } - let(:tag) { Fabricate.build :tag, name: 'Tester' } + let(:tag) { Fabricate.build :tag, display_name: 'LinkedTagDisplayName' } - it 'uses name value' do - expect(subject.display_name).to eq('Tester') - end + it { is_expected.to eq('LinkedTagDisplayName') } end end From 9762b4a75cf7e8d48c04390f5515ed76223751bc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 10:56:08 -0500 Subject: [PATCH 28/89] Use `normalizes` for tag name attribute (#37119) --- app/controllers/api/v1/tags_controller.rb | 2 +- app/models/featured_tag.rb | 2 +- app/models/tag.rb | 19 +++++----- app/services/tag_search_service.rb | 2 +- spec/models/tag_spec.rb | 42 +++++++++++++++-------- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/app/controllers/api/v1/tags_controller.rb b/app/controllers/api/v1/tags_controller.rb index 67a4d8ef492904..36822d831b16d5 100644 --- a/app/controllers/api/v1/tags_controller.rb +++ b/app/controllers/api/v1/tags_controller.rb @@ -39,6 +39,6 @@ def unfeature def set_or_create_tag return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id]) - @tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id]) + @tag = Tag.find_normalized(params[:id]) || Tag.new(name: params[:id], display_name: params[:id]) end end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index 9a91ab3bed75c3..a0938d6c0a4a3c 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -26,7 +26,7 @@ class FeaturedTag < ApplicationRecord normalizes :name, with: ->(name) { name.strip.delete_prefix('#') } - scope :by_name, ->(name) { joins(:tag).where(tag: { name: HashtagNormalizer.new.normalize(name) }) } + scope :by_name, ->(name) { joins(:tag).where(tag: { name: }) } before_validation :set_tag diff --git a/app/models/tag.rb b/app/models/tag.rb index b87fbc4246388f..97edda879e5026 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -65,8 +65,9 @@ class Tag < ApplicationRecord .where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) }) .group(:id).order(Arel.star.count.desc) } - scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index + scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(normalize_value_for(:name, term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index + normalizes :name, with: ->(value) { HashtagNormalizer.new.normalize(value) } normalizes :display_name, with: ->(value) { value.gsub(HASHTAG_INVALID_CHARS_RE, '') } update_index('tags', :self) @@ -111,13 +112,13 @@ def history class << self def find_or_create_by_names(name_or_names) - names = Array(name_or_names).map { |str| [normalize(str), str] }.uniq(&:first) + names = Array(name_or_names).map { |str| [normalize_value_for(:name, str), str] }.uniq(&:first) - names.map do |(normalized_name, display_name)| + names.map do |name, display_name| tag = begin - matching_name(normalized_name).first || create!(name: normalized_name, display_name:) + matching_name(name).first || create!(name:, display_name:) rescue ActiveRecord::RecordNotUnique - find_normalized(normalized_name) + find_normalized(name) end yield tag if block_given? @@ -148,7 +149,7 @@ def find_normalized!(name) end def matching_name(name_or_names) - names = Array(name_or_names).map { |name| arel_table.lower(normalize(name)) } + names = Array(name_or_names).map { |name| arel_table.lower(normalize_value_for(:name, name)) } if names.size == 1 where(arel_table[:name].lower.eq(names.first)) @@ -156,10 +157,6 @@ def matching_name(name_or_names) where(arel_table[:name].lower.in(names)) end end - - def normalize(str) - HashtagNormalizer.new.normalize(str) - end end private @@ -173,6 +170,6 @@ def validate_display_name_change end def display_name_matches_name? - HashtagNormalizer.new.normalize(display_name).casecmp(name).zero? + self.class.normalize_value_for(:name, display_name).casecmp(name).zero? end end diff --git a/app/services/tag_search_service.rb b/app/services/tag_search_service.rb index 6a4af5c9a0a8cb..1557c07bf0bb59 100644 --- a/app/services/tag_search_service.rb +++ b/app/services/tag_search_service.rb @@ -39,7 +39,7 @@ def from_elasticsearch def ensure_exact_match(results) return results unless @offset.nil? || @offset.zero? - normalized_query = Tag.normalize(@query) + normalized_query = Tag.normalize_value_for(:name, @query) exact_match = results.find { |tag| tag.name.downcase == normalized_query } exact_match ||= Tag.find_normalized(normalized_query) unless exact_match.nil? diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index a6406a69a72169..9fe723b3ba6296 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -40,26 +40,40 @@ def previous_name_error_message I18n.t('tags.does_not_match_previous_name') end - it 'invalid with #' do - expect(described_class.new(name: '#hello_world')).to_not be_valid - end + describe 'when skipping normalizations' do + subject { described_class.new } - it 'invalid with .' do - expect(described_class.new(name: '.abcdef123')).to_not be_valid - end + before { subject.attributes[:name] = name } - it 'invalid with spaces' do - expect(described_class.new(name: 'hello world')).to_not be_valid - end + context 'with a # in string' do + let(:name) { '#hello_world' } + + it { is_expected.to_not be_valid } + end + + context 'with a . in string' do + let(:name) { '.abcdef123' } - it 'valid with aesthetic' do - expect(described_class.new(name: 'aesthetic')).to be_valid + it { is_expected.to_not be_valid } + end + + context 'with a space in string' do + let(:name) { 'hello world' } + + it { is_expected.to_not be_valid } + end end + + it { is_expected.to allow_value('aesthetic').for(:name) } end describe 'Normalizations' do it { is_expected.to normalize(:display_name).from('#HelloWorld').to('HelloWorld') } it { is_expected.to normalize(:display_name).from('Hello❤️World').to('HelloWorld') } + + it { is_expected.to normalize(:name).from('#hello_world').to('hello_world') } + it { is_expected.to normalize(:name).from('hello world').to('helloworld') } + it { is_expected.to normalize(:name).from('.abcdef123').to('abcdef123') } end describe 'HASHTAG_RE' do @@ -210,7 +224,7 @@ def previous_name_error_message upcase_string = 'abcABCabcABCやゆよ' downcase_string = 'abcabcabcabcやゆよ' - tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) + tag = Fabricate(:tag, name: downcase_string) expect(described_class.find_normalized(upcase_string)).to eq tag end end @@ -239,7 +253,7 @@ def previous_name_error_message upcase_string = 'abcABCabcABCやゆよ' downcase_string = 'abcabcabcabcやゆよ' - tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) + tag = Fabricate(:tag, name: downcase_string) expect(described_class.matches_name(upcase_string)).to eq [tag] end @@ -254,7 +268,7 @@ def previous_name_error_message upcase_string = 'abcABCabcABCやゆよ' downcase_string = 'abcabcabcabcやゆよ' - tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) + tag = Fabricate(:tag, name: downcase_string) expect(described_class.matching_name(upcase_string)).to eq [tag] end end From 7e5e96739f077f3a3001ca334c0d41530b21f812 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 11:38:34 -0500 Subject: [PATCH 29/89] Use consistent calling style for `TagManager#normalize_domain` (#35764) --- app/controllers/admin/domain_blocks_controller.rb | 2 +- .../admin/instances/moderation_notes_controller.rb | 7 +++++-- app/controllers/admin/instances_controller.rb | 7 +++++-- app/controllers/api/v1/peers/search_controller.rb | 6 +----- app/lib/tag_manager.rb | 6 +++--- app/models/concerns/domain_normalizable.rb | 2 +- app/services/report_service.rb | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 5e1074b224ae3e..fdc8e53f531c67 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -54,7 +54,7 @@ def create end # Allow transparently upgrading a domain block - if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain.strip) + if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain) @domain_block = existing_domain_block @domain_block.assign_attributes(resource_params) end diff --git a/app/controllers/admin/instances/moderation_notes_controller.rb b/app/controllers/admin/instances/moderation_notes_controller.rb index 635c09734933ee..dd6c32bda5799e 100644 --- a/app/controllers/admin/instances/moderation_notes_controller.rb +++ b/app/controllers/admin/instances/moderation_notes_controller.rb @@ -34,8 +34,11 @@ def resource_params end def set_instance - domain = params[:instance_id]&.strip - @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain)) + @instance = Instance.find_or_initialize_by(domain: normalized_domain) + end + + def normalized_domain + TagManager.instance.normalize_domain(params[:instance_id]) end def set_instance_note diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 6ab4acab99cf58..033d250a2e07c3 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -55,8 +55,11 @@ def stop_delivery private def set_instance - domain = params[:id]&.strip - @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain)) + @instance = Instance.find_or_initialize_by(domain: normalized_domain) + end + + def normalized_domain + TagManager.instance.normalize_domain(params[:id]) end def set_instances diff --git a/app/controllers/api/v1/peers/search_controller.rb b/app/controllers/api/v1/peers/search_controller.rb index d9c82327022194..27b7503e9f0784 100644 --- a/app/controllers/api/v1/peers/search_controller.rb +++ b/app/controllers/api/v1/peers/search_controller.rb @@ -47,10 +47,6 @@ def set_domains end def normalized_domain - TagManager.instance.normalize_domain(query_value) - end - - def query_value - params[:q].strip + TagManager.instance.normalize_domain(params[:q]) end end diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index 5a6284cc5b6281..4b2f96da634100 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -17,9 +17,9 @@ def local_domain?(domain) def normalize_domain(domain) return if domain.nil? - uri = Addressable::URI.new - uri.host = domain.strip.delete_suffix('/') - uri.normalized_host + Addressable::URI.new.tap do |uri| + uri.host = domain.strip.delete_suffix('/') + end.normalized_host end def local_url?(url) diff --git a/app/models/concerns/domain_normalizable.rb b/app/models/concerns/domain_normalizable.rb index 76f91c5b64029c..6571a40c54f4a2 100644 --- a/app/models/concerns/domain_normalizable.rb +++ b/app/models/concerns/domain_normalizable.rb @@ -22,7 +22,7 @@ def domain_char_length private def normalize_domain - self.domain = TagManager.instance.normalize_domain(domain&.strip) + self.domain = TagManager.instance.normalize_domain(domain) rescue Addressable::URI::InvalidURIError errors.add(:domain, :invalid) end diff --git a/app/services/report_service.rb b/app/services/report_service.rb index 433cd9cb8c7efc..a666450af0a277 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -77,7 +77,7 @@ def forward_to_origin? end def forward_to_domains - @forward_to_domains ||= (@options[:forward_to_domains] || [@target_account.domain]).filter_map { |domain| TagManager.instance.normalize_domain(domain&.strip) }.uniq + @forward_to_domains ||= (@options[:forward_to_domains] || [@target_account.domain]).filter_map { |domain| TagManager.instance.normalize_domain(domain) }.uniq end def reported_status_ids From 7f16397f3c37a8e378239974b73afbfe2b6e6844 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 27 Feb 2026 12:05:29 -0500 Subject: [PATCH 30/89] Add validation spec for `Form::Redirect` model (#38011) --- spec/models/form/redirect_spec.rb | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 spec/models/form/redirect_spec.rb diff --git a/spec/models/form/redirect_spec.rb b/spec/models/form/redirect_spec.rb new file mode 100644 index 00000000000000..4427a0bb8646bd --- /dev/null +++ b/spec/models/form/redirect_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Form::Redirect do + describe 'Validations' do + it { is_expected.to validate_presence_of(:acct) } + + describe 'target account validation' do + subject { described_class.new(account:) } + + context 'when target_account is missing' do + let(:account) { Fabricate.build :account } + + it { is_expected.to_not allow_value(nil).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.not_found')) } + end + + context 'when account already moved' do + let(:account) { Fabricate.build :account, moved_to_account_id: target_account.id } + let(:target_account) { Fabricate :account } + + it { is_expected.to_not allow_value(target_account).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.already_moved')) } + end + + context 'when moving to self' do + let(:account) { Fabricate :account } + + it { is_expected.to_not allow_value(account).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.move_to_self')) } + end + end + end +end From d845a8a28951e1ca91a4f541e7bef3319f7af721 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Mar 2026 03:47:45 -0500 Subject: [PATCH 31/89] Fix violations to existing cops from rubocop v1.85.0 (#38015) --- app/lib/webhooks/payload_renderer.rb | 2 +- app/workers/move_worker.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/webhooks/payload_renderer.rb b/app/lib/webhooks/payload_renderer.rb index 3d2731f6dc773b..73ae30b5725cae 100644 --- a/app/lib/webhooks/payload_renderer.rb +++ b/app/lib/webhooks/payload_renderer.rb @@ -39,7 +39,7 @@ class TemplateParser < Parslet::Parser rule(:digit) { match('[0-9]') } rule(:property_name) { match('[a-z_]').repeat(1) } rule(:array_index) { digit.repeat(1) } - rule(:segment) { (property_name | array_index) } + rule(:segment) { property_name | array_index } rule(:path) { property_name >> (dot >> segment).repeat } rule(:variable) { (str('}}').absent? >> path).repeat.as(:variable) } rule(:expression) { str('{{') >> variable >> str('}}') } diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 76d3765c1c0328..8133a4de0766d5 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -53,7 +53,7 @@ def rewrite_follows! rescue ActiveRecord::RecordInvalid nil end - end + end # Finally, handle the common case of accounts not following the new account source_local_followers @@ -72,7 +72,7 @@ def rewrite_follows! ['relationships', @target_account.id, follow.account_id], ] end) - end + end num_moved end From 786fcac4a1a1a1b5e4e5122faf5d9bcef1199acd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 08:50:50 +0000 Subject: [PATCH 32/89] Update dependency ioredis to v5.10.0 (#38010) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index e09e2aa1cbec4a..54a14ebc380f3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2662,10 +2662,10 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:1.5.0": - version: 1.5.0 - resolution: "@ioredis/commands@npm:1.5.0" - checksum: 10c0/2d192d967a21f0192e17310d27ead02b0bdd504e834c782714abe641190ebfb548ad307fd89fd2d80db97c462afdc69ab4a4383831ab64ce61fe92f130d8b466 +"@ioredis/commands@npm:1.5.1": + version: 1.5.1 + resolution: "@ioredis/commands@npm:1.5.1" + checksum: 10c0/cb8f6d13cff0753e3e7ef001fb895491985d9a623248192538f13bc2fd9bfdfde3c18cf2ba6f20ec8ceaa681b0771070d3a09b82eed044c798bcfef5e3ae54b3 languageName: node linkType: hard @@ -8660,10 +8660,10 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.9.3 - resolution: "ioredis@npm:5.9.3" + version: 5.10.0 + resolution: "ioredis@npm:5.10.0" dependencies: - "@ioredis/commands": "npm:1.5.0" + "@ioredis/commands": "npm:1.5.1" cluster-key-slot: "npm:^1.1.0" debug: "npm:^4.3.4" denque: "npm:^2.1.0" @@ -8672,7 +8672,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/f3d5c811bc1f320236f488ee6bd8a4857c7c0e8e5de1154b064afc03eb35aafbf24156e430025061b6939c58a5a6b424d86814d726439f5811b2e9c56c710dd4 + checksum: 10c0/294e8cdef963f922b04ad023d4165f1d399b27279b9890f3e1311da57fb2a082c8dca52d18bd14a54acd6fb82853783641a0c47ff18661a8756221049f807e88 languageName: node linkType: hard From db241b5c2c98fa60f7e4ee9914c834cd51da912a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:50:54 +0100 Subject: [PATCH 33/89] Update dependency axios to v1.13.6 (#38009) 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 54a14ebc380f3f..5d13c3e405f8a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5635,13 +5635,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.13.5 - resolution: "axios@npm:1.13.5" + version: 1.13.6 + resolution: "axios@npm:1.13.6" dependencies: follow-redirects: "npm:^1.15.11" form-data: "npm:^4.0.5" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/abf468c34f2d145f3dc7dbc0f1be67e520630624307bda69a41bbe8d386bd672d87b4405c4ee77f9ff54b235ab02f96a9968fb00e75b13ce64706e352a3068fd + checksum: 10c0/51fb5af055c3b85662fa97df17d986ae2c37d13bf86d50b6bb36b6b3a2dec6966a1d3a14ab3774b71707b155ae3597ed9b7babdf1a1a863d1a31840cb8e7ec71 languageName: node linkType: hard From 5fd1235ba2e27638309c8806d32f6051b70819d4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Mar 2026 03:51:09 -0500 Subject: [PATCH 34/89] Update `binding_of_caller` to version 2.0.0 (#38017) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index a294fb7206ee82..4c47b0861c2a8e 100644 --- a/Gemfile +++ b/Gemfile @@ -180,7 +180,7 @@ group :development do # Enhanced error message pages for development gem 'better_errors', '~> 2.9' - gem 'binding_of_caller', '~> 1.0' + gem 'binding_of_caller' # Preview mail in the browser gem 'letter_opener', '~> 1.8' diff --git a/Gemfile.lock b/Gemfile.lock index 1d94c3ec050a82..74a5404a93e468 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,7 +129,7 @@ GEM rouge (>= 1.0.0) bigdecimal (3.3.1) bindata (2.5.1) - binding_of_caller (1.0.1) + binding_of_caller (2.0.0) debug_inspector (>= 1.2.0) blurhash (0.1.8) bootsnap (1.23.0) @@ -953,7 +953,7 @@ DEPENDENCIES aws-sdk-core aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) - binding_of_caller (~> 1.0) + binding_of_caller blurhash (~> 0.1) bootsnap brakeman (~> 8.0) From 7444c8f82ecaf0cfbebf7692cfc949cd997e3718 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:51:13 +0100 Subject: [PATCH 35/89] New Crowdin Translations (automated) (#38018) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 10 ++++ app/javascript/mastodon/locales/de.json | 11 +++- 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/fi.json | 17 ++++++ app/javascript/mastodon/locales/fo.json | 11 +++- app/javascript/mastodon/locales/fr-CA.json | 17 ++++++ app/javascript/mastodon/locales/fr.json | 17 ++++++ app/javascript/mastodon/locales/ga.json | 9 ++++ app/javascript/mastodon/locales/gl.json | 16 ++++++ app/javascript/mastodon/locales/he.json | 17 ++++++ app/javascript/mastodon/locales/is.json | 11 +++- app/javascript/mastodon/locales/it.json | 13 ++++- app/javascript/mastodon/locales/nan-TW.json | 33 ++++++++++++ app/javascript/mastodon/locales/sq.json | 9 ++++ app/javascript/mastodon/locales/sv.json | 10 ++++ app/javascript/mastodon/locales/tr.json | 9 ++++ app/javascript/mastodon/locales/uk.json | 58 ++++++++++++++++++++- app/javascript/mastodon/locales/vi.json | 19 ++++++- app/javascript/mastodon/locales/zh-CN.json | 9 ++++ app/javascript/mastodon/locales/zh-TW.json | 9 ++++ config/locales/doorkeeper.sv.yml | 4 ++ config/locales/nan-TW.yml | 17 ++++++ config/locales/simple_form.sv.yml | 3 ++ config/locales/sv.yml | 14 +++++ config/locales/uk.yml | 15 +++++- 29 files changed, 396 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 4fcb4cc5e2b4d4..108a450c8a2116 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Fremhævede hashtags", "account_edit.name_modal.add_title": "Tilføj visningsnavn", "account_edit.name_modal.edit_title": "Rediger visningsnavn", + "account_edit.profile_tab.button_label": "Tilpas", + "account_edit.profile_tab.hint.description": "Disse indstillinger tilpasser hvad brugere ser på {server} i de officielle apps, men de gælder muligvis ikke for brugere på andre servere og tredjeparts apps.", + "account_edit.profile_tab.hint.title": "Visninger vil stadig variere", + "account_edit.profile_tab.show_featured.description": "‘Fremhævet‘ er en valgfri fane, hvor du kan fremvise andre konti.", + "account_edit.profile_tab.show_featured.title": "Vis fanebladet ‘Fremhævet‘", + "account_edit.profile_tab.show_media.description": "‘Medier’ er en valgfri fane, der viser dine indlæg, der indeholder billeder eller videoer.", + "account_edit.profile_tab.show_media.title": "Vis fanebladet ‘Medier‘", + "account_edit.profile_tab.show_media_replies.description": "Når aktiveret, viser fanen Medier både dine indlæg og svar på andres indlæg.", + "account_edit.profile_tab.show_media_replies.title": "Inkludér svar på fanen 'Medier'", "account_edit.profile_tab.subtitle": "Tilpas fanerne på din profil og det, de viser.", "account_edit.profile_tab.title": "Indstillinger for profil-fane", "account_edit.save": "Gem", @@ -272,6 +281,7 @@ "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", "collection.share_modal.share_link_label": "Invitationlink til deling", + "collection.share_modal.share_via_post": "Del på Mastodon", "collection.share_modal.share_via_system": "Del med…", "collection.share_modal.title": "Del samling", "collection.share_modal.title_new": "Del din nye samling!", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index ce49025b5a7b1c..65418f851ed4a3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -161,11 +161,20 @@ "account_edit.featured_hashtags.title": "Vorgestellte Hashtags", "account_edit.name_modal.add_title": "Anzeigenamen hinzufügen", "account_edit.name_modal.edit_title": "Anzeigenamen bearbeiten", + "account_edit.profile_tab.button_label": "Anpassen", + "account_edit.profile_tab.hint.description": "Diese Einstellungen legen fest, was andere in den offiziellen Apps auf {server} sehen können. Möglicherweise werden sie nicht von anderen Servern oder Apps von Dritten berücksichtigt.", + "account_edit.profile_tab.hint.title": "Darstellung kann abweichen", + "account_edit.profile_tab.show_featured.description": "„Vorgestellt“ ist ein optionaler Tab, der von dir ausgewählte Profile hervorhebt.", + "account_edit.profile_tab.show_featured.title": "„Vorgestellt“-Tab anzeigen", + "account_edit.profile_tab.show_media.description": "„Medien“ ist ein optionaler Tab, der deine Beiträge mit Bildern oder Videos anzeigt.", + "account_edit.profile_tab.show_media.title": "„Medien“-Tab anzeigen", + "account_edit.profile_tab.show_media_replies.description": "Durch das Aktivieren werden sowohl deine Beiträge als auch deine Antworten auf Beiträge anderer im „Medien“-Tab angezeigt.", + "account_edit.profile_tab.show_media_replies.title": "Antworten im „Medien“-Tab anzeigen", "account_edit.profile_tab.subtitle": "Passe die Tabs deines Profils und deren Inhalte an.", "account_edit.profile_tab.title": "Profil-Tab-Einstellungen", "account_edit.save": "Speichern", "account_edit_tags.column_title": "Vorgestellte Hashtags bearbeiten", - "account_edit_tags.help_text": "Vorgestellte Hashtags können dabei helfen, dein Profil zu entdecken und besser mit dir zu interagieren. Sie erscheinen in der Aktivitätenübersicht deines Profils und dienen als Filter.", + "account_edit_tags.help_text": "Vorgestellte Hashtags können dabei helfen, dein Profil zu entdecken und besser mit dir zu interagieren. Sie dienen als Filter in der Aktivitätenübersicht deines Profils.", "account_edit_tags.search_placeholder": "Gib einen Hashtag ein …", "account_edit_tags.suggestions": "Vorschläge:", "account_edit_tags.tag_status_count": "{count, plural, one {# Beitrag} other {# Beiträge}}", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 68de7881495060..a649feef705dc2 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Αναδεδειγμένες ετικέτες", "account_edit.name_modal.add_title": "Προσθήκη εμφανιζόμενου ονόματος", "account_edit.name_modal.edit_title": "Επεξεργασία εμφανιζόμενου ονόματος", + "account_edit.profile_tab.button_label": "Προσαρμογή", + "account_edit.profile_tab.hint.description": "Αυτές οι ρυθμίσεις προσαρμόζουν τι βλέπουν οι χρήστες στο {server} στις επίσημες εφαρμογές, αλλά δεν μπορούν να εφαρμοστούν για χρήστες σε άλλους διακομιστές και εφαρμογές τρίτων.", + "account_edit.profile_tab.hint.title": "Οι εμφανίσεις εξακολουθούν να ποικίλλουν", + "account_edit.profile_tab.show_featured.description": "«Αναδεδειγμένα» είναι μια προαιρετική καρτέλα όπου μπορείτε να αναδείξετε άλλους λογαριασμούς.", + "account_edit.profile_tab.show_featured.title": "Εμφάνιση καρτέλας «Αναδεδειγμένα»", + "account_edit.profile_tab.show_media.description": "«Πολυμέσα» είναι μια προαιρετική καρτέλα που δείχνει τις αναρτήσεις σας που περιέχουν εικόνες ή βίντεο.", + "account_edit.profile_tab.show_media.title": "Εμφάνιση καρτέλας «Πολυμέσα»", + "account_edit.profile_tab.show_media_replies.description": "Όταν ενεργοποιηθεί, η καρτέλα Πολυμέσα εμφανίζει τόσο τις αναρτήσεις σας όσο και τις απαντήσεις σας σε αναρτήσεις άλλων ατόμων.", + "account_edit.profile_tab.show_media_replies.title": "Συμπερίληψη απαντήσεων στην καρτέλα «Πολυμέσα»", "account_edit.profile_tab.subtitle": "Προσαρμόστε τις καρτέλες στο προφίλ σας και τι εμφανίζουν.", "account_edit.profile_tab.title": "Ρυθμίσεις καρτέλας προφίλ", "account_edit.save": "Αποθήκευση", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index cac4cf72e095f5..49e117724993a4 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Featured hashtags", "account_edit.name_modal.add_title": "Add display name", "account_edit.name_modal.edit_title": "Edit display name", + "account_edit.profile_tab.button_label": "Customise", + "account_edit.profile_tab.hint.description": "These settings customise what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.", + "account_edit.profile_tab.hint.title": "Displays still vary", + "account_edit.profile_tab.show_featured.description": "‘Featured’ is an optional tab where you can showcase other accounts.", + "account_edit.profile_tab.show_featured.title": "Show ‘Featured’ tab", + "account_edit.profile_tab.show_media.description": "‘Media’ is an optional tab that shows your posts containing images or videos.", + "account_edit.profile_tab.show_media.title": "Show ‘Media’ tab", + "account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.", + "account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab", "account_edit.profile_tab.subtitle": "Customise the tabs on your profile and what they display.", "account_edit.profile_tab.title": "Profile tab settings", "account_edit.save": "Save", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 3e81941bc83697..6ecc2c1c3817c8 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Etiquetas destacadas", "account_edit.name_modal.add_title": "Agregar nombre a mostrar", "account_edit.name_modal.edit_title": "Editar nombre a mostrar", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.description": "Esta configuración personaliza lo que los usuarios ven en {server} en las aplicaciones oficiales, pero es posible que no se apliquen a los usuarios en otros servidores y aplicaciones de terceros.", + "account_edit.profile_tab.hint.title": "Las pantallas pueden variar", + "account_edit.profile_tab.show_featured.description": "«Destacados» es una pestaña opcional en donde podés mostrar otras cuentas.", + "account_edit.profile_tab.show_featured.title": "Mostrar pestaña «Destacados»", + "account_edit.profile_tab.show_media.description": "«Medios» es una pestaña opcional que muestra tus mensajes conteniendo imágenes, videos o audios.", + "account_edit.profile_tab.show_media.title": "Mostrar pestaña «Medios»", + "account_edit.profile_tab.show_media_replies.description": "Cuando está habilitada, la pestaña «Medios» muestra tanto tus mensajes como tus respuestas a otras cuentas.", + "account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Medios»", "account_edit.profile_tab.subtitle": "Personalizá las pestañas en tu perfil y qué van a mostrar.", "account_edit.profile_tab.title": "Configuración de pestaña de perfil", "account_edit.save": "Guardar", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 51c98822612207..a884b94a58214e 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Etiquetas destacadas", "account_edit.name_modal.add_title": "Añadir nombre para mostrar", "account_edit.name_modal.edit_title": "Editar nombre para mostrar", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.description": "Esta configuración personaliza lo que los usuarios ven en {server} en las aplicaciones oficiales, pero es posible que no se aplique a los usuarios de otros servidores y aplicaciones de terceros.", + "account_edit.profile_tab.hint.title": "Las pantallas todavía varían", + "account_edit.profile_tab.show_featured.description": "«Destacados» es una pestaña opcional en la que puedes mostrar otras cuentas.", + "account_edit.profile_tab.show_featured.title": "Mostrar la pestaña «Destacados»", + "account_edit.profile_tab.show_media.description": "«Multimedia» es una pestaña opcional que muestra tus publicaciones que contienen imágenes o videos.", + "account_edit.profile_tab.show_media.title": "Mostrar pestaña «Multimedia»", + "account_edit.profile_tab.show_media_replies.description": "Cuando está habilitada, la pestaña Multimedia muestra tanto tus publicaciones como tus respuestas a las publicaciones de otras personas.", + "account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Multimedia»", "account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.", "account_edit.profile_tab.title": "Configuración de la pestaña de perfil", "account_edit.save": "Guardar", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 62abafc4bc99d5..cafd1419373b28 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Etiquetas destacadas", "account_edit.name_modal.add_title": "Añadir nombre para mostrar", "account_edit.name_modal.edit_title": "Editar nombre para mostrar", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.description": "Estos ajustes personalizan lo que los usuarios ven en {server} en las aplicaciones oficiales, pero es posible que no se apliquen a los usuarios en otros servidores y aplicaciones de terceros.", + "account_edit.profile_tab.hint.title": "La disposición puede variar", + "account_edit.profile_tab.show_featured.description": "«Destacado» es una pestaña opcional donde puedes mostrar otras cuentas.", + "account_edit.profile_tab.show_featured.title": "Mostrar pestaña «Destacado»", + "account_edit.profile_tab.show_media.description": "«Multimedia» es una pestaña opcional que muestra tus publicaciones que contienen imágenes o videos.", + "account_edit.profile_tab.show_media.title": "Mostrar pestaña «Multimedia»", + "account_edit.profile_tab.show_media_replies.description": "Cuando está activado, la pestaña Multimedia muestra tanto tus publicaciones como tus respuestas a las publicaciones de otras personas.", + "account_edit.profile_tab.show_media_replies.title": "Incluir respuestas en la pestaña «Multimedia»", "account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.", "account_edit.profile_tab.title": "Configuración de la pestaña de perfil", "account_edit.save": "Guardar", @@ -313,7 +322,7 @@ "collections.new_collection": "Nueva colección", "collections.no_collections_yet": "Aún no hay colecciones.", "collections.old_last_post_note": "Última publicación hace más de una semana", - "collections.remove_account": "Borrar esta cuenta", + "collections.remove_account": "Quitar esta cuenta", "collections.report_collection": "Informar de esta colección", "collections.search_accounts_label": "Buscar cuentas para añadir…", "collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index d9c7ee97a1ed75..4b95f2ff94796f 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Esiteltävät aihetunnisteet", "account_edit.name_modal.add_title": "Lisää näyttönimi", "account_edit.name_modal.edit_title": "Muokkaa näyttönimeä", + "account_edit.profile_tab.button_label": "Mukauta", + "account_edit.profile_tab.hint.description": "Nämä asetukset mukauttavat sitä, mitä käyttäjät näkevät palvelimella {server} virallisissa sovelluksissa, mutta ne eivät välttämättä vaikuta muiden palvelinten tai kolmannen osapuolen sovellusten käyttäjiin.", + "account_edit.profile_tab.hint.title": "Näkymät vaihtelevat edelleen", + "account_edit.profile_tab.show_featured.description": "”Esittelyssä” on valinnainen välilehti, jossa voit esitellä muita tilejä.", + "account_edit.profile_tab.show_featured.title": "Näytä Esittelyssä-välilehti", + "account_edit.profile_tab.show_media.description": "”Media” on valinnainen välilehti, joka näyttää kuvia tai videoita sisältävät julkaisusi.", + "account_edit.profile_tab.show_media.title": "Näytä Media-välilehti", + "account_edit.profile_tab.show_media_replies.description": "Kun käytössä, Media-välilehti näyttää sekä julkaisusi että vastauksesi muiden julkaisuihin.", + "account_edit.profile_tab.show_media_replies.title": "Sisällytä vastaukset Media-välilehdelle", "account_edit.profile_tab.subtitle": "Mukauta profiilisi välilehtiä ja sitä, mitä niissä näkyy.", "account_edit.profile_tab.title": "Profiilin välilehtien asetukset", "account_edit.save": "Tallenna", @@ -271,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Etsi toinen palvelin", "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", + "collection.share_modal.share_link_label": "Kutsun jakolinkki", + "collection.share_modal.share_via_post": "Jaa Mastodonissa", + "collection.share_modal.share_via_system": "Jaa kohteeseen…", + "collection.share_modal.title": "Jaa kokoelma", + "collection.share_modal.title_new": "Jaa uusi kokoelmasi!", + "collection.share_template_other": "Katso tämä siisti kokoelma: {link}", + "collection.share_template_own": "Katso uusi kokoelmani: {link}", "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ä", @@ -448,6 +464,7 @@ "conversation.open": "Näytä keskustelu", "conversation.with": "{names} kanssa", "copy_icon_button.copied": "Kopioitu leikepöydälle", + "copy_icon_button.copy_this_text": "Kopioi linkki leikepöydälle", "copypaste.copied": "Kopioitu", "copypaste.copy_to_clipboard": "Kopioi leikepöydälle", "directory.federated": "Tunnetusta fediversumista", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 417947a25b22f8..10f727109e5afe 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -42,7 +42,7 @@ "account.familiar_followers_many": "{name1}, {name2} og {othersCount, plural, one {ein annar/onnur tú kennir} other {# onnur tú kennir}} fylgja", "account.familiar_followers_one": "{name1} fylgir", "account.familiar_followers_two": "{name1} og {name2} fylgja", - "account.featured": "Tikin fram", + "account.featured": "Sermerkt", "account.featured.accounts": "Vangar", "account.featured.collections": "Søvn", "account.featured.hashtags": "Frámerki", @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Sermerkt frámerki", "account_edit.name_modal.add_title": "Legg víst navn afturat", "account_edit.name_modal.edit_title": "Rætta víst navn", + "account_edit.profile_tab.button_label": "Tillaga", + "account_edit.profile_tab.hint.description": "Hesi stillingarnar tillaga tað, sum brúkarar síggja á {server} í almennu appunum, men tær eru kanska ikki galdandi fyri brúkarar á øðrum servarum og appum hjá triðjapørtum.", + "account_edit.profile_tab.hint.title": "Vísingar eru framvegis ymiskar", + "account_edit.profile_tab.show_featured.description": "'Sermerkt' er eitt valfrítt spjaldur, har tú kanst sýna aðrar kontur fram.", + "account_edit.profile_tab.show_featured.title": "Vís 'Sermerkt\" spjaldur", + "account_edit.profile_tab.show_media.description": "'Miðlar' er eitt valfrítt spjaldur, sum vísur tínar postar, sum innihalda myndir ella sjónfílur.", + "account_edit.profile_tab.show_media.title": "Vís 'Miðlar' spjaldur", + "account_edit.profile_tab.show_media_replies.description": "Tá tað er virkið, vísir Miðlar spjaldrið bæði tínar postar og svar til postar hjá øðrum.", + "account_edit.profile_tab.show_media_replies.title": "Írokna svar á 'Miðlar' spjaldrinum", "account_edit.profile_tab.subtitle": "Tillaga spjøldrini á vanganum hjá tær og tað, tey vísa.", "account_edit.profile_tab.title": "Stillingar fyri spjøldur á vanga", "account_edit.save": "Goym", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 54036c953fc8b2..ac99eb7ba89834 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Hashtags mis en avant", "account_edit.name_modal.add_title": "Ajouter un nom public", "account_edit.name_modal.edit_title": "Modifier le nom public", + "account_edit.profile_tab.button_label": "Personnaliser", + "account_edit.profile_tab.hint.description": "Ces paramètres personnalisent ce que les personnes voient sur {server} dans les applications officielles, mais ils peuvent ne pas s'appliquer aux personnes sur d'autres serveurs ou utilisant une application tiers.", + "account_edit.profile_tab.hint.title": "L'affichage peut varier", + "account_edit.profile_tab.show_featured.description": "« En vedette » est un onglet facultatif où vous pouvez mettre en avant d'autres comptes.", + "account_edit.profile_tab.show_featured.title": "Afficher l'onglet « En vedette »", + "account_edit.profile_tab.show_media.description": "« Média » est un onglet facultatif qui affiche vos messages contenant des images ou des vidéos.", + "account_edit.profile_tab.show_media.title": "Afficher l'onglet « Média »", + "account_edit.profile_tab.show_media_replies.description": "En activant cette option l'onglet « Média » affiche à la fois vos messages et vos réponses aux messages d'autres personnes.", + "account_edit.profile_tab.show_media_replies.title": "Inclure les réponses dans l'onglet « Média »", "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", @@ -271,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Trouver un autre serveur", "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", + "collection.share_modal.share_link_label": "Lien à partager", + "collection.share_modal.share_via_post": "Publier sur Mastodon", + "collection.share_modal.share_via_system": "Partager avec…", + "collection.share_modal.title": "Partager la collection", + "collection.share_modal.title_new": "Partager votre nouvelle collection !", + "collection.share_template_other": "Découvrez cette collection incroyable : {link}", + "collection.share_template_own": "Découvrez ma nouvelle collection : {link}", "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", @@ -448,6 +464,7 @@ "conversation.open": "Afficher cette conversation", "conversation.with": "Avec {names}", "copy_icon_button.copied": "Copié dans le presse-papier", + "copy_icon_button.copy_this_text": "Copier le lien dans le presse-papier", "copypaste.copied": "Copié", "copypaste.copy_to_clipboard": "Copier dans le presse-papiers", "directory.federated": "D'un fediverse connu", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 18daca78930efa..2c6f4be00a8169 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Hashtags mis en avant", "account_edit.name_modal.add_title": "Ajouter un nom public", "account_edit.name_modal.edit_title": "Modifier le nom public", + "account_edit.profile_tab.button_label": "Personnaliser", + "account_edit.profile_tab.hint.description": "Ces paramètres personnalisent ce que les personnes voient sur {server} dans les applications officielles, mais ils peuvent ne pas s'appliquer aux personnes sur d'autres serveurs ou utilisant une application tiers.", + "account_edit.profile_tab.hint.title": "L'affichage peut varier", + "account_edit.profile_tab.show_featured.description": "« En vedette » est un onglet facultatif où vous pouvez mettre en avant d'autres comptes.", + "account_edit.profile_tab.show_featured.title": "Afficher l'onglet « En vedette »", + "account_edit.profile_tab.show_media.description": "« Média » est un onglet facultatif qui affiche vos messages contenant des images ou des vidéos.", + "account_edit.profile_tab.show_media.title": "Afficher l'onglet « Média »", + "account_edit.profile_tab.show_media_replies.description": "En activant cette option l'onglet « Média » affiche à la fois vos messages et vos réponses aux messages d'autres personnes.", + "account_edit.profile_tab.show_media_replies.title": "Inclure les réponses dans l'onglet « Média »", "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", @@ -271,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Trouver un autre serveur", "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", + "collection.share_modal.share_link_label": "Lien à partager", + "collection.share_modal.share_via_post": "Publier sur Mastodon", + "collection.share_modal.share_via_system": "Partager avec…", + "collection.share_modal.title": "Partager la collection", + "collection.share_modal.title_new": "Partager votre nouvelle collection !", + "collection.share_template_other": "Découvrez cette collection incroyable : {link}", + "collection.share_template_own": "Découvrez ma nouvelle collection : {link}", "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", @@ -448,6 +464,7 @@ "conversation.open": "Afficher la conversation", "conversation.with": "Avec {names}", "copy_icon_button.copied": "Copié dans le presse-papier", + "copy_icon_button.copy_this_text": "Copier le lien dans le presse-papier", "copypaste.copied": "Copié", "copypaste.copy_to_clipboard": "Copier dans le presse-papiers", "directory.federated": "Du fédivers connu", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 5ae8d75fce4150..776df2db7bf293 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Haischlibeanna Réadmhaoine", "account_edit.name_modal.add_title": "Cuir ainm taispeána leis", "account_edit.name_modal.edit_title": "Cuir ainm taispeána in eagar", + "account_edit.profile_tab.button_label": "Saincheap", + "account_edit.profile_tab.hint.description": "Saincheapann na socruithe seo a bhfeiceann úsáideoirí ar {server} sna haipeanna oifigiúla, ach ní fhéadfaidh siad a bheith infheidhme maidir le húsáideoirí ar fhreastalaithe eile agus ar aipeanna tríú páirtí.", + "account_edit.profile_tab.hint.title": "Bíonn taispeántais fós éagsúil", + "account_edit.profile_tab.show_featured.description": "Is cluaisín roghnach é ‘Molta’ inar féidir leat cuntais eile a thaispeáint.", + "account_edit.profile_tab.show_featured.title": "Taispeáin an cluaisín 'Molta'", + "account_edit.profile_tab.show_media.description": "Is cluaisín roghnach é ‘Meáin’ a thaispeánann do phoist ina bhfuil íomhánna nó físeáin.", + "account_edit.profile_tab.show_media.title": "Taispeáin an cluaisín ‘Meáin’", + "account_edit.profile_tab.show_media_replies.description": "Nuair a bhíonn sé cumasaithe, taispeánann an cluaisín Meáin do phoist agus freagraí ar phoist daoine eile araon.", + "account_edit.profile_tab.show_media_replies.title": "Cuir freagraí san áireamh ar an táb ‘Meáin’", "account_edit.profile_tab.subtitle": "Saincheap na cluaisíní ar do phróifíl agus a bhfuil á thaispeáint iontu.", "account_edit.profile_tab.title": "Socruithe an chluaisín próifíle", "account_edit.save": "Sábháil", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 1a49f6ba36dd46..4301834a08998d 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -161,6 +161,14 @@ "account_edit.featured_hashtags.title": "Cancelos destacados", "account_edit.name_modal.add_title": "Engadir nome público", "account_edit.name_modal.edit_title": "Editar o nome público", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.description": "Estes axustes personalizan o que ven as usuarias de {server} nas apps oficiais, pero non se aplica ás usuarias de outros servidores nin apps alleas.", + "account_edit.profile_tab.show_featured.description": "'Destacado' é unha pestana optativa na que podes mostrar outras contas.", + "account_edit.profile_tab.show_featured.title": "Mostrar pestana 'Destacado'", + "account_edit.profile_tab.show_media.description": "'Multimedia' é unha pestana optativa na que aparecen as túas publicacións con fotos e vídeos.", + "account_edit.profile_tab.show_media.title": "Mostrar pestana 'Multimedia'", + "account_edit.profile_tab.show_media_replies.description": "Ao activala, a pestana Multimedia mostra tanto as túas publicacións como as respostas a outras persoas.", + "account_edit.profile_tab.show_media_replies.title": "Incluír respostas na pestana 'Multimedia'", "account_edit.profile_tab.subtitle": "Personaliza as pestanas e o seu contido no teu perfil.", "account_edit.profile_tab.title": "Perfil e axustes das pestanas", "account_edit.save": "Gardar", @@ -271,6 +279,13 @@ "closed_registrations_modal.find_another_server": "Atopa outro servidor", "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", + "collection.share_modal.share_link_label": "Ligazón para compartir", + "collection.share_modal.share_via_post": "Publicar en Mastodon", + "collection.share_modal.share_via_system": "Compartir con…", + "collection.share_modal.title": "Compartir colección", + "collection.share_modal.title_new": "Comparte a túa nova colección!", + "collection.share_template_other": "Mira que colección máis boa: {link}", + "collection.share_template_own": "Mira a miña nova colección: {link}", "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", @@ -448,6 +463,7 @@ "conversation.open": "Ver conversa", "conversation.with": "Con {names}", "copy_icon_button.copied": "Copiada ao portapapeis", + "copy_icon_button.copy_this_text": "Copiar a ligazón ao portapapeis", "copypaste.copied": "Copiado", "copypaste.copy_to_clipboard": "Copiar ao portapapeis", "directory.federated": "Do fediverso coñecido", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index abe3687f897d7e..7bea5cac0f7b8a 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "תגיות נבחרות", "account_edit.name_modal.add_title": "הוספת שם תצוגה", "account_edit.name_modal.edit_title": "עריכת שם תצוגה", + "account_edit.profile_tab.button_label": "התאמה אישית", + "account_edit.profile_tab.hint.description": "הגדרות אלו משנות מה משתמשים יראו על {server} ביישומונים הרשמיים, אבל הן לא בהכרח רלוונטיות למשתמשים על שרתים אחרים ועל יישומונים צד ג'.", + "account_edit.profile_tab.hint.title": "התצוגה עשויה להיות שונה", + "account_edit.profile_tab.show_featured.description": "\"נבחרים\" הוא טאב לבחירה שבו תוכלו להציג חשבונות אחרים.", + "account_edit.profile_tab.show_featured.title": "הצגת טאב \"נבחרים\"", + "account_edit.profile_tab.show_media.description": "\"מדיה\" הוא טאב לבחירה שבוא יוצגו הודעות שלך שמכילות תמונות או וידאו.", + "account_edit.profile_tab.show_media.title": "הצגת טאב \"מדיה\"", + "account_edit.profile_tab.show_media_replies.description": "כשמאופשר, טאב המדיה מציג גם הודעות מקוריות וגם תשובות להודעות של אחרים.", + "account_edit.profile_tab.show_media_replies.title": "לכלול תגובות בטאב המדיה", "account_edit.profile_tab.subtitle": "התאימו את הטאבים בפרופיל שלכם ומה שהם יציגו.", "account_edit.profile_tab.title": "הגדרות טאבים לפרופיל", "account_edit.save": "שמירה", @@ -271,6 +280,13 @@ "closed_registrations_modal.find_another_server": "חיפוש שרת אחר", "closed_registrations_modal.preamble": "מסטודון הוא רשת מבוזרת, כך שלא משנה היכן החשבון שלך, קיימת האפשרות לעקוב ולתקשר עם משתמשים בשרת הזה. אפשר אפילו להריץ שרת בעצמך!", "closed_registrations_modal.title": "להרשם למסטודון", + "collection.share_modal.share_link_label": "קישור להזמנה לשיתוף", + "collection.share_modal.share_via_post": "לפרסם במסטודון", + "collection.share_modal.share_via_system": "לשתף אל…", + "collection.share_modal.title": "שיתוף אוסף", + "collection.share_modal.title_new": "שתפו את האוסף החדש שלכם!", + "collection.share_template_other": "הציצו על האוסף המעניין הזה: {link}", + "collection.share_template_own": "הציצו על האוסף החדש שלי: {link}", "collections.account_count": "{count, plural, one {חשבון אחד} other {# חשבונות}}", "collections.accounts.empty_description": "להוסיף עד ל־{count} חשבונות שאתם עוקבים אחריהם", "collections.accounts.empty_title": "האוסף הזה ריק", @@ -448,6 +464,7 @@ "conversation.open": "צפו בשיחה", "conversation.with": "עם {names}", "copy_icon_button.copied": "הועתק ללוח", + "copy_icon_button.copy_this_text": "העתקת הקישור", "copypaste.copied": "הועתק", "copypaste.copy_to_clipboard": "העתקה ללוח הגזירים", "directory.federated": "מהפדרציה הידועה", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index ee31fcac5d207e..ced1cb70f8d49f 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -78,7 +78,7 @@ "account.languages": "Breyta tungumálum í áskrift", "account.link_verified_on": "Eignarhald á þessum tengli var athugað þann {date}", "account.locked_info": "Staða gagnaleyndar á þessum aðgangi er stillt á læsingu. Eigandinn yfirfer handvirkt hverjir geti fylgst með honum.", - "account.media": "Myndskrár", + "account.media": "Myndefni", "account.mention": "Minnast á @{name}", "account.menu.add_to_list": "Bæta á lista…", "account.menu.block": "Útiloka notandaaðgang", @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Myllumerki með aukið vægi", "account_edit.name_modal.add_title": "Bættu við birtingarnafni", "account_edit.name_modal.edit_title": "Breyta birtingarnafni", + "account_edit.profile_tab.button_label": "Sérsníða", + "account_edit.profile_tab.hint.description": "Þessar stillingar sérsníða hvað notendur sjá á {server} í opinberu forritunum, en ekki er víst hvernig þetta virkar fyrir notendur á öðrum netþjónum og utanaðkomandi forritum.", + "account_edit.profile_tab.hint.title": "Birting er ennþá breytileg", + "account_edit.profile_tab.show_featured.description": "'Með aukið vægi' er valkvæður flipi þar sem þú getur kynnt aðra notendaaðganga.", + "account_edit.profile_tab.show_featured.title": "Birta 'Með aukið vægi' flipa", + "account_edit.profile_tab.show_media.description": "‘Myndefni’ er valkvæður flipi þar sem birtast þær færslur þínar sem innihalda myndir eða myndskeið.", + "account_edit.profile_tab.show_media.title": "Birta 'Myndefni' flipa", + "account_edit.profile_tab.show_media_replies.description": "Þegar þetta er virkt, sýnir myndefnisflipinn bæði færslurnar þínar og svör þín við færslum annarra.", + "account_edit.profile_tab.show_media_replies.title": "Hafa með svör á flipanum ‘Myndefni’", "account_edit.profile_tab.subtitle": "Sérsníddu flipana á notandasniðinu þínu og hvað þeir birta.", "account_edit.profile_tab.title": "Stillingar notandasniðsflipa", "account_edit.save": "Vista", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 97461539613ac5..226314eba1eba8 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -42,7 +42,7 @@ "account.familiar_followers_many": "Seguito da {name1}, {name2}, e {othersCount, plural, one {un altro che conosci} other {altri # che conosci}}", "account.familiar_followers_one": "Seguito da {name1}", "account.familiar_followers_two": "Seguito da {name1} e {name2}", - "account.featured": "In primo piano", + "account.featured": "In evidenza", "account.featured.accounts": "Profili", "account.featured.collections": "Collezioni", "account.featured.hashtags": "Hashtag", @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Hashtag in evidenza", "account_edit.name_modal.add_title": "Aggiungi il nome mostrato", "account_edit.name_modal.edit_title": "Modifica il nome mostrato", + "account_edit.profile_tab.button_label": "Personalizza", + "account_edit.profile_tab.hint.description": "Queste impostazioni personalizzano ciò che gli utenti vedono su {server} nelle app ufficiali, ma potrebbero non applicarsi agli utenti su altri server e app di terze parti.", + "account_edit.profile_tab.hint.title": "La visualizzazione può variare", + "account_edit.profile_tab.show_featured.description": "\"In evidenza\" è una scheda opzionale in cui puoi mostrare altri account.", + "account_edit.profile_tab.show_featured.title": "Mostra la scheda \"In evidenza\"", + "account_edit.profile_tab.show_media.description": "\"Media\" è una scheda opzionale che mostra i tuoi post contenenti immagini o video.", + "account_edit.profile_tab.show_media.title": "Mostra la scheda \"Media\"", + "account_edit.profile_tab.show_media_replies.description": "Quando abilitata, la scheda Media mostra sia i tuoi post che le risposte ai post di altre persone.", + "account_edit.profile_tab.show_media_replies.title": "Includi le risposte nella scheda \"Media\"", "account_edit.profile_tab.subtitle": "Personalizza le schede del tuo profilo e ciò che mostrano.", "account_edit.profile_tab.title": "Impostazioni della scheda del profilo", "account_edit.save": "Salva", @@ -509,7 +518,7 @@ "emoji_button.travel": "Viaggi & Luoghi", "empty_column.account_featured.me": "Non hai ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?", "empty_column.account_featured.other": "{acct} non ha ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?", - "empty_column.account_featured_other.unknown": "Questo account non ha ancora pubblicato nulla.", + "empty_column.account_featured_other.unknown": "Questo account non ha ancora messo nulla in evidenza.", "empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni", "empty_column.account_suspended": "Profilo sospeso", "empty_column.account_timeline": "Nessun post qui!", diff --git a/app/javascript/mastodon/locales/nan-TW.json b/app/javascript/mastodon/locales/nan-TW.json index 60639f9756a1cc..634ea4e2482a7a 100644 --- a/app/javascript/mastodon/locales/nan-TW.json +++ b/app/javascript/mastodon/locales/nan-TW.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "Hōo {name1} kap {name2} 跟tuè", "account.featured": "精選ê", "account.featured.accounts": "個人資料", + "account.featured.collections": "收藏", "account.featured.hashtags": "Hashtag", "account.featured_tags.last_status_at": "頂kái tī {date} Po文", "account.featured_tags.last_status_never": "無PO文", + "account.field_overflow": "展示規篇內容", "account.filters.all": "逐ē活動", "account.filters.boosts_toggle": "顯示轉PO", "account.filters.posts_boosts": "PO文kap轉PO", @@ -144,6 +146,9 @@ "account_edit.bio.title": "個人紹介", "account_edit.bio_modal.add_title": "加添個人紹介", "account_edit.bio_modal.edit_title": "編個人紹介", + "account_edit.button.add": "加 {item}", + "account_edit.button.delete": "Thâi {item}", + "account_edit.button.edit": "編 {item}", "account_edit.char_counter": "{currentLength}/{maxLength} ê字", "account_edit.column_button": "做好ah", "account_edit.column_title": "編輯個人資料", @@ -151,13 +156,28 @@ "account_edit.custom_fields.title": "自訂欄", "account_edit.display_name.placeholder": "Lí ê顯示ê名是lí ê名佇lí ê個人資料kap時間線出現ê方式。", "account_edit.display_name.title": "顯示ê名", + "account_edit.featured_hashtags.item": "hashtag", "account_edit.featured_hashtags.placeholder": "幫tsān別lâng認捌,kap緊緊接近使用lí收藏ê主題。", "account_edit.featured_hashtags.title": "特色ê hashtag", "account_edit.name_modal.add_title": "加添顯示ê名", "account_edit.name_modal.edit_title": "編顯示ê名", + "account_edit.profile_tab.button_label": "自訂", + "account_edit.profile_tab.hint.description": "Tsiah ê設定自訂用官方應用程式佇 {server} 所看著ê內容,毋kú in可能無用佇別ê服侍器或者用第三方應用程式ê用者。", + "account_edit.profile_tab.hint.title": "展示結果uân-á可能無kâng款", + "account_edit.profile_tab.show_featured.description": "「精選ê」是通揀ê分頁,其中lí ē當展示別ê口座。", + "account_edit.profile_tab.show_featured.title": "顯示「精選ê」分頁", + "account_edit.profile_tab.show_media.description": "「媒體」是通揀ê分頁,展示lí包含影像kap影片ê PO文。", + "account_edit.profile_tab.show_media.title": "展示「媒體」分頁", + "account_edit.profile_tab.show_media_replies.description": "若是啟用,媒體分頁展示lí ê PO文kap對別lâng ê PO文ê回應。", + "account_edit.profile_tab.show_media_replies.title": "佇媒體分頁內包含回應", "account_edit.profile_tab.subtitle": "自訂lí ê個人資料ê分頁kap顯示ê內容。", "account_edit.profile_tab.title": "個人資料分頁設定", "account_edit.save": "儲存", + "account_edit_tags.column_title": "編收藏ê hashtag", + "account_edit_tags.help_text": "收藏ê hashtag幫tsān用者發現kap hām lí ê個人資料互動。In會成做過濾器,佇lí ê個人資料頁ê活動內底出現。", + "account_edit_tags.search_placeholder": "編輯hashtag……", + "account_edit_tags.suggestions": "建議:", + "account_edit_tags.tag_status_count": "{count, plural, other {# 篇PO文}}", "account_note.placeholder": "Tshi̍h tse加註kha", "admin.dashboard.daily_retention": "註冊以後ê用者維持率(用kang計算)", "admin.dashboard.monthly_retention": "註冊以後ê用者維持率", @@ -260,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Tshuē別ê服侍器", "closed_registrations_modal.preamble": "因為Mastodon非中心化,所以bô論tī tá tsi̍t ê服侍器建立口座,lí lóng ē當跟tuè tsi̍t ê服侍器ê逐ê lâng,kap hām in交流。Lí iā ē當ka-tī起tsi̍t ê站!", "closed_registrations_modal.title": "註冊 Mastodon ê口座", + "collection.share_modal.share_link_label": "邀請分享ê連結", + "collection.share_modal.share_via_post": "PO佇Mastodon頂", + "collection.share_modal.share_via_system": "分享kàu……", + "collection.share_modal.title": "分享收藏", + "collection.share_modal.title_new": "分享lí ê新收藏!", + "collection.share_template_other": "緊看覓chit ê時行ê收藏:{link}", + "collection.share_template_own": "緊看覓我ê收藏:{link}", "collections.account_count": "{count, plural, other {# ê口座}}", "collections.accounts.empty_description": "加lí跟tuè ê口座,上tsē {count} ê", "collections.accounts.empty_title": "收藏內底無半項", @@ -296,11 +323,13 @@ "collections.no_collections_yet": "Iáu無收藏。", "collections.old_last_post_note": "頂改佇超過一禮拜進前PO文", "collections.remove_account": "Suá掉tsit ê口座", + "collections.report_collection": "檢舉tsit ê收藏", "collections.search_accounts_label": "Tshuē口座來加添……", "collections.search_accounts_max_reached": "Lí已經加kàu口座數ê盡磅ah。", "collections.sensitive": "敏感ê", "collections.topic_hint": "加 hashtag,幫tsān別lâng了解tsit ê收藏ê主題。", "collections.view_collection": "看收藏", + "collections.view_other_collections_by_user": "看tsit ê用者ê別ê收藏", "collections.visibility_public": "公共ê", "collections.visibility_public_hint": "佇tshiau-tshuē結果kap別位有推薦ê所在通發現。", "collections.visibility_title": "通看ê程度", @@ -435,6 +464,7 @@ "conversation.open": "顯示會話", "conversation.with": "Kap {names}", "copy_icon_button.copied": "有khóo-pih kàu tsián貼pang", + "copy_icon_button.copy_this_text": "Khóo-pih連結kàu tsián貼pang", "copypaste.copied": "有khóo-pih", "copypaste.copy_to_clipboard": "Khóo-pih kàu tsián貼pang", "directory.federated": "Uì知影ê Fediverse", @@ -964,6 +994,7 @@ "report.category.title_account": "個人資料", "report.category.title_status": "PO文", "report.close": "完成", + "report.collection_comment": "Lí是án-tsuánn beh檢舉tsit ê收藏?", "report.comment.title": "Lí敢有別ê想beh hōo guán知ê?", "report.forward": "轉送kàu {target}", "report.forward_hint": "Tsit ê口座是別ê服侍器ê。Kám iā beh送bô落名ê檢舉ê khóo-pih?", @@ -985,6 +1016,8 @@ "report.rules.title": "違反siánn-mih規則?", "report.statuses.subtitle": "請揀所有符合ê選項", "report.statuses.title": "Kám有任何PO文證明tsit ê檢舉?", + "report.submission_error": "檢舉bē當送", + "report.submission_error_details": "請檢查lí ê網路連線,等leh koh試。", "report.submit": "送出", "report.target": "檢舉 {target}", "report.thanks.take_action": "下kha是你控制所beh 佇Mastodon看ê內容ê選項:", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 4fd73e3cea5c5f..e3a698cd207b5e 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Hashtag-ë të zgjedhur", "account_edit.name_modal.add_title": "Shtoni emër në ekran", "account_edit.name_modal.edit_title": "Përpunoni emër në ekran", + "account_edit.profile_tab.button_label": "Përshtateni", + "account_edit.profile_tab.hint.description": "Këto rregullime përshtasin ç’shohin përdoruesit në {server} te aplikacionet zyrtare, por mund të mos kenë vend për përdorues në shërbyes të tjerë dhe aplikacione nga palë të treta.", + "account_edit.profile_tab.hint.title": "Ç’duket, ende varion", + "account_edit.profile_tab.show_featured.description": "“Të zgjedhura” është një skedë opsionale ku mund të shpalosni llogari të tjera.", + "account_edit.profile_tab.show_featured.title": "Shfaq skedë “Të zgjedhura”", + "account_edit.profile_tab.show_media.description": "“Media” është një skedë opsionale që shfaq postime tuajat që përmbajnë figura ose video.", + "account_edit.profile_tab.show_media.title": "Shfaq skedë “Media”", + "account_edit.profile_tab.show_media_replies.description": "Kur aktivizohet, skeda Media shfaq si postimet tuaja, ashtu edhe përgjigje te postime nga persona të tjerë.", + "account_edit.profile_tab.show_media_replies.title": "Përfshi te skeda “Media” përgjigje", "account_edit.profile_tab.subtitle": "Përshtatni skedat në profilin tuaj dhe ato çka shfaqet në to.", "account_edit.profile_tab.title": "Rregullime skede profili", "account_edit.save": "Ruaje", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 17f254a1e0f524..78b511735f727b 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -14,9 +14,16 @@ "about.powered_by": "En decentraliserad plattform for sociala medier, drivet av {mastodon}", "about.rules": "Serverregler", "account.account_note_header": "Personlig anteckning", + "account.activity": "Aktivitet", + "account.add_note": "Lägg till en personlig anteckning", "account.add_or_remove_from_list": "Lägg till i eller ta bort från listor", + "account.badges.admin": "Administratör", + "account.badges.blocked": "Blockerad", "account.badges.bot": "Bot", + "account.badges.domain_blocked": "Blockerad domän", "account.badges.group": "Grupp", + "account.badges.muted": "Tystad", + "account.badges.muted_until": "Tystade till {until}", "account.block": "Blockera @{name}", "account.block_domain": "Blockera domänen {domain}", "account.block_short": "Blockera", @@ -27,6 +34,7 @@ "account.direct": "Nämn @{name} privat", "account.disable_notifications": "Sluta meddela mig när @{name} skriver ett inlägg", "account.domain_blocking": "Blockerad domän", + "account.edit_note": "Redigera personlig anteckning", "account.edit_profile": "Redigera profil", "account.edit_profile_short": "Redigera", "account.enable_notifications": "Notifiera mig när @{name} gör inlägg", @@ -36,6 +44,7 @@ "account.familiar_followers_two": "Följs av {name1} och {name2}", "account.featured": "Utvalda", "account.featured.accounts": "Profiler", + "account.featured.collections": "Samlingar", "account.featured.hashtags": "Fyrkantstaggar", "account.featured_tags.last_status_at": "Senaste inlägg den {date}", "account.featured_tags.last_status_never": "Inga inlägg", @@ -93,6 +102,7 @@ "account.unmute_short": "Avtysta", "account_edit.column_button": "Klar", "account_edit.column_title": "Redigera profil", + "account_edit.profile_tab.button_label": "Anpassa", "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 270f130cade5b2..2ad5a23d5092f2 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Öne çıkan etiketler", "account_edit.name_modal.add_title": "Görünen ad ekle", "account_edit.name_modal.edit_title": "Görünen adı düzenle", + "account_edit.profile_tab.button_label": "Özelleştir", + "account_edit.profile_tab.hint.description": "Bu ayarlar kullanıcıların resmi uygulamalarda {server} üzerinde gördüklerini özelleştirir, ancak diğer sunuculardaki ve üçüncü taraf uygulamalardaki kullanıcılar için geçerli olmayabilir.", + "account_edit.profile_tab.hint.title": "Görünümler yine de farklı olabilir", + "account_edit.profile_tab.show_featured.description": "\"Öne Çıkanlar\" diğer hesapları sergileyebileceğiniz isteğe bağlı bir sekmedir.", + "account_edit.profile_tab.show_featured.title": "\"Öne Çıkanlar\" sekmesini göster", + "account_edit.profile_tab.show_media.description": "\"Medya\" resim veya video içeren gönderilerinizi gösteren isteğe bağlı bir sekmedir.", + "account_edit.profile_tab.show_media.title": "\"Medya\" sekmesini göster", + "account_edit.profile_tab.show_media_replies.description": "Etkinleştirildiğinde Medya sekmesi hem kendi gönderilerinizi hem de diğer kişilerin gönderilerine verdiğiniz yanıtları gösterir.", + "account_edit.profile_tab.show_media_replies.title": "\"Medya\" sekmesinde yanıtları göster", "account_edit.profile_tab.subtitle": "Profilinizdeki sekmeleri ve bunların görüntülediği bilgileri özelleştirin.", "account_edit.profile_tab.title": "Profil sekme ayarları", "account_edit.save": "Kaydet", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 5a62060560746c..3aa940e9524e13 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -14,8 +14,13 @@ "about.powered_by": "Децентралізовані соціальні мережі від {mastodon}", "about.rules": "Правила сервера", "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": "Блокований домен", "account.badges.group": "Група", "account.block": "Заблокувати @{name}", "account.block_domain": "Заблокувати домен {domain}", @@ -25,6 +30,7 @@ "account.copy": "Копіювати посилання на профіль", "account.direct": "Особиста згадка @{name}", "account.disable_notifications": "Не повідомляти мене про дописи @{name}", + "account.edit_note": "Редагувати особисту нотатку", "account.edit_profile": "Редагувати профіль", "account.edit_profile_short": "Редагувати", "account.enable_notifications": "Повідомляти мене про дописи @{name}", @@ -37,6 +43,13 @@ "account.featured.hashtags": "Хештеги", "account.featured_tags.last_status_at": "Останній допис {date}", "account.featured_tags.last_status_never": "Немає дописів", + "account.field_overflow": "Показати повністю", + "account.filters.all": "Усі дії", + "account.filters.boosts_toggle": "Показати поширення", + "account.filters.posts_boosts": "Дописи й поширення", + "account.filters.posts_only": "Дописи", + "account.filters.posts_replies": "Дописи й відповіді", + "account.filters.replies_toggle": "Показати відповіді", "account.follow": "Підписатися", "account.follow_back": "Стежити також", "account.follow_back_short": "Підписатись навзаєм", @@ -55,12 +68,27 @@ "account.go_to_profile": "Перейти до профілю", "account.hide_reblogs": "Сховати поширення від @{name}", "account.in_memoriam": "Пам'ятник.", + "account.joined_long": "Долучилися {date}", "account.joined_short": "Дата приєднання", "account.languages": "Змінити обрані мови", "account.link_verified_on": "Права власності на це посилання були перевірені {date}", "account.locked_info": "Це закритий обліковий запис. Власник вручну обирає, хто може на нього підписуватися.", "account.media": "Медіа", "account.mention": "Згадати @{name}", + "account.menu.add_to_list": "Додати до списку…", + "account.menu.block": "Блокувати обліковий запис", + "account.menu.block_domain": "Блокувати {domain}", + "account.menu.copied": "Посилання на обліковий запис скопійовано до буферу обміну", + "account.menu.copy": "Копіювати посилання", + "account.menu.direct": "Особиста згадка", + "account.menu.mention": "Згадка", + "account.menu.note.description": "Видно лише вам", + "account.menu.open_original_page": "Переглянути на {domain}", + "account.menu.remove_follower": "Вилучити підписника", + "account.menu.report": "Скарга на обліковий запис", + "account.menu.share": "Поділитися…", + "account.menu.unblock": "Розблокувати обліковий запис", + "account.menu.unblock_domain": "Розблокувати {domain}", "account.moved_to": "{name} вказує, що їхній новий обліковий запис тепер:", "account.mute": "Приховати @{name}", "account.mute_notifications_short": "Не сповіщати", @@ -68,6 +96,7 @@ "account.muted": "Приховується", "account.mutual": "Підписані навзаєм", "account.no_bio": "Немає опису.", + "account.node_modal.save": "Зберегти", "account.open_original_page": "Відкрити оригінальну сторінку", "account.posts": "Дописи", "account.posts_with_replies": "Дописи й відповіді", @@ -87,6 +116,9 @@ "account.unmute": "Не приховувати @{name}", "account.unmute_notifications_short": "Сповіщати", "account.unmute_short": "Не приховувати", + "account_edit.save": "Зберегти", + "account_edit_tags.suggestions": "Пропозиції:", + "account_edit_tags.tag_status_count": "{count, plural, one {# допис} few {# дописи} many {# дописів} other {# допису}}", "account_note.placeholder": "Натисніть, щоб додати примітку", "admin.dashboard.daily_retention": "Щоденний показник утримання користувачів після реєстрації", "admin.dashboard.monthly_retention": "Щомісячний показник утримання користувачів після реєстрації", @@ -110,6 +142,23 @@ "alt_text_modal.describe_for_people_with_visual_impairments": "Опишіть цю ідею для людей із порушеннями зору…", "alt_text_modal.done": "Готово", "announcement.announcement": "Оголошення", + "annual_report.announcement.action_dismiss": "Ні, дякую", + "annual_report.nav_item.badge": "Нове", + "annual_report.shared_page.donate": "Підтримати", + "annual_report.summary.archetype.booster.name": "Лучник", + "annual_report.summary.archetype.lurker.name": "Стоїк", + "annual_report.summary.archetype.oracle.name": "Провидець", + "annual_report.summary.archetype.pollster.name": "Допитливий", + "annual_report.summary.archetype.replier.name": "Метелик", + "annual_report.summary.archetype.title_public": "Архетип {name}", + "annual_report.summary.archetype.title_self": "Ваш архетип", + "annual_report.summary.close": "Закрити", + "annual_report.summary.copy_link": "Копіювати посилання", + "annual_report.summary.followers.new_followers": "{count, plural, one {новий підписник} few {нові підписники} many {нових підписників} other {нового підписника}}", + "annual_report.summary.highlighted_post.boost_count": "Допис поширено {count, plural, one {# раз} few {# рази} many {# разів} other {# раза}}.", + "annual_report.summary.highlighted_post.favourite_count": "Допис уподобано {count, plural, one {# раз} few {# рази} many {# разів} other {# раза}}.", + "annual_report.summary.highlighted_post.reply_count": "Допис отримав {count, plural, one {# відповідь} few {# відповіді} many {# відповідей} other {# відповіді}}.", + "annual_report.summary.highlighted_post.title": "Найпопулярніший допис", "annual_report.summary.most_used_app.most_used_app": "найчастіше використовуваний застосунок", "annual_report.summary.most_used_hashtag.most_used_hashtag": "найчастіший хештег", "annual_report.summary.new_posts.new_posts": "нові дописи", @@ -145,6 +194,9 @@ "closed_registrations_modal.find_another_server": "Знайти інший сервер", "closed_registrations_modal.preamble": "Mastodon децентралізований, тож незалежно від того, де ви створюєте свій обліковий запис, ви зможете слідкувати та взаємодіяти з будь-ким на цьому сервері. Ви навіть можете розмістити його самостійно!", "closed_registrations_modal.title": "Реєстрація на Mastodon", + "collections.detail.accounts_heading": "Облікові записи", + "collections.edit_details": "Редагувати подробиці", + "collections.remove_account": "Вилучити обліковий запис", "column.about": "Про застосунок", "column.blocks": "Заблоковані користувачі", "column.bookmarks": "Закладки", @@ -205,6 +257,7 @@ "confirmations.delete.confirm": "Видалити", "confirmations.delete.message": "Ви впевнені, що хочете видалити цей допис?", "confirmations.delete.title": "Видалити допис?", + "confirmations.delete_collection.confirm": "Видалити", "confirmations.delete_list.confirm": "Видалити", "confirmations.delete_list.message": "Ви впевнені, що хочете видалити цей список назавжди?", "confirmations.delete_list.title": "Видалити список?", @@ -381,6 +434,8 @@ "follow_suggestions.who_to_follow": "На кого підписатися", "followed_tags": "Відстежувані хештеґи", "footer.about": "Про проєкт", + "footer.about_mastodon": "Про Mastodon", + "footer.about_server": "Про {domain}", "footer.about_this_server": "Про сервер", "footer.directory": "Каталог профілів", "footer.get_app": "Завантажити застосунок", @@ -825,7 +880,7 @@ "search_results.no_results": "Жодних результатів.", "search_results.no_search_yet": "Спробуйте пошукати дописи, профілі або хештеґи.", "search_results.see_all": "Показати все", - "search_results.statuses": "Дописів", + "search_results.statuses": "Дописи", "search_results.title": "Пошук «{q}»", "server_banner.about_active_users": "Люди, які використовують цей сервер протягом останніх 30 днів (Щомісячні Активні Користувачі)", "server_banner.active_users": "активні користувачі", @@ -938,6 +993,7 @@ "tabs_bar.notifications": "Сповіщення", "tabs_bar.publish": "Новий допис", "tabs_bar.search": "Пошук", + "tag.remove": "Вилучити", "terms_of_service.effective_as_of": "Ефективний на {date}", "terms_of_service.title": "Умови використання", "terms_of_service.upcoming_changes_on": "Майбутні зміни {date}", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index ad14df1a1a92e3..711ae4dd3528c2 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -78,7 +78,7 @@ "account.languages": "Đổi ngôn ngữ mong muốn", "account.link_verified_on": "Liên kết này đã được xác minh vào {date}", "account.locked_info": "Đây là tài khoản riêng tư. Chủ tài khoản tự mình xét duyệt các yêu cầu theo dõi.", - "account.media": "Media", + "account.media": "Phương tiện", "account.mention": "Nhắc đến @{name}", "account.menu.add_to_list": "Thêm vào danh sách…", "account.menu.block": "Chặn người này", @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "Hashtag thường dùng", "account_edit.name_modal.add_title": "Thêm tên gọi", "account_edit.name_modal.edit_title": "Sửa tên gọi", + "account_edit.profile_tab.button_label": "Tùy chỉnh", + "account_edit.profile_tab.hint.description": "Những thiết lập này tùy chỉnh những gì người dùng thấy trên {server} trong ứng dụng chính thức, nhưng có thể sẽ không thấy trên các máy chủ khác hoặc các ứng dụng thứ ba.", + "account_edit.profile_tab.hint.title": "Hiển thị vẫn đa dạng", + "account_edit.profile_tab.show_featured.description": "‘Nêu bật’ là một tab tùy chọn, giúp bạn nêu bật các tài khoản khác.", + "account_edit.profile_tab.show_featured.title": "Hiện tab ‘Nêu bật’", + "account_edit.profile_tab.show_media.description": "‘Phương tiện’ là một tab tùy chọn, hiển thị những tút có chứa hình ảnh hoặc video của bạn.", + "account_edit.profile_tab.show_media.title": "Hiện tab ‘Phương tiện’", + "account_edit.profile_tab.show_media_replies.description": "Nếu bật, tab Phương tiện sẽ hiện cả những tút của bạn lẫn những trả lời tới tút của người khác.", + "account_edit.profile_tab.show_media_replies.title": "Bao gồm tút dạng trả lời trong tab ‘Phương tiện’", "account_edit.profile_tab.subtitle": "Tùy chỉnh tab trên hồ sơ của bạn và những gì chúng hiển thị.", "account_edit.profile_tab.title": "Thiết lập tab hồ sơ", "account_edit.save": "Lưu", @@ -271,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Tìm máy chủ khác", "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", + "collection.share_modal.share_link_label": "Mời chia sẻ liên kết", + "collection.share_modal.share_via_post": "Đăng lên Mastodon", + "collection.share_modal.share_via_system": "Chia sẻ qua…", + "collection.share_modal.title": "Chia sẻ collection", + "collection.share_modal.title_new": "Chia sẻ collection mới của bạn!", + "collection.share_template_other": "Xem ngay collection tuyệt vời này: {link}", + "collection.share_template_own": "Xem ngay collection mới của tôi: {link}", "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", @@ -448,6 +464,7 @@ "conversation.open": "Xem toàn bộ tin nhắn", "conversation.with": "Với {names}", "copy_icon_button.copied": "Đã sao chép vào bộ nhớ tạm", + "copy_icon_button.copy_this_text": "Sao chép liên kết vào clipboard", "copypaste.copied": "Đã sao chép", "copypaste.copy_to_clipboard": "Sao chép vào bộ nhớ tạm", "directory.federated": "Ở mạng liên hợp", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 6295ca9b50ec0a..be8a700c037cde 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "精选话题标签", "account_edit.name_modal.add_title": "添加显示名称", "account_edit.name_modal.edit_title": "编辑显示名称", + "account_edit.profile_tab.button_label": "自定义", + "account_edit.profile_tab.hint.description": "这些设置可以自定义用户使用官方应用程序时在 {server} 上可以看到的内容,但可能不适用于外站及第三方应用程序上的用户。", + "account_edit.profile_tab.hint.title": "实际的显示情况可能不同", + "account_edit.profile_tab.show_featured.description": "你可以使用可选的“精选”选项卡来展示其他账号。", + "account_edit.profile_tab.show_featured.title": "显示“精选”选项卡", + "account_edit.profile_tab.show_media.description": "可选的“媒体”选项卡可以显示你发布的包含图片或视频的嘟文。", + "account_edit.profile_tab.show_media.title": "显示“媒体”选项卡", + "account_edit.profile_tab.show_media_replies.description": "如果启用,媒体选项卡将同时显示你的嘟文及你对其他人嘟文的回复。", + "account_edit.profile_tab.show_media_replies.title": "在“媒体”选项卡中包括回复", "account_edit.profile_tab.subtitle": "自定义你个人资料的标签页及其显示的内容。", "account_edit.profile_tab.title": "个人资料标签页设置", "account_edit.save": "保存", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 31443295bb4cd7..20cad7639f248e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -161,6 +161,15 @@ "account_edit.featured_hashtags.title": "推薦主題標籤", "account_edit.name_modal.add_title": "新增顯示名稱", "account_edit.name_modal.edit_title": "編輯顯示名稱", + "account_edit.profile_tab.button_label": "自訂", + "account_edit.profile_tab.hint.description": "這些設定能自訂使用者在官方應用程式中於 {server} 所見之內容,但可能不適用於其他伺服器或第三方應用程式的使用者。", + "account_edit.profile_tab.hint.title": "檢視結果仍可能有所不同", + "account_edit.profile_tab.show_featured.description": "「精選內容」為您能展現其他帳號之可選分頁。", + "account_edit.profile_tab.show_featured.title": "顯示「精選內容」分頁", + "account_edit.profile_tab.show_media.description": "「媒體」為顯示您包含圖片或影片嘟文之可選分頁。", + "account_edit.profile_tab.show_media.title": "顯示「媒體」分頁", + "account_edit.profile_tab.show_media_replies.description": "當啟用時,媒體分頁將顯示您的嘟文與對其他人嘟文之回嘟。", + "account_edit.profile_tab.show_media_replies.title": "於「媒體」分頁中包含回嘟", "account_edit.profile_tab.subtitle": "自訂您個人檔案之分頁與內容。", "account_edit.profile_tab.title": "個人檔案分頁設定", "account_edit.save": "儲存", diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml index 01a441b556c0c7..9b8d428b39a463 100644 --- a/config/locales/doorkeeper.sv.yml +++ b/config/locales/doorkeeper.sv.yml @@ -83,6 +83,10 @@ 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: + one: code_challenge_method måste vara %{challenge_methods}. + other: code_challenge_method måste vara en av %{challenge_methods}. + zero: Auktoriseringsservern stöder inte PKCE eftersom det inte finns några accepterade code_challenge_metod-värden. 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/nan-TW.yml b/config/locales/nan-TW.yml index 5d15b9685d6c0c..3b89a0270745c8 100644 --- a/config/locales/nan-TW.yml +++ b/config/locales/nan-TW.yml @@ -263,6 +263,7 @@ nan-TW: demote_user_html: "%{name} kā用者 %{target} 降級" destroy_announcement_html: "%{name} kā公告 %{target} thâi掉ah" destroy_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue取消封鎖ah" + destroy_collection_html: "%{name} kā %{target} ê收藏thâi掉" destroy_custom_emoji_html: "%{name} kā 新ê emoji %{target} thâi掉ah" destroy_domain_allow_html: "%{name} 無允准 %{target} 域名加入聯邦" destroy_domain_block_html: "%{name} 取消封鎖域名 %{target}" @@ -302,6 +303,7 @@ nan-TW: unsilence_account_html: "%{name} 取消限制 %{target} ê口座" unsuspend_account_html: "%{name} kā %{target} ê口座恢復權限ah" update_announcement_html: "%{name} kā公告 %{target} 更新ah" + update_collection_html: "%{name} kā %{target} ê收藏更新" update_custom_emoji_html: "%{name} kā 新ê emoji %{target} 更新ah" update_domain_block_html: "%{name} kā %{target} ê域名封鎖更新ah" update_ip_block_html: "%{name} 改變 IP %{target} ê規則" @@ -675,6 +677,7 @@ nan-TW: cancel: 取消 category: 類別 category_description_html: Tsit ê 受檢舉ê口座kap/á是內容,ē佇kap tsit ê口座ê聯絡內底引用。 + collections: 收藏 (%{count}) comment: none: 無 comment_description_html: 為著提供其他資訊,%{name} 寫: @@ -710,6 +713,7 @@ nan-TW: resolved_msg: 檢舉成功解決ah! skip_to_actions: 跳kàu行動 status: 狀態 + statuses: PO文 (%{count}) statuses_description_html: 冒犯ê內容ē引用tī kap受檢舉口座ê聯絡 summary: action_preambles: @@ -1815,6 +1819,19 @@ nan-TW: activity: 上尾ê活動 browser: 瀏覽器 browsers: + alipay: Alipay + blackberry: BlackBerry + chrome: Chrome + edge: Microsoft Edge + electron: Electron + firefox: Firefox + generic: 毋知影ê瀏覽器 + huawei_browser: 華為ê瀏覽器 + ie: Internet Explorer + micro_messenger: 微信 + nokia: Nokia S40 Ovi 瀏覽器 + opera: Opera + otter: Otter qq: QQ 瀏覽器 safari: Safari uc_browser: UC 瀏覽器 diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index a2839293fee884..66b6ab26eac442 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -164,6 +164,7 @@ sv: name: Offentligt namn på rollen, om rollen är inställd på att visas som ett emblem permissions_as_keys: Användare med denna roll kommer ha tillgång till... position: Högre roll avgör konfliktlösning i vissa situationer. Vissa åtgärder kan endast utföras på roller med lägre prioritet + require_2fa: Användare med denna roll kommer att krävas för att ställa in tvåfaktorsautentisering för att använda Mastodon username_block: allow_with_approval: I stället för att förhindra direkt registrering kräver matchning av registreringar ditt godkännande comparison: Var uppmärksam på Scunthorpe Problem vid blockering av partiella matcher @@ -223,6 +224,7 @@ sv: email: E-postadress expires_in: Utgår efter fields: Profil-metadata + filter_action: Filtrera åtgärd header: Sidhuvud honeypot: "%{label} (fyll inte i)" inbox_url: Webbadress för ombudsinkorg @@ -387,6 +389,7 @@ sv: name: Namn permissions_as_keys: Behörigheter position: Prioritet + require_2fa: Kräv tvåfaktorsautentisering username_block: allow_with_approval: Tillåt registreringar med godkännande comparison: Jämförelsesätt diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 8311e29d66a9f2..72a8b4cdbb6c8e 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -53,6 +53,7 @@ sv: label: Ändra roll no_role: Ingen roll title: Ändra roll för %{username} + collections: Samlingar confirm: Bekräfta confirmed: Bekräftad confirming: Bekräftande @@ -263,6 +264,7 @@ sv: demote_user_html: "%{name} nedgraderade användare %{target}" destroy_announcement_html: "%{name} raderade kungörelsen %{target}" destroy_canonical_email_block_html: "%{name} avblockerade e-post med hash%{target}" + destroy_collection_html: "%{name} tog bort samlingen av %{target}" destroy_custom_emoji_html: "%{name} raderade emoji %{target}" destroy_domain_allow_html: "%{name} raderade domän %{target} från vitlistan" destroy_domain_block_html: "%{name} avblockerade domänen %{target}" @@ -302,6 +304,7 @@ sv: unsilence_account_html: "%{name} tog bort begränsning av %{target}s konto" unsuspend_account_html: "%{name} ångrade avstängningen av %{target}s konto" update_announcement_html: "%{name} uppdaterade kungörelsen %{target}" + update_collection_html: "%{name} uppdaterade samlingen av %{target}" update_custom_emoji_html: "%{name} uppdaterade emoji %{target}" update_domain_block_html: "%{name} uppdaterade domän-block för %{target}" update_ip_block_html: "%{name} ändrade regel för IP %{target}" @@ -339,9 +342,13 @@ sv: updated_msg: Kungörelsen uppdaterades! collections: accounts: Konton + collection_title: Samling av %{name} + contents: Innehåll number_of_accounts: one: 1 konto other: "%{count} konton" + open: Öppna + view_publicly: Visa offentligt critical_update_pending: Kritisk uppdatering väntar custom_emojis: assign_category: Tilldela kategori @@ -681,6 +688,7 @@ sv: cancel: Avbryt category: Kategori category_description_html: Anledningen till att kontot och/eller innehållet rapporterades kommer att visas i kommunikation med det rapporterade kontot + collections: Samlingar (%{count}) comment: none: Ingen comment_description_html: 'För att ge mer information, skrev %{name}:' @@ -716,6 +724,7 @@ sv: resolved_msg: Anmälan har lösts framgångsrikt! skip_to_actions: Hoppa till åtgärder status: Status + statuses: Inlägg (%{count}) statuses_description_html: Stötande innehåll kommer att citeras i kommunikationen med det rapporterade kontot summary: action_preambles: @@ -804,6 +813,7 @@ sv: view_devops_description: Ger användare tillgång till instrumentpanelerna Sidekiq och pgHero view_feeds: Visa live- och ämnesflöden view_feeds_description: Ger användare tillgång till live-och ämnesflöden oavsett serverinställningar + requires_2fa: Kräver tvåfaktorsautentisering title: Roller rules: add_new: Lägg till regel @@ -2022,6 +2032,8 @@ sv: past_preamble_html: Vi har ändrat våra användarvillkor sedan ditt senaste besök. Vi uppmanar dig att granska de uppdaterade villkoren. review_link: Granska villkoren för tjänsten title: Villkoren för tjänsten i %{domain} ändras + themes: + default: Mastodon time: formats: default: "%d %b %Y, %H:%M" @@ -2046,6 +2058,8 @@ sv: recovery_codes: Backup återställningskod recovery_codes_regenerated: Återställningskoder genererades på nytt recovery_instructions_html: Om du någonsin tappar åtkomst till din telefon kan du använda någon av återställningskoderna nedan för att återställa åtkomst till ditt konto. Håll återställningskoderna säkra . Du kan till exempel skriva ut dem och lagra dem med andra viktiga dokument. + resume_app_authorization: Fortsätt app-autentisering + role_requirement: "%{domain} kräver att du ställer in tvåfaktorsautentisering innan du kan använda Mastodon." webauthn: Säkerhetsnycklar user_mailer: announcement_published: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index e6ff16639201d8..11dcf51c6eb73f 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -23,7 +23,7 @@ uk: many: Дописів one: Допис other: Дописів - posts_tab_heading: Дописів + posts_tab_heading: Дописи self_follow_error: Ви не можете стежити за власним обліковим записом admin: account_actions: @@ -335,6 +335,8 @@ uk: unpublish: Скасувати публікацію unpublished_msg: Оголошення успішно приховано! updated_msg: Оголошення успішно оновлено! + collections: + open: Відкрити critical_update_pending: Очікується критичне оновлення custom_emojis: assign_category: Призначити категорію @@ -498,6 +500,7 @@ uk: fasp: debug: callbacks: + created_at: Створено delete: Видалити providers: delete: Видалити @@ -701,6 +704,7 @@ uk: resolved_msg: Скаргу успішно вирішено! skip_to_actions: Перейти до дій status: Стан + statuses: Дописи (%{count}) statuses_description_html: Замінений вміст буде цитований у спілкуванні з обліковим записом, на який поскаржилися summary: action_preambles: @@ -794,11 +798,16 @@ uk: title: Ролі rules: add_new: Додати правило + add_translation: Додати переклад delete: Видалити description_html: Хоча більшість заявляє про прочитання та погодження з умовами обслуговування, як правило, люди не читають їх до появи проблеми. Спростіть перегляд правил вашого сервера, зробивши їх у вигляді маркованого списку. Спробуйте створити короткі та просі правила, але не розділяйте їх на багато окремих частин. edit: Змінити правило empty: Жодних правил сервера ще не визначено. + move_down: Посунути вниз + move_up: Посунути вгору title: Правила сервера + translation: Переклад + translations: Переклади settings: about: manage_rules: Керувати правилами сервера @@ -1278,6 +1287,10 @@ uk: hint_html: "Підказка: ми не будемо запитувати ваш пароль впродовж наступної години." invalid_password: Невірний пароль prompt: Підтвердіть пароль для продовження + color_scheme: + auto: Авто + dark: Темна + light: Світла crypto: errors: invalid_key: не є припустимим ключем Ed25519 або Curve25519 From c9282d980d9cce4a3d57dc6b80a8e4bc1c55edde Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:04:53 +0100 Subject: [PATCH 36/89] Update dependency addressable to v2.8.9 (#38013) 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 74a5404a93e468..360ee4a43d5d8a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -89,7 +89,7 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.8.8) + addressable (2.8.9) public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) From 68163e59756c3131f0451f87f80d5418e8898985 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:06:15 +0000 Subject: [PATCH 37/89] Update dependency brakeman to v8.0.4 (#38002) 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 360ee4a43d5d8a..7f9f82cb39b939 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM blurhash (0.1.8) bootsnap (1.23.0) msgpack (~> 1.2) - brakeman (8.0.2) + brakeman (8.0.4) racc browser (6.2.0) builder (3.3.0) From 74f7a5e8e92f6ddfd8af046752743a434ceb6174 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Mar 2026 04:07:50 -0500 Subject: [PATCH 38/89] Remove unneeded `stylelint-config-prettier-scss` package (#37973) --- package.json | 1 - stylelint.config.js | 2 +- yarn.lock | 13 ------------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/package.json b/package.json index 4bed416daf4483..2aa2ecd1cb3218 100644 --- a/package.json +++ b/package.json @@ -185,7 +185,6 @@ "react-test-renderer": "^18.2.0", "storybook": "^10.0.5", "stylelint": "^17.0.0", - "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-standard-scss": "^17.0.0", "typescript": "~5.9.0", "typescript-eslint": "^8.55.0", diff --git a/stylelint.config.js b/stylelint.config.js index ff47848a3ef9cb..1db58de646d4cd 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -1,5 +1,5 @@ module.exports = { - extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], + extends: ['stylelint-config-standard-scss'], ignoreFiles: [ 'app/javascript/styles/mastodon/reset.scss', 'coverage/**/*', diff --git a/yarn.lock b/yarn.lock index 5d13c3e405f8a6..066cb3144e44a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2930,7 +2930,6 @@ __metadata: storybook: "npm:^10.0.5" stringz: "npm:^2.1.0" stylelint: "npm:^17.0.0" - stylelint-config-prettier-scss: "npm:^1.0.0" stylelint-config-standard-scss: "npm:^17.0.0" substring-trie: "npm:^1.0.2" tesseract.js: "npm:^7.0.0" @@ -13332,18 +13331,6 @@ __metadata: languageName: node linkType: hard -"stylelint-config-prettier-scss@npm:^1.0.0": - version: 1.0.0 - resolution: "stylelint-config-prettier-scss@npm:1.0.0" - peerDependencies: - stylelint: ">=15.0.0" - bin: - stylelint-config-prettier-scss: bin/check.js - stylelint-config-prettier-scss-check: bin/check.js - checksum: 10c0/4d5e1d1c200d4611b5b7bd2d2528cc9e301f26645802a2774aec192c4c2949cbf5a0147eba8b2e6e4ff14a071b03024f3034bb1b4fda37a8ed5a0081a9597d4d - languageName: node - linkType: hard - "stylelint-config-recommended-scss@npm:^17.0.0": version: 17.0.0 resolution: "stylelint-config-recommended-scss@npm:17.0.0" From e3a53b6f5ac805e7f608ce34bff603425773cb74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:08:04 +0000 Subject: [PATCH 39/89] Update Node.js to 24.14 (#37971) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 12fd1fc27773ea..fd655f8a35b4f3 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.13 +24.14 From c86a8f879862097b844838efc8e8b443b5953554 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Mar 2026 04:27:04 -0500 Subject: [PATCH 40/89] Use `file_field` support for array to `accept` attribute (#37844) --- app/models/form/admin_settings.rb | 2 ++ app/views/admin/custom_emojis/new.html.haml | 2 +- app/views/admin/settings/branding/show.html.haml | 4 ++-- app/views/settings/profiles/show.html.haml | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 77f23ae5a474bb..29523be542ea23 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -85,6 +85,8 @@ class Form::AdminSettings authorized_fetch: :authorized_fetch_mode?, }.freeze + UPLOAD_MIME_TYPES = %w(image/jpeg image/png image/gif image/webp).freeze + DESCRIPTION_LIMIT = 200 DOMAIN_BLOCK_AUDIENCES = %w(disabled users all).freeze REGISTRATION_MODES = %w(open approved none).freeze diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/admin/custom_emojis/new.html.haml index acf2231b8ec1de..99f5aa06d84c17 100644 --- a/app/views/admin/custom_emojis/new.html.haml +++ b/app/views/admin/custom_emojis/new.html.haml @@ -12,7 +12,7 @@ .fields-group = f.input :image, wrapper: :with_label, - input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(',') }, + input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT)) .actions diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 22116167eb36b8..02da7b2c8ff456 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -44,7 +44,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :favicon, as: :file, - input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + input_html: { accept: f.object.class::UPLOAD_MIME_TYPES }, wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group @@ -58,7 +58,7 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :app_icon, as: :file, - input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + input_html: { accept: f.object.class::UPLOAD_MIME_TYPES }, wrapper: :with_block_label .fields-row__column.fields-row__column-6.fields-group diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 3815eb7fc2e096..8df50a5a8ce901 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -35,7 +35,7 @@ .fields-group = f.input :avatar, hint: t('simple_form.hints.defaults.avatar', dimensions: Account::Avatar::AVATAR_GEOMETRY, size: number_to_human_size(Account::Avatar::AVATAR_LIMIT)), - input_html: { accept: Account::Avatar::AVATAR_IMAGE_MIME_TYPES.join(',') }, + input_html: { accept: Account::Avatar::AVATAR_IMAGE_MIME_TYPES }, wrapper: :with_block_label .fields-row__column.fields-row__column-6 @@ -51,7 +51,7 @@ .fields-group = f.input :header, hint: t('simple_form.hints.defaults.header', dimensions: Account::Header::HEADER_GEOMETRY, size: number_to_human_size(Account::Header::HEADER_LIMIT)), - input_html: { accept: Account::Header::HEADER_IMAGE_MIME_TYPES.join(',') }, + input_html: { accept: Account::Header::HEADER_IMAGE_MIME_TYPES }, wrapper: :with_block_label .fields-row__column.fields-row__column-6 From 178e8c23b10a08a13210fc3a22ec7607bcbc30b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:30:17 +0000 Subject: [PATCH 41/89] Update dependency eslint-plugin-jsdoc to v62 (#37433) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 99 ++++++++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 2aa2ecd1cb3218..9428c0e08f776e 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-import": "~2.32.0", - "eslint-plugin-jsdoc": "^61.7.1", + "eslint-plugin-jsdoc": "^62.0.0", "eslint-plugin-jsx-a11y": "~6.10.2", "eslint-plugin-promise": "~7.2.1", "eslint-plugin-react": "^7.37.4", diff --git a/yarn.lock b/yarn.lock index 066cb3144e44a9..0d5cf787bdbbd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2093,16 +2093,16 @@ __metadata: languageName: node linkType: hard -"@es-joy/jsdoccomment@npm:~0.78.0": - version: 0.78.0 - resolution: "@es-joy/jsdoccomment@npm:0.78.0" +"@es-joy/jsdoccomment@npm:~0.84.0": + version: 0.84.0 + resolution: "@es-joy/jsdoccomment@npm:0.84.0" dependencies: "@types/estree": "npm:^1.0.8" - "@typescript-eslint/types": "npm:^8.46.4" - comment-parser: "npm:1.4.1" - esquery: "npm:^1.6.0" - jsdoc-type-pratt-parser: "npm:~7.0.0" - checksum: 10c0/be18b8149303e8e7c9414b0b0453a0fa959c1c8db6f721b75178336e01b65a9f251db98ecfedfb1b3cfa5e717f3e2abdb06a0f8dbe45d3330a62262c5331c327 + "@typescript-eslint/types": "npm:^8.54.0" + comment-parser: "npm:1.4.5" + esquery: "npm:^1.7.0" + jsdoc-type-pratt-parser: "npm:~7.1.1" + checksum: 10c0/b5562c176dde36cd2956bb115b79229d2253b27d6d7e52820eb55c509f75a72048ae8ea8d57193b33be42728c1aa7a5ee20937b4967175291cb4ae60fdda318d languageName: node linkType: hard @@ -2873,7 +2873,7 @@ __metadata: eslint-import-resolver-typescript: "npm:^4.2.5" eslint-plugin-formatjs: "npm:^5.3.1" eslint-plugin-import: "npm:~2.32.0" - eslint-plugin-jsdoc: "npm:^61.7.1" + eslint-plugin-jsdoc: "npm:^62.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" @@ -4815,13 +4815,20 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.55.0, @typescript-eslint/types@npm:^8.46.4, @typescript-eslint/types@npm:^8.55.0": +"@typescript-eslint/types@npm:8.55.0": version: 8.55.0 resolution: "@typescript-eslint/types@npm:8.55.0" checksum: 10c0/dc572f55966e2f0fee149e5d5e42a91cedcdeac451bff29704eb701f9336f123bbc7d7abcfbda717f9e1ef6b402fa24679908bc6032e67513287403037ef345f languageName: node linkType: hard +"@typescript-eslint/types@npm:^8.54.0, @typescript-eslint/types@npm:^8.55.0": + version: 8.56.1 + resolution: "@typescript-eslint/types@npm:8.56.1" + checksum: 10c0/e5a0318abddf0c4f98da3039cb10b3c0601c8601f7a9f7043630f0d622dabfe83a4cd833545ad3531fc846e46ca2874377277b392c2490dffec279d9242d827b + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.55.0": version: 8.55.0 resolution: "@typescript-eslint/typescript-estree@npm:8.55.0" @@ -5283,12 +5290,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.15.0, acorn@npm:^8.8.2": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" +"acorn@npm:^8.15.0, acorn@npm:^8.16.0, acorn@npm:^8.8.2": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" bin: acorn: bin/acorn - checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e languageName: node linkType: hard @@ -6176,10 +6183,10 @@ __metadata: languageName: node linkType: hard -"comment-parser@npm:1.4.1": - version: 1.4.1 - resolution: "comment-parser@npm:1.4.1" - checksum: 10c0/d6c4be3f5be058f98b24f2d557f745d8fe1cc9eb75bebbdccabd404a0e1ed41563171b16285f593011f8b6a5ec81f564fb1f2121418ac5cbf0f49255bf0840dd +"comment-parser@npm:1.4.5": + version: 1.4.5 + resolution: "comment-parser@npm:1.4.5" + checksum: 10c0/6a6a74697c79927e3bd42bde9608a471f1a9d4995affbc22fa3364cc42b4017f82ef477431a1558b0b6bef959f9bb6964c01c1bbfc06a58ba1730dec9c423b44 languageName: node linkType: hard @@ -7311,27 +7318,27 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^61.7.1": - version: 61.7.1 - resolution: "eslint-plugin-jsdoc@npm:61.7.1" +"eslint-plugin-jsdoc@npm:^62.0.0": + version: 62.7.1 + resolution: "eslint-plugin-jsdoc@npm:62.7.1" dependencies: - "@es-joy/jsdoccomment": "npm:~0.78.0" + "@es-joy/jsdoccomment": "npm:~0.84.0" "@es-joy/resolve.exports": "npm:1.2.0" are-docs-informative: "npm:^0.0.2" - comment-parser: "npm:1.4.1" + comment-parser: "npm:1.4.5" debug: "npm:^4.4.3" escape-string-regexp: "npm:^4.0.0" - espree: "npm:^11.0.0" + espree: "npm:^11.1.0" esquery: "npm:^1.7.0" html-entities: "npm:^2.6.0" object-deep-merge: "npm:^2.0.0" parse-imports-exports: "npm:^0.2.4" - semver: "npm:^7.7.3" + semver: "npm:^7.7.4" spdx-expression-parse: "npm:^4.0.0" to-valid-identifier: "npm:^1.0.0" peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/d0904b923f68a4e9e6da156316a4e2a972445bf79118bde9618ad80b4ef5927fc2c9dd597b22b776742ef548d65914e75fca190ab3be942385f268a3b83c1087 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 10c0/949c1f11ed86ddac0903ffe65e5e30d36766badea2e42ceeaf85168ec6f540f94a9974896600410ce1fcbbf34809b39236cc9614411c15d05b93be166c21ec3c languageName: node linkType: hard @@ -7450,10 +7457,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^5.0.0": - version: 5.0.0 - resolution: "eslint-visitor-keys@npm:5.0.0" - checksum: 10c0/5ec68b7ae350f6e7813a9ab469f8c64e01e5a954e6e6ee6dc441cc24d315eb342e5fb81ab5fc21f352cf0125096ab4ed93ca892f602a1576ad1eedce591fe64a +"eslint-visitor-keys@npm:^5.0.1": + version: 5.0.1 + resolution: "eslint-visitor-keys@npm:5.0.1" + checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678 languageName: node linkType: hard @@ -7517,14 +7524,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^11.0.0": - version: 11.1.0 - resolution: "espree@npm:11.1.0" +"espree@npm:^11.1.0": + version: 11.1.1 + resolution: "espree@npm:11.1.1" dependencies: - acorn: "npm:^8.15.0" + acorn: "npm:^8.16.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^5.0.0" - checksum: 10c0/32228d12896f5aa09f59fad8bf5df228d73310e436c21389876cdd21513b620c087d24b40646cdcff848540d11b078653db0e37ea67ac9c7012a12595d86630c + eslint-visitor-keys: "npm:^5.0.1" + checksum: 10c0/2feae74efdfb037b9e9fcb30506799845cf20900de5e441ed03e5c51aaa249f85ea5818ff177682acc0c9bfb4ac97e1965c238ee44ac7c305aab8747177bab69 languageName: node linkType: hard @@ -7538,7 +7545,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.5.0, esquery@npm:^1.6.0, esquery@npm:^1.7.0": +"esquery@npm:^1.5.0, esquery@npm:^1.7.0": version: 1.7.0 resolution: "esquery@npm:1.7.0" dependencies: @@ -9219,10 +9226,10 @@ __metadata: languageName: node linkType: hard -"jsdoc-type-pratt-parser@npm:~7.0.0": - version: 7.0.0 - resolution: "jsdoc-type-pratt-parser@npm:7.0.0" - checksum: 10c0/3ede53c80dddf940a51dcdc79e3923537650f6fb6e9001fc76023c2d5cb0195cc8b24b7eebf9b3f20a7bc00d5e6b7f70318f0b8cb5972f6aff884152e6698014 +"jsdoc-type-pratt-parser@npm:~7.1.1": + version: 7.1.1 + resolution: "jsdoc-type-pratt-parser@npm:7.1.1" + checksum: 10c0/5a5216a75962b3a8a3a1e7e09a19b31b5a373c06c726a00b081480daee00196250d4acc8dfbecc0a7846d439a5bcf4a326df6348b879cf95f60c62ce5818dadb languageName: node linkType: hard @@ -12629,12 +12636,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.1, semver@npm:^7.7.3": - version: 7.7.3 - resolution: "semver@npm:7.7.3" +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.1, semver@npm:^7.7.3, semver@npm:^7.7.4": + version: 7.7.4 + resolution: "semver@npm:7.7.4" bin: semver: bin/semver.js - checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 languageName: node linkType: hard From ba8e495631e5cf79568b7cedefc793038c023c3f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:30:52 +0000 Subject: [PATCH 42/89] Update dependency pg to v8.19.0 (#37978) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0d5cf787bdbbd8..18796b72473878 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10712,19 +10712,19 @@ __metadata: languageName: node linkType: hard -"pg-pool@npm:^3.11.0": - version: 3.11.0 - resolution: "pg-pool@npm:3.11.0" +"pg-pool@npm:^3.12.0": + version: 3.12.0 + resolution: "pg-pool@npm:3.12.0" peerDependencies: pg: ">=8.0" - checksum: 10c0/4b104b48a47257a0edad0c62e5ea1908b72cb79386270264b452e69895e9e4c589d00cdbf6e46d4e9c05bc7e7d191656b66814b5282d65f33b12648a21df3c7f + checksum: 10c0/b09da392ae2d0dae7bdf62b8557d3643bb7d84335894bc536f16ddd0da116b44c2f2770c88ff71a80252988ca115ec95a480cc047a75df34fbbbb2432b3a75da languageName: node linkType: hard -"pg-protocol@npm:*, pg-protocol@npm:^1.11.0": - version: 1.11.0 - resolution: "pg-protocol@npm:1.11.0" - checksum: 10c0/93e83581781418c9173eba4e4545f73392cfe66b78dd1d3624d7339fbd37e7f4abebaf2615e68e0701a9bf0edf5b81a4ad533836f388f775fe25fa24a691c464 +"pg-protocol@npm:*, pg-protocol@npm:^1.12.0": + version: 1.12.0 + resolution: "pg-protocol@npm:1.12.0" + checksum: 10c0/577f33c756f6503682d9ac17fd813f9edbe4a1716e497f17d36b6edaf9bf8383accaf8cd7422c49e2fbe4eb28ef275bc52fbd8287e154d4510f50b9ccefe4165 languageName: node linkType: hard @@ -10742,13 +10742,13 @@ __metadata: linkType: hard "pg@npm:^8.5.0": - version: 8.18.0 - resolution: "pg@npm:8.18.0" + version: 8.19.0 + resolution: "pg@npm:8.19.0" dependencies: pg-cloudflare: "npm:^1.3.0" pg-connection-string: "npm:^2.11.0" - pg-pool: "npm:^3.11.0" - pg-protocol: "npm:^1.11.0" + pg-pool: "npm:^3.12.0" + pg-protocol: "npm:^1.12.0" pg-types: "npm:2.2.0" pgpass: "npm:1.0.5" peerDependencies: @@ -10759,7 +10759,7 @@ __metadata: peerDependenciesMeta: pg-native: optional: true - checksum: 10c0/9525e34d603ee5d715b8952269b2fa9fdd350a55fc5a3360104e7613724441858e57d52eed435fb16e993d028b45d8175dc277d270d31f69e5746987a549f772 + checksum: 10c0/7713d6ef9f32746370f2bd599ebfa54d22bbdbb3a256a64b76997dd8a7279a5734dde260d6a1da22047171604fba88bd273d570c77b16f08ebe32a47ffbe2aba languageName: node linkType: hard From 6ab24de659a02797afef54fc0e401290081db536 Mon Sep 17 00:00:00 2001 From: Shlee Date: Mon, 2 Mar 2026 20:33:21 +1030 Subject: [PATCH 43/89] trustworthy_attribution - Bounce bad URIs (#37372) --- app/services/activitypub/fetch_remote_status_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index e08f82f7d9abd8..4ef5a6571ef4f2 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -60,6 +60,7 @@ def call(uri, prefetched_body: nil, on_behalf_of: nil, expected_actor_uri: nil, def trustworthy_attribution?(uri, attributed_to) return false if uri.nil? || attributed_to.nil? + return false if unsupported_uri_scheme?(uri) || unsupported_uri_scheme?(attributed_to) Addressable::URI.parse(uri).normalized_host.casecmp(Addressable::URI.parse(attributed_to).normalized_host).zero? end From 2f657019201d73c9d86d1ece339e66e72954f0eb Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 2 Mar 2026 11:16:41 +0100 Subject: [PATCH 44/89] Add service to revoke inclusion in a Collection (#38026) --- app/models/collection_item.rb | 4 +++ ...delete_feature_authorization_serializer.rb | 25 +++++++++++++++++ .../revoke_collection_item_service.rb | 24 +++++++++++++++++ ...e_feature_authorization_serializer_spec.rb | 27 +++++++++++++++++++ .../revoke_collection_item_service_spec.rb | 25 +++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 app/serializers/activitypub/delete_feature_authorization_serializer.rb create mode 100644 app/services/revoke_collection_item_service.rb create mode 100644 spec/serializers/activitypub/delete_feature_authorization_serializer_spec.rb create mode 100644 spec/services/revoke_collection_item_service_spec.rb diff --git a/app/models/collection_item.rb b/app/models/collection_item.rb index c5c9ebc16e91fd..e113b3b5227e88 100644 --- a/app/models/collection_item.rb +++ b/app/models/collection_item.rb @@ -42,6 +42,10 @@ class CollectionItem < ApplicationRecord scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) } scope :local, -> { joins(:collection).merge(Collection.local) } + def revoke! + update!(state: :revoked) + end + def local_item_with_remote_account? local? && account&.remote? end diff --git a/app/serializers/activitypub/delete_feature_authorization_serializer.rb b/app/serializers/activitypub/delete_feature_authorization_serializer.rb new file mode 100644 index 00000000000000..eb9f39fb4b9915 --- /dev/null +++ b/app/serializers/activitypub/delete_feature_authorization_serializer.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class ActivityPub::DeleteFeatureAuthorizationSerializer < ActivityPub::Serializer + include RoutingHelper + + attributes :id, :type, :actor, :to + + has_one :object, serializer: ActivityPub::FeatureAuthorizationSerializer + + def id + [ap_account_feature_authorization_url(object.account_id, object), '#delete'].join + end + + def type + 'Delete' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.account) + end + + def to + [ActivityPub::TagManager::COLLECTIONS[:public]] + end +end diff --git a/app/services/revoke_collection_item_service.rb b/app/services/revoke_collection_item_service.rb new file mode 100644 index 00000000000000..d299b567f225b5 --- /dev/null +++ b/app/services/revoke_collection_item_service.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class RevokeCollectionItemService < BaseService + include Payloadable + + def call(collection_item) + @collection_item = collection_item + @account = collection_item.account + + @collection_item.revoke! + + distribute_stamp_deletion! if Mastodon::Feature.collections_federation_enabled? && @collection_item.remote? + end + + private + + def distribute_stamp_deletion! + ActivityPub::AccountRawDistributionWorker.perform_async(signed_activity_json, @collection_item.collection.account_id) + end + + def signed_activity_json + @signed_activity_json ||= Oj.dump(serialize_payload(@collection_item, ActivityPub::DeleteFeatureAuthorizationSerializer, signer: @account, always_sign: true)) + end +end diff --git a/spec/serializers/activitypub/delete_feature_authorization_serializer_spec.rb b/spec/serializers/activitypub/delete_feature_authorization_serializer_spec.rb new file mode 100644 index 00000000000000..a1b2de79667bf3 --- /dev/null +++ b/spec/serializers/activitypub/delete_feature_authorization_serializer_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::DeleteFeatureAuthorizationSerializer do + include RoutingHelper + + subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) } + + describe 'serializing an object' do + let(:collection) { Fabricate(:remote_collection) } + let(:collection_item) { Fabricate(:collection_item, collection:, uri: 'https://example.com') } + + it 'returns expected json structure' do + expect(subject) + .to include({ + 'type' => 'Delete', + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'actor' => ActivityPub::TagManager.instance.uri_for(collection_item.account), + 'object' => a_hash_including({ + 'type' => 'FeatureAuthorization', + 'id' => ap_account_feature_authorization_url(collection_item.account_id, collection_item), + }), + }) + end + end +end diff --git a/spec/services/revoke_collection_item_service_spec.rb b/spec/services/revoke_collection_item_service_spec.rb new file mode 100644 index 00000000000000..8ea753dcc5ec43 --- /dev/null +++ b/spec/services/revoke_collection_item_service_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RevokeCollectionItemService do + subject { described_class.new } + + let(:collection_item) { Fabricate(:collection_item) } + + it 'revokes the collection item and sends a Delete activity' do + expect { subject.call(collection_item) } + .to change { collection_item.reload.state }.from('accepted').to('revoked') + end + + context 'when the collection is remote', feature: :collections_federation do + let(:collection) { Fabricate(:remote_collection) } + let(:collection_item) { Fabricate(:collection_item, collection:, uri: 'https://example.com') } + + it 'federates a `Delete` activity' do + subject.call(collection_item) + + expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job + end + end +end From f953d40289e026057ed1a564b6bf547ad067aeda Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 2 Mar 2026 14:38:03 +0100 Subject: [PATCH 45/89] Add API to revoke collection item (#38027) --- .../v1_alpha/collection_items_controller.rb | 10 +++++- app/policies/collection_item_policy.rb | 13 ++++++++ config/routes/api.rb | 6 +++- spec/policies/collection_item_policy_spec.rb | 23 ++++++++++++++ .../api/v1_alpha/collection_items_spec.rb | 31 +++++++++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 app/policies/collection_item_policy.rb create mode 100644 spec/policies/collection_item_policy_spec.rb diff --git a/app/controllers/api/v1_alpha/collection_items_controller.rb b/app/controllers/api/v1_alpha/collection_items_controller.rb index 5c78de14e9592b..2c46cc4f9fcda0 100644 --- a/app/controllers/api/v1_alpha/collection_items_controller.rb +++ b/app/controllers/api/v1_alpha/collection_items_controller.rb @@ -11,7 +11,7 @@ class Api::V1Alpha::CollectionItemsController < Api::BaseController before_action :set_collection before_action :set_account, only: [:create] - before_action :set_collection_item, only: [:destroy] + before_action :set_collection_item, only: [:destroy, :revoke] after_action :verify_authorized @@ -32,6 +32,14 @@ def destroy head 200 end + def revoke + authorize @collection_item, :revoke? + + RevokeCollectionItemService.new.call(@collection_item) + + head 200 + end + private def set_collection diff --git a/app/policies/collection_item_policy.rb b/app/policies/collection_item_policy.rb new file mode 100644 index 00000000000000..73552a3b8a169f --- /dev/null +++ b/app/policies/collection_item_policy.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class CollectionItemPolicy < ApplicationPolicy + def revoke? + featured_account.present? && current_account == featured_account + end + + private + + def featured_account + record.account + end +end diff --git a/config/routes/api.rb b/config/routes/api.rb index 5322f15c8f0b7d..285b032d01f22f 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -13,7 +13,11 @@ resources :async_refreshes, only: :show resources :collections, only: [:show, :create, :update, :destroy] do - resources :items, only: [:create, :destroy], controller: 'collection_items' + resources :items, only: [:create, :destroy], controller: 'collection_items' do + member do + post :revoke + end + end end end diff --git a/spec/policies/collection_item_policy_spec.rb b/spec/policies/collection_item_policy_spec.rb new file mode 100644 index 00000000000000..d12384209e4816 --- /dev/null +++ b/spec/policies/collection_item_policy_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CollectionItemPolicy do + subject { described_class } + + let(:account) { Fabricate(:account) } + + permissions :revoke? do + context 'when collection item features the revoking account' do + let(:collection_item) { Fabricate.build(:collection_item, account:) } + + it { is_expected.to permit(account, collection_item) } + end + + context 'when collection item does not feature the revoking account' do + let(:collection_item) { Fabricate.build(:collection_item) } + + it { is_expected.to_not permit(account, collection_item) } + end + end +end diff --git a/spec/requests/api/v1_alpha/collection_items_spec.rb b/spec/requests/api/v1_alpha/collection_items_spec.rb index 6d33e6a7113ec3..e7ee854e67add7 100644 --- a/spec/requests/api/v1_alpha/collection_items_spec.rb +++ b/spec/requests/api/v1_alpha/collection_items_spec.rb @@ -102,4 +102,35 @@ end end end + + describe 'POST /api/v1_alpha/collections/:collection_id/items/:id/revoke' do + subject do + post "/api/v1_alpha/collections/#{collection.id}/items/#{item.id}/revoke", headers: headers + end + + let(:collection) { Fabricate(:collection) } + let(:item) { Fabricate(:collection_item, collection:, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'when user is in item' do + it 'revokes the collection item and returns http success' do + subject + + expect(item.reload).to be_revoked + + expect(response).to have_http_status(200) + end + end + + context 'when user is not in the item' do + let(:item) { Fabricate(:collection_item, collection:) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end end From 87004ddb96c766a3dce889bad098c16f80bb3d0a Mon Sep 17 00:00:00 2001 From: Antoine Cellerier Date: Mon, 2 Mar 2026 14:49:28 +0100 Subject: [PATCH 46/89] Add g+e keyboard shortcut for /explore (trending) (#38014) --- .../mastodon/components/hotkeys/hotkeys.stories.tsx | 6 ++++++ app/javascript/mastodon/components/hotkeys/index.tsx | 1 + .../mastodon/features/keyboard_shortcuts/index.jsx | 4 ++++ app/javascript/mastodon/features/ui/index.jsx | 5 +++++ app/javascript/mastodon/locales/en.json | 1 + 5 files changed, 17 insertions(+) diff --git a/app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx b/app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx index 9baef18668435c..43002013a301f4 100644 --- a/app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx +++ b/app/javascript/mastodon/components/hotkeys/hotkeys.stories.tsx @@ -50,6 +50,9 @@ const hotkeyTest: Story['play'] = async ({ canvas, userEvent }) => { await userEvent.keyboard('gh'); await confirmHotkey('goToHome'); + await userEvent.keyboard('ge'); + await confirmHotkey('goToExplore'); + await userEvent.keyboard('gn'); await confirmHotkey('goToNotifications'); @@ -106,6 +109,9 @@ export const Default = { goToHome: () => { setMatchedHotkey('goToHome'); }, + goToExplore: () => { + setMatchedHotkey('goToExplore'); + }, goToNotifications: () => { setMatchedHotkey('goToNotifications'); }, diff --git a/app/javascript/mastodon/components/hotkeys/index.tsx b/app/javascript/mastodon/components/hotkeys/index.tsx index c62fc0c20acd77..751ec01fe571fb 100644 --- a/app/javascript/mastodon/components/hotkeys/index.tsx +++ b/app/javascript/mastodon/components/hotkeys/index.tsx @@ -118,6 +118,7 @@ const hotkeyMatcherMap = { openMedia: just('e'), onTranslate: just('t'), goToHome: sequence('g', 'h'), + goToExplore: sequence('g', 'e'), goToNotifications: sequence('g', 'n'), goToLocal: sequence('g', 'l'), goToFederated: sequence('g', 't'), diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx index 8a6ebe6def861a..d2b041ec3f9570 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx @@ -134,6 +134,10 @@ class KeyboardShortcuts extends ImmutablePureComponent { g+h + + g+e + + g+n diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 17822929a78d4c..d40891ef620040 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -534,6 +534,10 @@ class UI extends PureComponent { this.props.history.push('/home'); }; + handleHotkeyGoToExplore = () => { + this.props.history.push('/explore'); + }; + handleHotkeyGoToNotifications = () => { this.props.history.push('/notifications'); }; @@ -595,6 +599,7 @@ class UI extends PureComponent { moveToTop: this.handleMoveToTop, back: this.handleHotkeyBack, goToHome: this.handleHotkeyGoToHome, + goToExplore: this.handleHotkeyGoToExplore, goToNotifications: this.handleHotkeyGoToNotifications, goToLocal: this.handleHotkeyGoToLocal, goToFederated: this.handleHotkeyGoToFederated, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 03b7a5d2c49791..e7a346a38fa01a 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -682,6 +682,7 @@ "keyboard_shortcuts.direct": "Open private mentions column", "keyboard_shortcuts.down": "Move down in the list", "keyboard_shortcuts.enter": "Open post", + "keyboard_shortcuts.explore": "Open trending timeline", "keyboard_shortcuts.favourite": "Favorite post", "keyboard_shortcuts.favourites": "Open favorites list", "keyboard_shortcuts.federated": "Open federated timeline", From 816e63d2a553059af4dc88509a327bfc02b05be8 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 2 Mar 2026 15:37:33 +0100 Subject: [PATCH 47/89] Add "skip to content", "skip to navigation" links (#38006) --- .../components/column_back_button.tsx | 11 ++- .../mastodon/components/column_header.tsx | 17 ++-- .../features/navigation_panel/index.tsx | 7 +- .../features/ui/components/columns_area.jsx | 23 +++-- .../ui/components/skip_links/index.tsx | 84 +++++++++++++++++++ .../skip_links/skip_links.module.scss | 64 ++++++++++++++ app/javascript/mastodon/features/ui/index.jsx | 19 ++++- .../mastodon/features/ui/util/focusUtils.ts | 70 ++++++++++++---- app/javascript/mastodon/locales/en.json | 3 + .../styles/mastodon/components.scss | 26 +++--- 10 files changed, 281 insertions(+), 43 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/components/skip_links/index.tsx create mode 100644 app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss diff --git a/app/javascript/mastodon/components/column_back_button.tsx b/app/javascript/mastodon/components/column_back_button.tsx index 8012ba7df694e6..bb6939e24c60e7 100644 --- a/app/javascript/mastodon/components/column_back_button.tsx +++ b/app/javascript/mastodon/components/column_back_button.tsx @@ -4,8 +4,11 @@ import { FormattedMessage } from 'react-intl'; import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; import { Icon } from 'mastodon/components/icon'; +import { getColumnSkipLinkId } from 'mastodon/features/ui/components/skip_links'; import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; +import { useColumnIndexContext } from '../features/ui/components/columns_area'; + import { useAppHistory } from './router'; type OnClickCallback = () => void; @@ -28,9 +31,15 @@ export const ColumnBackButton: React.FC<{ onClick?: OnClickCallback }> = ({ onClick, }) => { const handleClick = useHandleClick(onClick); + const columnIndex = useColumnIndexContext(); const component = ( - @@ -221,7 +226,7 @@ export const ColumnHeader: React.FC = ({ !pinned && ((multiColumn && history.location.state?.fromMastodon) || showBackButton) ) { - backButton = ; + backButton = ; } const collapsedContent = [extraContent]; @@ -260,6 +265,7 @@ export const ColumnHeader: React.FC = ({ const hasIcon = icon && iconComponent; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const hasTitle = (hasIcon || backButton) && title; + const columnIndex = useColumnIndexContext(); const component = (
@@ -272,6 +278,7 @@ export const ColumnHeader: React.FC = ({ onClick={handleTitleClick} className='column-header__title' type='button' + id={getColumnSkipLinkId(columnIndex)} > {!backButton && hasIcon && ( = ({ return (
- +
diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 753f7e9ac38efe..5609a52b3108d5 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import { Children, cloneElement, useCallback } from 'react'; +import { Children, cloneElement, createContext, useContext, useCallback } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -53,6 +53,13 @@ const TabsBarPortal = () => { return
; }; +// Simple context to allow column children to know which column they're in +export const ColumnIndexContext = createContext(1); +/** + * @returns {number} + */ +export const useColumnIndexContext = () => useContext(ColumnIndexContext); + export default class ColumnsArea extends ImmutablePureComponent { static propTypes = { columns: ImmutablePropTypes.list.isRequired, @@ -140,18 +147,22 @@ export default class ColumnsArea extends ImmutablePureComponent { return (
- {columns.map(column => { + {columns.map((column, index) => { const params = column.get('params', null) === null ? null : column.get('params').toJS(); const other = params && params.other ? params.other : {}; return ( - - {SpecificComponent => } - + + + {SpecificComponent => } + + ); })} - {Children.map(children, child => cloneElement(child, { multiColumn: true }))} + + {Children.map(children, child => cloneElement(child, { multiColumn: true }))} +
); } diff --git a/app/javascript/mastodon/features/ui/components/skip_links/index.tsx b/app/javascript/mastodon/features/ui/components/skip_links/index.tsx new file mode 100644 index 00000000000000..7ecff95287c34a --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/skip_links/index.tsx @@ -0,0 +1,84 @@ +import { useCallback, useId } from 'react'; + +import { useIntl } from 'react-intl'; + +import { useAppSelector } from 'mastodon/store'; + +import classes from './skip_links.module.scss'; + +export const getNavigationSkipLinkId = () => 'skip-link-target-nav'; +export const getColumnSkipLinkId = (index: number) => + `skip-link-target-content-${index}`; + +export const SkipLinks: React.FC<{ + multiColumn: boolean; + onFocusGettingStartedColumn: () => void; +}> = ({ multiColumn, onFocusGettingStartedColumn }) => { + const intl = useIntl(); + const columnCount = useAppSelector((state) => { + const settings = state.settings as Immutable.Collection; + return (settings.get('columns') as Immutable.Map).size; + }); + + const focusMultiColumnNavbar = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + onFocusGettingStartedColumn(); + }, + [onFocusGettingStartedColumn], + ); + + return ( +
    +
  • + + {intl.formatMessage({ + id: 'skip_links.skip_to_content', + defaultMessage: 'Skip to main content', + })} + +
  • +
  • + + {intl.formatMessage({ + id: 'skip_links.skip_to_navigation', + defaultMessage: 'Skip to main navigation', + })} + +
  • +
+ ); +}; + +const SkipLink: React.FC<{ + children: string; + target: string; + onRouterLinkClick?: React.MouseEventHandler; + hotkey: string; +}> = ({ children, hotkey, target, onRouterLinkClick }) => { + const intl = useIntl(); + const id = useId(); + return ( + <> + + {children} + + + {intl.formatMessage( + { + id: 'skip_links.hotkey', + defaultMessage: 'Hotkey {hotkey}', + }, + { + hotkey, + span: (text) => {text}, + }, + )} + + + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss new file mode 100644 index 00000000000000..1d4dc1c3f549f5 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss @@ -0,0 +1,64 @@ +.list { + position: fixed; + z-index: 100; + margin: 10px; + padding: 10px 16px; + border-radius: 10px; + font-size: 15px; + color: var(--color-text-primary); + background: var(--color-bg-primary); + box-shadow: var(--dropdown-shadow); + + /* Hide visually when not focused */ + &:not(:focus-within) { + width: 1px; + height: 1px; + margin: 0; + padding: 0; + clip-path: inset(50%); + overflow: hidden; + } +} + +.listItem { + display: flex; + align-items: center; + gap: 10px; + padding-inline-end: 4px; + border-radius: 4px; + + &:not(:first-child) { + margin-top: 2px; + } + + &:focus-within { + outline: var(--outline-focus-default); + background: var(--color-bg-brand-softer); + } + + :any-link { + display: block; + padding: 8px; + color: inherit; + text-decoration-color: var(--color-text-secondary); + text-underline-offset: 0.2em; + + &:focus, + &:focus-visible { + outline: none; + } + } +} + +.hotkeyHint { + display: inline-block; + box-sizing: border-box; + min-width: 2.5ch; + margin-inline-start: auto; + padding: 3px 5px; + font-family: 'Courier New', Courier, monospace; + text-align: center; + background: var(--color-bg-primary); + border: 1px solid var(--color-border-primary); + border-radius: 4px; +} diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index d40891ef620040..b46f61745e7b94 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -92,6 +92,7 @@ import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; // Without this it ends up in ~8 very commonly used bundles. import '../../components/status'; import { areCollectionsEnabled } from '../collections/utils'; +import { getNavigationSkipLinkId, SkipLinks } from './components/skip_links'; const messages = defineMessages({ beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, @@ -253,9 +254,9 @@ class SwitchingColumnsArea extends PureComponent { {areCollectionsEnabled() && [ - , - , - + , + , + ] } @@ -556,6 +557,14 @@ class UI extends PureComponent { handleHotkeyGoToStart = () => { this.props.history.push('/getting-started'); + // Set focus to the navigation after a timeout + // to allow for it to be displayed first + setTimeout(() => { + const navbarSkipTarget = document.querySelector( + `#${getNavigationSkipLinkId()}`, + ); + navbarSkipTarget?.focus(); + }, 0); }; handleHotkeyGoToFavourites = () => { @@ -617,6 +626,10 @@ class UI extends PureComponent { return (
+ ( + `#${getColumnSkipLinkId(index - 1)}, #${getNavigationSkipLinkId()}`, + ) + ?.focus(); + } + } else { + const idSelector = + index === 2 + ? `#${getNavigationSkipLinkId()}` + : `#${getColumnSkipLinkId(1)}`; + + document.querySelector(idSelector)?.focus(); + } +} + /** * Move focus to the column of the passed index (1-based). - * Focus is placed on the topmost visible item + * Focus is placed on the topmost visible item, or the column title */ export function focusColumn(index = 1) { // Skip the leftmost drawer in multi-column mode @@ -35,11 +60,21 @@ export function focusColumn(index = 1) { `.column:nth-child(${index + indexOffset})`, ); - if (!column) return; + function fallback() { + focusColumnTitle(index + indexOffset, isMultiColumnLayout); + } + + if (!column) { + fallback(); + return; + } const container = column.querySelector('.scrollable'); - if (!container) return; + if (!container) { + fallback(); + return; + } const focusableItems = Array.from( container.querySelectorAll( @@ -50,20 +85,23 @@ export function focusColumn(index = 1) { // Find first item visible in the viewport const itemToFocus = findFirstVisibleWithRect(focusableItems); - if (itemToFocus) { - const viewportWidth = - window.innerWidth || document.documentElement.clientWidth; - const { item, rect } = itemToFocus; - - if ( - container.scrollTop > item.offsetTop || - rect.right > viewportWidth || - rect.left < 0 - ) { - itemToFocus.item.scrollIntoView(true); - } - itemToFocus.item.focus(); + if (!itemToFocus) { + fallback(); + return; + } + + const viewportWidth = + window.innerWidth || document.documentElement.clientWidth; + const { item, rect } = itemToFocus; + + if ( + container.scrollTop > item.offsetTop || + rect.right > viewportWidth || + rect.left < 0 + ) { + itemToFocus.item.scrollIntoView(true); } + itemToFocus.item.focus(); } /** diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index e7a346a38fa01a..e5d62e94362bd9 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -1072,6 +1072,9 @@ "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Login", "sign_in_banner.sso_redirect": "Login or Register", + "skip_links.hotkey": "Hotkey {hotkey}", + "skip_links.skip_to_content": "Skip to main content", + "skip_links.skip_to_navigation": "Skip to main navigation", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1e29fcb2b3e74d..7697d6c273e688 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3903,6 +3903,11 @@ a.account__display-name { &:hover { text-decoration: underline; } + + &:focus-visible { + outline: var(--outline-focus-default); + outline-offset: -2px; + } } .column-header__back-button { @@ -4036,15 +4041,17 @@ a.account__display-name { } .column-link { + box-sizing: border-box; display: flex; align-items: center; gap: 8px; width: 100%; - padding: 12px; + padding: 10px; + padding-inline-start: 14px; + overflow: hidden; font-size: 16px; font-weight: 400; text-decoration: none; - overflow: hidden; white-space: nowrap; color: color-mix( in oklab, @@ -4052,9 +4059,8 @@ a.account__display-name { var(--color-text-secondary) ); background: transparent; - border: 0; - border-left: 4px solid transparent; - box-sizing: border-box; + border: 2px solid transparent; + border-radius: 4px; &:hover, &:active, @@ -4066,17 +4072,15 @@ a.account__display-name { color: var(--color-text-brand); } - &:focus { - outline: 0; - } - &:focus-visible { + outline: none; border-color: var(--color-text-brand); - border-radius: 0; + background: var(--color-bg-brand-softer); } &--logo { - padding: 10px; + padding: 8px; + padding-inline-start: 12px; } } From e7cec161fdb468df56197f4471554c2530dc282b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 2 Mar 2026 09:43:57 -0500 Subject: [PATCH 48/89] Reduce haml-lint `LineLength` configuration to 240 (#37287) --- .haml-lint.yml | 2 +- app/helpers/registration_helper.rb | 8 ++++++++ app/helpers/settings_helper.rb | 10 ++++++++++ app/views/admin/announcements/previews/show.html.haml | 11 +++++++++-- .../admin/terms_of_service/previews/show.html.haml | 11 +++++++++-- app/views/auth/registrations/new.html.haml | 2 +- .../settings/preferences/appearance/show.html.haml | 10 ++++++++-- app/views/settings/verifications/show.html.haml | 7 +++++-- .../user_mailer/confirmation_instructions.html.haml | 4 +++- 9 files changed, 54 insertions(+), 11 deletions(-) diff --git a/.haml-lint.yml b/.haml-lint.yml index 74d243a3ad63f9..4048895806e66a 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -10,6 +10,6 @@ linters: MiddleDot: enabled: true LineLength: - max: 300 + max: 240 # Override default value of 80 inherited from rubocop ViewLength: max: 200 # Override default value of 100 inherited from rubocop diff --git a/app/helpers/registration_helper.rb b/app/helpers/registration_helper.rb index 002d167c05833a..fd3979f5af23e7 100644 --- a/app/helpers/registration_helper.rb +++ b/app/helpers/registration_helper.rb @@ -18,4 +18,12 @@ def omniauth_only? def ip_blocked?(remote_ip) IpBlock.severity_sign_up_block.containing(remote_ip.to_s).exists? end + + def terms_agreement_label + if TermsOfService.live.exists? + t('auth.user_agreement_html', privacy_policy_path: privacy_policy_path, terms_of_service_path: terms_of_service_path) + else + t('auth.user_privacy_agreement_html', privacy_policy_path: privacy_policy_path) + end + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index fd631ce92ecd30..44113f3d475d9e 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -23,6 +23,16 @@ def featured_tags_hint(recently_used_tags) ) end + def author_attribution_name(account) + return if account.nil? + + link_to(root_url, class: 'story__details__shared__author-link') do + safe_join( + [image_tag(account.avatar.url, class: 'account__avatar', size: 16, alt: ''), tag.bdi(display_name(account))] + ) + end + end + def session_device_icon(session) device = session.detection.device diff --git a/app/views/admin/announcements/previews/show.html.haml b/app/views/admin/announcements/previews/show.html.haml index 54d5d45ed6f0ee..917d66363888fd 100644 --- a/app/views/admin/announcements/previews/show.html.haml +++ b/app/views/admin/announcements/previews/show.html.haml @@ -18,5 +18,12 @@ %hr.spacer/ .content__heading__actions - = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), admin_announcement_test_path(@announcement), method: :post, class: 'button button-secondary' - = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), admin_announcement_distribution_path(@announcement), method: :post, class: 'button', data: { confirm: t('admin.reports.are_you_sure') } + = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), + admin_announcement_test_path(@announcement), + class: 'button button-secondary', + method: :post + = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), + admin_announcement_distribution_path(@announcement), + class: 'button', + data: { confirm: t('admin.reports.are_you_sure') }, + method: :post diff --git a/app/views/admin/terms_of_service/previews/show.html.haml b/app/views/admin/terms_of_service/previews/show.html.haml index 48c94cb052454a..907ac32f4506d8 100644 --- a/app/views/admin/terms_of_service/previews/show.html.haml +++ b/app/views/admin/terms_of_service/previews/show.html.haml @@ -16,5 +16,12 @@ %hr.spacer/ .content__heading__actions - = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), admin_terms_of_service_test_path(@terms_of_service), method: :post, class: 'button button-secondary' - = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), admin_terms_of_service_distribution_path(@terms_of_service), method: :post, class: 'button', data: { confirm: t('admin.reports.are_you_sure') } + = link_to t('admin.terms_of_service.preview.send_preview', email: current_user.email), + admin_terms_of_service_test_path(@terms_of_service), + class: 'button button-secondary', + method: :post + = link_to t('admin.terms_of_service.preview.send_to_all', count: @user_count, display_count: number_with_delimiter(@user_count)), + admin_terms_of_service_distribution_path(@terms_of_service), + class: 'button', + data: { confirm: t('admin.reports.are_you_sure') }, + method: :post diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index eef4b485f6a36e..0dc1d894ec735e 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -79,7 +79,7 @@ .fields-group = f.input :agreement, as: :boolean, - label: TermsOfService.live.exists? ? t('auth.user_agreement_html', privacy_policy_path: privacy_policy_path, terms_of_service_path: terms_of_service_path) : t('auth.user_privacy_agreement_html', privacy_policy_path: privacy_policy_path), + label: terms_agreement_label, required: false, wrapper: :with_label diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index f5dba2606f94ab..57c3b4713ea8f0 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -93,9 +93,15 @@ %h4= t 'appearance.boosting_preferences' .fields-group - = ff.input :'web.reblog_modal', wrapper: :with_label, hint: I18n.t('simple_form.hints.defaults.setting_boost_modal'), label: I18n.t('simple_form.labels.defaults.setting_boost_modal') + = ff.input :'web.reblog_modal', + hint: I18n.t('simple_form.hints.defaults.setting_boost_modal'), + label: I18n.t('simple_form.labels.defaults.setting_boost_modal'), + wrapper: :with_label .fields-group - = ff.input :'web.quick_boosting', wrapper: :with_label, hint: t('simple_form.hints.defaults.setting_quick_boosting_html', boost_icon: material_symbol('repeat'), options_icon: material_symbol('more_horiz')), label: I18n.t('simple_form.labels.defaults.setting_quick_boosting') + = ff.input :'web.quick_boosting', + hint: t('simple_form.hints.defaults.setting_quick_boosting_html', boost_icon: material_symbol('repeat'), options_icon: material_symbol('more_horiz')), + label: I18n.t('simple_form.labels.defaults.setting_quick_boosting'), + wrapper: :with_label .flash-message.hidden-on-touch-devices= t('appearance.boosting_preferences_info_html', icon: material_symbol('repeat')) %h4= t 'appearance.sensitive_content' diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml index ac8778a7ec5235..b348b7bc03bad2 100644 --- a/app/views/settings/verifications/show.html.haml +++ b/app/views/settings/verifications/show.html.haml @@ -51,7 +51,7 @@ %strong.status-card__title= t('author_attribution.example_title') .more-from-author = logo_as_symbol(:icon) - = t('author_attribution.more_from_html', name: link_to(root_url, class: 'story__details__shared__author-link') { image_tag(@account.avatar.url, class: 'account__avatar', width: 16, height: 16, alt: '') + tag.bdi(display_name(@account)) }) + = t('author_attribution.more_from_html', name: author_attribution_name(@account)) %h4= t('verification.here_is_how') @@ -65,7 +65,10 @@ %p.lead= t('author_attribution.then_instructions') .fields-group - = f.input :attribution_domains, as: :text, wrapper: :with_block_label, input_html: { value: @account.attribution_domains.join("\n"), placeholder: "example1.com\nexample2.com\nexample3.com", rows: 4, autocapitalize: 'none', autocorrect: 'off' } + = f.input :attribution_domains, + as: :text, + input_html: { value: @account.attribution_domains.join("\n"), placeholder: "example1.com\nexample2.com\nexample3.com", rows: 4, autocapitalize: 'none', autocorrect: 'off' }, + wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/user_mailer/confirmation_instructions.html.haml b/app/views/user_mailer/confirmation_instructions.html.haml index d3e3e8f9305102..d5d5e0dc1efa91 100644 --- a/app/views/user_mailer/confirmation_instructions.html.haml +++ b/app/views/user_mailer/confirmation_instructions.html.haml @@ -10,7 +10,9 @@ %td.email-inner-card-td.email-prose %p= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname - if @resource.created_by_application - = render 'application/mailer/button', text: t('devise.mailer.confirmation_instructions.action_with_app', app: @resource.created_by_application.name), url: confirmation_url(@resource, confirmation_token: @token, redirect_to_app: 'true') + = render 'application/mailer/button', + text: t('devise.mailer.confirmation_instructions.action_with_app', app: @resource.created_by_application.name), + url: confirmation_url(@resource, confirmation_token: @token, redirect_to_app: 'true') - else = render 'application/mailer/button', text: t('devise.mailer.confirmation_instructions.action'), url: confirmation_url(@resource, confirmation_token: @token) %p= t 'devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: privacy_policy_url From ceaadc791e83ec9f484da4b4789602e2604ab3e4 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 2 Mar 2026 16:13:56 +0100 Subject: [PATCH 49/89] Change cursor to make clear `summary` is clickable (#38029) --- app/javascript/styles/mastodon/admin.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index dd95f4e49b3a57..02137004297a99 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -385,6 +385,7 @@ $content-width: 840px; color: var(--color-text-secondary); padding-top: 24px; margin-bottom: 8px; + cursor: pointer; } @media screen and (max-width: $no-columns-breakpoint) { From 03b2f77ad243b3b0d419f5e292669e5c40483061 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 2 Mar 2026 17:19:13 +0100 Subject: [PATCH 50/89] Collection share modal cleanup (#38030) --- .../mastodon/components/modal_shell/index.tsx | 19 +++++++------------ .../account_timeline/modals/field_modal.tsx | 14 +++++++++----- .../collections/detail/share_modal.tsx | 14 +++++++++----- .../confirmation_modal.tsx | 14 +++++++++----- .../features/ui/components/modal_root.jsx | 3 +-- .../features/ui/util/async-components.js | 6 ------ .../styles/mastodon/components.scss | 4 ++++ 7 files changed, 39 insertions(+), 35 deletions(-) diff --git a/app/javascript/mastodon/components/modal_shell/index.tsx b/app/javascript/mastodon/components/modal_shell/index.tsx index 8b6fdcc6ad279c..8b060875327451 100644 --- a/app/javascript/mastodon/components/modal_shell/index.tsx +++ b/app/javascript/mastodon/components/modal_shell/index.tsx @@ -1,16 +1,14 @@ import classNames from 'classnames'; -interface SimpleComponentProps { +interface ModalShellProps { className?: string; children?: React.ReactNode; } -interface ModalShellComponent extends React.FC { - Body: React.FC; - Actions: React.FC; -} - -export const ModalShell: ModalShellComponent = ({ children, className }) => { +export const ModalShell: React.FC = ({ + children, + className, +}) => { return (
{ ); }; -const ModalShellBody: ModalShellComponent['Body'] = ({ +export const ModalShellBody: React.FC = ({ children, className, }) => { @@ -39,7 +37,7 @@ const ModalShellBody: ModalShellComponent['Body'] = ({ ); }; -const ModalShellActions: ModalShellComponent['Actions'] = ({ +export const ModalShellActions: React.FC = ({ children, className, }) => { @@ -51,6 +49,3 @@ const ModalShellActions: ModalShellComponent['Actions'] = ({
); }; - -ModalShell.Body = ModalShellBody; -ModalShell.Actions = ModalShellActions; diff --git a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx index f7251f7b4181e9..c778e08fa26a5b 100644 --- a/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx +++ b/app/javascript/mastodon/features/account_timeline/modals/field_modal.tsx @@ -4,7 +4,11 @@ import { FormattedMessage } from 'react-intl'; import { Button } from '@/mastodon/components/button'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; -import { ModalShell } from '@/mastodon/components/modal_shell'; +import { + ModalShell, + ModalShellActions, + ModalShellBody, +} from '@/mastodon/components/modal_shell'; import type { AccountField } from '../common'; import { useFieldHtml } from '../hooks/useFieldHtml'; @@ -19,7 +23,7 @@ export const AccountFieldModal: FC<{ const handleValueElement = useFieldHtml(field.valueHasEmojis); return ( - + - - + + - + ); }; diff --git a/app/javascript/mastodon/features/collections/detail/share_modal.tsx b/app/javascript/mastodon/features/collections/detail/share_modal.tsx index 3bff066ee6db65..137794d95b6cc3 100644 --- a/app/javascript/mastodon/features/collections/detail/share_modal.tsx +++ b/app/javascript/mastodon/features/collections/detail/share_modal.tsx @@ -13,7 +13,11 @@ import { AvatarGroup } from 'mastodon/components/avatar_group'; import { Button } from 'mastodon/components/button'; import { CopyLinkField } from 'mastodon/components/form_fields'; import { IconButton } from 'mastodon/components/icon_button'; -import { ModalShell } from 'mastodon/components/modal_shell'; +import { + ModalShell, + ModalShellActions, + ModalShellBody, +} from 'mastodon/components/modal_shell'; import { useAppDispatch } from 'mastodon/store'; import { AuthorNote } from '.'; @@ -64,7 +68,7 @@ export const CollectionShareModal: React.FC<{ return ( - +

{isNew ? ( - + - +
- + ); }; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 385ec6a794191c..5fbf7fff662d8e 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -3,7 +3,11 @@ import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import { Button } from 'mastodon/components/button'; -import { ModalShell } from 'mastodon/components/modal_shell'; +import { + ModalShell, + ModalShellActions, + ModalShellBody, +} from 'mastodon/components/modal_shell'; export interface BaseConfirmationModalProps { onClose: () => void; @@ -58,14 +62,14 @@ export const ConfirmationModal: React.FC< return ( - +

{title}

{message &&

{message}

} {extraContent ?? children} -
+ - + @@ -47,7 +61,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ rootClose onHide={handleClose} show={open} - target={anchorRef} + target={buttonRef} placement='top-end' flip offset={offset} @@ -57,17 +71,34 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
-

+

+ + +

{description}

diff --git a/app/javascript/mastodon/components/alt_text_badge/styles.module.scss b/app/javascript/mastodon/components/alt_text_badge/styles.module.scss new file mode 100644 index 00000000000000..1b7d5ec788a7c4 --- /dev/null +++ b/app/javascript/mastodon/components/alt_text_badge/styles.module.scss @@ -0,0 +1,17 @@ +.closeButton { + position: absolute; + top: 5px; + inset-inline-end: 2px; + padding: 10px; + + --default-icon-color: var(--color-text-on-media); + --default-bg-color: transparent; + --hover-icon-color: var(--color-text-on-media); + --hover-bg-color: rgb(from var(--color-text-on-media) r g b / 10%); + --focus-outline-color: var(--color-text-on-media); + + svg { + width: 20px; + height: 20px; + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 8e67274a621bba..aee074e4538338 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -285,6 +285,7 @@ --default-bg-color: transparent; --hover-icon-color: var(--color-text-primary); --hover-bg-color: var(--color-bg-brand-softer); + --focus-outline-color: var(--color-text-brand); display: inline-flex; color: var(--default-icon-color); @@ -313,7 +314,7 @@ } &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: 2px solid var(--focus-outline-color); } &.disabled { @@ -5016,6 +5017,13 @@ a.status-card { background-color: rgb(from var(--color-bg-media-base) r g b / 90%); } } + + &:focus-visible { + .spoiler-button__overlay__label { + outline: 2px solid var(--color-text-on-media); + outline-offset: -4px; + } + } } } @@ -7077,6 +7085,13 @@ a.status-card { font-size: 14px; font-weight: 700; line-height: 20px; + + &:focus-visible { + outline: none; + box-shadow: + inset 0 0 0 2px var(--color-text-on-media), + 0 0 0 2px var(--color-bg-media); + } } } @@ -7106,6 +7121,13 @@ a.status-card { cursor: pointer; pointer-events: auto; + &:focus-visible { + outline: none; + box-shadow: + inset 0 0 0 2px var(--color-text-on-media), + 0 0 0 2px var(--color-bg-media); + } + &--non-interactive { pointer-events: none; } @@ -7130,6 +7152,16 @@ a.status-card { overflow-y: auto; z-index: 10; + &:focus-visible { + box-shadow: + var(--dropdown-shadow), + inset 0 0 0 2px var(--color-text-on-media); + + // Extend background color for better visibility of the + // inset box-shadow "outline" + outline: 2px solid var(--color-bg-media); + } + &--solid { color: var(--color-text-primary); background: var(--color-bg-primary); @@ -7370,6 +7402,7 @@ a.status-card { color: var(--color-text-primary); position: relative; z-index: -1; + border-radius: inherit; &, img { @@ -7380,6 +7413,23 @@ a.status-card { img { object-fit: cover; } + + &:focus { + outline: none; + border-radius: inherit; + } + + // Double focus outline for better visibility on photos + &:focus-visible::after { + content: ''; + position: absolute; + inset: 2px; + z-index: 1; + border-radius: inherit; + border: 2px solid var(--color-text-on-inverted); + outline: 2px solid var(--color-bg-inverted); + pointer-events: none; + } } .media-gallery__preview { From 80066631eedc32a4eb0f756c038ba379eae5d92e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 11:47:19 +0100 Subject: [PATCH 53/89] New Crowdin Translations (automated) (#38032) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 14 ++++++ app/javascript/mastodon/locales/cy.json | 39 +++++++++++++++- app/javascript/mastodon/locales/da.json | 5 ++ app/javascript/mastodon/locales/de.json | 5 ++ app/javascript/mastodon/locales/el.json | 9 +++- 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 | 5 ++ app/javascript/mastodon/locales/fi.json | 5 ++ app/javascript/mastodon/locales/fr-CA.json | 2 + app/javascript/mastodon/locales/fr.json | 2 + app/javascript/mastodon/locales/ga.json | 4 ++ app/javascript/mastodon/locales/gl.json | 6 +++ 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/nl.json | 53 ++++++++++++++-------- app/javascript/mastodon/locales/pt-BR.json | 22 +++++++++ app/javascript/mastodon/locales/sq.json | 4 ++ app/javascript/mastodon/locales/vi.json | 17 ++++--- app/javascript/mastodon/locales/zh-TW.json | 5 ++ config/locales/cy.yml | 4 ++ config/locales/el.yml | 6 +-- config/locales/nl.yml | 6 +-- config/locales/simple_form.el.yml | 2 +- config/locales/simple_form.nl.yml | 8 ++-- 27 files changed, 213 insertions(+), 40 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index c3e879c7c2193e..f5ddf3193629f9 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -161,9 +161,19 @@ "account_edit.featured_hashtags.title": "Выбраныя хэштэгі", "account_edit.name_modal.add_title": "Дадаць бачнае імя", "account_edit.name_modal.edit_title": "Змяніць бачнае імя", + "account_edit.profile_tab.button_label": "Змяніць", + "account_edit.profile_tab.hint.description": "Гэтыя налады змяняюць тое, што бачаць карыстальнікі {server} у афіцыйных праграм, але гэта можа не ўплываць на карыстальнікаў іншых сервераў і неафіцыйных праграм.", + "account_edit.profile_tab.hint.title": "Знешні выгляд можа адрознівацца", + "account_edit.profile_tab.show_featured.description": "\"Рэкамендаванае\" — гэта неабавязковая ўкладка, куды вы можаце дадаць для паказу іншыя ўліковыя запісы.", + "account_edit.profile_tab.show_featured.title": "Паказваць укладку \"Рэкамендаванае\"", + "account_edit.profile_tab.show_media.description": "\"Медыя\" — гэта неабавязковая ўкладка, якая паказвае Вашыя допісы, у якіх ёсць відарысы і відэа.", + "account_edit.profile_tab.show_media.title": "Паказваць укладку \"Медыя\"", + "account_edit.profile_tab.show_media_replies.description": "Калі ўключыць, укладка \"Медыя\" будзе адлюстроўваць як Вашыя допісы, гэтак і Вашыя адказы на допісы іншых людзей.", + "account_edit.profile_tab.show_media_replies.title": "Змяшчаць адказы ва ўкладцы \"Медыя\"", "account_edit.profile_tab.subtitle": "Змяняйце на свой густ укладкі свайго профілю і тое, што яны паказваюць.", "account_edit.profile_tab.title": "Налады ўкладкі профілю", "account_edit.save": "Захаваць", + "account_edit_tags.add_tag": "Дадаць #{tagName}", "account_edit_tags.column_title": "Змяніць выбраныя хэштэгі", "account_edit_tags.help_text": "Выбраныя хэштэгі дапамагаюць карыстальнікам знаходзіць Ваш профіль і ўзаемадзейнічаць з ім. Яны дзейнічаюць як фільтры пры праглядзе актыўнасці на Вашай старонцы.", "account_edit_tags.search_placeholder": "Увядзіце хэштэг…", @@ -673,6 +683,7 @@ "keyboard_shortcuts.direct": "Адкрыць слупок прыватных згадванняў", "keyboard_shortcuts.down": "Перамясціцца ўніз па спісе", "keyboard_shortcuts.enter": "Адкрыць допіс", + "keyboard_shortcuts.explore": "Адкрыць трэндавую стужку", "keyboard_shortcuts.favourite": "Упадабаць допіс", "keyboard_shortcuts.favourites": "Адкрыць спіс упадабанага", "keyboard_shortcuts.federated": "Адкрыць інтэграваную стужку", @@ -1062,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon - найлепшы спосаб быць у курсе ўсяго, што адбываецца.", "sign_in_banner.sign_in": "Увайсці", "sign_in_banner.sso_redirect": "Уваход ці рэгістрацыя", + "skip_links.hotkey": "Спалучэнне клавіш {hotkey}", + "skip_links.skip_to_content": "Перайсці да асноўнага зместу", + "skip_links.skip_to_navigation": "Перайсці да асноўнай навігацыі", "status.admin_account": "Адкрыць інтэрфейс мадэратара для @{name}", "status.admin_domain": "Адкрыць інтэрфейс мадэратара для {domain}", "status.admin_status": "Адкрыць гэты допіс у інтэрфейсе мадэрацыі", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index de9b235878de89..d53972f052845c 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "Wedi'i ddilyn gan {name1} a {name2}", "account.featured": "Nodwedd", "account.featured.accounts": "Proffilau", + "account.featured.collections": "Casgliadau", "account.featured.hashtags": "Hashnodau", "account.featured_tags.last_status_at": "Y postiad olaf ar {date}", "account.featured_tags.last_status_never": "Dim postiadau", + "account.field_overflow": "Dangos cynnwys llawn", "account.filters.all": "Pob gweithgaredd", "account.filters.boosts_toggle": "Dangos hybiau", "account.filters.posts_boosts": "Postiadau a hybiau", @@ -144,6 +146,9 @@ "account_edit.bio.title": "Cyflwyniad", "account_edit.bio_modal.add_title": "Ychwanegu cyflwyniad", "account_edit.bio_modal.edit_title": "Golygu'r cyflwyniad", + "account_edit.button.add": "Ychwanegu {item}", + "account_edit.button.delete": "Dileu {item}", + "account_edit.button.edit": "Golygu {item}", "account_edit.char_counter": "{currentLength}/{maxLength} nod", "account_edit.column_button": "Gorffen", "account_edit.column_title": "Golygu Proffil", @@ -151,13 +156,28 @@ "account_edit.custom_fields.title": "Meysydd cyfaddas", "account_edit.display_name.placeholder": "Eich enw dangos yw sut mae'ch enw'n ymddangos ar eich proffil ac mewn llinellau amser.", "account_edit.display_name.title": "Enw dangos", + "account_edit.featured_hashtags.item": "hashnodau", "account_edit.featured_hashtags.placeholder": "Helpwch eraill i adnabod, a chael mynediad cyflym at eich hoff bynciau.", "account_edit.featured_hashtags.title": "Hashnodau dan sylw", "account_edit.name_modal.add_title": "Ychwanegu enw dangos", "account_edit.name_modal.edit_title": "Golygu enw dangos", + "account_edit.profile_tab.button_label": "Cyfaddasu", + "account_edit.profile_tab.hint.description": "Mae'r gosodiadau hyn yn cyfaddasu'r hyn y mae defnyddwyr yn ei weld ar {server} yn yr apiau swyddogol, ond efallai fyddan nhw ddim yn berthnasol i ddefnyddwyr ar weinyddion eraill ac apiau trydydd parti.", + "account_edit.profile_tab.hint.title": "Mae'r dangosyddion yn dal i amrywio", + "account_edit.profile_tab.show_featured.description": "Tab dewisol yw ‘Nodwedd’ lle gallwch ddangos cyfrifon eraill.", + "account_edit.profile_tab.show_featured.title": "Dangos y tab ‘Nodwedd’", + "account_edit.profile_tab.show_media.description": "Tab dewisol yw ‘Cyfryngau’ sy’n dangos eich postiadau sy’n cynnwys delweddau neu fideos.", + "account_edit.profile_tab.show_media.title": "Dangos y tab ‘Cyfryngau’", + "account_edit.profile_tab.show_media_replies.description": "Pan fydd wedi'i alluogi, mae'r tab Cyfryngau yn dangos eich postiadau ac atebion i bostiadau pobl eraill.", + "account_edit.profile_tab.show_media_replies.title": "Cynhwyswch atebion ar y tab ‘Cyfryngau’", "account_edit.profile_tab.subtitle": "Cyfaddaswch y tabiau ar eich proffil a'r hyn maen nhw'n ei ddangos.", "account_edit.profile_tab.title": "Gosodiadau tab proffil", "account_edit.save": "Cadw", + "account_edit_tags.column_title": "Golygu hashnodau dan sylw", + "account_edit_tags.help_text": "Mae hashnodau dan sylw yn helpu defnyddwyr i ddarganfod a rhyngweithio â'ch proffil. Maen nhw'n ymddangos fel hidlwyr ar olwg Gweithgaredd eich tudalen Proffil.", + "account_edit_tags.search_placeholder": "Rhowch hashnod…", + "account_edit_tags.suggestions": "Awgrymiadau:", + "account_edit_tags.tag_status_count": "{count, plural, one {# postiad} other {# postsiad}}", "account_note.placeholder": "Clicio i ychwanegu nodyn", "admin.dashboard.daily_retention": "Cyfradd cadw defnyddwyr fesul diwrnod ar ôl cofrestru", "admin.dashboard.monthly_retention": "Cyfradd cadw defnyddwyr fesul mis ar ôl cofrestru", @@ -260,6 +280,13 @@ "closed_registrations_modal.find_another_server": "Dod o hyd i weinydd arall", "closed_registrations_modal.preamble": "Mae Mastodon wedi'i ddatganoli, felly does dim gwahaniaeth ble rydych chi'n creu eich cyfrif, byddwch chi'n gallu dilyn a rhyngweithio ag unrhyw un ar y gweinydd hwn. Gallwch hyd yn oed ei gynnal un eich hun!", "closed_registrations_modal.title": "Cofrestru ar Mastodon", + "collection.share_modal.share_link_label": "Dolen rhannu gwahoddiad", + "collection.share_modal.share_via_post": "Postio ar Mastodon", + "collection.share_modal.share_via_system": "Rhannwch i…", + "collection.share_modal.title": "Rhannu casgliad", + "collection.share_modal.title_new": "Rhannwch eich casgliad newydd!", + "collection.share_template_other": "Edrychwch ar y casgliad trawiadol hwn: {link}", + "collection.share_template_own": "Edrychwch ar fy nghasgliad newydd: {link}", "collections.account_count": "{count, plural, one {# cyfrif} other {# cyfrif}}", "collections.accounts.empty_description": "Ychwanegwch hyd at {count} cyfrif rydych chi'n eu dilyn", "collections.accounts.empty_title": "Mae'r casgliad hwn yn wag", @@ -294,12 +321,15 @@ "collections.name_length_hint": "Terfyn o 40 nod", "collections.new_collection": "Casgliad newydd", "collections.no_collections_yet": "Dim casgliadau eto.", + "collections.old_last_post_note": "Postiwyd ddiwethaf dros wythnos yn ôl", "collections.remove_account": "Dileu'r cyfrif hwn", + "collections.report_collection": "Adroddwch am y casgliad hwn", "collections.search_accounts_label": "Chwiliwch am gyfrifon i'w hychwanegu…", "collections.search_accounts_max_reached": "Rydych chi wedi ychwanegu'r nifer mwyaf o gyfrifon", "collections.sensitive": "Sensitif", "collections.topic_hint": "Ychwanegwch hashnod sy'n helpu eraill i ddeall prif bwnc y casgliad hwn.", "collections.view_collection": "Gweld y casgliad", + "collections.view_other_collections_by_user": "Edrychwch ar gasgliadau eraill gan y defnyddiwr hwn", "collections.visibility_public": "Cyhoeddus", "collections.visibility_public_hint": "Mae modd eu canfod mewn canlyniadau chwilio a mannau eraill lle mae argymhellion yn ymddangos.", "collections.visibility_title": "Gwelededd", @@ -434,6 +464,7 @@ "conversation.open": "Gweld sgwrs", "conversation.with": "Gyda {names}", "copy_icon_button.copied": "Copïwyd i'r clipfwrdd", + "copy_icon_button.copy_this_text": "Copïo dolen i'r clipfwrdd", "copypaste.copied": "Wedi ei gopïo", "copypaste.copy_to_clipboard": "Copïo i'r clipfwrdd", "directory.federated": "O'r ffedysawd cyfan", @@ -442,7 +473,7 @@ "directory.recently_active": "Ar-lein yn ddiweddar", "disabled_account_banner.account_settings": "Gosodiadau'r cyfrif", "disabled_account_banner.text": "Mae eich cyfrif {disabledAccount} wedi ei analluogi ar hyn o bryd.", - "dismissable_banner.community_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl y caiff eu cyfrifon eu cynnal ar {domain}.", + "dismissable_banner.community_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl sydd a'u cyfrifon ar {domain}.", "dismissable_banner.dismiss": "Diystyru", "dismissable_banner.public_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl ar y ffedysawd y mae pobl ar {domain} yn eu dilyn.", "domain_block_modal.block": "Blocio gweinydd", @@ -451,7 +482,7 @@ "domain_block_modal.they_cant_follow": "All neb o'r gweinydd hwn eich dilyn.", "domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.", "domain_block_modal.title": "Blocio parth?", - "domain_block_modal.you_will_lose_num_followers": "Byddwch yn colli {followersCount, plural, one {{followersCountDisplay} dilynwr} other {{followersCountDisplay} dilynwyr}} a {followingCount, plural, one {{followingCountDisplay} person rydych yn dilyn} other {{followingCountDisplay} o bobl rydych yn eu dilyn}}.", + "domain_block_modal.you_will_lose_num_followers": "Byddwch yn colli {followersCount, plural, one {{followersCountDisplay} dilynwr} other {{followersCountDisplay} dilynwr}} a {followingCount, plural, one {{followingCountDisplay} person rydych yn dilyn} other {{followingCountDisplay} o bobl rydych yn eu dilyn}}.", "domain_block_modal.you_will_lose_relationships": "Byddwch yn colli'r holl ddilynwyr a phobl rydych chi'n eu dilyn o'r gweinydd hwn.", "domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.", "domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.", @@ -651,6 +682,7 @@ "keyboard_shortcuts.direct": "Agor colofn sylwadau preifat", "keyboard_shortcuts.down": "Symud lawr yn y rhestr", "keyboard_shortcuts.enter": "Agor post", + "keyboard_shortcuts.explore": "Agor llinell amser trendio", "keyboard_shortcuts.favourite": "Ffafrio postiad", "keyboard_shortcuts.favourites": "Agor rhestr ffefrynnau", "keyboard_shortcuts.federated": "Agor ffrwd y ffederasiwn", @@ -963,6 +995,7 @@ "report.category.title_account": "proffil", "report.category.title_status": "post", "report.close": "Iawn", + "report.collection_comment": "Pam ydych chi eisiau rhoi gwybod am y casgliad hwn?", "report.comment.title": "Oes unrhyw beth arall y dylem ei wybod yn eich barn chi?", "report.forward": "Ymlaen i {target}", "report.forward_hint": "Mae'r cyfrif o weinydd arall. Anfon copi anhysbys o'r adroddiad yno hefyd?", @@ -984,6 +1017,8 @@ "report.rules.title": "Pa reolau sy'n cael eu torri?", "report.statuses.subtitle": "Dewiswch bob un sy'n berthnasol", "report.statuses.title": "Oes postiadau eraill sy'n cefnogi'r adroddiad hwn?", + "report.submission_error": "Doedd dim modd cyflwyno'r adroddiad", + "report.submission_error_details": "Gwiriwch eich cysylltiad rhwydwaith a cheisiwch eto yn nes ymlaen.", "report.submit": "Cyflwyno", "report.target": "Adrodd am {target}", "report.thanks.take_action": "Dyma'ch dewisiadau i reoli'r hyn a welwch ar Mastodon:", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 108a450c8a2116..34215c3d6de2fb 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Tilpas fanerne på din profil og det, de viser.", "account_edit.profile_tab.title": "Indstillinger for profil-fane", "account_edit.save": "Gem", + "account_edit_tags.add_tag": "Tilføj #{tagName}", "account_edit_tags.column_title": "Rediger fremhævede hashtags", "account_edit_tags.help_text": "Fremhævede hashtags hjælper brugere med at finde og interagere med din profil. De vises som filtre i aktivitetsvisningen på din profilside.", "account_edit_tags.search_placeholder": "Angiv et hashtag…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Åbn kolonne med private omtaler", "keyboard_shortcuts.down": "Flyt nedad på listen", "keyboard_shortcuts.enter": "Åbn indlæg", + "keyboard_shortcuts.explore": "Åbn Trender-tidslinjen", "keyboard_shortcuts.favourite": "Føj indlæg til favoritter", "keyboard_shortcuts.favourites": "Åbn favoritlisten", "keyboard_shortcuts.federated": "Åbn fødereret tidslinje", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon er den bedste måde at holde sig ajour med, hvad der sker.", "sign_in_banner.sign_in": "Log ind", "sign_in_banner.sso_redirect": "Log ind eller Tilmeld", + "skip_links.hotkey": "Genvejstast {hotkey}", + "skip_links.skip_to_content": "Gå til hovedindholdet", + "skip_links.skip_to_navigation": "Gå til hovednavigation", "status.admin_account": "Åbn modereringsbrugerflade for @{name}", "status.admin_domain": "Åbn modereringsbrugerflade for {domain}", "status.admin_status": "Åbn dette indlæg i modereringsbrugerfladen", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 65418f851ed4a3..689dcc230d6dac 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Passe die Tabs deines Profils und deren Inhalte an.", "account_edit.profile_tab.title": "Profil-Tab-Einstellungen", "account_edit.save": "Speichern", + "account_edit_tags.add_tag": "#{tagName} hinzufügen", "account_edit_tags.column_title": "Vorgestellte Hashtags bearbeiten", "account_edit_tags.help_text": "Vorgestellte Hashtags können dabei helfen, dein Profil zu entdecken und besser mit dir zu interagieren. Sie dienen als Filter in der Aktivitätenübersicht deines Profils.", "account_edit_tags.search_placeholder": "Gib einen Hashtag ein …", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Private Erwähnungen öffnen", "keyboard_shortcuts.down": "Auswahl nach unten bewegen", "keyboard_shortcuts.enter": "Beitrag öffnen", + "keyboard_shortcuts.explore": "Trends aufrufen", "keyboard_shortcuts.favourite": "Beitrag favorisieren", "keyboard_shortcuts.favourites": "Favoriten öffnen", "keyboard_shortcuts.federated": "Föderierte Timeline öffnen", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon ist der beste Zugang, um auf dem Laufenden zu bleiben.", "sign_in_banner.sign_in": "Anmelden", "sign_in_banner.sso_redirect": "Anmelden oder registrieren", + "skip_links.hotkey": "Tastenkürzel {hotkey}", + "skip_links.skip_to_content": "Zum Hauptinhalt wechseln", + "skip_links.skip_to_navigation": "Zur Hauptnavigation wechseln", "status.admin_account": "@{name} moderieren", "status.admin_domain": "{domain} moderieren", "status.admin_status": "Beitrag moderieren", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index a649feef705dc2..8f31850a8cad53 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Προσαρμόστε τις καρτέλες στο προφίλ σας και τι εμφανίζουν.", "account_edit.profile_tab.title": "Ρυθμίσεις καρτέλας προφίλ", "account_edit.save": "Αποθήκευση", + "account_edit_tags.add_tag": "Προσθήκη #{tagName}", "account_edit_tags.column_title": "Επεξεργασία αναδεδειγμένων ετικετών", "account_edit_tags.help_text": "Οι αναδεδειγμένες ετικέτες βοηθούν τους χρήστες να ανακαλύψουν και να αλληλεπιδράσουν με το προφίλ σας. Εμφανίζονται ως φίλτρα στην προβολή Δραστηριότητας της σελίδας προφίλ σας.", "account_edit_tags.search_placeholder": "Εισάγετε μια ετικέτα…", @@ -496,7 +497,7 @@ "domain_pill.who_they_are": "Από τη στιγμή που τα πλήρη ονόματα χρηστών λένε ποιος είναι κάποιος και πού είναι, μπορείς να αλληλεπιδράσεις με άτομα απ' όλο τον κοινωνικό ιστό των .", "domain_pill.who_you_are": "Επειδή το πλήρες όνομα χρήστη σου λέει ποιος είσαι και πού βρίσκεσαι, άτομα μπορούν να αλληλεπιδράσουν μαζί σου στον κοινωνικό ιστό των .", "domain_pill.your_handle": "Το πλήρες όνομα χρήστη σου:", - "domain_pill.your_server": "Το ψηφιακό σου σπίτι, όπου ζουν όλες σου οι αναρτήσεις. Δε σ' αρέσει αυτός; Μετακινήσου σε διακομιστές ανά πάσα στιγμή και πάρε και τους ακόλουθούς σου.", + "domain_pill.your_server": "Το ψηφιακό σου σπίτι, όπου ζουν όλες σου οι αναρτήσεις. Δε σ' αρέσει αυτός; Μετακινήσου σε διακομιστές ανά πάσα στιγμή και πάρε και τους ακόλουθούς σου μαζί.", "domain_pill.your_username": "Το μοναδικό σου αναγνωριστικό σε τούτο τον διακομιστή. Είναι πιθανό να βρεις χρήστες με το ίδιο όνομα χρήστη σε διαφορετικούς διακομιστές.", "dropdown.empty": "Διαλέξτε μια επιλογή", "embed.instructions": "Ενσωμάτωσε αυτή την ανάρτηση στην ιστοσελίδα σου αντιγράφοντας τον παρακάτω κώδικα.", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Άνοιγμα της στήλης ιδιωτικών επισημάνσεων", "keyboard_shortcuts.down": "Μετακίνηση προς τα κάτω στη λίστα", "keyboard_shortcuts.enter": "Άνοιγμα ανάρτησης", + "keyboard_shortcuts.explore": "Άνοιγμα χρονολογίου τάσεων", "keyboard_shortcuts.favourite": "Αγάπησε την ανάρτηση", "keyboard_shortcuts.favourites": "Άνοιγμα λίστας αγαπημένων", "keyboard_shortcuts.federated": "Άνοιγμα ομοσπονδιακής ροής", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Το Mastodon είναι ο καλύτερος τρόπος για να συμβαδίσεις με τα γεγονότα.", "sign_in_banner.sign_in": "Σύνδεση", "sign_in_banner.sso_redirect": "Συνδεθείτε ή Εγγραφείτε", + "skip_links.hotkey": "Πλήκτρο συντόμευσης {hotkey}", + "skip_links.skip_to_content": "Μετάβαση στο κύριο περιεχόμενο", + "skip_links.skip_to_navigation": "Μετάβαση στην κύρια πλοήγηση", "status.admin_account": "Άνοιγμα διεπαφής συντονισμού για @{name}", "status.admin_domain": "Άνοιγμα διεπαφής συντονισμού για {domain}", "status.admin_status": "Άνοιγμα αυτής της ανάρτησης σε διεπαφή συντονισμού", @@ -1128,7 +1133,7 @@ "status.quote_followers_only": "Μόνο οι ακόλουθοι μπορούν να παραθέσουν αυτή την ανάρτηση", "status.quote_manual_review": "Ο συντάκτης θα επανεξετάσει χειροκίνητα", "status.quote_noun": "Παράθεση", - "status.quote_policy_change": "Αλλάξτε ποιός μπορεί να κάνει παράθεση", + "status.quote_policy_change": "Άλλαξε ποιός μπορεί να κάνει παράθεση", "status.quote_post_author": "Παρατίθεται μια ανάρτηση από @{name}", "status.quote_private": "Ιδιωτικές αναρτήσεις δεν μπορούν να παρατεθούν", "status.quotes.empty": "Κανείς δεν έχει παραθέσει αυτή την ανάρτηση ακόμη. Μόλις το κάνει, θα εμφανιστεί εδώ.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 49e117724993a4..8863ae2e701077 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Customise the tabs on your profile and what they display.", "account_edit.profile_tab.title": "Profile tab settings", "account_edit.save": "Save", + "account_edit_tags.add_tag": "Add #{tagName}", "account_edit_tags.column_title": "Edit featured hashtags", "account_edit_tags.help_text": "Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.", "account_edit_tags.search_placeholder": "Enter a hashtag…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Open private mentions column", "keyboard_shortcuts.down": "to move down in the list", "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.explore": "Open trending timeline", "keyboard_shortcuts.favourite": "Favourite post", "keyboard_shortcuts.favourites": "Open favourites list", "keyboard_shortcuts.federated": "to open federated timeline", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Sign in", "sign_in_banner.sso_redirect": "Login or Register", + "skip_links.hotkey": "Hotkey {hotkey}", + "skip_links.skip_to_content": "Skip to main content", + "skip_links.skip_to_navigation": "Skip to main navigation", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 6ecc2c1c3817c8..08e06b3088eef9 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personalizá las pestañas en tu perfil y qué van a mostrar.", "account_edit.profile_tab.title": "Configuración de pestaña de perfil", "account_edit.save": "Guardar", + "account_edit_tags.add_tag": "Agregar #{tagName}", "account_edit_tags.column_title": "Editar etiquetas destacadas", "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir e interactuar con tu perfil. Las etiquetas destacadas aparecen como filtros en la vista de actividad de la página de tu perfil.", "account_edit_tags.search_placeholder": "Ingresá una etiqueta…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Abrir columna de menciones privadas", "keyboard_shortcuts.down": "Bajar en la lista", "keyboard_shortcuts.enter": "Abrir mensaje", + "keyboard_shortcuts.explore": "Abrir línea temporal de tendencias", "keyboard_shortcuts.favourite": "Marcar mensaje como favorito", "keyboard_shortcuts.favourites": "Abrir lista de favoritos", "keyboard_shortcuts.federated": "Abrir línea temporal federada", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon es la mejor manera de mantenerse al día sobre lo que está sucediendo.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Iniciá sesión o registrate", + "skip_links.hotkey": "Atajo {hotkey}", + "skip_links.skip_to_content": "Ir al contenido principal", + "skip_links.skip_to_navigation": "Ir a la navegación principal", "status.admin_account": "Abrir interface de moderación para @{name}", "status.admin_domain": "Abrir interface de moderación para {domain}", "status.admin_status": "Abrir este mensaje en la interface de moderación", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index a884b94a58214e..795422514b2869 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.", "account_edit.profile_tab.title": "Configuración de la pestaña de perfil", "account_edit.save": "Guardar", + "account_edit_tags.add_tag": "Añadir #{tagName}", "account_edit_tags.column_title": "Editar etiquetas destacadas", "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir tu perfil e interactuar con él. Aparecen como filtros en la vista Actividad de tu página de perfil.", "account_edit_tags.search_placeholder": "Introduce una etiqueta…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Abrir columna de menciones privadas", "keyboard_shortcuts.down": "Descender en la lista", "keyboard_shortcuts.enter": "Abrir publicación", + "keyboard_shortcuts.explore": "Abrir tendencias", "keyboard_shortcuts.favourite": "Marcar como favorita la publicación", "keyboard_shortcuts.favourites": "Abrir lista de favoritos", "keyboard_shortcuts.federated": "Abrir cronología federada", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon es el mejor modo de mantenerse al día sobre qué está ocurriendo.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Iniciar sesión o Registrarse", + "skip_links.hotkey": "Atajos {hotkey}", + "skip_links.skip_to_content": "Ir al contenido principal", + "skip_links.skip_to_navigation": "Ir a la navegación principal", "status.admin_account": "Abrir interfaz de moderación para @{name}", "status.admin_domain": "Abrir interfaz de moderación para {domain}", "status.admin_status": "Abrir este estado en la interfaz de moderación", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index cafd1419373b28..6b93ec8a228266 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.", "account_edit.profile_tab.title": "Configuración de la pestaña de perfil", "account_edit.save": "Guardar", + "account_edit_tags.add_tag": "Agregar #{tagName}", "account_edit_tags.column_title": "Editar etiquetas destacadas", "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir e interactuar con tu perfil. Aparecen como filtros en la vista de actividad de tu página de perfil.", "account_edit_tags.search_placeholder": "Introduce una etiqueta…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Abrir la columna de menciones privadas", "keyboard_shortcuts.down": "Moverse hacia abajo en la lista", "keyboard_shortcuts.enter": "Abrir publicación", + "keyboard_shortcuts.explore": "Abrir tendencias", "keyboard_shortcuts.favourite": "Marcar como favorita la publicación", "keyboard_shortcuts.favourites": "Abrir lista de favoritos", "keyboard_shortcuts.federated": "Abrir la cronología federada", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon es el mejor modo de mantenerse al día sobre qué está ocurriendo.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Iniciar sesión o Registrarse", + "skip_links.hotkey": "Atajo {hotkey}", + "skip_links.skip_to_content": "Abrir Saltar al contenido principal", + "skip_links.skip_to_navigation": "Saltar a la navegación principal", "status.admin_account": "Abrir interfaz de moderación para @{name}", "status.admin_domain": "Abrir interfaz de moderación para {domain}", "status.admin_status": "Abrir esta publicación en la interfaz de moderación", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 4b95f2ff94796f..544344cfa61b23 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Mukauta profiilisi välilehtiä ja sitä, mitä niissä näkyy.", "account_edit.profile_tab.title": "Profiilin välilehtien asetukset", "account_edit.save": "Tallenna", + "account_edit_tags.add_tag": "Lisää #{tagName}", "account_edit_tags.column_title": "Muokkaa esiteltäviä aihetunnisteita", "account_edit_tags.help_text": "Esiteltävät aihetunnisteet auttavat käyttäjiä löytämään profiilisi ja olemaan vuorovaikutuksessa sen kanssa. Ne näkyvät suodattimina profiilisivusi Toiminta-näkymässä.", "account_edit_tags.search_placeholder": "Syötä aihetunniste…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Avaa yksityismainintojen sarake", "keyboard_shortcuts.down": "Siirry luettelossa eteenpäin", "keyboard_shortcuts.enter": "Avaa julkaisu", + "keyboard_shortcuts.explore": "Avaa Suosittua-aikajana", "keyboard_shortcuts.favourite": "Lisää julkaisu suosikkeihin", "keyboard_shortcuts.favourites": "Avaa suosikkiluettelo", "keyboard_shortcuts.federated": "Avaa yleinen aikajana", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon on paras tapa pysyä ajan tasalla siitä, mitä ympärillä tapahtuu.", "sign_in_banner.sign_in": "Kirjaudu", "sign_in_banner.sso_redirect": "Kirjaudu tai rekisteröidy", + "skip_links.hotkey": "Pikanäppäin {hotkey}", + "skip_links.skip_to_content": "Siitty pääsisältöön", + "skip_links.skip_to_navigation": "Siirry päänavigaatioon", "status.admin_account": "Avaa tilin @{name} moderointinäkymä", "status.admin_domain": "Avaa palvelimen {domain} moderointinäkymä", "status.admin_status": "Avaa julkaisu moderointinäkymässä", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index ac99eb7ba89834..5e753c0e5c7bfc 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -682,6 +682,7 @@ "keyboard_shortcuts.direct": "Ouvrir la colonne des mentions privées", "keyboard_shortcuts.down": "Descendre dans la liste", "keyboard_shortcuts.enter": "Ouvrir cette publication", + "keyboard_shortcuts.explore": "Ouvrir le fil des tendances", "keyboard_shortcuts.favourite": "Ajouter la publication aux favoris", "keyboard_shortcuts.favourites": "Ouvrir la liste des favoris", "keyboard_shortcuts.federated": "Ouvrir le fil global", @@ -1071,6 +1072,7 @@ "sign_in_banner.mastodon_is": "Mastodon est le meilleur moyen de suivre ce qui se passe.", "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou s’inscrire", + "skip_links.hotkey": "Raccourci {hotkey}", "status.admin_account": "Ouvrir l’interface de modération pour @{name}", "status.admin_domain": "Ouvrir l’interface de modération pour {domain}", "status.admin_status": "Ouvrir ce message dans l’interface de modération", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 2c6f4be00a8169..d52c9c6f346754 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -682,6 +682,7 @@ "keyboard_shortcuts.direct": "Ouvrir la colonne des mentions privées", "keyboard_shortcuts.down": "Descendre dans la liste", "keyboard_shortcuts.enter": "Ouvrir le message", + "keyboard_shortcuts.explore": "Ouvrir le fil des tendances", "keyboard_shortcuts.favourite": "Ajouter le message aux favoris", "keyboard_shortcuts.favourites": "Ouvrir la liste des favoris", "keyboard_shortcuts.federated": "Ouvrir le fil fédéré", @@ -1071,6 +1072,7 @@ "sign_in_banner.mastodon_is": "Mastodon est le meilleur moyen de suivre ce qui se passe.", "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou s’inscrire", + "skip_links.hotkey": "Raccourci {hotkey}", "status.admin_account": "Ouvrir l’interface de modération pour @{name}", "status.admin_domain": "Ouvrir l’interface de modération pour {domain}", "status.admin_status": "Ouvrir ce message dans l’interface de modération", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 776df2db7bf293..e8b0c623e7fcea 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -682,6 +682,7 @@ "keyboard_shortcuts.direct": "Oscail colún tráchtanna príobháideacha", "keyboard_shortcuts.down": "Bog síos ar an liosta", "keyboard_shortcuts.enter": "Oscail postáil", + "keyboard_shortcuts.explore": "Oscail an amlíne treochta", "keyboard_shortcuts.favourite": "Postáil is fearr leat", "keyboard_shortcuts.favourites": "Oscail liosta ceanáin", "keyboard_shortcuts.federated": "Oscail amlíne cónaidhmithe", @@ -1071,6 +1072,9 @@ "sign_in_banner.mastodon_is": "Is é Mastodon an bealach is fearr le coinneáil suas lena bhfuil ag tarlú.", "sign_in_banner.sign_in": "Sinigh isteach", "sign_in_banner.sso_redirect": "Logáil isteach nó Cláraigh", + "skip_links.hotkey": "Eochair Their {hotkey}", + "skip_links.skip_to_content": "Léim go dtí an príomhábhar", + "skip_links.skip_to_navigation": "Léim go dtí an príomh-loingseoireacht", "status.admin_account": "Oscail comhéadan modhnóireachta do @{name}", "status.admin_domain": "Oscail comhéadan modhnóireachta le haghaidh {domain}", "status.admin_status": "Oscail an postáil seo sa chomhéadan modhnóireachta", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 4301834a08998d..c636d203c2fccb 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -163,6 +163,7 @@ "account_edit.name_modal.edit_title": "Editar o nome público", "account_edit.profile_tab.button_label": "Personalizar", "account_edit.profile_tab.hint.description": "Estes axustes personalizan o que ven as usuarias de {server} nas apps oficiais, pero non se aplica ás usuarias de outros servidores nin apps alleas.", + "account_edit.profile_tab.hint.title": "A aparencia pode variar", "account_edit.profile_tab.show_featured.description": "'Destacado' é unha pestana optativa na que podes mostrar outras contas.", "account_edit.profile_tab.show_featured.title": "Mostrar pestana 'Destacado'", "account_edit.profile_tab.show_media.description": "'Multimedia' é unha pestana optativa na que aparecen as túas publicacións con fotos e vídeos.", @@ -172,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personaliza as pestanas e o seu contido no teu perfil.", "account_edit.profile_tab.title": "Perfil e axustes das pestanas", "account_edit.save": "Gardar", + "account_edit_tags.add_tag": "Engadir #{tagName}", "account_edit_tags.column_title": "Editar cancelos destacados", "account_edit_tags.help_text": "Os cancelos destacados axúdanlle ás usuarias a atopar e interactuar co teu perfil. Aparecen como filtros na túa páxina de perfil na vista Actividade.", "account_edit_tags.search_placeholder": "Escribe un cancelo…", @@ -681,6 +683,7 @@ "keyboard_shortcuts.direct": "Abrir a columna de mencións privadas", "keyboard_shortcuts.down": "Para mover cara abaixo na listaxe", "keyboard_shortcuts.enter": "Para abrir publicación", + "keyboard_shortcuts.explore": "Abrir cronoloxía coas tendencias", "keyboard_shortcuts.favourite": "Marcar como favorita", "keyboard_shortcuts.favourites": "Para abrir a lista das favoritas", "keyboard_shortcuts.federated": "Para abrir a cronoloxía federada", @@ -1070,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon é o mellor xeito de estar ao día do que acontece.", "sign_in_banner.sign_in": "Iniciar sesión", "sign_in_banner.sso_redirect": "Acceder ou Crear conta", + "skip_links.hotkey": "Atallo {hotkey}", + "skip_links.skip_to_content": "Ir ao contido principal", + "skip_links.skip_to_navigation": "Ir ao menú principal", "status.admin_account": "Abrir interface de moderación para @{name}", "status.admin_domain": "Abrir interface de moderación para {domain}", "status.admin_status": "Abrir esta publicación na interface de moderación", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 7bea5cac0f7b8a..a44204d630d294 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "התאימו את הטאבים בפרופיל שלכם ומה שהם יציגו.", "account_edit.profile_tab.title": "הגדרות טאבים לפרופיל", "account_edit.save": "שמירה", + "account_edit_tags.add_tag": "הוספת #{tagName}", "account_edit_tags.column_title": "עריכת תגיות נבחרות", "account_edit_tags.help_text": "תגיות נבחרות עוזרות למשתמשים לגלות ולהשתמש בפרופיל שלך. הן יופיעו כסננים במבט הפעילויות על עמוד הפרופיל שלך.", "account_edit_tags.search_placeholder": "הזנת תגית…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "לפתוח עמודת שיחות פרטיות", "keyboard_shortcuts.down": "לנוע במורד הרשימה", "keyboard_shortcuts.enter": "פתח הודעה", + "keyboard_shortcuts.explore": "הצגת קו הזמן של ההודעות החמות", "keyboard_shortcuts.favourite": "חיבוב הודעה", "keyboard_shortcuts.favourites": "פתיחת רשימת מחובבות", "keyboard_shortcuts.federated": "פתיחת ציר זמן בין-קהילתי", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "מסטודון הוא הדרך הטובה ביותר לעקוב אחרי מה שקורה.", "sign_in_banner.sign_in": "התחברות", "sign_in_banner.sso_redirect": "התחברות/הרשמה", + "skip_links.hotkey": "מקש קיצור {hotkey}", + "skip_links.skip_to_content": "דלג לתוכן הראשי", + "skip_links.skip_to_navigation": "דילוג לניווט המרכזי", "status.admin_account": "פתח/י ממשק פיקוח דיון עבור @{name}", "status.admin_domain": "פתיחת ממשק פיקוח דיון עבור {domain}", "status.admin_status": "לפתוח הודעה זו במסך ניהול הדיונים", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index ced1cb70f8d49f..79719c0082b8b1 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Sérsníddu flipana á notandasniðinu þínu og hvað þeir birta.", "account_edit.profile_tab.title": "Stillingar notandasniðsflipa", "account_edit.save": "Vista", + "account_edit_tags.add_tag": "Bæta við #{tagName}", "account_edit_tags.column_title": "Breyta myllumerkjum með aukið vægi", "account_edit_tags.help_text": "Myllumerki með aukið vægi hjálpa lesendum að finna og eiga við notandasíðuna þína. Þau birtast sem síur í virkniflipa notandasíðunnar þinnar.", "account_edit_tags.search_placeholder": "Settu inn myllumerki…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Opna dálk með einkaspjalli", "keyboard_shortcuts.down": "Fara neðar í listanum", "keyboard_shortcuts.enter": "Opna færslu", + "keyboard_shortcuts.explore": "Opna tímalínu yfir vinsælt", "keyboard_shortcuts.favourite": "Eftirlætisfærsla", "keyboard_shortcuts.favourites": "Opna eftirlætislista", "keyboard_shortcuts.federated": "Opna sameiginlega tímalínu", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon er besta leiðin til að fylgjast með hvað sé í gangi.", "sign_in_banner.sign_in": "Skrá inn", "sign_in_banner.sso_redirect": "Skrá inn eða nýskrá", + "skip_links.hotkey": "Flýtilykill {hotkey}", + "skip_links.skip_to_content": "Fara í aðalefni", + "skip_links.skip_to_navigation": "Fara í aðalflakk", "status.admin_account": "Opna umsjónarviðmót fyrir @{name}", "status.admin_domain": "Opna umsjónarviðmót fyrir @{domain}", "status.admin_status": "Opna þessa færslu í umsjónarviðmótinu", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 226314eba1eba8..482032ebe5f299 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personalizza le schede del tuo profilo e ciò che mostrano.", "account_edit.profile_tab.title": "Impostazioni della scheda del profilo", "account_edit.save": "Salva", + "account_edit_tags.add_tag": "Aggiungi #{tagName}", "account_edit_tags.column_title": "Modifica gli hashtag in evidenza", "account_edit_tags.help_text": "Gli hashtag in evidenza aiutano gli utenti a scoprire e interagire con il tuo profilo. Appaiono come filtri nella visualizzazione Attività della tua pagina del profilo.", "account_edit_tags.search_placeholder": "Inserisci un hashtag…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Aprire la colonna delle menzioni private", "keyboard_shortcuts.down": "Scorri in basso nell'elenco", "keyboard_shortcuts.enter": "Apre il post", + "keyboard_shortcuts.explore": "Apri la timeline di tendenza", "keyboard_shortcuts.favourite": "Contrassegna il post come preferito", "keyboard_shortcuts.favourites": "Apre l'elenco dei preferiti", "keyboard_shortcuts.federated": "Apre la cronologia federata", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon è il modo migliore per tenere il passo con quello che sta accadendo.", "sign_in_banner.sign_in": "Accedi", "sign_in_banner.sso_redirect": "Accedi o Registrati", + "skip_links.hotkey": "Scorciatoia {hotkey}", + "skip_links.skip_to_content": "Salta al contenuto principale", + "skip_links.skip_to_navigation": "Salta alla navigazione principale", "status.admin_account": "Apri interfaccia di moderazione per @{name}", "status.admin_domain": "Apri l'interfaccia di moderazione per {domain}", "status.admin_status": "Apri questo post nell'interfaccia di moderazione", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 3bdec56300f1c8..05dd5e11b5d36b 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -86,7 +86,7 @@ "account.menu.copied": "Accountlink naar het klembord gekopieerd", "account.menu.copy": "Link kopiëren", "account.menu.direct": "Privébericht", - "account.menu.hide_reblogs": "Boosts in tijdlijn verbergen", + "account.menu.hide_reblogs": "Boosts op tijdlijn verbergen", "account.menu.mention": "Vermelding", "account.menu.mute": "Account negeren", "account.menu.note.description": "Alleen voor jou zichtbaar", @@ -94,7 +94,7 @@ "account.menu.remove_follower": "Volger verwijderen", "account.menu.report": "Account rapporteren", "account.menu.share": "Delen…", - "account.menu.show_reblogs": "Boosts in tijdlijn tonen", + "account.menu.show_reblogs": "Boosts op tijdlijn tonen", "account.menu.unblock": "Account deblokkeren", "account.menu.unblock_domain": "{domain} deblokkeren", "account.menu.unmute": "Account niet langer negeren", @@ -142,7 +142,7 @@ "account.unmute": "@{name} niet langer negeren", "account.unmute_notifications_short": "Meldingen niet langer negeren", "account.unmute_short": "Niet langer negeren", - "account_edit.bio.placeholder": "Voeg een korte introductie toe om anderen te helpen je te identificeren.", + "account_edit.bio.placeholder": "Vertel iets over jezelf, zodat anderen inzicht krijgen in wat voor persoon je bent.", "account_edit.bio.title": "Biografie", "account_edit.bio_modal.add_title": "Biografie toevoegen", "account_edit.bio_modal.edit_title": "Biografie bewerken", @@ -153,19 +153,29 @@ "account_edit.column_button": "Klaar", "account_edit.column_title": "Profiel bewerken", "account_edit.custom_fields.placeholder": "Voeg je voornaamwoorden, externe links of iets anders toe dat je wilt delen.", - "account_edit.custom_fields.title": "Aangepaste velden", - "account_edit.display_name.placeholder": "Je weergavenaam wordt weergegeven op jouw profiel en in tijdlijnen.", + "account_edit.custom_fields.title": "Extra velden", + "account_edit.display_name.placeholder": "Je weergavenaam wordt op jouw profiel en op tijdlijnen weergegeven.", "account_edit.display_name.title": "Weergavenaam", "account_edit.featured_hashtags.item": "hashtags", - "account_edit.featured_hashtags.placeholder": "Help anderen je favoriete onderwerpen te identificeren en er snel toegang toe te hebben.", + "account_edit.featured_hashtags.placeholder": "Geef anderen een overzicht van en snel toegang tot je favoriete onderwerpen.", "account_edit.featured_hashtags.title": "Uitgelichte hashtags", "account_edit.name_modal.add_title": "Weergavenaam toevoegen", "account_edit.name_modal.edit_title": "Weergavenaam bewerken", - "account_edit.profile_tab.subtitle": "Pas de tabbladen op je profiel aan en wat ze weergeven.", + "account_edit.profile_tab.button_label": "Aanpassen", + "account_edit.profile_tab.hint.description": "Deze instellingen passen wat gebruikers op {server} zien in de officiële apps aan, maar ze zijn mogelijk niet van toepassing op gebruikers op andere servers en in apps van derden.", + "account_edit.profile_tab.hint.title": "De weergave kan verschillen", + "account_edit.profile_tab.show_featured.description": "'Uitgelicht' is een optioneel tabblad waarop je andere accounts kunt aanbevelen.", + "account_edit.profile_tab.show_featured.title": "Tabblad 'Uitgelicht' tonen", + "account_edit.profile_tab.show_media.description": "'Media' is een optioneel tabblad waarop jouw berichten met afbeeldingen, video en audio staan.", + "account_edit.profile_tab.show_media.title": "Tabblad 'Media' tonen", + "account_edit.profile_tab.show_media_replies.description": "Wanneer dit is ingeschakeld, worden op het tabblad Media zowel jouw berichten en reacties op berichten van anderen weergegeven.", + "account_edit.profile_tab.show_media_replies.title": "Jouw reacties aan het tabblad 'Media' toevoegen", + "account_edit.profile_tab.subtitle": "De tabbladen op je profiel aanpassen en wat er op wordt weergegeven.", "account_edit.profile_tab.title": "Instellingen voor tabblad Profiel", "account_edit.save": "Opslaan", - "account_edit_tags.column_title": "Aanbevolen hashtags bewerken", - "account_edit_tags.help_text": "Aanbevolen hashtags helpen gebruikers je profiel te ontdekken en te communiceren. Ze verschijnen als filters op de activiteitenweergave van je pagina.", + "account_edit_tags.add_tag": "#{tagName} toevoegen", + "account_edit_tags.column_title": "Uitgelichte hashtags bewerken", + "account_edit_tags.help_text": "Uitgelichte hashtags helpen gebruikers je profiel te ontdekken en om er interactie mee te communiceren. Ze verschijnen als filters op je Profielpagina onder het tabblad Activiteit.", "account_edit_tags.search_placeholder": "Voer een hashtag in…", "account_edit_tags.suggestions": "Suggesties:", "account_edit_tags.tag_status_count": "{count, plural, one {# bericht} other {# berichten}}", @@ -271,14 +281,15 @@ "closed_registrations_modal.find_another_server": "Een andere server zoeken", "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", + "collection.share_modal.share_link_label": "Uitnodigingslink delen", "collection.share_modal.share_via_post": "Bericht op Mastodon", "collection.share_modal.share_via_system": "Delen met…", "collection.share_modal.title": "Verzameling delen", - "collection.share_modal.title_new": "Deel je nieuwe verzameling!", + "collection.share_modal.title_new": "Je nieuwe verzameling delen!", "collection.share_template_other": "Bekijk deze coole verzameling: {link}", "collection.share_template_own": "Bekijk mijn nieuwe verzameling: {link}", "collections.account_count": "{count, plural, one {# account} other {# accounts}}", - "collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt", + "collections.accounts.empty_description": "Tot {count} accounts die je volgt toevoegen", "collections.accounts.empty_title": "Deze verzameling is leeg", "collections.collection_description": "Omschrijving", "collections.collection_name": "Naam", @@ -311,10 +322,10 @@ "collections.name_length_hint": "Limiet van 40 tekens", "collections.new_collection": "Nieuwe verzameling", "collections.no_collections_yet": "Nog geen verzamelingen.", - "collections.old_last_post_note": "Laatst gepost over een week geleden", - "collections.remove_account": "Deze account verwijderen", + "collections.old_last_post_note": "Meer dan een week geleden voor het laatst een bericht geplaatst", + "collections.remove_account": "Dit account verwijderen", "collections.report_collection": "Deze verzameling rapporteren", - "collections.search_accounts_label": "Zoek naar accounts om toe te voegen…", + "collections.search_accounts_label": "Naar accounts zoeken om toe te voegen…", "collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd", "collections.sensitive": "Gevoelig", "collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze verzameling te begrijpen.", @@ -408,11 +419,11 @@ "confirmations.discard_draft.post.title": "Conceptbericht verwijderen?", "confirmations.discard_edit_media.confirm": "Verwijderen", "confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch verwijderen?", - "confirmations.follow_to_collection.confirm": "Volgen en toevoegen aan verzameling", - "confirmations.follow_to_collection.message": "Je moet {name} volgen om ze aan een verzameling toe te voegen.", + "confirmations.follow_to_collection.confirm": "Volgen en aan verzameling toevoegen", + "confirmations.follow_to_collection.message": "Je moet {name} volgen om dit account aan een verzameling toe te kunnen voegen.", "confirmations.follow_to_collection.title": "Account volgen?", "confirmations.follow_to_list.confirm": "Volgen en toevoegen aan de lijst", - "confirmations.follow_to_list.message": "Je moet {name} volgen om ze toe te voegen aan een lijst.", + "confirmations.follow_to_list.message": "Je moet {name} volgen om dit account aan een lijst toe te kunnen voegen.", "confirmations.follow_to_list.title": "Gebruiker volgen?", "confirmations.logout.confirm": "Uitloggen", "confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?", @@ -454,7 +465,7 @@ "conversation.open": "Gesprek tonen", "conversation.with": "Met {names}", "copy_icon_button.copied": "Gekopieerd naar klembord", - "copy_icon_button.copy_this_text": "Link kopiëren naar klembord", + "copy_icon_button.copy_this_text": "Link naar klembord kopiëren", "copypaste.copied": "Gekopieerd", "copypaste.copy_to_clipboard": "Naar klembord kopiëren", "directory.federated": "Fediverse (wat bekend is)", @@ -672,6 +683,7 @@ "keyboard_shortcuts.direct": "Kolom met privéberichten openen", "keyboard_shortcuts.down": "Naar beneden in de lijst bewegen", "keyboard_shortcuts.enter": "Volledig bericht tonen", + "keyboard_shortcuts.explore": "Trends openen", "keyboard_shortcuts.favourite": "Bericht als favoriet markeren", "keyboard_shortcuts.favourites": "Lijst met favorieten tonen", "keyboard_shortcuts.federated": "Globale tijdlijn tonen", @@ -1006,7 +1018,7 @@ "report.rules.title": "Welke regels worden geschonden?", "report.statuses.subtitle": "Selecteer wat van toepassing is", "report.statuses.title": "Zijn er berichten die deze rapportage ondersteunen?", - "report.submission_error": "Rapportering kon niet worden ingediend", + "report.submission_error": "Deze rapportage kon niet worden ingediend", "report.submission_error_details": "Controleer de netwerkverbinding en probeer het later opnieuw.", "report.submit": "Verzenden", "report.target": "{target} rapporteren", @@ -1061,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon is de beste manier om wat er gebeurt bij te houden.", "sign_in_banner.sign_in": "Inloggen", "sign_in_banner.sso_redirect": "Inloggen of Registreren", + "skip_links.hotkey": "Sneltoets {hotkey}", + "skip_links.skip_to_content": "Ga naar de hoofdinhoud", + "skip_links.skip_to_navigation": "Ga naar de hoofdnavigatie", "status.admin_account": "Moderatie-omgeving van @{name} openen", "status.admin_domain": "Moderatie-omgeving van {domain} openen", "status.admin_status": "Dit bericht in de moderatie-omgeving tonen", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index d6084366cd568e..1dfabccddc547c 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "Seguido por {name1} e {name2}", "account.featured": "Em destaque", "account.featured.accounts": "Perfis", + "account.featured.collections": "Coleções", "account.featured.hashtags": "Hashtags", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", + "account.field_overflow": "Mostrar todo conteúdo", "account.filters.all": "Todas atividades", "account.filters.boosts_toggle": "Mostrar impulsos", "account.filters.posts_boosts": "Publicações e impulsos", @@ -159,6 +161,13 @@ "account_edit.featured_hashtags.title": "Hashtags em destaque", "account_edit.name_modal.add_title": "Inserir nome de exibição", "account_edit.name_modal.edit_title": "Editar nome de exibição", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.title": "Exibições divergem", + "account_edit.profile_tab.show_featured.title": "Mostrar aba \"Destaque\"", + "account_edit.profile_tab.show_media.description": "\"Mídia\" é uma aba opcional que mostra seus posts, contendo imagens ou vídeos.", + "account_edit.profile_tab.show_media.title": "Mostrar aba \"Mídia\"", + "account_edit.profile_tab.show_media_replies.description": "Quando ativa, a aba Mídia mostra seus posts e respostas nos posts de outras pessoas.", + "account_edit.profile_tab.show_media_replies.title": "Incluir respostas na aba \"Mídia\"", "account_edit.profile_tab.subtitle": "Personalizar as abas em seu perfil e o que elas exibem.", "account_edit.profile_tab.title": "Configurações da aba de perfil", "account_edit.save": "Salvar", @@ -269,6 +278,13 @@ "closed_registrations_modal.find_another_server": "Encontrar outro servidor", "closed_registrations_modal.preamble": "O Mastodon é descentralizado, não importa onde você criou a sua conta, será possível seguir e interagir com qualquer pessoa neste servidor. Você pode até mesmo criar o seu próprio servidor!", "closed_registrations_modal.title": "Inscrevendo-se no Mastodon", + "collection.share_modal.share_link_label": "Link para convite", + "collection.share_modal.share_via_post": "Postar no Mastodon", + "collection.share_modal.share_via_system": "Enviar para...", + "collection.share_modal.title": "Compartilhar coleção", + "collection.share_modal.title_new": "Compartilhe sua nova coleção!", + "collection.share_template_other": "Confira esta coleção incrível: {link}", + "collection.share_template_own": "Confira minha nova coleção: {link}", "collections.account_count": "{count, plural, one {# conta} other {# conta}}", "collections.accounts.empty_description": "Adicionar até {count} contas que você segue", "collections.accounts.empty_title": "Esta coleção está vazia", @@ -305,11 +321,13 @@ "collections.no_collections_yet": "Ainda não há coleções.", "collections.old_last_post_note": "Publicado pela última vez semana passada", "collections.remove_account": "Remover esta conta", + "collections.report_collection": "Denunciar esta coleção", "collections.search_accounts_label": "Buscar contas para adicionar…", "collections.search_accounts_max_reached": "Você acrescentou o numero máximo de contas", "collections.sensitive": "Sensível", "collections.topic_hint": "Adicione uma hashtag que ajude os outros a entender o tópico principal desta coleção.", "collections.view_collection": "Ver coleção", + "collections.view_other_collections_by_user": "Ver outras coleções deste usuário", "collections.visibility_public": "Público", "collections.visibility_public_hint": "Localizável em resultados de buscas e outras áreas onde recomendações aparecem.", "collections.visibility_title": "Visibilidade", @@ -444,6 +462,7 @@ "conversation.open": "Ver conversa", "conversation.with": "Com {names}", "copy_icon_button.copied": "Copiado para a área de transferência", + "copy_icon_button.copy_this_text": "Copiar link para área de transferência", "copypaste.copied": "Copiado", "copypaste.copy_to_clipboard": "Copiar para a área de transferência", "directory.federated": "Do fediverso conhecido", @@ -973,6 +992,7 @@ "report.category.title_account": "perfil", "report.category.title_status": "publicação", "report.close": "Concluir", + "report.collection_comment": "Por que você quer denunciar esta coleção?", "report.comment.title": "Há algo mais que você acredita que devemos saber?", "report.forward": "Encaminhar para {target}", "report.forward_hint": "A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá?", @@ -994,6 +1014,8 @@ "report.rules.title": "Quais regras estão sendo violadas?", "report.statuses.subtitle": "Selecione tudo que se aplica", "report.statuses.title": "Existem postagens que respaldam esse relatório?", + "report.submission_error": "A denúncia não pôde ser enviada", + "report.submission_error_details": "Verifique sua conexão e tente novamente mais tarde", "report.submit": "Enviar", "report.target": "Denunciando {target}", "report.thanks.take_action": "Aqui estão suas opções para controlar o que você vê no Mastodon:", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index e3a698cd207b5e..a1966c609daf51 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Përshtatni skedat në profilin tuaj dhe ato çka shfaqet në to.", "account_edit.profile_tab.title": "Rregullime skede profili", "account_edit.save": "Ruaje", + "account_edit_tags.add_tag": "Shtoje #{tagName}", "account_edit_tags.column_title": "Përpunoni hashtag-ë të zgjedhur", "account_edit_tags.help_text": "Hashtag-ët e zgjedhur i ndihmojnë përdoruesit të zbulojnë dhe ndërveprojnë me profilin tuaj. Ata duken si filtra te pamja Veprimtari e faqes tuaj të Profilit.", "account_edit_tags.search_placeholder": "Jepni një hashtag…", @@ -678,6 +679,7 @@ "keyboard_shortcuts.direct": "Hap shtyllë përmendjesh private", "keyboard_shortcuts.down": "Për zbritje poshtë nëpër listë", "keyboard_shortcuts.enter": "Për hapje postimi", + "keyboard_shortcuts.explore": "Hap rrjedhë kohore të gjërave në modë", "keyboard_shortcuts.favourite": "I vini shenjë postimit si të parapëlqyer", "keyboard_shortcuts.favourites": "Hapni listë të parapëlqyerish", "keyboard_shortcuts.federated": "Për hapje rrjedhe kohore të të federuarave", @@ -1067,6 +1069,8 @@ "sign_in_banner.mastodon_is": "Mastodon-i është rruga më e mirë për të ndjekur se ç’ndodh.", "sign_in_banner.sign_in": "Hyni", "sign_in_banner.sso_redirect": "Bëni hyrjen, ose Regjistrohuni", + "skip_links.skip_to_content": "Kalo te lënda kryesore", + "skip_links.skip_to_navigation": "Kalo te menuja kryesore e lëvizjeve", "status.admin_account": "Hap ndërfaqe moderimi për @{name}", "status.admin_domain": "Hap ndërfaqe moderimi për {domain}", "status.admin_status": "Hape këtë mesazh te ndërfaqja e moderimit", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 711ae4dd3528c2..d16fb8636ab29f 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -64,7 +64,7 @@ "account.follow_request_short": "Yêu cầu", "account.followers": "Người theo dõi", "account.followers.empty": "Chưa có người theo dõi nào.", - "account.followers_counter": "{count, plural, other {{counter} Người theo dõi}}", + "account.followers_counter": "{count, plural, other {{counter} người theo dõi}}", "account.followers_you_know_counter": "{counter} bạn biết", "account.following": "Đang theo dõi", "account.following_counter": "{count, plural, other {{counter} theo dõi}}", @@ -105,12 +105,12 @@ "account.muted": "Đã phớt lờ", "account.muting": "Đang ẩn", "account.mutual": "Theo dõi nhau", - "account.name.help.domain": "{domain} là máy chủ lưu trữ hồ sơ và tút của người dùng.", + "account.name.help.domain": "{domain} là máy chủ lưu trữ hồ sơ và tút của tài khoản.", "account.name.help.domain_self": "{domain} là máy chủ lưu trữ hồ sơ và tút của bạn.", - "account.name.help.footer": "Giống như bạn có thể gửi email cho mọi người bằng các dịch vụ email khác nhau, bạn có thể tương tác với mọi người trên các máy chủ Mastodon khác – và với bất kỳ ai trên các ứng dụng xã hội khác được cung cấp bởi cùng một bộ quy tắc mà Mastodon sử dụng (giao thức ActivityPub).", - "account.name.help.header": "Một địa chỉ giống như một địa chỉ email", - "account.name.help.username": "{username} là tên người dùng của tài khoản này trên máy chủ của họ. Trên máy chủ khác có thể có tên người dùng giống vậy.", - "account.name.help.username_self": "{username} là tên người dùng của bạn trên máy chủ này. Trên máy chủ khác có thể có tên người dùng giống bạn.", + "account.name.help.footer": "Giống như bạn có thể gửi email cho mọi người trên các dịch vụ email khác nhau, bạn có thể tương tác với mọi người trên các máy chủ Mastodon khác – và trên các ứng dụng xã hội khác sử dụng cùng giao thức mà Mastodon sử dụng (ActivityPub).", + "account.name.help.header": "Một địa chỉ giống như địa chỉ email", + "account.name.help.username": "{username} là tên người dùng duy nhất trên máy chủ này. Các máy chủ khác có thể cũng có tên người dùng giống vậy.", + "account.name.help.username_self": "{username} là tên người dùng của bạn trên máy chủ này. Các máy chủ khác cũng có thể có tên người dùng giống bạn.", "account.name_info": "Điều này nghĩa là gì?", "account.no_bio": "Chưa có miêu tả.", "account.node_modal.callout": "Các ghi chú chỉ hiển thị với bạn.", @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Tùy chỉnh tab trên hồ sơ của bạn và những gì chúng hiển thị.", "account_edit.profile_tab.title": "Thiết lập tab hồ sơ", "account_edit.save": "Lưu", + "account_edit_tags.add_tag": "Thêm #{tagName}", "account_edit_tags.column_title": "Sửa hashtag thường dùng", "account_edit_tags.help_text": "Hashtag thường dùng giúp bạn mọi người khám phá và tương tác với hồ sơ của bạn. Chúng xuất hiện như những bộ lọc trên phần Hoạt động hồ sơ.", "account_edit_tags.search_placeholder": "Nhập một hashtag…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "mở nhắn riêng", "keyboard_shortcuts.down": "di chuyển xuống dưới danh sách", "keyboard_shortcuts.enter": "mở tút", + "keyboard_shortcuts.explore": "mở bảng tin xu hướng", "keyboard_shortcuts.favourite": "thích tút", "keyboard_shortcuts.favourites": "mở lượt thích", "keyboard_shortcuts.federated": "mở mạng liên hợp", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon là cách tốt nhất để nắm bắt những gì đang xảy ra.", "sign_in_banner.sign_in": "Đăng nhập", "sign_in_banner.sso_redirect": "Đăng nhập", + "skip_links.hotkey": "Phím tắt {hotkey}", + "skip_links.skip_to_content": "Chuyển tới nội dung chính", + "skip_links.skip_to_navigation": "Chuyển đến điều hướng chính", "status.admin_account": "Mở giao diện quản trị @{name}", "status.admin_domain": "Mở giao diện quản trị @{domain}", "status.admin_status": "Mở tút này trong giao diện quản trị", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 20cad7639f248e..10b6726c8dad2f 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "自訂您個人檔案之分頁與內容。", "account_edit.profile_tab.title": "個人檔案分頁設定", "account_edit.save": "儲存", + "account_edit_tags.add_tag": "加入 #{tagName}", "account_edit_tags.column_title": "編輯推薦主題標籤", "account_edit_tags.help_text": "推薦主題標籤幫助其他人發現並與您的個人檔案互動。它們將作為過濾器出現於您個人檔案頁面之動態中。", "account_edit_tags.search_placeholder": "請輸入主題標籤…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "開啟私訊對話欄", "keyboard_shortcuts.down": "向下移動", "keyboard_shortcuts.enter": "檢視嘟文", + "keyboard_shortcuts.explore": "開啟熱門趨勢時間軸", "keyboard_shortcuts.favourite": "加到最愛", "keyboard_shortcuts.favourites": "開啟最愛列表", "keyboard_shortcuts.federated": "開啟聯邦時間軸", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon 是跟上時代潮流的最佳工具!", "sign_in_banner.sign_in": "登入", "sign_in_banner.sso_redirect": "登入或註冊", + "skip_links.hotkey": "快速鍵 {hotkey}", + "skip_links.skip_to_content": "跳至主內容", + "skip_links.skip_to_navigation": "跳至主導航區", "status.admin_account": "開啟 @{name} 的管理介面", "status.admin_domain": "開啟 {domain} 的管理介面", "status.admin_status": "於管理介面開啟此嘟文", diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 86523be9629c09..46843c122dd0ff 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -283,6 +283,7 @@ cy: demote_user_html: Mae %{name} wedi israddio defnyddiwr %{target} destroy_announcement_html: Mae %{name} wedi dileu cyhoeddiad %{target} destroy_canonical_email_block_html: Mae %{name} wedi dad-rwystro parth e-bost %{target} + destroy_collection_html: Mae %{name} wedi tynnu casgliad gan %{target} destroy_custom_emoji_html: Mae %{name} wedi dileu emoji %{target} destroy_domain_allow_html: Mae %{name} wedi gwrthod ffederasiwn gyda pharth %{target} destroy_domain_block_html: Mae %{name} wedi dad rwystro parth %{target} @@ -322,6 +323,7 @@ cy: unsilence_account_html: Mae %{name} wedi dadwneud terfyn cyfrif %{target} unsuspend_account_html: Mae %{name} wedi dad atal cyfrif %{target} update_announcement_html: Mae %{name} wedi diweddaru cyhoeddiad %{target} + update_collection_html: Mae casgliad %{name} wedi'i ddiweddaru gan %{target} update_custom_emoji_html: Mae %{name} wedi diweddaru emoji %{target} update_domain_block_html: Mae %{name} wedi diweddaru bloc parth %{target} update_ip_block_html: Mae %{name} wedi newid rheol IP %{target} @@ -745,6 +747,7 @@ cy: cancel: Canslo category: Categori category_description_html: Bydd y rheswm dros adrodd am y cyfrif a/neu’r cynnwys hwn yn cael ei ddyfynnu wrth gyfathrebu â’r cyfrif a adroddwyd + collections: "(%{count}) casgliad" comment: none: Dim comment_description_html: 'I ddarparu rhagor o wybodaeth, ysgrifennodd %{name}:' @@ -780,6 +783,7 @@ cy: resolved_msg: Llwyddwyd i ddatrys yr adroddiad! skip_to_actions: Mynd i gamau gweithredu status: Statws + statuses: "(%{count}) postiad" statuses_description_html: Bydd cynnwys tramgwyddus yn cael ei ddyfynnu wrth gyfathrebu â'r cyfrif a adroddwyd summary: action_preambles: diff --git a/config/locales/el.yml b/config/locales/el.yml index d806558a6d116c..ea588f347d6ccb 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1326,7 +1326,7 @@ el: author_attribution: example_title: Δείγμα κειμένου hint_html: Γράφεις ειδήσεις ή άρθρα blog εκτός του Mastodon; Έλεγξε πώς μπορείς να πάρεις τα εύσημα όταν κοινοποιούνται στο Mastodon. - instructions: 'Βεβαιώσου ότι ο κώδικας αυτός είναι στο HTML του άρθρου σου:' + instructions: 'Βεβαιώσου ότι ο κώδικας αυτός είναι στην HTML του άρθρου σου:' more_from_html: Περισσότερα από %{name} s_blog: Ιστολόγιο του/της %{name} then_instructions: Στη συνέχεια, πρόσθεσε το όνομα τομέα της δημοσίευσης στο παρακάτω πεδίο. @@ -2187,9 +2187,9 @@ el: seamless_external_login: Επειδή έχεις συνδεθεί μέσω τρίτης υπηρεσίας, οι ρυθμίσεις συνθηματικού και email δεν είναι διαθέσιμες. signed_in_as: 'Έχεις συνδεθεί ως:' verification: - extra_instructions_html: Συμβουλή: Ο σύνδεσμος στην ιστοσελίδα σου μπορεί να είναι αόρατος. Το σημαντικό μέρος είναι το rel="me" που αποτρέπει την μίμηση σε ιστοσελίδες με περιεχόμενο παραγόμενο από χρήστες. Μπορείς ακόμα να χρησιμοποιήσεις μια ετικέτα συνδέσμου στην κεφαλίδα της σελίδας αντί για a, αλλά ο κώδικας HTML πρέπει να είναι προσβάσιμος χωρίς την εκτέλεση JavaScript. + extra_instructions_html: Συμβουλή: Ο σύνδεσμος στην ιστοσελίδα σου μπορεί να είναι αόρατος. Το σημαντικό μέρος είναι το rel="me" που αποτρέπει την μίμηση σε ιστοσελίδες με περιεχόμενο παραγόμενο από χρήστες. Μπορείς ακόμα να χρησιμοποιήσεις μια ετικέτα link στην κεφαλίδα της σελίδας αντί για a, αλλά η HTML πρέπει να είναι προσβάσιμη χωρίς την εκτέλεση JavaScript. here_is_how: Δείτε πώς - hint_html: Η επαλήθευση της ταυτότητας στο Mastodon είναι για όλους. Βασισμένο σε ανοιχτά πρότυπα ιστού, τώρα και για πάντα δωρεάν. Το μόνο που χρειάζεσαι είναι μια προσωπική ιστοσελίδα που ο κόσμος να σε αναγνωρίζει από αυτή. Όταν συνδέεσαι σε αυτήν την ιστοσελίδα από το προφίλ σου, θα ελέγξουμε ότι η ιστοσελίδα συνδέεται πίσω στο προφίλ σου και θα δείξει μια οπτική ένδειξη σε αυτό. + hint_html: Η επαλήθευση της ταυτότητας στο Mastodon είναι για όλους. Βασισμένο σε ανοιχτά πρότυπα ιστού, τώρα και για πάντα δωρεάν. Το μόνο που χρειάζεσαι είναι μια προσωπική ιστοσελίδα που ο κόσμος να σε αναγνωρίζει από αυτή. Όταν βάζεις σύνδεσμο προς αυτήν την ιστοσελίδα από το προφίλ σου, θα ελέγξουμε ότι η ιστοσελίδα συνδέει πίσω στο προφίλ σου και θα δείξουμε μια οπτική ένδειξη σε αυτό. instructions_html: Αντέγραψε και επικόλλησε τον παρακάτω κώδικα στην HTML της ιστοσελίδας σου. Στη συνέχεια, πρόσθεσε τη διεύθυνση της ιστοσελίδας σου σε ένα από τα επιπλέον πεδία στο προφίλ σου από την καρτέλα "Επεξεργασία προφίλ" και αποθήκευσε τις αλλαγές. verification: Επαλήθευση verified_links: Οι επαληθευμένοι σύνδεσμοι σας diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6f52b8ec97de8e..ffa8a49459b6b1 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -267,7 +267,7 @@ nl: demote_user_html: Gebruiker %{target} is door %{name} gedegradeerd destroy_announcement_html: "%{name} heeft de mededeling %{target} verwijderd" destroy_canonical_email_block_html: "%{name} deblokkeerde e-mail met de hash %{target}" - destroy_collection_html: "%{name} heeft de verzameling van %{target} verwijderd" + destroy_collection_html: "%{name} heeft een verzameling van %{target} verwijderd" destroy_custom_emoji_html: "%{name} verwijderde de emoji %{target}" destroy_domain_allow_html: "%{name} heeft de federatie met het domein %{target} afgekeurd" destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd @@ -307,7 +307,7 @@ nl: unsilence_account_html: Beperking van account %{target} is door %{name} opgeheven unsuspend_account_html: Opschorten van account %{target} is door %{name} opgeheven update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt" - update_collection_html: "%{name} heeft de verzameling van %{target} bijgewerkt" + update_collection_html: "%{name} heeft een verzameling van %{target} bijgewerkt" update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}" update_ip_block_html: "%{name} wijzigde de IP-regel voor %{target}" @@ -351,7 +351,7 @@ nl: one: 1 account other: "%{count} accounts" open: Openen - view_publicly: Openbaar bericht bekijken + view_publicly: Openbare verzameling bekijken critical_update_pending: Kritieke update in behandeling custom_emojis: assign_category: Categorie toewijzen diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index b710cd819cdf5d..abd00ff7dd9fc6 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -77,7 +77,7 @@ el: domain: Αυτό μπορεί να είναι το όνομα τομέα που εμφανίζεται στη διεύθυνση email ή η εγγραφή MX που χρησιμοποιεί. Θα ελέγχονται κατά την εγγραφή. with_dns_records: Θα γίνει απόπειρα ανάλυσης των εγγραφών DNS του τομέα και τα αποτελέσματα θα μπουν και αυτά σε μαύρη λίστα featured_tag: - name: 'Εδώ είναι μερικές από τις ετικέτες που χρησιμοποιήσατε περισσότερο πρόσφατα:' + name: 'Εδώ είναι μερικές από τις ετικέτες που χρησιμοποίησες περισσότερο πρόσφατα:' filters: action: Επιλέξτε ποια ενέργεια θα εκτελεστεί όταν μια ανάρτηση ταιριάζει με το φίλτρο actions: diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 6d25b184c468ea..bdf627973a724d 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -54,7 +54,7 @@ nl: password: Gebruik tenminste 8 tekens phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen. - setting_advanced_layout: Geef Mastodon in meerdere kolommen weer, waarmee je jouw tijdlijn, meldingen en een derde kolom naar keuze in een opslag kunt bekijken. Niet aanbevolen voor kleinere schermen. + setting_advanced_layout: Geef Mastodon in meerdere kolommen weer, waarmee je jouw tijdlijn, meldingen en een derde kolom naar keuze in één opslag kunt bekijken. Niet aanbevolen voor kleinere schermen. setting_aggregate_reblogs: Geen nieuwe boosts tonen voor berichten die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts) setting_always_send_emails: Normaliter worden er geen e-mailmeldingen verstuurd wanneer je actief Mastodon gebruikt setting_boost_modal: Wanneer dit is ingeschakeld, krijg je eerst een bevestigingsvenster te zien waarmee je de zichtbaarheid van je boost kunt wijzigen. @@ -93,7 +93,7 @@ nl: content_cache_retention_period: Alle berichten van andere servers (inclusief boosts en reacties) worden verwijderd na het opgegeven aantal dagen, ongeacht enige lokale gebruikersinteractie met die berichten. Dit betreft ook berichten die een lokale gebruiker aan diens bladwijzers heeft toegevoegd of als favoriet heeft gemarkeerd. Privéberichten tussen gebruikers van verschillende servers gaan ook verloren en zijn onmogelijk te herstellen. Het gebruik van deze instelling is bedoeld voor servers die een speciaal doel dienen en overtreedt veel gebruikersverwachtingen wanneer deze voor algemeen gebruik wordt geïmplementeerd. custom_css: Je kunt aangepaste CSS toepassen op de webversie van deze Mastodon-server. favicon: WEBP, PNG, GIF of JPG. Vervangt de standaard Mastodon favicon met een aangepast pictogram. - landing_page: Selecteert welke pagina nieuwe bezoekers te zien krijgen wanneer ze voor het eerst op jouw server terechtkomen. Wanneer je ‘Trends’ selecteert, moeten trends ingeschakeld zijn onder 'Serverinstellingen > Ontdekken'. Als je ‘Lokale tijdlijn’ selecteert, moet ‘Toegang tot openbare lokale berichten’ worden ingesteld op ‘Iedereen’ onder 'Serverinstellingen > Ontdekken'. + landing_page: Selecteert welke pagina nieuwe bezoekers te zien krijgen wanneer ze voor het eerst op jouw server terechtkomen. Wanneer je ‘Trends’ selecteert, moeten trends ingeschakeld zijn onder 'Serverinstellingen > Ontdekken'. Wanneer je ‘Lokale tijdlijn’ selecteert, moet ‘Toegang tot openbare lokale berichten’ worden ingesteld op ‘Iedereen’ onder 'Serverinstellingen > Ontdekken'. mascot: Overschrijft de illustratie in de geavanceerde webomgeving. media_cache_retention_period: Mediabestanden van berichten van externe gebruikers worden op jouw server in de cache opgeslagen. Indien ingesteld op een positieve waarde, worden media verwijderd na het opgegeven aantal dagen. Als de mediagegevens worden opgevraagd nadat ze zijn verwijderd, worden ze opnieuw gedownload wanneer de originele inhoud nog steeds beschikbaar is. Vanwege beperkingen op hoe vaak linkvoorbeelden sites van derden raadplegen, wordt aanbevolen om deze waarde in te stellen op ten minste 14 dagen. Anders worden linkvoorbeelden niet op aanvraag bijgewerkt. min_age: Gebruikers krijgen tijdens hun inschrijving de vraag om hun geboortedatum te bevestigen @@ -224,7 +224,7 @@ nl: email: E-mailadres expires_in: Vervalt na fields: Extra velden - filter_action: Filter-actie + filter_action: Filteractie header: Omslagfoto honeypot: "%{label} (niet invullen)" inbox_url: Inbox-URL van de relayserver @@ -237,7 +237,7 @@ nl: password: Wachtwoord phrase: Trefwoord of zinsdeel setting_advanced_layout: Geavanceerde webomgeving inschakelen - setting_aggregate_reblogs: Boosts in tijdlijnen groeperen + setting_aggregate_reblogs: Boosts op tijdlijnen groeperen setting_always_send_emails: Altijd e-mailmeldingen verzenden setting_auto_play_gif: Geanimeerde GIF's automatisch afspelen setting_boost_modal: Zichtbaarheid van boosts From 6e5aa00436053a3cb51d6eeeb1f9ed0d771d5087 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 3 Mar 2026 12:02:04 +0100 Subject: [PATCH 54/89] Anchor post navigation via hotkeys to top of viewport (#38036) --- .../mastodon/features/ui/util/focusUtils.ts | 32 ++++++++----------- app/javascript/styles/mastodon/basics.scss | 9 ++++++ .../styles/mastodon/components.scss | 6 ---- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/app/javascript/mastodon/features/ui/util/focusUtils.ts b/app/javascript/mastodon/features/ui/util/focusUtils.ts index 1043d09ed7e904..8237eb286b9624 100644 --- a/app/javascript/mastodon/features/ui/util/focusUtils.ts +++ b/app/javascript/mastodon/features/ui/util/focusUtils.ts @@ -47,7 +47,7 @@ function focusColumnTitle(index: number, multiColumn: boolean) { /** * Move focus to the column of the passed index (1-based). - * Focus is placed on the topmost visible item, or the column title + * Focus is placed on the topmost visible item, or the column title. */ export function focusColumn(index = 1) { // Skip the leftmost drawer in multi-column mode @@ -94,8 +94,16 @@ export function focusColumn(index = 1) { window.innerWidth || document.documentElement.clientWidth; const { item, rect } = itemToFocus; + const scrollParent = isMultiColumnLayout + ? container + : document.documentElement; + const columnHeaderHeight = + parseInt( + getComputedStyle(scrollParent).getPropertyValue('--column-header-height'), + ) || 0; + if ( - container.scrollTop > item.offsetTop || + scrollParent.scrollTop > item.offsetTop - columnHeaderHeight || rect.right > viewportWidth || rect.left < 0 ) { @@ -141,11 +149,7 @@ export function focusFirstItem() { /** * Focus the item next to the one with the provided index */ -export function focusItemSibling( - index: number, - direction: 1 | -1, - scrollThreshold = 62, -) { +export function focusItemSibling(index: number, direction: 1 | -1) { const focusedElement = document.activeElement; const itemList = focusedElement?.closest('.item-list'); @@ -173,17 +177,9 @@ export function focusItemSibling( } if (targetElement) { - const elementRect = targetElement.getBoundingClientRect(); - - const isFullyVisible = - elementRect.top >= scrollThreshold && - elementRect.bottom <= window.innerHeight; - - if (!isFullyVisible) { - targetElement.scrollIntoView({ - block: direction === 1 ? 'start' : 'center', - }); - } + targetElement.scrollIntoView({ + block: 'start', + }); targetElement.focus(); } diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 8cc06a50b30d49..610730df5a73b9 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -19,6 +19,15 @@ html { &.rtl { --text-x-direction: -1; } + + // Compensate for column header height when scrolling elements into view + --column-header-height: 62px; + + scroll-padding-top: var(--column-header-height); + + &:has(.layout-multi-column) { + --column-header-height: 0; + } } html.has-modal { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index aee074e4538338..1b6b47397d27e3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2984,8 +2984,6 @@ a.account__display-name { } &__main { - --column-header-height: 62px; - box-sizing: border-box; width: 100%; flex: 0 1 auto; @@ -9143,10 +9141,6 @@ noscript { .conversation { position: relative; - // When scrolling these elements into view, take into account - // the column header height - scroll-margin-top: var(--column-header-height, 0); - &.unread { &::before { content: ''; From 1d21d9d4c2266eea50b422fe1e2c6c8e0c793712 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 3 Mar 2026 13:48:50 +0100 Subject: [PATCH 55/89] Convert `ColumnsArea` component to TS (#38031) --- .../features/ui/components/columns_area.jsx | 170 ----------------- .../features/ui/components/columns_area.tsx | 177 ++++++++++++++++++ .../ui/containers/columns_area_container.js | 10 - app/javascript/mastodon/features/ui/index.jsx | 19 +- 4 files changed, 192 insertions(+), 184 deletions(-) delete mode 100644 app/javascript/mastodon/features/ui/components/columns_area.jsx create mode 100644 app/javascript/mastodon/features/ui/components/columns_area.tsx delete mode 100644 app/javascript/mastodon/features/ui/containers/columns_area_container.js diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx deleted file mode 100644 index 5609a52b3108d5..00000000000000 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ /dev/null @@ -1,170 +0,0 @@ -import PropTypes from 'prop-types'; -import { Children, cloneElement, createContext, useContext, useCallback } from 'react'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { scrollRight } from '../../../scroll'; -import { - Compose, - Notifications, - HomeTimeline, - CommunityTimeline, - PublicTimeline, - HashtagTimeline, - DirectTimeline, - FavouritedStatuses, - BookmarkedStatuses, - ListTimeline, - Directory, -} from '../util/async-components'; -import { useColumnsContext } from '../util/columns_context'; - -import Bundle from './bundle'; -import BundleColumnError from './bundle_column_error'; -import { ColumnLoading } from './column_loading'; -import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel'; -import DrawerLoading from './drawer_loading'; -import { CollapsibleNavigationPanel } from 'mastodon/features/navigation_panel'; - -const componentMap = { - 'COMPOSE': Compose, - 'HOME': HomeTimeline, - 'NOTIFICATIONS': Notifications, - 'PUBLIC': PublicTimeline, - 'REMOTE': PublicTimeline, - 'COMMUNITY': CommunityTimeline, - 'HASHTAG': HashtagTimeline, - 'DIRECT': DirectTimeline, - 'FAVOURITES': FavouritedStatuses, - 'BOOKMARKS': BookmarkedStatuses, - 'LIST': ListTimeline, - 'DIRECTORY': Directory, -}; - -const TabsBarPortal = () => { - const {setTabsBarElement} = useColumnsContext(); - - const setRef = useCallback((element) => { - if(element) - setTabsBarElement(element); - }, [setTabsBarElement]); - - return
; -}; - -// Simple context to allow column children to know which column they're in -export const ColumnIndexContext = createContext(1); -/** - * @returns {number} - */ -export const useColumnIndexContext = () => useContext(ColumnIndexContext); - -export default class ColumnsArea extends ImmutablePureComponent { - static propTypes = { - columns: ImmutablePropTypes.list.isRequired, - isModalOpen: PropTypes.bool.isRequired, - singleColumn: PropTypes.bool, - children: PropTypes.node, - }; - - // Corresponds to (max-width: $no-gap-breakpoint - 1px) in SCSS - mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)'); - - state = { - renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), - }; - - componentDidMount() { - if (this.mediaQuery) { - if (this.mediaQuery.addEventListener) { - this.mediaQuery.addEventListener('change', this.handleLayoutChange); - } else { - this.mediaQuery.addListener(this.handleLayoutChange); - } - this.setState({ renderComposePanel: !this.mediaQuery.matches }); - } - - this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); - } - - componentWillUnmount () { - if (this.mediaQuery) { - if (this.mediaQuery.removeEventListener) { - this.mediaQuery.removeEventListener('change', this.handleLayoutChange); - } else { - this.mediaQuery.removeListener(this.handleLayoutChange); - } - } - } - - handleChildrenContentChange() { - if (!this.props.singleColumn) { - const modifier = this.isRtlLayout ? -1 : 1; - scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); - } - } - - handleLayoutChange = (e) => { - this.setState({ renderComposePanel: !e.matches }); - }; - - setRef = (node) => { - this.node = node; - }; - - renderLoading = columnId => () => { - return columnId === 'COMPOSE' ? : ; - }; - - renderError = (props) => { - return ; - }; - - render () { - const { columns, children, singleColumn, isModalOpen } = this.props; - const { renderComposePanel } = this.state; - - if (singleColumn) { - return ( -
-
-
- {renderComposePanel && } - -
-
- -
-
-
{children}
-
- - -
- ); - } - - return ( -
- {columns.map((column, index) => { - const params = column.get('params', null) === null ? null : column.get('params').toJS(); - const other = params && params.other ? params.other : {}; - - return ( - - - {SpecificComponent => } - - - ); - })} - - - {Children.map(children, child => cloneElement(child, { multiColumn: true }))} - -
- ); - } - -} diff --git a/app/javascript/mastodon/features/ui/components/columns_area.tsx b/app/javascript/mastodon/features/ui/components/columns_area.tsx new file mode 100644 index 00000000000000..6861410367c27a --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/columns_area.tsx @@ -0,0 +1,177 @@ +import { + Children, + cloneElement, + createContext, + forwardRef, + useCallback, + useContext, +} from 'react'; + +import classNames from 'classnames'; + +import type { List, Record } from 'immutable'; + +import { useAppSelector } from '@/mastodon/store'; +import { CollapsibleNavigationPanel } from 'mastodon/features/navigation_panel'; + +import { useBreakpoint } from '../hooks/useBreakpoint'; +import { + Compose, + Notifications, + HomeTimeline, + CommunityTimeline, + PublicTimeline, + HashtagTimeline, + DirectTimeline, + FavouritedStatuses, + BookmarkedStatuses, + ListTimeline, + Directory, +} from '../util/async-components'; +import { useColumnsContext } from '../util/columns_context'; + +import Bundle from './bundle'; +import BundleColumnError from './bundle_column_error'; +import { ColumnLoading } from './column_loading'; +import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel'; +import DrawerLoading from './drawer_loading'; + +const componentMap = { + COMPOSE: Compose, + HOME: HomeTimeline, + NOTIFICATIONS: Notifications, + PUBLIC: PublicTimeline, + REMOTE: PublicTimeline, + COMMUNITY: CommunityTimeline, + HASHTAG: HashtagTimeline, + DIRECT: DirectTimeline, + FAVOURITES: FavouritedStatuses, + BOOKMARKS: BookmarkedStatuses, + LIST: ListTimeline, + DIRECTORY: Directory, +} as const; + +const TabsBarPortal = () => { + const { setTabsBarElement } = useColumnsContext(); + + const setRef = useCallback( + (element: HTMLDivElement | null) => { + if (element) { + setTabsBarElement(element); + } + }, + [setTabsBarElement], + ); + + return
; +}; + +export const ColumnIndexContext = createContext(1); +export const useColumnIndexContext = () => useContext(ColumnIndexContext); + +interface Column { + uuid: string; + id: keyof typeof componentMap; + params?: null | Record<{ other?: unknown }>; +} + +type FetchedComponent = React.FC<{ + columnId?: string; + multiColumn?: boolean; + params: unknown; +}>; + +export const ColumnsArea = forwardRef< + HTMLDivElement, + { + singleColumn?: boolean; + children: React.ReactElement | React.ReactElement[]; + } +>(({ children, singleColumn }, ref) => { + const renderComposePanel = !useBreakpoint('full'); + const columns = useAppSelector((state) => + (state.settings as Record<{ columns: List> }>).get( + 'columns', + ), + ); + const isModalOpen = useAppSelector( + (state) => !state.modal.get('stack').isEmpty(), + ); + + if (singleColumn) { + return ( +
+
+
+ {renderComposePanel && } + +
+
+ +
+
+ +
+
{children}
+
+ + +
+ ); + } + + return ( +
+ {columns.map((column, index) => { + const params = column.get('params') + ? column.get('params')?.toJS() + : null; + const other = params?.other ?? {}; + const uuid = column.get('uuid'); + const id = column.get('id'); + + return ( + + + {(SpecificComponent: FetchedComponent) => ( + + )} + + + ); + })} + + + {Children.map(children, (child) => + cloneElement(child, { multiColumn: true }), + )} + +
+ ); +}); + +ColumnsArea.displayName = 'ColumnsArea'; + +const ErrorComponent = (props: { onRetry: () => void }) => { + return ; +}; + +const renderLoading = (columnId: string) => { + const LoadingComponent = + columnId === 'COMPOSE' ? : ; + return () => LoadingComponent; +}; diff --git a/app/javascript/mastodon/features/ui/containers/columns_area_container.js b/app/javascript/mastodon/features/ui/containers/columns_area_container.js deleted file mode 100644 index f8473d38baa34f..00000000000000 --- a/app/javascript/mastodon/features/ui/containers/columns_area_container.js +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux'; - -import ColumnsArea from '../components/columns_area'; - -const mapStateToProps = state => ({ - columns: state.getIn(['settings', 'columns']), - isModalOpen: !!state.get('modal').modalType, -}); - -export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea); diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index b46f61745e7b94..55bc8f990114fe 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -10,6 +10,7 @@ import { connect } from 'react-redux'; import { debounce } from 'lodash'; +import { scrollRight } from '../../scroll'; import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; import { fetchNotifications } from 'mastodon/actions/notification_groups'; @@ -34,7 +35,7 @@ import BundleColumnError from './components/bundle_column_error'; import { NavigationBar } from './components/navigation_bar'; import { UploadArea } from './components/upload_area'; import { HashtagMenuController } from './components/hashtag_menu_controller'; -import ColumnsAreaContainer from './containers/columns_area_container'; +import { ColumnsArea } from './components/columns_area'; import LoadingBarContainer from './containers/loading_bar_container'; import ModalContainer from './containers/modal_container'; import { @@ -125,7 +126,7 @@ class SwitchingColumnsArea extends PureComponent { componentDidUpdate (prevProps) { if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) { - this.node.handleChildrenContentChange(); + this.handleChildrenContentChange(); } if (prevProps.singleColumn !== this.props.singleColumn) { @@ -134,6 +135,16 @@ class SwitchingColumnsArea extends PureComponent { } } + handleChildrenContentChange() { + if (!this.props.singleColumn) { + const isRtlLayout = document.getElementsByTagName('body')[0] + ?.classList.contains('rtl'); + const modifier = isRtlLayout ? -1 : 1; + + scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier); + } + } + setRef = c => { if (c) { this.node = c; @@ -181,7 +192,7 @@ class SwitchingColumnsArea extends PureComponent { return ( - + {redirect} @@ -261,7 +272,7 @@ class SwitchingColumnsArea extends PureComponent { } - + ); } From de4ee8565c658ae14aa5cc4504aa246694f92c1e Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 3 Mar 2026 16:26:56 +0100 Subject: [PATCH 56/89] Prevent hover card from showing on touch devices (#38039) --- .../components/hover_card_controller.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx index 624a69e057db04..d9352018bb8a26 100644 --- a/app/javascript/mastodon/components/hover_card_controller.tsx +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -23,6 +23,7 @@ export const HoverCardController: React.FC = () => { const [open, setOpen] = useState(false); const [accountId, setAccountId] = useState(); const [anchor, setAnchor] = useState(null); + const isUsingTouchRef = useRef(false); const cardRef = useRef(null); const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout(); @@ -62,6 +63,12 @@ export const HoverCardController: React.FC = () => { setAccountId(undefined); }; + const handleTouchStart = () => { + // Keeping track of touch events to prevent the + // hover card from being displayed on touch devices + isUsingTouchRef.current = true; + }; + const handleMouseEnter = (e: MouseEvent) => { const { target } = e; @@ -71,6 +78,11 @@ export const HoverCardController: React.FC = () => { return; } + // Bail out if a touch is active + if (isUsingTouchRef.current) { + return; + } + // We've entered an anchor if (!isScrolling && isHoverCardAnchor(target)) { cancelLeaveTimeout(); @@ -129,9 +141,16 @@ export const HoverCardController: React.FC = () => { }; const handleMouseMove = () => { + if (isUsingTouchRef.current) { + isUsingTouchRef.current = false; + } delayEnterTimeout(enterDelay); }; + document.body.addEventListener('touchstart', handleTouchStart, { + passive: true, + }); + document.body.addEventListener('mouseenter', handleMouseEnter, { passive: true, capture: true, @@ -153,6 +172,7 @@ export const HoverCardController: React.FC = () => { }); return () => { + document.body.removeEventListener('touchstart', handleTouchStart); document.body.removeEventListener('mouseenter', handleMouseEnter); document.body.removeEventListener('mousemove', handleMouseMove); document.body.removeEventListener('mouseleave', handleMouseLeave); From a3aeae02885408381c7c1f3f6a0cc88799823409 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 3 Mar 2026 16:36:54 +0100 Subject: [PATCH 57/89] Add test to cover proper URL for media in `DELETE /api/v1/statuses/:id` (#38037) --- app/controllers/api/v1/statuses_controller.rb | 2 ++ spec/requests/api/v1/statuses_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index f07dda1247f967..3a70d749266ad8 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -127,6 +127,8 @@ def destroy @status = Status.where(account: current_account).find(params[:id]) authorize @status, :destroy? + # JSON is generated before `discard_with_reblogs` in order to have the proper URL + # for media attachments, as it would otherwise redirect to the media proxy json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true @status.discard_with_reblogs diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index be227e801e4e81..f9845ada7e3df1 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -459,6 +459,7 @@ let(:scopes) { 'write:statuses' } let(:status) { Fabricate(:status, account: user.account) } + let!(:media) { Fabricate(:media_attachment, status: status) } it_behaves_like 'forbidden for wrong scope', 'read read:statuses' @@ -468,6 +469,15 @@ expect(response).to have_http_status(200) expect(response.content_type) .to start_with('application/json') + expect(response.parsed_body).to include( + id: status.id.to_s, + media_attachments: contain_exactly( + a_hash_including( + id: media.id.to_s, + url: %r{/system/media_attachments/files/} + ) + ) + ) expect(Status.find_by(id: status.id)).to be_nil expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => true }) end From c0b22868719052728883b121ca3322c6b0895c78 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 3 Mar 2026 18:34:01 +0100 Subject: [PATCH 58/89] Profile redesign: Fix timeline filter button color on Safari iOS (#38040) --- .../mastodon/features/account_timeline/v2/styles.module.scss | 1 + 1 file changed, 1 insertion(+) 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 2ef62a7d258a24..b39892beec88d1 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss +++ b/app/javascript/mastodon/features/account_timeline/v2/styles.module.scss @@ -5,6 +5,7 @@ .filterSelectButton { appearance: none; border: none; + color: inherit; background: none; padding: 8px 0; font-size: 15px; From d0e7692d79ce1a2d3f9346b415037379059b2897 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 3 Mar 2026 18:34:37 +0100 Subject: [PATCH 59/89] Fix local collection link resulting in error page (#38038) --- app/javascript/mastodon/features/collections/detail/index.tsx | 4 ++-- .../mastodon/features/collections/detail/share_modal.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx index d2317e716f85ad..0fc13c8f792ef8 100644 --- a/app/javascript/mastodon/features/collections/detail/index.tsx +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -105,8 +105,8 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); }, [collection, dispatch]); - const location = useLocation<{ newCollection?: boolean }>(); - const wasJustCreated = location.state.newCollection; + const location = useLocation<{ newCollection?: boolean } | undefined>(); + const wasJustCreated = location.state?.newCollection; useEffect(() => { if (wasJustCreated) { handleShare(); diff --git a/app/javascript/mastodon/features/collections/detail/share_modal.tsx b/app/javascript/mastodon/features/collections/detail/share_modal.tsx index 137794d95b6cc3..0f4681d07762a3 100644 --- a/app/javascript/mastodon/features/collections/detail/share_modal.tsx +++ b/app/javascript/mastodon/features/collections/detail/share_modal.tsx @@ -40,8 +40,8 @@ export const CollectionShareModal: React.FC<{ }> = ({ collection, onClose }) => { const intl = useIntl(); const dispatch = useAppDispatch(); - const location = useLocation<{ newCollection?: boolean }>(); - const isNew = !!location.state.newCollection; + const location = useLocation<{ newCollection?: boolean } | undefined>(); + const isNew = !!location.state?.newCollection; const isOwnCollection = collection.account_id === me; const collectionLink = `${window.location.origin}/collections/${collection.id}`; From 0f2ad41f89c4ae27aa300f4dc90e5cfb9f36dd37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:01:44 +0100 Subject: [PATCH 60/89] Update dependency public_suffix to v7.0.5 (#38034) 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 7f9f82cb39b939..cf5f11a5ea38a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -628,7 +628,7 @@ GEM psych (5.3.1) date stringio - public_suffix (7.0.2) + public_suffix (7.0.5) puma (7.2.0) nio4r (~> 2.0) pundit (2.5.2) From 0a4f96be2156b7ea75ded313d74fe07e2b877529 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:01:47 +0100 Subject: [PATCH 61/89] Update dependency tzinfo-data to v1.2026.1 (#38035) 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 cf5f11a5ea38a2..ad3351278a4121 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -892,7 +892,7 @@ GEM unf (~> 0.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2025.3) + tzinfo-data (1.2026.1) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext From 177f4ee3ae29ebd55f43c1fdfd62af0ecd0d5a2a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 4 Mar 2026 06:03:25 -0500 Subject: [PATCH 62/89] Update haml_lint to version 0.72.0 (#38042) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ad3351278a4121..ee036b208b85bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -294,7 +294,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.71.0) + haml_lint (0.72.0) haml (>= 5.0) parallel (~> 1.10) rainbow From 078b87bdc18b6c243b9b1598d406d9e6c7cec0f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:06:26 +0100 Subject: [PATCH 63/89] New Crowdin Translations (automated) (#38047) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 4 ++-- app/javascript/mastodon/locales/fi.json | 2 +- app/javascript/mastodon/locales/fo.json | 5 +++++ app/javascript/mastodon/locales/fr-CA.json | 3 +++ app/javascript/mastodon/locales/fr.json | 3 +++ app/javascript/mastodon/locales/hu.json | 15 +++++++++++++ app/javascript/mastodon/locales/ko.json | 25 ++++++++++++++++++++++ app/javascript/mastodon/locales/pt-PT.json | 12 +++++++++++ app/javascript/mastodon/locales/tr.json | 5 +++++ app/javascript/mastodon/locales/zh-CN.json | 5 +++++ 10 files changed, 76 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 34215c3d6de2fb..d313e0001e1541 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -536,7 +536,7 @@ "empty_column.follow_requests": "Du har endnu ingen følgeanmodninger. Når du modtager én, vil den dukke op her.", "empty_column.followed_tags": "Ingen hashtags følges endnu. Når det sker, vil de fremgå her.", "empty_column.hashtag": "Der er intet med dette hashtag endnu.", - "empty_column.home": "Din hjem-tidslinje er tom! Følg nogle personer, for at fylde den op.", + "empty_column.home": "Din hjem-tidslinje er tom! Følg flere personer, for at fylde den op.", "empty_column.list": "Der er ikke noget på denne liste endnu. Når medlemmer af denne liste udgiver nye indlæg, vil de blive vist her.", "empty_column.mutes": "Du har endnu ikke skjult nogle brugere.", "empty_column.notification_requests": "Alt er klar! Der er intet her. Når der modtages nye notifikationer, fremgår de her jævnfør dine indstillinger.", @@ -683,7 +683,7 @@ "keyboard_shortcuts.direct": "Åbn kolonne med private omtaler", "keyboard_shortcuts.down": "Flyt nedad på listen", "keyboard_shortcuts.enter": "Åbn indlæg", - "keyboard_shortcuts.explore": "Åbn Trender-tidslinjen", + "keyboard_shortcuts.explore": "Åbn trender-tidslinjen", "keyboard_shortcuts.favourite": "Føj indlæg til favoritter", "keyboard_shortcuts.favourites": "Åbn favoritlisten", "keyboard_shortcuts.federated": "Åbn fødereret tidslinje", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 544344cfa61b23..55bf3daee7f0dc 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -1074,7 +1074,7 @@ "sign_in_banner.sign_in": "Kirjaudu", "sign_in_banner.sso_redirect": "Kirjaudu tai rekisteröidy", "skip_links.hotkey": "Pikanäppäin {hotkey}", - "skip_links.skip_to_content": "Siitty pääsisältöön", + "skip_links.skip_to_content": "Siirry pääsisältöön", "skip_links.skip_to_navigation": "Siirry päänavigaatioon", "status.admin_account": "Avaa tilin @{name} moderointinäkymä", "status.admin_domain": "Avaa palvelimen {domain} moderointinäkymä", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 10f727109e5afe..e6810d4e9750e8 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Tillaga spjøldrini á vanganum hjá tær og tað, tey vísa.", "account_edit.profile_tab.title": "Stillingar fyri spjøldur á vanga", "account_edit.save": "Goym", + "account_edit_tags.add_tag": "Legg #{tagName} afturat", "account_edit_tags.column_title": "Rætta sermerkt frámerki", "account_edit_tags.help_text": "Sermerkt frámerki hjálpa brúkarum at varnast og virka saman við vanga tínum. Tey síggjast sum filtur á virksemisvísingini av vanga tínum.", "account_edit_tags.search_placeholder": "Áset eitt frámerki…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Lat teigin við privatum umrøðum upp", "keyboard_shortcuts.down": "Flyt niðureftir listanum", "keyboard_shortcuts.enter": "Opna uppslag", + "keyboard_shortcuts.explore": "Lat rás við vælumtóktum postum upp", "keyboard_shortcuts.favourite": "Dáma post", "keyboard_shortcuts.favourites": "Lat listan av dámdum postum upp", "keyboard_shortcuts.federated": "Lat felags tíðslinju upp", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon er best mátin at fylgja við í tí, sum hendir.", "sign_in_banner.sign_in": "Rita inn", "sign_in_banner.sso_redirect": "Rita inn ella Skráset teg", + "skip_links.hotkey": "Snarknappur {hotkey}", + "skip_links.skip_to_content": "Far til høvuðsinnihald", + "skip_links.skip_to_navigation": "Far til høvuðs-navigatión", "status.admin_account": "Lat kjakleiðaramarkamót upp fyri @{name}", "status.admin_domain": "Lat umsjónarmarkamót upp fyri {domain}", "status.admin_status": "Lat hendan postin upp í kjakleiðaramarkamótinum", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 5e753c0e5c7bfc..85970ed2fe44f9 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", + "account_edit_tags.add_tag": "Ajouter #{tagName}", "account_edit_tags.column_title": "Modifier les hashtags mis en avant", "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", "account_edit_tags.search_placeholder": "Saisir un hashtag…", @@ -1073,6 +1074,8 @@ "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou s’inscrire", "skip_links.hotkey": "Raccourci {hotkey}", + "skip_links.skip_to_content": "Accéder au contenu principal", + "skip_links.skip_to_navigation": "Accéder à la navigation principale", "status.admin_account": "Ouvrir l’interface de modération pour @{name}", "status.admin_domain": "Ouvrir l’interface de modération pour {domain}", "status.admin_status": "Ouvrir ce message dans l’interface de modération", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index d52c9c6f346754..5f32e9361ccba9 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Personnaliser les onglets de votre profil et leur contenu.", "account_edit.profile_tab.title": "Paramètres de l'onglet du profil", "account_edit.save": "Enregistrer", + "account_edit_tags.add_tag": "Ajouter #{tagName}", "account_edit_tags.column_title": "Modifier les hashtags mis en avant", "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", "account_edit_tags.search_placeholder": "Saisir un hashtag…", @@ -1073,6 +1074,8 @@ "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou s’inscrire", "skip_links.hotkey": "Raccourci {hotkey}", + "skip_links.skip_to_content": "Accéder au contenu principal", + "skip_links.skip_to_navigation": "Accéder à la navigation principale", "status.admin_account": "Ouvrir l’interface de modération pour @{name}", "status.admin_domain": "Ouvrir l’interface de modération pour {domain}", "status.admin_status": "Ouvrir ce message dans l’interface de modération", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 6e2b30eb510a58..50efbb465536e9 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -44,9 +44,11 @@ "account.familiar_followers_two": "{name1} és {name2} követi", "account.featured": "Kiemelt", "account.featured.accounts": "Profilok", + "account.featured.collections": "Gyűjtemények", "account.featured.hashtags": "Hashtagek", "account.featured_tags.last_status_at": "Legutolsó bejegyzés ideje: {date}", "account.featured_tags.last_status_never": "Nincs bejegyzés", + "account.field_overflow": "Teljes tartalom megjelenítése", "account.filters.all": "Összes tevékenység", "account.filters.boosts_toggle": "Megtolások megjelenítése", "account.filters.posts_boosts": "Bejegyzések és megtolások", @@ -144,17 +146,30 @@ "account_edit.bio.title": "Bemutatkozás", "account_edit.bio_modal.add_title": "Bemutatkozás hozzáadása", "account_edit.bio_modal.edit_title": "Bemutatkozás szerkesztése", + "account_edit.button.add": "{item} hozzáadása", + "account_edit.button.delete": "{item} törlése", + "account_edit.button.edit": "{item} szerkesztése", "account_edit.char_counter": "{currentLength}/{maxLength} karakter", "account_edit.column_button": "Kész", "account_edit.column_title": "Profil szerkesztése", "account_edit.custom_fields.title": "Egyéni mezők", "account_edit.display_name.placeholder": "A megjelenítendő név az, ahogy a neved megjelenik a profilodon és az idővonalakon.", "account_edit.display_name.title": "Megjelenítendő név", + "account_edit.featured_hashtags.item": "hashtagek", + "account_edit.featured_hashtags.placeholder": "Segíts másoknak, hogy azonosíthassák a kedvenc témáid, és gyorsan elérjék azokat.", "account_edit.featured_hashtags.title": "Kiemelt hashtagek", "account_edit.name_modal.add_title": "Megjelenítendő név hozzáadása", "account_edit.name_modal.edit_title": "Megjelenítendő név szerkesztése", + "account_edit.profile_tab.button_label": "Testreszabás", + "account_edit.profile_tab.hint.description": "Ezek a beállítások szabják testre, hogy a felhasználók mit látnak a(z) {server} kiszolgálón a hivatalos alkalmazásokban, de nem biztos, hogy a külső kiszolgálókon vagy alkalmazásokban is érvényesek lesznek.", + "account_edit.profile_tab.hint.title": "A megjelenítés eltérő lehet", + "account_edit.profile_tab.show_featured.description": "A „Kiemelt” egy nem kötelező lap, ahol más fiókokat mutathatsz be.", + "account_edit.profile_tab.show_featured.title": "„Kiemelt” lap megjelenítése", + "account_edit.profile_tab.show_media.description": "A „Média” egy nem kötelező lap, amely a képeket vagy videókat tartalmazó bejegyzéseidet jeleníti meg.", + "account_edit.profile_tab.show_media.title": "„Média” lap megjelenítése", "account_edit.profile_tab.title": "Profil lap beállításai", "account_edit.save": "Mentés", + "account_edit_tags.add_tag": "#{tagName} hozzáadása", "account_note.placeholder": "Kattintás jegyzet hozzáadásához", "admin.dashboard.daily_retention": "Napi regisztráció utáni felhasználómegtartási arány", "admin.dashboard.monthly_retention": "Havi regisztráció utáni felhasználómegtartási arány", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index b08775be977262..aece536ee4b403 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -47,8 +47,12 @@ "account.featured.hashtags": "해시태그", "account.featured_tags.last_status_at": "{date}에 마지막으로 게시", "account.featured_tags.last_status_never": "게시물 없음", + "account.field_overflow": "내용 전체 보기", "account.filters.all": "모든 활동", "account.filters.boosts_toggle": "부스트 보기", + "account.filters.posts_boosts": "게시물과 부스트", + "account.filters.posts_only": "게시물", + "account.filters.posts_replies": "게시물과 답장", "account.filters.replies_toggle": "답글 보기", "account.follow": "팔로우", "account.follow_back": "맞팔로우", @@ -68,14 +72,23 @@ "account.go_to_profile": "프로필로 이동", "account.hide_reblogs": "@{name}의 부스트를 숨기기", "account.in_memoriam": "고인의 계정입니다.", + "account.joined_long": "{date}에 가입함", "account.joined_short": "가입", "account.languages": "구독한 언어 변경", "account.link_verified_on": "{date}에 이 링크의 소유권이 확인 됨", "account.locked_info": "이 계정의 프라이버시 설정은 잠금으로 설정되어 있습니다. 계정 소유자가 수동으로 팔로워를 승인합니다.", "account.media": "미디어", "account.mention": "@{name} 님에게 멘션", + "account.menu.add_to_list": "리스트에 추가…", + "account.menu.block": "계정 차단", + "account.menu.block_domain": "{domain} 차단", + "account.menu.copied": "계정 링크를 복사했습니다", "account.menu.copy": "링크 복사하기", "account.menu.mention": "멘션", + "account.menu.note.description": "나에게만 보입니다", + "account.menu.open_original_page": "{domain}에서 보기", + "account.menu.remove_follower": "팔로워 제거", + "account.menu.report": "계정 신고", "account.menu.share": "공유하기…", "account.moved_to": "{name} 님은 자신의 새 계정이 다음과 같다고 표시했습니다:", "account.mute": "@{name} 뮤트", @@ -85,6 +98,9 @@ "account.muting": "뮤트함", "account.mutual": "서로 팔로우", "account.no_bio": "제공된 설명이 없습니다.", + "account.node_modal.save": "저장", + "account.node_modal.title": "개인 메모 추가", + "account.note.edit_button": "편집", "account.open_original_page": "원본 페이지 열기", "account.posts": "게시물", "account.posts_with_replies": "게시물과 답장", @@ -104,6 +120,15 @@ "account.unmute": "@{name} 뮤트 해제", "account.unmute_notifications_short": "알림 뮤트 해제", "account.unmute_short": "뮤트 해제", + "account_edit.bio.title": "자기소개", + "account_edit.bio_modal.add_title": "자기소개 추가", + "account_edit.bio_modal.edit_title": "자기소개 편집", + "account_edit.button.add": "{item} 추가", + "account_edit.button.delete": "{item} 제거", + "account_edit.button.edit": "{item} 편집", + "account_edit.char_counter": "{currentLength}/{maxLength} 글자", + "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/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 56f3149fdb0d66..72cd874251ac0c 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -161,7 +161,11 @@ "account_edit.featured_hashtags.title": "Etiquetas em destaque", "account_edit.name_modal.add_title": "Adicionar nome a mostrar", "account_edit.name_modal.edit_title": "Editar o nome a mostrar", + "account_edit.profile_tab.button_label": "Personalizar", + "account_edit.profile_tab.hint.description": "Estas configurações personalizam o que os utilizadores veem no {server} nas aplicações oficiais, mas podem não se aplicar aos utilizadores de outros servidores nem aplicações de terceiros.", + "account_edit.profile_tab.hint.title": "A apresentação ainda pode variar", "account_edit.save": "Guardar", + "account_edit_tags.add_tag": "Adicionar #{tagName}", "account_edit_tags.column_title": "Editar etiquetas em destaque", "account_edit_tags.help_text": "As etiquetas destacadas ajudam os utilizadores a descobrir e interagir com o seu perfil. Aparecem como filtros na vista de atividade da sua página de perfil.", "account_edit_tags.search_placeholder": "Insira uma etiqueta…", @@ -269,6 +273,12 @@ "closed_registrations_modal.find_another_server": "Procurar outro servidor", "closed_registrations_modal.preamble": "O Mastodon é descentralizado, por isso não importa onde a tua conta é criada, pois continuarás a poder acompanhar e interagir com qualquer um neste servidor. Podes até alojar o teu próprio servidor!", "closed_registrations_modal.title": "Criar uma conta no Mastodon", + "collection.share_modal.share_via_post": "Publicar no Mastodon", + "collection.share_modal.share_via_system": "Compartilhar com…", + "collection.share_modal.title": "Partilhar coleção", + "collection.share_modal.title_new": "Partilhe a sua nova coleção!", + "collection.share_template_other": "Veja esta coleção interessante: {link}", + "collection.share_template_own": "Veja a minha nova coleção: {link}", "collections.account_count": "{count, plural, one {# conta} other {# contas}}", "collections.accounts.empty_description": "Adicione até {count} contas que segue", "collections.accounts.empty_title": "Esta coleção está vazia", @@ -287,6 +297,8 @@ "collections.delete_collection": "Eliminar coleção", "collections.description_length_hint": "Limite de 100 caracteres", "collections.detail.accounts_heading": "Contas", + "collections.detail.curated_by_author": "Curado por {author}", + "collections.detail.curated_by_you": "Curado por si", "collections.detail.loading": "A carregar a coleção…", "collections.detail.share": "Partilhar esta coleção", "collections.edit_details": "Editar detalhes", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 2ad5a23d5092f2..55cc5daef63206 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "Profilinizdeki sekmeleri ve bunların görüntülediği bilgileri özelleştirin.", "account_edit.profile_tab.title": "Profil sekme ayarları", "account_edit.save": "Kaydet", + "account_edit_tags.add_tag": "#{tagName} ekle", "account_edit_tags.column_title": "Öne çıkarılmış etiketleri düzenle", "account_edit_tags.help_text": "Öne çıkan etiketler kullanıcıların profilinizi keşfetmesine ve etkileşim kurmasına yardımcı olur. Profil sayfanızın Etkinlik görünümünde filtreler olarak görünürler.", "account_edit_tags.search_placeholder": "Bir etiket girin…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "Özel bahsetmeler sütununu aç", "keyboard_shortcuts.down": "Listede aşağıya inmek için", "keyboard_shortcuts.enter": "Gönderiyi açınız", + "keyboard_shortcuts.explore": "Öne çıkanlar zaman çizelgesini aç", "keyboard_shortcuts.favourite": "Gönderiyi favorilerine ekle", "keyboard_shortcuts.favourites": "Gözde listeni aç", "keyboard_shortcuts.federated": "Federe akışı aç", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Neler olup bittiğini izlemenin en iyi aracı Mastodon'dur.", "sign_in_banner.sign_in": "Giriş yap", "sign_in_banner.sso_redirect": "Giriş yap veya kaydol", + "skip_links.hotkey": "Kısayol tuşu {hotkey}", + "skip_links.skip_to_content": "Ana içeriğe git", + "skip_links.skip_to_navigation": "Ana gezinmeye git", "status.admin_account": "@{name} için denetim arayüzünü açın", "status.admin_domain": "{domain} için denetim arayüzünü açın", "status.admin_status": "Denetim arayüzünde bu gönderiyi açın", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index be8a700c037cde..047bc0df9af4d4 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -173,6 +173,7 @@ "account_edit.profile_tab.subtitle": "自定义你个人资料的标签页及其显示的内容。", "account_edit.profile_tab.title": "个人资料标签页设置", "account_edit.save": "保存", + "account_edit_tags.add_tag": "添加 #{tagName}", "account_edit_tags.column_title": "编辑精选话题标签", "account_edit_tags.help_text": "精选话题标签可以帮助他人发现并与你的个人资料互动。这些标签会作为过滤器条件出现在你个人资料页面的活动视图中。", "account_edit_tags.search_placeholder": "输入话题标签…", @@ -682,6 +683,7 @@ "keyboard_shortcuts.direct": "打开私下提及栏", "keyboard_shortcuts.down": "在列表中让光标下移", "keyboard_shortcuts.enter": "展开嘟文", + "keyboard_shortcuts.explore": "打开当前热门时间线", "keyboard_shortcuts.favourite": "喜欢嘟文", "keyboard_shortcuts.favourites": "打开喜欢列表", "keyboard_shortcuts.federated": "打开跨站时间线", @@ -1071,6 +1073,9 @@ "sign_in_banner.mastodon_is": "Mastodon 是了解最新动态的最佳途径。", "sign_in_banner.sign_in": "登录", "sign_in_banner.sso_redirect": "登录或注册", + "skip_links.hotkey": "快捷键 {hotkey}", + "skip_links.skip_to_content": "跳转到主内容", + "skip_links.skip_to_navigation": "跳转到主导航", "status.admin_account": "打开 @{name} 的管理界面", "status.admin_domain": "打开 {domain} 的管理界面", "status.admin_status": "在管理界面查看此嘟文", From 5472ab251a33842427ff7658f2ebe80d4c30de00 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 4 Mar 2026 12:18:28 +0100 Subject: [PATCH 64/89] Fix existing posts not being removed from lists when a list member is unfollowed (#38048) --- app/services/unfollow_service.rb | 43 +++++++++------- spec/services/unfollow_service_spec.rb | 69 ++++++++++++++------------ 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index 1ea8af6992646f..385aa0c7b17dba 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -6,16 +6,16 @@ class UnfollowService < BaseService include Lockable # Unfollow and notify the remote user - # @param [Account] source_account Where to unfollow from - # @param [Account] target_account Which to unfollow + # @param [Account] follower Where to unfollow from + # @param [Account] followee Which to unfollow # @param [Hash] options # @option [Boolean] :skip_unmerge - def call(source_account, target_account, options = {}) - @source_account = source_account - @target_account = target_account - @options = options + def call(follower, followee, options = {}) + @follower = follower + @followee = followee + @options = options - with_redis_lock("relationship:#{[source_account.id, target_account.id].sort.join(':')}") do + with_redis_lock("relationship:#{[follower.id, followee.id].sort.join(':')}") do unfollow! || undo_follow_request! end end @@ -23,19 +23,25 @@ def call(source_account, target_account, options = {}) private def unfollow! - follow = Follow.find_by(account: @source_account, target_account: @target_account) - + follow = Follow.find_by(account: @follower, target_account: @followee) return unless follow + # List members are removed immediately with the follow relationship removal, + # so we need to fetch the list IDs first + list_ids = @follower.owned_lists.with_list_account(@followee).pluck(:list_id) unless @options[:skip_unmerge] + follow.destroy! - create_notification(follow) if !@target_account.local? && @target_account.activitypub? - create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub? + if @followee.local? && @follower.remote? && @follower.activitypub? + send_reject_follow(follow) + elsif @followee.remote? && @followee.activitypub? + send_undo_follow(follow) + end unless @options[:skip_unmerge] - UnmergeWorker.perform_async(@target_account.id, @source_account.id, 'home') - UnmergeWorker.push_bulk(@source_account.owned_lists.with_list_account(@target_account).pluck(:list_id)) do |list_id| - [@target_account.id, list_id, 'list'] + UnmergeWorker.perform_async(@followee.id, @follower.id, 'home') + UnmergeWorker.push_bulk(list_ids) do |list_id| + [@followee.id, list_id, 'list'] end end @@ -43,22 +49,21 @@ def unfollow! end def undo_follow_request! - follow_request = FollowRequest.find_by(account: @source_account, target_account: @target_account) - + follow_request = FollowRequest.find_by(account: @follower, target_account: @followee) return unless follow_request follow_request.destroy! - create_notification(follow_request) unless @target_account.local? + send_undo_follow(follow_request) unless @followee.local? follow_request end - def create_notification(follow) + def send_undo_follow(follow) ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.account_id, follow.target_account.inbox_url) end - def create_reject_notification(follow) + def send_reject_follow(follow) ActivityPub::DeliveryWorker.perform_async(build_reject_json(follow), follow.target_account_id, follow.account.inbox_url) end diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb index 6cf24ca5e1ce65..365468e432d76c 100644 --- a/spec/services/unfollow_service_spec.rb +++ b/spec/services/unfollow_service_spec.rb @@ -5,54 +5,57 @@ RSpec.describe UnfollowService do subject { described_class.new } - let(:sender) { Fabricate(:account, username: 'alice') } + let(:follower) { Fabricate(:account) } + let(:followee) { Fabricate(:account) } - describe 'local' do - let(:bob) { Fabricate(:account, username: 'bob') } - - before { sender.follow!(bob) } - - it 'destroys the following relation' do - subject.call(sender, bob) - - expect(sender) - .to_not be_following(bob) - end + before do + follower.follow!(followee) end - describe 'remote ActivityPub', :inline_jobs do - let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + shared_examples 'when the followee is in a list' do + let(:list) { Fabricate(:list, account: follower) } before do - sender.follow!(bob) - stub_request(:post, 'http://example.com/inbox').to_return(status: 200) + list.accounts << followee end - it 'destroys the following relation and sends unfollow activity' do - subject.call(sender, bob) + it 'schedules removal of posts from this user from the list' do + expect { subject.call(follower, followee) } + .to enqueue_sidekiq_job(UnmergeWorker).with(followee.id, list.id, 'list') + end + end - expect(sender) - .to_not be_following(bob) - expect(a_request(:post, 'http://example.com/inbox')) - .to have_been_made.once + describe 'a local user unfollowing another local user' do + it 'destroys the following relation and unmerge from home' do + expect { subject.call(follower, followee) } + .to change { follower.following?(followee) }.from(true).to(false) + .and enqueue_sidekiq_job(UnmergeWorker).with(followee.id, follower.id, 'home') end + + it_behaves_like 'when the followee is in a list' end - describe 'remote ActivityPub (reverse)', :inline_jobs do - let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + describe 'a local user unfollowing a remote ActivityPub user' do + let(:followee) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } - before do - bob.follow!(sender) - stub_request(:post, 'http://example.com/inbox').to_return(status: 200) + it 'destroys the following relation, unmerge from home and sends undo activity' do + expect { subject.call(follower, followee) } + .to change { follower.following?(followee) }.from(true).to(false) + .and enqueue_sidekiq_job(UnmergeWorker).with(followee.id, follower.id, 'home') + .and enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Undo'), follower.id, followee.inbox_url) end - it 'destroys the following relation and sends a reject activity' do - subject.call(bob, sender) + it_behaves_like 'when the followee is in a list' + end + + describe 'a remote ActivityPub user unfollowing a local user' do + let(:follower) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } - expect(sender) - .to_not be_following(bob) - expect(a_request(:post, 'http://example.com/inbox')) - .to have_been_made.once + it 'destroys the following relation, unmerge from home and sends a reject activity' do + expect { subject.call(follower, followee) } + .to change { follower.following?(followee) }.from(true).to(false) + .and enqueue_sidekiq_job(UnmergeWorker).with(followee.id, follower.id, 'home') + .and enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Reject'), followee.id, follower.inbox_url) end end end From 8a0261c51caf76b6d12e3801da471759c31c9608 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 4 Mar 2026 12:18:37 +0100 Subject: [PATCH 65/89] Add `missing_attribution` boolean to preview cards (#38043) --- app/lib/status_cache_hydrator.rb | 2 ++ app/models/preview_card.rb | 2 ++ .../rest/preview_card_serializer.rb | 10 ++++++++ app/services/fetch_link_card_service.rb | 9 +++++++- app/services/update_account_service.rb | 21 +++++++++++++++++ .../update_link_card_attribution_worker.rb | 23 +++++++++++++++++++ ...fied_author_account_id_to_preview_cards.rb | 10 ++++++++ db/schema.rb | 4 +++- spec/lib/status_cache_hydrator_spec.rb | 12 ++++++++++ .../rest/preview_card_serializer_spec.rb | 8 ++++++- spec/services/update_account_service_spec.rb | 14 +++++++++++ ...pdate_link_card_attribution_worker_spec.rb | 22 ++++++++++++++++++ 12 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 app/workers/update_link_card_attribution_worker.rb create mode 100644 db/migrate/20260303144409_add_unverified_author_account_id_to_preview_cards.rb create mode 100644 spec/workers/update_link_card_attribution_worker_spec.rb diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index b830e509bf360e..1f1184d42faae9 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -75,6 +75,8 @@ def fill_status_payload(payload, status, account_id, nested: false, fresh: true) end end + payload[:card][:missing_attribution] = status.preview_card.unverified_author_account_id == account_id if payload[:card] + # Nested statuses are more likely to have a stale cache fill_status_stats(payload, status) if nested end diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 644be2671a98be..4c8b52a8d57916 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -33,6 +33,7 @@ # published_at :datetime # image_description :string default(""), not null # author_account_id :bigint(8) +# unverified_author_account_id :bigint(8) # class PreviewCard < ApplicationRecord @@ -61,6 +62,7 @@ class PreviewCard < ApplicationRecord has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy belongs_to :author_account, class_name: 'Account', optional: true + belongs_to :unverified_author_account, class_name: 'Account', optional: true has_attached_file :image, processors: [:lazy_thumbnail, :blurhash_transcoder], diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb index f73a051ac0f468..3517be619b1880 100644 --- a/app/serializers/rest/preview_card_serializer.rb +++ b/app/serializers/rest/preview_card_serializer.rb @@ -15,6 +15,8 @@ class AuthorSerializer < ActiveModel::Serializer has_many :authors, serializer: AuthorSerializer + attribute :missing_attribution, if: :current_user? + def url object.original_url.presence || object.url end @@ -26,4 +28,12 @@ def image def html Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED) end + + def missing_attribution + object.unverified_author_account_id.present? && object.unverified_author_account_id == current_user.account_id + end + + def current_user? + !current_user.nil? + end end diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 84c4ba06f16bc7..53b6861349b420 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -159,7 +159,14 @@ def attempt_opengraph @card = PreviewCard.find_or_initialize_by(url: link_details_extractor.canonical_url) if link_details_extractor.canonical_url != @card.url @card.assign_attributes(link_details_extractor.to_preview_card_attributes) - @card.author_account = linked_account if linked_account&.can_be_attributed_from?(domain) || provider&.trendable? + + if linked_account.present? + # There is an overlap in the two conditions when `provider` is trendable. This is on purpose to give users + # a heads-up before we remove the `provider&.trendable?` condition. + @card.author_account = linked_account if linked_account.can_be_attributed_from?(domain) || provider&.trendable? + @card.unverified_author_account = linked_account if linked_account.local? && !linked_account.can_be_attributed_from?(domain) + end + @card.save_with_optional_image! unless @card.title.blank? && @card.html.blank? end end diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb index 78a846e03ea30f..fed9d530092b03 100644 --- a/app/services/update_account_service.rb +++ b/app/services/update_account_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class UpdateAccountService < BaseService + PREVIEW_CARD_REATTRIBUTION_LIMIT = 1_000 + def call(account, params, raise_error: false) was_locked = account.locked update_method = raise_error ? :update! : :update @@ -11,6 +13,7 @@ def call(account, params, raise_error: false) authorize_all_follow_requests(account) if was_locked && !account.locked check_links(account) process_hashtags(account) + process_attribution_domains(account) end rescue Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e account.errors.add(:avatar, e.message) @@ -36,4 +39,22 @@ def check_links(account) def process_hashtags(account) account.tags_as_strings = Extractor.extract_hashtags(account.note) end + + def process_attribution_domains(account) + return unless account.attribute_previously_changed?(:attribution_domains) + + # Go through the most recent cards, and do the rest in a background job + preview_cards = PreviewCard.where(unverified_author_account: account).reorder(id: :desc).limit(PREVIEW_CARD_REATTRIBUTION_LIMIT).to_a + should_queue_worker = preview_cards.size == PREVIEW_CARD_REATTRIBUTION_LIMIT + + preview_cards = preview_cards.filter do |preview_card| + account.can_be_attributed_from?(preview_card.domain) + rescue Addressable::URI::InvalidURIError + false + end + + PreviewCard.where(id: preview_cards.pluck(:id), unverified_author_account: account).update_all(author_account_id: account.id, unverified_author_account_id: nil) + + UpdateLinkCardAttributionWorker.perform_async(account.id) if should_queue_worker + end end diff --git a/app/workers/update_link_card_attribution_worker.rb b/app/workers/update_link_card_attribution_worker.rb new file mode 100644 index 00000000000000..9b4973aaa5eb8b --- /dev/null +++ b/app/workers/update_link_card_attribution_worker.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class UpdateLinkCardAttributionWorker + include Sidekiq::IterableJob + + def build_enumerator(account_id, cursor:) + @account = Account.find_by(id: account_id) + return if @account.blank? + + scope = PreviewCard.where(unverified_author_account: @account) + active_record_batches_enumerator(scope, cursor:) + end + + def each_iteration(preview_cards, account_id) + preview_cards = preview_cards.filter do |preview_card| + @account.can_be_attributed_from?(preview_card.domain) + rescue Addressable::URI::InvalidURIError + false + end + + PreviewCard.where(id: preview_cards.pluck(:id), unverified_author_account: @account).update_all(author_account_id: account_id, unverified_author_account_id: nil) + end +end diff --git a/db/migrate/20260303144409_add_unverified_author_account_id_to_preview_cards.rb b/db/migrate/20260303144409_add_unverified_author_account_id_to_preview_cards.rb new file mode 100644 index 00000000000000..6ce2cffc6a77c1 --- /dev/null +++ b/db/migrate/20260303144409_add_unverified_author_account_id_to_preview_cards.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddUnverifiedAuthorAccountIdToPreviewCards < ActiveRecord::Migration[8.1] + disable_ddl_transaction! + + def change + safety_assured { add_reference :preview_cards, :unverified_author_account, null: true, foreign_key: { to_table: 'accounts', on_delete: :nullify }, index: false } + add_index :preview_cards, [:unverified_author_account_id, :id], algorithm: :concurrently, where: 'unverified_author_account_id IS NOT NULL' + end +end diff --git a/db/schema.rb b/db/schema.rb index 8a53b24d0ad061..86bf0776ba1899 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_17_154542) do +ActiveRecord::Schema[8.0].define(version: 2026_03_03_144409) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -964,7 +964,9 @@ t.datetime "published_at" t.string "image_description", default: "", null: false t.bigint "author_account_id" + t.bigint "unverified_author_account_id" t.index ["author_account_id"], name: "index_preview_cards_on_author_account_id", where: "(author_account_id IS NOT NULL)" + t.index ["unverified_author_account_id", "id"], name: "index_preview_cards_on_unverified_author_account_id_and_id", where: "(unverified_author_account_id IS NOT NULL)" t.index ["url"], name: "index_preview_cards_on_url", unique: true end diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index a6fea36397a0ca..3eb781dfba03e2 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -19,6 +19,18 @@ end end + context 'when handling a new status with a preview card with unverified account attribution' do + let(:preview_card) { Fabricate(:preview_card, unverified_author_account: account) } + + before do + PreviewCardsStatus.create(status: status, preview_card: preview_card) + end + + it 'renders the same attributes as a full render' do + expect(subject).to eql(compare_to_hash) + end + end + context 'when handling a new status with own poll' do let(:poll) { Fabricate(:poll, account: account) } let(:status) { Fabricate(:status, poll: poll, account: account) } diff --git a/spec/serializers/rest/preview_card_serializer_spec.rb b/spec/serializers/rest/preview_card_serializer_spec.rb index 41ba305b7ce285..695d02f964bff1 100644 --- a/spec/serializers/rest/preview_card_serializer_spec.rb +++ b/spec/serializers/rest/preview_card_serializer_spec.rb @@ -6,10 +6,16 @@ subject do serialized_record_json( preview_card, - described_class + described_class, + options: { + scope: current_user, + scope_name: :current_user, + } ) end + let(:current_user) { nil } + context 'when preview card does not have author data' do let(:preview_card) { Fabricate.build :preview_card } diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb index f9059af07f5ab3..d9a66bb24f1e66 100644 --- a/spec/services/update_account_service_spec.rb +++ b/spec/services/update_account_service_spec.rb @@ -33,4 +33,18 @@ expect(eve).to_not be_requested(account) end end + + describe 'adding domains to attribution_domains' do + let(:account) { Fabricate(:account) } + let!(:preview_card) { Fabricate(:preview_card, url: 'https://writer.example.com/article', unverified_author_account: account, author_account: nil) } + let!(:unattributable_preview_card) { Fabricate(:preview_card, url: 'https://otherwriter.example.com/article', unverified_author_account: account, author_account: nil) } + let!(:unrelated_preview_card) { Fabricate(:preview_card) } + + it 'reattributes expected preview cards' do + expect { subject.call(account, { attribution_domains: ['writer.example.com'] }) } + .to change { preview_card.reload.author_account }.from(nil).to(account) + .and not_change { unattributable_preview_card.reload.author_account } + .and(not_change { unrelated_preview_card.reload.author_account }) + end + end end diff --git a/spec/workers/update_link_card_attribution_worker_spec.rb b/spec/workers/update_link_card_attribution_worker_spec.rb new file mode 100644 index 00000000000000..e1726af6d2a232 --- /dev/null +++ b/spec/workers/update_link_card_attribution_worker_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UpdateLinkCardAttributionWorker do + let(:worker) { described_class.new } + + let(:account) { Fabricate(:account, attribution_domains: ['writer.example.com']) } + + describe '#perform' do + let!(:preview_card) { Fabricate(:preview_card, url: 'https://writer.example.com/article', unverified_author_account: account, author_account: nil) } + let!(:unattributable_preview_card) { Fabricate(:preview_card, url: 'https://otherwriter.example.com/article', unverified_author_account: account, author_account: nil) } + let!(:unrelated_preview_card) { Fabricate(:preview_card) } + + it 'reattributes expected preview cards' do + expect { worker.perform(account.id) } + .to change { preview_card.reload.author_account }.from(nil).to(account) + .and not_change { unattributable_preview_card.reload.author_account } + .and(not_change { unrelated_preview_card.reload.author_account }) + end + end +end From a89754f28847ebccee36fab248537d5f49f37024 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 4 Mar 2026 08:57:53 -0500 Subject: [PATCH 66/89] Re-run `db:schema:dump` with rails 8.1 (#38044) --- db/schema.rb | 879 ++++++++++++++++++++++++++------------------------- 1 file changed, 440 insertions(+), 439 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 86bf0776ba1899..f1dddcbe26af8d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,26 +10,26 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_03_03_144409) do +ActiveRecord::Schema[8.1].define(version: 2026_03_03_144409) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" create_table "account_aliases", force: :cascade do |t| t.bigint "account_id", null: false t.string "acct", default: "", null: false - t.string "uri", default: "", null: false t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.string "uri", default: "", null: false t.index ["account_id", "uri"], name: "index_account_aliases_on_account_id_and_uri", unique: true end create_table "account_conversations", force: :cascade do |t| t.bigint "account_id", null: false t.bigint "conversation_id", null: false - t.bigint "participant_account_ids", default: [], null: false, array: true - t.bigint "status_ids", default: [], null: false, array: true t.bigint "last_status_id" t.integer "lock_version", default: 0, null: false + t.bigint "participant_account_ids", default: [], null: false, array: true + t.bigint "status_ids", default: [], null: false, array: true t.boolean "unread", default: false, null: false t.index ["account_id", "conversation_id", "participant_account_ids"], name: "index_unique_conversations", unique: true t.index ["conversation_id"], name: "index_account_conversations_on_conversation_id" @@ -43,29 +43,29 @@ end create_table "account_domain_blocks", force: :cascade do |t| - t.string "domain", null: false + t.bigint "account_id", null: false t.datetime "created_at", precision: nil, null: false + t.string "domain", null: false t.datetime "updated_at", precision: nil, null: false - t.bigint "account_id", null: false t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true end create_table "account_migrations", force: :cascade do |t| t.bigint "account_id" t.string "acct", default: "", null: false + t.datetime "created_at", precision: nil, null: false t.bigint "followers_count", default: 0, null: false t.bigint "target_account_id" - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_account_migrations_on_account_id" t.index ["target_account_id"], name: "index_account_migrations_on_target_account_id", where: "(target_account_id IS NOT NULL)" end create_table "account_moderation_notes", force: :cascade do |t| - t.text "content", null: false t.bigint "account_id", null: false - t.bigint "target_account_id", null: false + t.text "content", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "target_account_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_account_moderation_notes_on_account_id" t.index ["target_account_id"], name: "index_account_moderation_notes_on_target_account_id" @@ -73,9 +73,9 @@ create_table "account_notes", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "target_account_id", null: false t.text "comment", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "target_account_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "target_account_id"], name: "index_account_notes_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_account_notes_on_target_account_id" @@ -83,8 +83,8 @@ create_table "account_pins", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "target_account_id", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "target_account_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "target_account_id"], name: "index_account_pins_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_account_pins_on_target_account_id" @@ -92,120 +92,120 @@ create_table "account_relationship_severance_events", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "relationship_severance_event_id", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.integer "followers_count", default: 0, null: false t.integer "following_count", default: 0, null: false + t.bigint "relationship_severance_event_id", null: false + t.datetime "updated_at", null: false t.index ["account_id", "relationship_severance_event_id"], name: "idx_on_account_id_relationship_severance_event_id_7bd82bf20e", unique: true t.index ["relationship_severance_event_id"], name: "idx_on_relationship_severance_event_id_403f53e707" end create_table "account_stats", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "statuses_count", default: 0, null: false - t.bigint "following_count", default: 0, null: false - t.bigint "followers_count", default: 0, null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.bigint "followers_count", default: 0, null: false + t.bigint "following_count", default: 0, null: false t.datetime "last_status_at", precision: nil + t.bigint "statuses_count", default: 0, null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_account_stats_on_account_id", unique: true t.index ["last_status_at", "account_id"], name: "index_account_stats_on_last_status_at_and_account_id", order: { last_status_at: "DESC NULLS LAST" } end create_table "account_statuses_cleanup_policies", force: :cascade do |t| t.bigint "account_id", null: false + t.datetime "created_at", null: false t.boolean "enabled", default: true, null: false - t.integer "min_status_age", default: 1209600, null: false t.boolean "keep_direct", default: true, null: false + t.boolean "keep_media", default: false, null: false t.boolean "keep_pinned", default: true, null: false t.boolean "keep_polls", default: false, null: false - t.boolean "keep_media", default: false, null: false - t.boolean "keep_self_fav", default: true, null: false t.boolean "keep_self_bookmark", default: true, null: false + t.boolean "keep_self_fav", default: true, null: false t.integer "min_favs" t.integer "min_reblogs" - t.datetime "created_at", null: false + t.integer "min_status_age", default: 1209600, null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_account_statuses_cleanup_policies_on_account_id" end create_table "account_warning_presets", force: :cascade do |t| - t.text "text", default: "", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.text "text", default: "", null: false t.string "title", default: "", null: false + t.datetime "updated_at", precision: nil, null: false end create_table "account_warnings", force: :cascade do |t| t.bigint "account_id" - t.bigint "target_account_id" t.integer "action", default: 0, null: false - t.text "text", default: "", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "overruled_at", precision: nil t.bigint "report_id" t.string "status_ids", array: true - t.datetime "overruled_at", precision: nil + t.bigint "target_account_id" + t.text "text", default: "", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_account_warnings_on_account_id" t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id" end create_table "accounts", id: :bigint, default: -> { "timestamp_id('accounts'::text)" }, force: :cascade do |t| - t.string "username", default: "", null: false - t.string "domain" - t.text "private_key" - t.text "public_key", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.text "note", default: "", null: false - t.string "display_name", default: "", null: false - t.string "uri", default: "", null: false - t.string "url" - t.string "avatar_file_name" + t.string "actor_type" + t.string "also_known_as", array: true + t.string "attribution_domains", default: [], array: true t.string "avatar_content_type" + t.string "avatar_description", default: "", null: false + t.string "avatar_file_name" t.integer "avatar_file_size" + t.string "avatar_remote_url" + t.integer "avatar_storage_schema_version" t.datetime "avatar_updated_at", precision: nil - t.string "header_file_name" + t.datetime "created_at", precision: nil, null: false + t.boolean "discoverable" + t.string "display_name", default: "", null: false + t.string "domain" + t.integer "feature_approval_policy", default: 0, null: false + t.string "featured_collection_url" + t.jsonb "fields" + t.string "followers_url", default: "", null: false + t.string "following_url", default: "", null: false t.string "header_content_type" + t.string "header_description", default: "", null: false + t.string "header_file_name" t.integer "header_file_size" - t.datetime "header_updated_at", precision: nil - t.string "avatar_remote_url" - t.boolean "locked", default: false, null: false t.string "header_remote_url", default: "", null: false - t.datetime "last_webfingered_at", precision: nil + t.integer "header_storage_schema_version" + t.datetime "header_updated_at", precision: nil + t.boolean "hide_collections" + t.integer "id_scheme", default: 1 t.string "inbox_url", default: "", null: false - t.string "outbox_url", default: "", null: false - t.string "shared_inbox_url", default: "", null: false - t.string "followers_url", default: "", null: false - t.integer "protocol", default: 0, null: false + t.boolean "indexable", default: false, null: false + t.datetime "last_webfingered_at", precision: nil + t.boolean "locked", default: false, null: false t.boolean "memorial", default: false, null: false t.bigint "moved_to_account_id" - t.string "featured_collection_url" - t.jsonb "fields" - t.string "actor_type" - t.boolean "discoverable" - t.string "also_known_as", array: true + t.text "note", default: "", null: false + t.string "outbox_url", default: "", null: false + t.text "private_key" + t.integer "protocol", default: 0, null: false + t.text "public_key", default: "", null: false + t.datetime "requested_review_at", precision: nil + t.datetime "reviewed_at", precision: nil + t.datetime "sensitized_at", precision: nil + t.string "shared_inbox_url", default: "", null: false + t.boolean "show_featured", default: true, null: false + t.boolean "show_media", default: true, null: false + t.boolean "show_media_replies", default: true, null: false t.datetime "silenced_at", precision: nil t.datetime "suspended_at", precision: nil - t.boolean "hide_collections" - t.integer "avatar_storage_schema_version" - t.integer "header_storage_schema_version" - t.datetime "sensitized_at", precision: nil t.integer "suspension_origin" t.boolean "trendable" - t.datetime "reviewed_at", precision: nil - t.datetime "requested_review_at", precision: nil - t.boolean "indexable", default: false, null: false - t.string "attribution_domains", default: [], array: true - t.string "following_url", default: "", null: false - t.integer "id_scheme", default: 1 - 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.datetime "updated_at", precision: nil, null: false + t.string "uri", default: "", null: false + t.string "url" + t.string "username", default: "", 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" @@ -223,13 +223,13 @@ create_table "admin_action_logs", force: :cascade do |t| t.bigint "account_id", null: false t.string "action", default: "", null: false - t.string "target_type" - t.bigint "target_id" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.string "human_identifier" - t.string "route_param" t.string "permalink" + t.string "route_param" + t.bigint "target_id" + t.string "target_type" + t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_admin_action_logs_on_account_id" t.index ["target_type", "target_id"], name: "index_admin_action_logs_on_target_type_and_target_id" end @@ -246,9 +246,9 @@ create_table "announcement_reactions", force: :cascade do |t| t.bigint "account_id", null: false t.bigint "announcement_id", null: false - t.string "name", default: "", null: false - t.bigint "custom_emoji_id" t.datetime "created_at", precision: nil, null: false + t.bigint "custom_emoji_id" + t.string "name", default: "", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "announcement_id", "name"], name: "index_announcement_reactions_on_account_id_and_announcement_id", unique: true t.index ["announcement_id"], name: "index_announcement_reactions_on_announcement_id" @@ -256,35 +256,35 @@ end create_table "announcements", force: :cascade do |t| - t.text "text", default: "", null: false - t.boolean "published", default: false, null: false t.boolean "all_day", default: false, null: false - t.datetime "scheduled_at", precision: nil - t.datetime "starts_at", precision: nil - t.datetime "ends_at", precision: nil t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "ends_at", precision: nil + t.datetime "notification_sent_at" + t.boolean "published", default: false, null: false t.datetime "published_at", precision: nil + t.datetime "scheduled_at", precision: nil + t.datetime "starts_at", precision: nil t.bigint "status_ids", array: true - t.datetime "notification_sent_at" + t.text "text", default: "", null: false + t.datetime "updated_at", precision: nil, null: false end create_table "annual_report_statuses_per_account_counts", force: :cascade do |t| - t.integer "year", null: false t.bigint "account_id", null: false t.bigint "statuses_count", null: false + t.integer "year", null: false t.index ["year", "account_id"], name: "idx_on_year_account_id_ff3e167cef", unique: true end create_table "appeals", force: :cascade do |t| t.bigint "account_id", null: false t.bigint "account_warning_id", null: false - t.text "text", default: "", null: false t.datetime "approved_at", precision: nil t.bigint "approved_by_account_id" + t.datetime "created_at", null: false t.datetime "rejected_at", precision: nil t.bigint "rejected_by_account_id" - t.datetime "created_at", null: false + t.text "text", default: "", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_appeals_on_account_id" t.index ["account_warning_id"], name: "index_appeals_on_account_warning_id", unique: true @@ -293,22 +293,22 @@ end create_table "backups", force: :cascade do |t| - t.bigint "user_id" - t.string "dump_file_name" + t.datetime "created_at", precision: nil, null: false t.string "dump_content_type" + t.string "dump_file_name" + t.bigint "dump_file_size" t.datetime "dump_updated_at", precision: nil t.boolean "processed", default: false, null: false - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.bigint "dump_file_size" + t.bigint "user_id" t.index ["user_id"], name: "index_backups_on_user_id" end create_table "blocks", force: :cascade do |t| - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false + t.datetime "created_at", precision: nil, null: false t.bigint "target_account_id", null: false + t.datetime "updated_at", precision: nil, null: false t.string "uri" t.index ["account_id", "target_account_id"], name: "index_blocks_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_blocks_on_target_account_id" @@ -316,8 +316,8 @@ create_table "bookmarks", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "status_id", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "status_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "status_id"], name: "index_bookmarks_on_account_id_and_status_id", unique: true t.index ["status_id"], name: "index_bookmarks_on_status_id" @@ -325,24 +325,24 @@ create_table "bulk_import_rows", force: :cascade do |t| t.bigint "bulk_import_id", null: false - t.jsonb "data" t.datetime "created_at", null: false + t.jsonb "data" t.datetime "updated_at", null: false t.index ["bulk_import_id"], name: "index_bulk_import_rows_on_bulk_import_id" end create_table "bulk_imports", force: :cascade do |t| - t.integer "type", null: false - t.integer "state", null: false - t.integer "total_items", default: 0, null: false - t.integer "imported_items", default: 0, null: false - t.integer "processed_items", default: 0, null: false + t.bigint "account_id", null: false + t.datetime "created_at", null: false t.datetime "finished_at", precision: nil - t.boolean "overwrite", default: false, null: false + t.integer "imported_items", default: 0, null: false t.boolean "likely_mismatched", default: false, null: false t.string "original_filename", default: "", null: false - t.bigint "account_id", null: false - t.datetime "created_at", null: false + t.boolean "overwrite", default: false, null: false + t.integer "processed_items", default: 0, null: false + t.integer "state", null: false + t.integer "total_items", default: 0, null: false + t.integer "type", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_bulk_imports_on_account_id" t.index ["id"], name: "index_bulk_imports_unconfirmed", where: "(state = 0)" @@ -350,23 +350,23 @@ create_table "canonical_email_blocks", force: :cascade do |t| t.string "canonical_email_hash", default: "", null: false - t.bigint "reference_account_id" t.datetime "created_at", null: false + t.bigint "reference_account_id" t.datetime "updated_at", null: false t.index ["canonical_email_hash"], name: "index_canonical_email_blocks_on_canonical_email_hash", unique: true t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id" end create_table "collection_items", id: :bigint, default: -> { "timestamp_id('collection_items'::text)" }, force: :cascade do |t| - t.bigint "collection_id", null: false t.bigint "account_id" - t.integer "position", default: 1, null: false - t.string "object_uri" - t.string "approval_uri" t.string "activity_uri" t.datetime "approval_last_verified_at" - t.integer "state", default: 0, null: false + t.string "approval_uri" + t.bigint "collection_id", null: false t.datetime "created_at", null: false + t.string "object_uri" + t.integer "position", default: 1, null: false + t.integer "state", default: 0, null: false t.datetime "updated_at", null: false t.string "uri" t.index ["account_id"], name: "index_collection_items_on_account_id" @@ -377,8 +377,8 @@ create_table "collection_reports", force: :cascade do |t| t.bigint "collection_id", null: false - t.bigint "report_id", null: false t.datetime "created_at", null: false + t.bigint "report_id", null: false t.datetime "updated_at", null: false t.index ["collection_id"], name: "index_collection_reports_on_collection_id" t.index ["report_id"], name: "index_collection_reports_on_report_id" @@ -386,77 +386,77 @@ create_table "collections", id: :bigint, default: -> { "timestamp_id('collections'::text)" }, force: :cascade do |t| t.bigint "account_id", null: false - t.string "name", null: false + t.datetime "created_at", null: false t.text "description", null: false - t.string "uri" + t.boolean "discoverable", null: false + t.integer "item_count", default: 0, null: false + t.string "language" t.boolean "local", null: false + t.string "name", null: false + t.integer "original_number_of_items" t.boolean "sensitive", null: false - t.boolean "discoverable", null: false t.bigint "tag_id" - t.integer "original_number_of_items" - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "item_count", default: 0, null: false - t.string "language" + t.string "uri" t.index ["account_id"], name: "index_collections_on_account_id" t.index ["tag_id"], name: "index_collections_on_tag_id" end create_table "conversation_mutes", force: :cascade do |t| - t.bigint "conversation_id", null: false t.bigint "account_id", null: false + t.bigint "conversation_id", null: false t.index ["account_id", "conversation_id"], name: "index_conversation_mutes_on_account_id_and_conversation_id", unique: true end create_table "conversations", force: :cascade do |t| - t.string "uri" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.bigint "parent_status_id" t.bigint "parent_account_id" + t.bigint "parent_status_id" + t.datetime "updated_at", precision: nil, null: false + t.string "uri" t.index ["parent_status_id"], name: "index_conversations_on_parent_status_id", unique: true, where: "(parent_status_id IS NOT NULL)" t.index ["uri"], name: "index_conversations_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)" end create_table "custom_emoji_categories", force: :cascade do |t| - t.string "name" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "featured_emoji_id" + t.string "name" + t.datetime "updated_at", precision: nil, null: false t.index ["name"], name: "index_custom_emoji_categories_on_name", unique: true end create_table "custom_emojis", force: :cascade do |t| - t.string "shortcode", default: "", null: false + t.bigint "category_id" + t.datetime "created_at", precision: nil, null: false + t.boolean "disabled", default: false, null: false t.string "domain" - t.string "image_file_name" t.string "image_content_type" + t.string "image_file_name" t.integer "image_file_size" + t.string "image_remote_url" + t.integer "image_storage_schema_version" t.datetime "image_updated_at", precision: nil - t.datetime "created_at", precision: nil, null: false + t.string "shortcode", default: "", null: false t.datetime "updated_at", precision: nil, null: false - t.boolean "disabled", default: false, null: false t.string "uri" - t.string "image_remote_url" t.boolean "visible_in_picker", default: true, null: false - t.bigint "category_id" - t.integer "image_storage_schema_version" t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true end create_table "custom_filter_keywords", force: :cascade do |t| + t.datetime "created_at", null: false t.bigint "custom_filter_id", null: false t.text "keyword", default: "", null: false - t.boolean "whole_word", default: true, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "whole_word", default: true, null: false t.index ["custom_filter_id"], name: "index_custom_filter_keywords_on_custom_filter_id" end create_table "custom_filter_statuses", force: :cascade do |t| + t.datetime "created_at", null: false t.bigint "custom_filter_id", null: false t.bigint "status_id", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["custom_filter_id"], name: "index_custom_filter_statuses_on_custom_filter_id" t.index ["status_id", "custom_filter_id"], name: "index_custom_filter_statuses_on_status_id_and_custom_filter_id", unique: true @@ -464,110 +464,110 @@ create_table "custom_filters", force: :cascade do |t| t.bigint "account_id", null: false - t.datetime "expires_at", precision: nil - t.text "phrase", default: "", null: false + t.integer "action", default: 0, null: false t.string "context", default: [], null: false, array: true t.datetime "created_at", precision: nil, null: false + t.datetime "expires_at", precision: nil + t.text "phrase", default: "", null: false t.datetime "updated_at", precision: nil, null: false - t.integer "action", default: 0, null: false t.index ["account_id"], name: "index_custom_filters_on_account_id" end create_table "domain_allows", force: :cascade do |t| - t.string "domain", default: "", null: false t.datetime "created_at", precision: nil, null: false + t.string "domain", default: "", null: false t.datetime "updated_at", precision: nil, null: false t.index ["domain"], name: "index_domain_allows_on_domain", unique: true end create_table "domain_blocks", force: :cascade do |t| - t.string "domain", default: "", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.integer "severity", default: 0 - t.boolean "reject_media", default: false, null: false - t.boolean "reject_reports", default: false, null: false + t.string "domain", default: "", null: false + t.boolean "obfuscate", default: false, null: false t.text "private_comment" t.text "public_comment" - t.boolean "obfuscate", default: false, null: false + t.boolean "reject_media", default: false, null: false + t.boolean "reject_reports", default: false, null: false + t.integer "severity", default: 0 + t.datetime "updated_at", precision: nil, null: false t.index ["domain"], name: "index_domain_blocks_on_domain", unique: true end create_table "email_domain_blocks", force: :cascade do |t| - t.string "domain", default: "", null: false + t.boolean "allow_with_approval", default: false, null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.string "domain", default: "", null: false t.bigint "parent_id" - t.boolean "allow_with_approval", default: false, null: false + t.datetime "updated_at", precision: nil, null: false t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end create_table "fasp_backfill_requests", force: :cascade do |t| t.string "category", null: false - t.integer "max_count", default: 100, null: false + t.datetime "created_at", null: false t.string "cursor" - t.boolean "fulfilled", default: false, null: false t.bigint "fasp_provider_id", null: false - t.datetime "created_at", null: false + t.boolean "fulfilled", default: false, null: false + t.integer "max_count", default: 100, null: false t.datetime "updated_at", null: false t.index ["fasp_provider_id"], name: "index_fasp_backfill_requests_on_fasp_provider_id" end create_table "fasp_debug_callbacks", force: :cascade do |t| + t.datetime "created_at", null: false t.bigint "fasp_provider_id", null: false t.string "ip", null: false t.text "request_body", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["fasp_provider_id"], name: "index_fasp_debug_callbacks_on_fasp_provider_id" end create_table "fasp_follow_recommendations", force: :cascade do |t| - t.bigint "requesting_account_id", null: false - t.bigint "recommended_account_id", null: false t.datetime "created_at", null: false + t.bigint "recommended_account_id", null: false + t.bigint "requesting_account_id", null: false t.datetime "updated_at", null: false t.index ["recommended_account_id"], name: "index_fasp_follow_recommendations_on_recommended_account_id" t.index ["requesting_account_id"], name: "index_fasp_follow_recommendations_on_requesting_account_id" end create_table "fasp_providers", force: :cascade do |t| - t.boolean "confirmed", default: false, null: false - t.string "name", null: false t.string "base_url", null: false - t.string "sign_in_url" - t.string "remote_identifier", null: false - t.string "provider_public_key_pem", null: false - t.string "server_private_key_pem", null: false t.jsonb "capabilities", default: [], null: false - t.jsonb "privacy_policy" + t.boolean "confirmed", default: false, null: false t.string "contact_email" - t.string "fediverse_account" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.datetime "delivery_last_failed_at" + t.string "fediverse_account" + t.string "name", null: false + t.jsonb "privacy_policy" + t.string "provider_public_key_pem", null: false + t.string "remote_identifier", null: false + t.string "server_private_key_pem", null: false + t.string "sign_in_url" + t.datetime "updated_at", null: false t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true end create_table "fasp_subscriptions", force: :cascade do |t| t.string "category", null: false - t.string "subscription_type", null: false + t.datetime "created_at", null: false + t.bigint "fasp_provider_id", null: false t.integer "max_batch_size", null: false - t.integer "threshold_timeframe" - t.integer "threshold_shares" + t.string "subscription_type", null: false t.integer "threshold_likes" t.integer "threshold_replies" - t.bigint "fasp_provider_id", null: false - t.datetime "created_at", null: false + t.integer "threshold_shares" + t.integer "threshold_timeframe" t.datetime "updated_at", null: false t.index ["fasp_provider_id"], name: "index_fasp_subscriptions_on_fasp_provider_id" end create_table "favourites", force: :cascade do |t| - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false + t.datetime "created_at", precision: nil, null: false t.bigint "status_id", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "id"], name: "index_favourites_on_account_id_and_id" t.index ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true t.index ["status_id"], name: "index_favourites_on_status_id" @@ -575,20 +575,20 @@ create_table "featured_tags", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "tag_id", null: false - t.bigint "statuses_count", default: 0, null: false - t.datetime "last_status_at", precision: nil t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "last_status_at", precision: nil t.string "name" + t.bigint "statuses_count", default: 0, null: false + t.bigint "tag_id", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "tag_id"], name: "index_featured_tags_on_account_id_and_tag_id", unique: true t.index ["tag_id"], name: "index_featured_tags_on_tag_id" end create_table "follow_recommendation_mutes", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "target_account_id", null: false t.datetime "created_at", null: false + t.bigint "target_account_id", null: false t.datetime "updated_at", null: false t.index ["account_id", "target_account_id"], name: "idx_on_account_id_target_account_id_a8c8ddf44e", unique: true t.index ["target_account_id"], name: "index_follow_recommendation_mutes_on_target_account_id" @@ -602,46 +602,46 @@ end create_table "follow_requests", force: :cascade do |t| - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false - t.bigint "target_account_id", null: false + t.datetime "created_at", precision: nil, null: false + t.string "languages", array: true + t.boolean "notify", default: false, null: false t.boolean "show_reblogs", default: true, null: false + t.bigint "target_account_id", null: false + t.datetime "updated_at", precision: nil, null: false t.string "uri" - t.boolean "notify", default: false, null: false - t.string "languages", array: true t.index ["account_id", "target_account_id"], name: "index_follow_requests_on_account_id_and_target_account_id", unique: true end create_table "follows", force: :cascade do |t| - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false - t.bigint "target_account_id", null: false + t.datetime "created_at", precision: nil, null: false + t.string "languages", array: true + t.boolean "notify", default: false, null: false t.boolean "show_reblogs", default: true, null: false + t.bigint "target_account_id", null: false + t.datetime "updated_at", precision: nil, null: false t.string "uri" - t.boolean "notify", default: false, null: false - t.string "languages", array: true t.index ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true t.index ["target_account_id", "account_id"], name: "index_follows_on_target_account_id_and_account_id" end create_table "generated_annual_reports", force: :cascade do |t| t.bigint "account_id", null: false - t.integer "year", null: false + t.datetime "created_at", null: false t.jsonb "data", null: false t.integer "schema_version", null: false - t.datetime "viewed_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.string "share_key" + t.datetime "updated_at", null: false + t.datetime "viewed_at" + t.integer "year", null: false t.index ["account_id", "year"], name: "index_generated_annual_reports_on_account_id_and_year", unique: true end create_table "identities", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false t.string "provider", default: "", null: false t.string "uid", default: "", null: false - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false t.bigint "user_id" t.index ["uid", "provider"], name: "index_identities_on_uid_and_provider", unique: true @@ -649,43 +649,43 @@ end create_table "instance_moderation_notes", force: :cascade do |t| - t.string "domain", null: false t.bigint "account_id", null: false t.text "content" t.datetime "created_at", null: false + t.string "domain", null: false t.datetime "updated_at", null: false t.index ["domain"], name: "index_instance_moderation_notes_on_domain" end create_table "invites", force: :cascade do |t| - t.bigint "user_id", null: false + t.boolean "autofollow", default: false, null: false t.string "code", default: "", null: false + t.text "comment" + t.datetime "created_at", precision: nil, null: false t.datetime "expires_at", precision: nil t.integer "max_uses" - t.integer "uses", default: 0, null: false - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.boolean "autofollow", default: false, null: false - t.text "comment" + t.bigint "user_id", null: false + t.integer "uses", default: 0, null: false t.index ["code"], name: "index_invites_on_code", unique: true t.index ["user_id"], name: "index_invites_on_user_id" end create_table "ip_blocks", force: :cascade do |t| - t.inet "ip", default: "0.0.0.0", null: false - t.integer "severity", default: 0, null: false - t.datetime "expires_at", precision: nil t.text "comment", default: "", null: false t.datetime "created_at", precision: nil, null: false + t.datetime "expires_at", precision: nil + t.inet "ip", default: "0.0.0.0", null: false + t.integer "severity", default: 0, null: false t.datetime "updated_at", precision: nil, null: false t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true end create_table "list_accounts", force: :cascade do |t| - t.bigint "list_id", null: false t.bigint "account_id", null: false t.bigint "follow_id" t.bigint "follow_request_id" + t.bigint "list_id", null: false t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true t.index ["follow_id"], name: "index_list_accounts_on_follow_id", where: "(follow_id IS NOT NULL)" t.index ["follow_request_id"], name: "index_list_accounts_on_follow_request_id", where: "(follow_request_id IS NOT NULL)" @@ -694,60 +694,60 @@ create_table "lists", force: :cascade do |t| t.bigint "account_id", null: false - t.string "title", default: "", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.integer "replies_policy", default: 0, null: false t.boolean "exclusive", default: false, null: false + t.integer "replies_policy", default: 0, null: false + t.string "title", default: "", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_lists_on_account_id" end create_table "login_activities", force: :cascade do |t| - t.bigint "user_id", null: false t.string "authentication_method" - t.string "provider" - t.boolean "success" + t.datetime "created_at", precision: nil t.string "failure_reason" t.inet "ip" + t.string "provider" + t.boolean "success" t.string "user_agent" - t.datetime "created_at", precision: nil + t.bigint "user_id", null: false t.index ["user_id"], name: "index_login_activities_on_user_id" end create_table "markers", force: :cascade do |t| - t.bigint "user_id", null: false - t.string "timeline", default: "", null: false + t.datetime "created_at", precision: nil, null: false t.bigint "last_read_id", default: 0, null: false t.integer "lock_version", default: 0, null: false - t.datetime "created_at", precision: nil, null: false + t.string "timeline", default: "", null: false t.datetime "updated_at", precision: nil, null: false + t.bigint "user_id", null: false t.index ["user_id", "timeline"], name: "index_markers_on_user_id_and_timeline", unique: true end create_table "media_attachments", id: :bigint, default: -> { "timestamp_id('media_attachments'::text)" }, force: :cascade do |t| - t.bigint "status_id" - t.string "file_file_name" + t.bigint "account_id" + t.string "blurhash" + t.datetime "created_at", precision: nil, null: false + t.text "description" t.string "file_content_type" + t.string "file_file_name" t.integer "file_file_size" + t.json "file_meta" + t.integer "file_storage_schema_version" t.datetime "file_updated_at", precision: nil + t.integer "processing" t.string "remote_url", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.string "shortcode" - t.integer "type", default: 0, null: false - t.json "file_meta" - t.bigint "account_id" - t.text "description" t.bigint "scheduled_status_id" - t.string "blurhash" - t.integer "processing" - t.integer "file_storage_schema_version" - t.string "thumbnail_file_name" + t.string "shortcode" + t.bigint "status_id" t.string "thumbnail_content_type" + t.string "thumbnail_file_name" t.integer "thumbnail_file_size" - t.datetime "thumbnail_updated_at", precision: nil t.string "thumbnail_remote_url" t.integer "thumbnail_storage_schema_version" + t.datetime "thumbnail_updated_at", precision: nil + t.integer "type", default: 0, null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "status_id"], name: "index_media_attachments_on_account_id_and_status_id", order: { status_id: :desc } t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id", where: "(scheduled_status_id IS NOT NULL)" t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true, opclass: :text_pattern_ops, where: "(shortcode IS NOT NULL)" @@ -755,30 +755,30 @@ end create_table "mentions", force: :cascade do |t| - t.bigint "status_id", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false + t.datetime "created_at", precision: nil, null: false t.boolean "silent", default: false, null: false + t.bigint "status_id", null: false + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true t.index ["status_id"], name: "index_mentions_on_status_id" end create_table "mutes", force: :cascade do |t| + t.bigint "account_id", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "expires_at", precision: nil t.boolean "hide_notifications", default: true, null: false - t.bigint "account_id", null: false t.bigint "target_account_id", null: false - t.datetime "expires_at", precision: nil + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true t.index ["target_account_id"], name: "index_mutes_on_target_account_id" end create_table "notification_permissions", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "from_account_id", null: false t.datetime "created_at", null: false + t.bigint "from_account_id", null: false t.datetime "updated_at", null: false t.index ["account_id"], name: "index_notification_permissions_on_account_id" t.index ["from_account_id"], name: "index_notification_permissions_on_from_account_id" @@ -787,21 +787,21 @@ create_table "notification_policies", force: :cascade do |t| t.bigint "account_id", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "for_not_following", default: 0, null: false - t.integer "for_not_followers", default: 0, null: false + t.integer "for_limited_accounts", default: 1, null: false t.integer "for_new_accounts", default: 0, null: false + t.integer "for_not_followers", default: 0, null: false + t.integer "for_not_following", default: 0, null: false t.integer "for_private_mentions", default: 1, null: false - t.integer "for_limited_accounts", default: 1, null: false + t.datetime "updated_at", null: false t.index ["account_id"], name: "index_notification_policies_on_account_id", unique: true end create_table "notification_requests", id: :bigint, default: -> { "timestamp_id('notification_requests'::text)" }, force: :cascade do |t| t.bigint "account_id", null: false + t.datetime "created_at", null: false t.bigint "from_account_id", null: false t.bigint "last_status_id" t.bigint "notifications_count", default: 0, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["account_id", "from_account_id"], name: "index_notification_requests_on_account_id_and_from_account_id", unique: true t.index ["from_account_id"], name: "index_notification_requests_on_from_account_id" @@ -809,15 +809,15 @@ end create_table "notifications", force: :cascade do |t| + t.bigint "account_id", null: false t.bigint "activity_id", null: false t.string "activity_type", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.bigint "account_id", null: false - t.bigint "from_account_id", null: false - t.string "type" t.boolean "filtered", default: false, null: false + t.bigint "from_account_id", null: false t.string "group_key" + t.string "type" + t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "group_key"], name: "index_notifications_on_account_id_and_group_key", where: "(group_key IS NOT NULL)" t.index ["account_id", "id", "type"], name: "index_notifications_on_account_id_and_id_and_type", order: { id: :desc } t.index ["account_id", "id", "type"], name: "index_notifications_on_filtered", order: { id: :desc }, where: "(filtered = false)" @@ -826,68 +826,68 @@ end create_table "oauth_access_grants", force: :cascade do |t| - t.string "token", null: false + t.bigint "application_id", null: false + t.string "code_challenge" + t.string "code_challenge_method" + t.datetime "created_at", precision: nil, null: false t.integer "expires_in", null: false t.text "redirect_uri", null: false - t.datetime "created_at", precision: nil, null: false + t.bigint "resource_owner_id", null: false t.datetime "revoked_at", precision: nil t.string "scopes" - t.bigint "application_id", null: false - t.bigint "resource_owner_id", null: false - t.string "code_challenge" - t.string "code_challenge_method" + t.string "token", null: false t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id" t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true end create_table "oauth_access_tokens", force: :cascade do |t| - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" - t.datetime "revoked_at", precision: nil - t.datetime "created_at", precision: nil, null: false - t.string "scopes" t.bigint "application_id" - t.bigint "resource_owner_id" + t.datetime "created_at", precision: nil, null: false + t.integer "expires_in" t.datetime "last_used_at", precision: nil t.inet "last_used_ip" + t.string "refresh_token" + t.bigint "resource_owner_id" + t.datetime "revoked_at", precision: nil + t.string "scopes" + t.string "token", null: false t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, opclass: :text_pattern_ops, where: "(refresh_token IS NOT NULL)" t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", where: "(resource_owner_id IS NOT NULL)" t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true end create_table "oauth_applications", force: :cascade do |t| + t.boolean "confidential", default: true, null: false + t.datetime "created_at", precision: nil t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false + t.bigint "owner_id" + t.string "owner_type" t.text "redirect_uri", null: false t.string "scopes", default: "", null: false - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil + t.string "secret", null: false t.boolean "superapp", default: false, null: false + t.string "uid", null: false + t.datetime "updated_at", precision: nil t.string "website" - t.string "owner_type" - t.bigint "owner_id" - t.boolean "confidential", default: true, null: false t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type" t.index ["superapp"], name: "index_oauth_applications_on_superapp", where: "(superapp = true)" t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end create_table "pghero_space_stats", force: :cascade do |t| + t.datetime "captured_at", precision: nil t.text "database" - t.text "schema" t.text "relation" + t.text "schema" t.bigint "size" - t.datetime "captured_at", precision: nil t.index ["database", "captured_at"], name: "index_pghero_space_stats_on_database_and_captured_at" end create_table "poll_votes", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "poll_id", null: false t.integer "choice", default: 0, null: false t.datetime "created_at", precision: nil, null: false + t.bigint "poll_id", null: false t.datetime "updated_at", precision: nil, null: false t.string "uri" t.index ["account_id"], name: "index_poll_votes_on_account_id" @@ -896,75 +896,75 @@ create_table "polls", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "status_id", null: false - t.datetime "expires_at", precision: nil - t.string "options", default: [], null: false, array: true t.bigint "cached_tallies", default: [], null: false, array: true - t.boolean "multiple", default: false, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "expires_at", precision: nil t.boolean "hide_totals", default: false, null: false - t.bigint "votes_count", default: 0, null: false t.datetime "last_fetched_at", precision: nil - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.integer "lock_version", default: 0, null: false + t.boolean "multiple", default: false, null: false + t.string "options", default: [], null: false, array: true + t.bigint "status_id", null: false + t.datetime "updated_at", precision: nil, null: false t.bigint "voters_count" + t.bigint "votes_count", default: 0, null: false t.index ["account_id"], name: "index_polls_on_account_id" t.index ["status_id"], name: "index_polls_on_status_id" end create_table "preview_card_providers", force: :cascade do |t| + t.datetime "created_at", null: false t.string "domain", default: "", null: false - t.string "icon_file_name" t.string "icon_content_type" + t.string "icon_file_name" t.bigint "icon_file_size" t.datetime "icon_updated_at", precision: nil - t.boolean "trendable" - t.datetime "reviewed_at", precision: nil t.datetime "requested_review_at", precision: nil - t.datetime "created_at", null: false + t.datetime "reviewed_at", precision: nil + t.boolean "trendable" t.datetime "updated_at", null: false t.index ["domain"], name: "index_preview_card_providers_on_domain", unique: true end create_table "preview_card_trends", force: :cascade do |t| - t.bigint "preview_card_id", null: false - t.float "score", default: 0.0, null: false - t.integer "rank", default: 0, null: false t.boolean "allowed", default: false, null: false t.string "language" + t.bigint "preview_card_id", null: false + t.integer "rank", default: 0, null: false + t.float "score", default: 0.0, null: false t.index ["preview_card_id"], name: "index_preview_card_trends_on_preview_card_id", unique: true end create_table "preview_cards", force: :cascade do |t| - t.string "url", default: "", null: false - t.string "title", default: "", null: false - t.string "description", default: "", null: false - t.string "image_file_name" - t.string "image_content_type" - t.integer "image_file_size" - t.datetime "image_updated_at", precision: nil - t.integer "type", default: 0, null: false - t.text "html", default: "", null: false + t.bigint "author_account_id" t.string "author_name", default: "", null: false t.string "author_url", default: "", null: false - t.string "provider_name", default: "", null: false - t.string "provider_url", default: "", null: false - t.integer "width", default: 0, null: false - t.integer "height", default: 0, null: false + t.string "blurhash" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.string "description", default: "", null: false t.string "embed_url", default: "", null: false + t.integer "height", default: 0, null: false + t.text "html", default: "", null: false + t.string "image_content_type" + t.string "image_description", default: "", null: false + t.string "image_file_name" + t.integer "image_file_size" t.integer "image_storage_schema_version" - t.string "blurhash" + t.datetime "image_updated_at", precision: nil t.string "language" + t.integer "link_type" t.float "max_score" t.datetime "max_score_at", precision: nil - t.boolean "trendable" - t.integer "link_type" + t.string "provider_name", default: "", null: false + t.string "provider_url", default: "", null: false t.datetime "published_at" - t.string "image_description", default: "", null: false - t.bigint "author_account_id" + t.string "title", default: "", null: false + t.boolean "trendable" + t.integer "type", default: 0, null: false t.bigint "unverified_author_account_id" + t.datetime "updated_at", precision: nil, null: false + t.string "url", default: "", null: false + t.integer "width", default: 0, null: false t.index ["author_account_id"], name: "index_preview_cards_on_author_account_id", where: "(author_account_id IS NOT NULL)" t.index ["unverified_author_account_id", "id"], name: "index_preview_cards_on_unverified_author_account_id_and_id", where: "(unverified_author_account_id IS NOT NULL)" t.index ["url"], name: "index_preview_cards_on_url", unique: true @@ -978,15 +978,15 @@ create_table "quotes", id: :bigint, default: -> { "timestamp_id('quotes'::text)" }, force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "status_id", null: false - t.bigint "quoted_status_id" - t.bigint "quoted_account_id" - t.integer "state", default: 0, null: false - t.string "approval_uri" t.string "activity_uri" + t.string "approval_uri" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.boolean "legacy", default: false, null: false + t.bigint "quoted_account_id" + t.bigint "quoted_status_id" + t.integer "state", default: 0, null: false + t.bigint "status_id", null: false + t.datetime "updated_at", null: false t.index ["account_id", "quoted_account_id", "id"], name: "index_quotes_on_account_id_and_quoted_account_id_and_id" t.index ["activity_uri"], name: "index_quotes_on_activity_uri", unique: true, where: "(activity_uri IS NOT NULL)" t.index ["approval_uri"], name: "index_quotes_on_approval_uri", where: "(approval_uri IS NOT NULL)" @@ -996,47 +996,47 @@ end create_table "relationship_severance_events", force: :cascade do |t| - t.integer "type", null: false - t.string "target_name", null: false - t.boolean "purged", default: false, null: false t.datetime "created_at", null: false + t.boolean "purged", default: false, null: false + t.string "target_name", null: false + t.integer "type", null: false t.datetime "updated_at", null: false t.index ["type", "target_name"], name: "index_relationship_severance_events_on_type_and_target_name" end create_table "relays", force: :cascade do |t| - t.string "inbox_url", default: "", null: false - t.string "follow_activity_id" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.string "follow_activity_id" + t.string "inbox_url", default: "", null: false t.integer "state", default: 0, null: false + t.datetime "updated_at", precision: nil, null: false end create_table "report_notes", force: :cascade do |t| - t.text "content", null: false - t.bigint "report_id", null: false t.bigint "account_id", null: false + t.text "content", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "report_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id"], name: "index_report_notes_on_account_id" t.index ["report_id"], name: "index_report_notes_on_report_id" end create_table "reports", force: :cascade do |t| - t.bigint "status_ids", default: [], null: false, array: true - t.text "comment", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false + t.datetime "action_taken_at", precision: nil t.bigint "action_taken_by_account_id" - t.bigint "target_account_id", null: false + t.bigint "application_id" t.bigint "assigned_account_id" - t.string "uri" - t.boolean "forwarded" t.integer "category", default: 0, null: false - t.datetime "action_taken_at", precision: nil + t.text "comment", default: "", null: false + t.datetime "created_at", precision: nil, null: false + t.boolean "forwarded" t.bigint "rule_ids", array: true - t.bigint "application_id" + t.bigint "status_ids", default: [], null: false, array: true + t.bigint "target_account_id", null: false + t.datetime "updated_at", precision: nil, null: false + t.string "uri" t.index ["account_id"], name: "index_reports_on_account_id" t.index ["action_taken_by_account_id"], name: "index_reports_on_action_taken_by_account_id", where: "(action_taken_by_account_id IS NOT NULL)" t.index ["assigned_account_id"], name: "index_reports_on_assigned_account_id", where: "(assigned_account_id IS NOT NULL)" @@ -1044,39 +1044,39 @@ end create_table "rule_translations", force: :cascade do |t| - t.text "text", default: "", null: false + t.datetime "created_at", null: false t.text "hint", default: "", null: false t.string "language", null: false t.bigint "rule_id", null: false - t.datetime "created_at", null: false + t.text "text", default: "", null: false t.datetime "updated_at", null: false t.index ["rule_id", "language"], name: "index_rule_translations_on_rule_id_and_language", unique: true end create_table "rules", force: :cascade do |t| - t.integer "priority", default: 0, null: false + t.datetime "created_at", precision: nil, null: false t.datetime "deleted_at", precision: nil + t.text "hint", default: "", null: false + t.integer "priority", default: 0, null: false t.text "text", default: "", null: false - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.text "hint", default: "", null: false end create_table "scheduled_statuses", force: :cascade do |t| t.bigint "account_id", null: false - t.datetime "scheduled_at", precision: nil t.jsonb "params" + t.datetime "scheduled_at", precision: nil t.index ["account_id"], name: "index_scheduled_statuses_on_account_id" t.index ["scheduled_at"], name: "index_scheduled_statuses_on_scheduled_at" end create_table "session_activations", force: :cascade do |t| - t.string "session_id", null: false + t.bigint "access_token_id" t.datetime "created_at", precision: nil, null: false + t.inet "ip" + t.string "session_id", null: false t.datetime "updated_at", precision: nil, null: false t.string "user_agent", default: "", null: false - t.inet "ip" - t.bigint "access_token_id" t.bigint "user_id", null: false t.bigint "web_push_subscription_id" t.index ["access_token_id"], name: "index_session_activations_on_access_token_id" @@ -1085,22 +1085,22 @@ end create_table "settings", force: :cascade do |t| - t.string "var", null: false - t.text "value" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil + t.text "value" + t.string "var", null: false t.index ["var"], name: "index_settings_on_var", unique: true end create_table "severed_relationships", force: :cascade do |t| - t.bigint "relationship_severance_event_id", null: false + t.datetime "created_at", null: false + t.integer "direction", null: false + t.string "languages", array: true t.bigint "local_account_id", null: false + t.boolean "notify" + t.bigint "relationship_severance_event_id", null: false t.bigint "remote_account_id", null: false - t.integer "direction", null: false t.boolean "show_reblogs" - t.boolean "notify" - t.string "languages", array: true - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["local_account_id", "relationship_severance_event_id"], name: "index_severed_relationships_on_local_account_and_event" t.index ["relationship_severance_event_id", "local_account_id", "direction", "remote_account_id"], name: "index_severed_relationships_on_unique_tuples", unique: true @@ -1108,102 +1108,102 @@ end create_table "site_uploads", force: :cascade do |t| - t.string "var", default: "", null: false - t.string "file_file_name" + t.string "blurhash" + t.datetime "created_at", precision: nil, null: false t.string "file_content_type" + t.string "file_file_name" t.integer "file_file_size" t.datetime "file_updated_at", precision: nil t.json "meta" - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.string "blurhash" + t.string "var", default: "", null: false t.index ["var"], name: "index_site_uploads_on_var", unique: true end create_table "software_updates", force: :cascade do |t| - t.string "version", null: false - t.boolean "urgent", default: false, null: false - t.integer "type", default: 0, null: false - t.string "release_notes", default: "", null: false t.datetime "created_at", null: false + t.string "release_notes", default: "", null: false + t.integer "type", default: 0, null: false t.datetime "updated_at", null: false + t.boolean "urgent", default: false, null: false + t.string "version", null: false t.index ["version"], name: "index_software_updates_on_version", unique: true end create_table "status_edits", force: :cascade do |t| - t.bigint "status_id", null: false t.bigint "account_id" - t.text "text", default: "", null: false - t.text "spoiler_text", default: "", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "ordered_media_attachment_ids", array: true t.text "media_descriptions", array: true + t.bigint "ordered_media_attachment_ids", array: true t.string "poll_options", array: true - t.boolean "sensitive" t.bigint "quote_id" + t.boolean "sensitive" + t.text "spoiler_text", default: "", null: false + t.bigint "status_id", null: false + t.text "text", default: "", null: false + t.datetime "updated_at", null: false t.index ["account_id"], name: "index_status_edits_on_account_id" t.index ["status_id"], name: "index_status_edits_on_status_id" end create_table "status_pins", force: :cascade do |t| t.bigint "account_id", null: false - t.bigint "status_id", null: false t.datetime "created_at", precision: nil, null: false + t.bigint "status_id", null: false t.datetime "updated_at", precision: nil, null: false t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true t.index ["status_id"], name: "index_status_pins_on_status_id" end create_table "status_stats", force: :cascade do |t| - t.bigint "status_id", null: false - t.bigint "replies_count", default: 0, null: false - t.bigint "reblogs_count", default: 0, null: false - t.bigint "favourites_count", default: 0, null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.bigint "favourites_count", default: 0, null: false + t.bigint "quotes_count", default: 0, null: false + t.bigint "reblogs_count", default: 0, null: false + t.bigint "replies_count", default: 0, null: false + t.bigint "status_id", null: false t.bigint "untrusted_favourites_count" t.bigint "untrusted_reblogs_count" - t.bigint "quotes_count", default: 0, null: false + t.datetime "updated_at", precision: nil, null: false t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true end create_table "status_trends", force: :cascade do |t| - t.bigint "status_id", null: false t.bigint "account_id", null: false - t.float "score", default: 0.0, null: false - t.integer "rank", default: 0, null: false t.boolean "allowed", default: false, null: false t.string "language" + t.integer "rank", default: 0, null: false + t.float "score", default: 0.0, null: false + t.bigint "status_id", null: false t.index ["account_id"], name: "index_status_trends_on_account_id" t.index ["status_id"], name: "index_status_trends_on_status_id", unique: true end create_table "statuses", id: :bigint, default: -> { "timestamp_id('statuses'::text)" }, force: :cascade do |t| - t.string "uri" - t.text "text", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.bigint "in_reply_to_id" - t.bigint "reblog_of_id" - t.string "url" - t.boolean "sensitive", default: false, null: false - t.integer "visibility", default: 0, null: false - t.text "spoiler_text", default: "", null: false - t.boolean "reply", default: false, null: false - t.string "language" - t.bigint "conversation_id" - t.boolean "local" t.bigint "account_id", null: false t.bigint "application_id" - t.bigint "in_reply_to_account_id" - t.bigint "poll_id" + t.bigint "conversation_id" + t.datetime "created_at", precision: nil, null: false t.datetime "deleted_at", precision: nil t.datetime "edited_at", precision: nil - t.boolean "trendable" - t.bigint "ordered_media_attachment_ids", array: true t.datetime "fetched_replies_at" + t.bigint "in_reply_to_account_id" + t.bigint "in_reply_to_id" + t.string "language" + t.boolean "local" + t.bigint "ordered_media_attachment_ids", array: true + t.bigint "poll_id" t.integer "quote_approval_policy", default: 0, null: false + t.bigint "reblog_of_id" + t.boolean "reply", default: false, null: false + t.boolean "sensitive", default: false, null: false + t.text "spoiler_text", default: "", null: false + t.text "text", default: "", null: false + t.boolean "trendable" + t.datetime "updated_at", precision: nil, null: false + t.string "uri" + t.string "url" + t.integer "visibility", default: 0, null: false t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" t.index ["account_id"], name: "index_statuses_on_account_id" t.index ["conversation_id"], name: "index_statuses_on_conversation_id" @@ -1223,133 +1223,133 @@ end create_table "tag_follows", force: :cascade do |t| - t.bigint "tag_id", null: false t.bigint "account_id", null: false t.datetime "created_at", null: false + t.bigint "tag_id", null: false t.datetime "updated_at", null: false t.index ["account_id", "tag_id"], name: "index_tag_follows_on_account_id_and_tag_id", unique: true t.index ["tag_id"], name: "index_tag_follows_on_tag_id" end create_table "tag_trends", force: :cascade do |t| - t.bigint "tag_id", null: false - t.float "score", default: 0.0, null: false - t.integer "rank", default: 0, null: false t.boolean "allowed", default: false, null: false t.string "language", default: "", null: false + t.integer "rank", default: 0, null: false + t.float "score", default: 0.0, null: false + t.bigint "tag_id", null: false t.index ["tag_id", "language"], name: "index_tag_trends_on_tag_id_and_language", unique: true end create_table "tags", force: :cascade do |t| - t.string "name", default: "", null: false t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.boolean "usable" - t.boolean "trendable" - t.boolean "listable" - t.datetime "reviewed_at", precision: nil - t.datetime "requested_review_at", precision: nil + t.string "display_name" t.datetime "last_status_at", precision: nil + t.boolean "listable" t.float "max_score" t.datetime "max_score_at", precision: nil - t.string "display_name" + t.string "name", default: "", null: false + t.datetime "requested_review_at", precision: nil + t.datetime "reviewed_at", precision: nil + t.boolean "trendable" + t.datetime "updated_at", precision: nil, null: false + t.boolean "usable" t.index "lower((name)::text) text_pattern_ops", name: "index_tags_on_name_lower_btree", unique: true end create_table "terms_of_services", force: :cascade do |t| - t.text "text", default: "", null: false t.text "changelog", default: "", null: false - t.datetime "published_at" - t.datetime "notification_sent_at" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.date "effective_date" + t.datetime "notification_sent_at" + t.datetime "published_at" + t.text "text", default: "", null: false + t.datetime "updated_at", null: false t.index ["effective_date"], name: "index_terms_of_services_on_effective_date", unique: true, where: "(effective_date IS NOT NULL)" end create_table "tombstones", force: :cascade do |t| t.bigint "account_id", null: false - t.string "uri", null: false + t.boolean "by_moderator" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false - t.boolean "by_moderator" + t.string "uri", null: false t.index ["account_id"], name: "index_tombstones_on_account_id" t.index ["uri"], name: "index_tombstones_on_uri" end create_table "unavailable_domains", force: :cascade do |t| - t.string "domain", default: "", null: false t.datetime "created_at", precision: nil, null: false + t.string "domain", default: "", null: false t.datetime "updated_at", precision: nil, null: false t.index ["domain"], name: "index_unavailable_domains_on_domain", unique: true end create_table "user_invite_requests", force: :cascade do |t| - t.bigint "user_id", null: false - t.text "text" t.datetime "created_at", precision: nil, null: false + t.text "text" t.datetime "updated_at", precision: nil, null: false + t.bigint "user_id", null: false t.index ["user_id"], name: "index_user_invite_requests_on_user_id" end create_table "user_roles", force: :cascade do |t| - t.string "name", default: "", null: false t.string "color", default: "", null: false - t.integer "position", default: 0, null: false - t.bigint "permissions", default: 0, null: false - t.boolean "highlighted", default: false, null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.boolean "highlighted", default: false, null: false + t.string "name", default: "", null: false + t.bigint "permissions", default: 0, null: false + t.integer "position", default: 0, null: false t.boolean "require_2fa", default: false, null: false + t.datetime "updated_at", null: false end create_table "username_blocks", force: :cascade do |t| - t.string "username", null: false - t.string "normalized_username", null: false - t.boolean "exact", default: false, null: false t.boolean "allow_with_approval", default: false, null: false t.datetime "created_at", null: false + t.boolean "exact", default: false, null: false + t.string "normalized_username", null: false t.datetime "updated_at", null: false + t.string "username", null: false t.index "lower((username)::text)", name: "index_username_blocks_on_username_lower_btree", unique: true t.index ["normalized_username"], name: "index_username_blocks_on_normalized_username" end create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.integer "sign_in_count", default: 0, null: false - t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil + t.bigint "account_id", null: false + t.datetime "age_verified_at" + t.boolean "approved", default: true, null: false + t.string "chosen_languages", array: true + t.datetime "confirmation_sent_at", precision: nil t.string "confirmation_token" t.datetime "confirmed_at", precision: nil - t.datetime "confirmation_sent_at", precision: nil - t.string "unconfirmed_email" - t.string "locale" t.integer "consumed_timestep" - t.boolean "otp_required_for_login", default: false, null: false - t.datetime "last_emailed_at", precision: nil - t.string "otp_backup_codes", array: true - t.bigint "account_id", null: false + t.datetime "created_at", precision: nil, null: false + t.bigint "created_by_application_id" + t.datetime "current_sign_in_at", precision: nil t.boolean "disabled", default: false, null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.bigint "invite_id" - t.string "chosen_languages", array: true - t.bigint "created_by_application_id" - t.boolean "approved", default: true, null: false + t.datetime "last_emailed_at", precision: nil + t.datetime "last_sign_in_at", precision: nil + t.string "locale" + t.string "otp_backup_codes", array: true + t.boolean "otp_required_for_login", default: false, null: false + t.string "otp_secret" + t.boolean "require_tos_interstitial", default: false, null: false + t.datetime "reset_password_sent_at", precision: nil + t.string "reset_password_token" + t.bigint "role_id" + t.text "settings" + t.integer "sign_in_count", default: 0, null: false t.string "sign_in_token" t.datetime "sign_in_token_sent_at", precision: nil - t.string "webauthn_id" t.inet "sign_up_ip" t.boolean "skip_sign_in_token" - t.bigint "role_id" - t.text "settings" t.string "time_zone" - t.string "otp_secret" - t.datetime "age_verified_at" - t.boolean "require_tos_interstitial", default: false, null: false + t.string "unconfirmed_email" + t.datetime "updated_at", precision: nil, null: false + t.string "webauthn_id" t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)" @@ -1360,47 +1360,47 @@ end create_table "web_push_subscriptions", force: :cascade do |t| + t.bigint "access_token_id", null: false + t.datetime "created_at", precision: nil, null: false + t.json "data" t.string "endpoint", null: false - t.string "key_p256dh", null: false t.string "key_auth", null: false - t.json "data" - t.datetime "created_at", precision: nil, null: false + t.string "key_p256dh", null: false + t.boolean "standard", default: false, null: false t.datetime "updated_at", precision: nil, null: false - t.bigint "access_token_id", null: false t.bigint "user_id", null: false - t.boolean "standard", default: false, null: false t.index ["access_token_id"], name: "index_web_push_subscriptions_on_access_token_id", where: "(access_token_id IS NOT NULL)" t.index ["user_id"], name: "index_web_push_subscriptions_on_user_id" end create_table "web_settings", force: :cascade do |t| - t.json "data" t.datetime "created_at", precision: nil, null: false + t.json "data" t.datetime "updated_at", precision: nil, null: false t.bigint "user_id", null: false t.index ["user_id"], name: "index_web_settings_on_user_id", unique: true end create_table "webauthn_credentials", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false t.string "external_id", null: false - t.string "public_key", null: false t.string "nickname", null: false + t.string "public_key", null: false t.bigint "sign_count", default: 0, null: false - t.bigint "user_id" - t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.bigint "user_id" t.index ["external_id"], name: "index_webauthn_credentials_on_external_id", unique: true t.index ["user_id", "nickname"], name: "index_webauthn_credentials_on_user_id_and_nickname", unique: true end create_table "webhooks", force: :cascade do |t| - t.string "url", null: false + t.datetime "created_at", null: false + t.boolean "enabled", default: true, null: false t.string "events", default: [], null: false, array: true t.string "secret", default: "", null: false - t.boolean "enabled", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.text "template" + t.datetime "updated_at", null: false + t.string "url", null: false t.index ["url"], name: "index_webhooks_on_url", unique: true end @@ -1510,6 +1510,7 @@ add_foreign_key "polls", "statuses", on_delete: :cascade add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade add_foreign_key "preview_cards", "accounts", column: "author_account_id", on_delete: :nullify + add_foreign_key "preview_cards", "accounts", column: "unverified_author_account_id", on_delete: :nullify add_foreign_key "quotes", "accounts", column: "quoted_account_id", on_delete: :nullify add_foreign_key "quotes", "accounts", on_delete: :cascade add_foreign_key "quotes", "statuses", column: "quoted_status_id", on_delete: :nullify From 43b0113a4ad3690e930a585703199eb0613c0132 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 4 Mar 2026 10:33:25 -0500 Subject: [PATCH 67/89] Update ES versions in CI/devcontainer to match primary compose (#38041) --- .devcontainer/compose.yaml | 2 +- .github/workflows/test-ruby.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index 217415c528bf97..d303c5c3c59265 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -56,7 +56,7 @@ services: - internal_network es: - image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2 + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29 restart: unless-stopped environment: ES_JAVA_OPTS: -Xms512m -Xmx512m diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 97d7cf1800e054..240261760ebe22 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -352,10 +352,10 @@ jobs: - '3.3' - '.ruby-version' search-image: - - docker.elastic.co/elasticsearch/elasticsearch:7.17.13 + - docker.elastic.co/elasticsearch/elasticsearch:7.17.29 include: - ruby-version: '.ruby-version' - search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2 + search-image: docker.elastic.co/elasticsearch/elasticsearch:8.19.2 - ruby-version: '.ruby-version' search-image: opensearchproject/opensearch:2 From 3fbb7424fa106a647d812f1bfa256eb74b501a53 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 4 Mar 2026 17:13:45 +0100 Subject: [PATCH 68/89] Emoji text input and character counter components (#38052) --- .../character_counter.stories.tsx | 34 ++++ .../components/character_counter/index.tsx | 63 +++++++ .../character_counter/styles.module.scss | 8 + .../components/emoji/picker_button.tsx | 29 +++ .../form_fields/emoji_text_field.module.scss | 24 +++ .../form_fields/emoji_text_field.stories.tsx | 59 ++++++ .../form_fields/emoji_text_field.tsx | 174 ++++++++++++++++++ .../form_fields/form_field_wrapper.tsx | 16 +- .../form_fields/text_area_field.tsx | 30 +-- .../form_fields/text_input_field.tsx | 6 +- .../components/emoji_picker_dropdown.jsx | 5 +- app/javascript/mastodon/locales/en.json | 2 + eslint.config.mjs | 2 + 13 files changed, 434 insertions(+), 18 deletions(-) create mode 100644 app/javascript/mastodon/components/character_counter/character_counter.stories.tsx create mode 100644 app/javascript/mastodon/components/character_counter/index.tsx create mode 100644 app/javascript/mastodon/components/character_counter/styles.module.scss create mode 100644 app/javascript/mastodon/components/emoji/picker_button.tsx create mode 100644 app/javascript/mastodon/components/form_fields/emoji_text_field.module.scss create mode 100644 app/javascript/mastodon/components/form_fields/emoji_text_field.stories.tsx create mode 100644 app/javascript/mastodon/components/form_fields/emoji_text_field.tsx diff --git a/app/javascript/mastodon/components/character_counter/character_counter.stories.tsx b/app/javascript/mastodon/components/character_counter/character_counter.stories.tsx new file mode 100644 index 00000000000000..a37a74af45e93a --- /dev/null +++ b/app/javascript/mastodon/components/character_counter/character_counter.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { CharacterCounter } from './index'; + +const meta = { + component: CharacterCounter, + title: 'Components/CharacterCounter', +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Required: Story = { + args: { + currentString: 'Hello, world!', + maxLength: 100, + }, +}; + +export const ExceedingLimit: Story = { + args: { + currentString: 'Hello, world!', + maxLength: 10, + }, +}; + +export const Recommended: Story = { + args: { + currentString: 'Hello, world!', + maxLength: 10, + recommended: true, + }, +}; diff --git a/app/javascript/mastodon/components/character_counter/index.tsx b/app/javascript/mastodon/components/character_counter/index.tsx new file mode 100644 index 00000000000000..dce410a7c1336f --- /dev/null +++ b/app/javascript/mastodon/components/character_counter/index.tsx @@ -0,0 +1,63 @@ +import { useMemo } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { polymorphicForwardRef } from '@/types/polymorphic'; + +import classes from './styles.module.scss'; + +interface CharacterCounterProps { + currentString: string; + maxLength: number; + recommended?: boolean; +} + +const segmenter = new Intl.Segmenter(); + +export const CharacterCounter = polymorphicForwardRef< + 'span', + CharacterCounterProps +>( + ( + { + currentString, + maxLength, + as: Component = 'span', + recommended = false, + ...props + }, + ref, + ) => { + const currentLength = useMemo( + () => [...segmenter.segment(currentString)].length, + [currentString], + ); + return ( + maxLength && !recommended && classes.counterError, + )} + > + {recommended ? ( + + ) : ( + + )} + + ); + }, +); +CharacterCounter.displayName = 'CharCounter'; diff --git a/app/javascript/mastodon/components/character_counter/styles.module.scss b/app/javascript/mastodon/components/character_counter/styles.module.scss new file mode 100644 index 00000000000000..05c9446545ac02 --- /dev/null +++ b/app/javascript/mastodon/components/character_counter/styles.module.scss @@ -0,0 +1,8 @@ +.counter { + margin-top: 4px; + font-size: 13px; +} + +.counterError { + color: var(--color-text-error); +} diff --git a/app/javascript/mastodon/components/emoji/picker_button.tsx b/app/javascript/mastodon/components/emoji/picker_button.tsx new file mode 100644 index 00000000000000..6440ad34b55a4a --- /dev/null +++ b/app/javascript/mastodon/components/emoji/picker_button.tsx @@ -0,0 +1,29 @@ +import { useCallback } from 'react'; +import type { FC } from 'react'; + +import EmojiPickerDropdown from '@/mastodon/features/compose/containers/emoji_picker_dropdown_container'; + +export const EmojiPickerButton: FC<{ + onPick: (emoji: string) => void; + disabled?: boolean; +}> = ({ onPick, disabled }) => { + const handlePick = useCallback( + (emoji: unknown) => { + if (disabled) { + return; + } + if (typeof emoji === 'object' && emoji !== null) { + if ('native' in emoji && typeof emoji.native === 'string') { + onPick(emoji.native); + } else if ( + 'shortcode' in emoji && + typeof emoji.shortcode === 'string' + ) { + onPick(`:${emoji.shortcode}:`); + } + } + }, + [disabled, onPick], + ); + return ; +}; diff --git a/app/javascript/mastodon/components/form_fields/emoji_text_field.module.scss b/app/javascript/mastodon/components/form_fields/emoji_text_field.module.scss new file mode 100644 index 00000000000000..e214f9aed569a6 --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/emoji_text_field.module.scss @@ -0,0 +1,24 @@ +.fieldWrapper div:has(:global(.emoji-picker-dropdown)) { + position: relative; + + > input, + > textarea { + padding-inline-end: 36px; + } + + > textarea { + min-height: 40px; // Button size with 8px margin + } +} + +.fieldWrapper :global(.emoji-picker-dropdown) { + position: absolute; + top: 8px; + right: 8px; + height: 24px; + z-index: 1; + + :global(.icon-button) { + color: var(--color-text-secondary); + } +} diff --git a/app/javascript/mastodon/components/form_fields/emoji_text_field.stories.tsx b/app/javascript/mastodon/components/form_fields/emoji_text_field.stories.tsx new file mode 100644 index 00000000000000..aed04bbcbf6f7d --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/emoji_text_field.stories.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; + +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import type { EmojiInputProps } from './emoji_text_field'; +import { EmojiTextAreaField, EmojiTextInputField } from './emoji_text_field'; + +const meta = { + title: 'Components/Form Fields/EmojiTextInputField', + args: { + label: 'Label', + hint: 'Hint text', + value: 'Insert text with emoji', + }, + render({ value: initialValue = '', ...args }) { + const [value, setValue] = useState(initialValue); + return ; + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Simple: Story = {}; + +export const WithMaxLength: Story = { + args: { + maxLength: 20, + }, +}; + +export const WithRecommended: Story = { + args: { + maxLength: 20, + recommended: true, + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + }, +}; + +export const TextArea: Story = { + render(args) { + const [value, setValue] = useState('Insert text with emoji'); + return ( + + ); + }, +}; diff --git a/app/javascript/mastodon/components/form_fields/emoji_text_field.tsx b/app/javascript/mastodon/components/form_fields/emoji_text_field.tsx new file mode 100644 index 00000000000000..9c29bcbf9155f3 --- /dev/null +++ b/app/javascript/mastodon/components/form_fields/emoji_text_field.tsx @@ -0,0 +1,174 @@ +import type { + ChangeEvent, + ChangeEventHandler, + ComponentPropsWithoutRef, + Dispatch, + FC, + ReactNode, + RefObject, + SetStateAction, +} from 'react'; +import { useCallback, useId, useRef } from 'react'; + +import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils'; +import type { OmitUnion } from '@/mastodon/utils/types'; + +import { CharacterCounter } from '../character_counter'; +import { EmojiPickerButton } from '../emoji/picker_button'; + +import classes from './emoji_text_field.module.scss'; +import type { CommonFieldWrapperProps, InputProps } from './form_field_wrapper'; +import { FormFieldWrapper } from './form_field_wrapper'; +import { TextArea } from './text_area_field'; +import type { TextAreaProps } from './text_area_field'; +import { TextInput } from './text_input_field'; + +export type EmojiInputProps = { + value?: string; + onChange?: Dispatch>; + maxLength?: number; + recommended?: boolean; +} & Omit; + +export const EmojiTextInputField: FC< + OmitUnion, EmojiInputProps> +> = ({ + onChange, + value, + label, + hint, + hasError, + maxLength, + recommended, + disabled, + ...otherProps +}) => { + const inputRef = useRef(null); + + const wrapperProps = { + label, + hint, + hasError, + maxLength, + recommended, + disabled, + inputRef, + value, + onChange, + }; + + return ( + + {(inputProps) => ( + + )} + + ); +}; + +export const EmojiTextAreaField: FC< + OmitUnion, EmojiInputProps> +> = ({ + onChange, + value, + label, + maxLength, + recommended = false, + disabled, + hint, + hasError, + ...otherProps +}) => { + const textareaRef = useRef(null); + + const wrapperProps = { + label, + hint, + hasError, + maxLength, + recommended, + disabled, + inputRef: textareaRef, + value, + onChange, + }; + + return ( + + {(inputProps) => ( +