From 49db8a9662807ca8c28006b30db5f6627209655c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 3 Jul 2025 03:28:03 -0400 Subject: [PATCH 001/660] Use `Account#targeted_reports` association where needed (#35249) --- app/models/admin/account_action.rb | 2 +- app/models/form/account_batch.rb | 2 +- app/services/delete_account_service.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index 7c66b6e6462..4443d6343b6 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -185,7 +185,7 @@ class Admin::AccountAction @reports ||= if type == 'none' with_report? ? [report] : [] else - Report.where(target_account: target_account).unresolved + target_account.targeted_reports.unresolved end end diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb index 4665a586798..98e3be1a0c9 100644 --- a/app/models/form/account_batch.rb +++ b/app/models/form/account_batch.rb @@ -128,7 +128,7 @@ class Form::AccountBatch # Suspending a single account closes their associated reports, so # mass-suspending would be consistent. - Report.where(target_account: account).unresolved.find_each do |report| + account.targeted_reports.unresolved.find_each do |report| authorize(report, :update?) log_action(:resolve, report) report.resolve!(current_account) diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index 20d6ee8d64e..6557dda48fb 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -297,7 +297,7 @@ class DeleteAccountService < BaseService end def reported_status_ids - @reported_status_ids ||= Report.where(target_account: @account).unresolved.pluck(:status_ids).flatten.uniq + @reported_status_ids ||= @account.targeted_reports.unresolved.pluck(:status_ids).flatten.uniq end def associations_for_destruction From 6e39b5ef04adca8c28b700b30ca06bbdd737e03b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 3 Jul 2025 03:28:07 -0400 Subject: [PATCH 002/660] Use `ActiveModel::Attributes` for admin/account_action boolean values (#35247) --- app/models/admin/account_action.rb | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index 4443d6343b6..e43a50ae634 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -2,6 +2,7 @@ class Admin::AccountAction include ActiveModel::Model + include ActiveModel::Attributes include AccountableConcern include Authorization @@ -20,7 +21,10 @@ class Admin::AccountAction :report_id, :warning_preset_id - attr_reader :warning, :send_email_notification, :include_statuses + attr_reader :warning + + attribute :include_statuses, :boolean, default: true + attribute :send_email_notification, :boolean, default: true alias send_email_notification? send_email_notification alias include_statuses? include_statuses @@ -28,21 +32,6 @@ class Admin::AccountAction validates :type, :target_account, :current_account, presence: true validates :type, inclusion: { in: TYPES } - def initialize(attributes = {}) - @send_email_notification = true - @include_statuses = true - - super - end - - def send_email_notification=(value) - @send_email_notification = ActiveModel::Type::Boolean.new.cast(value) - end - - def include_statuses=(value) - @include_statuses = ActiveModel::Type::Boolean.new.cast(value) - end - def save! raise ActiveRecord::RecordInvalid, self unless valid? From 39028dde40b028e3d6de63d2d787337aacdd57ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 07:32:15 +0000 Subject: [PATCH 003/660] chore(deps): update dependency scenic to v1.9.0 (#35226) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b2eff8ad359..c59f26c44db 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -682,7 +682,7 @@ GEM activesupport (= 8.0.2) bundler (>= 1.15.0) railties (= 8.0.2) - rails-dom-testing (2.2.0) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) @@ -815,7 +815,7 @@ GEM sanitize (7.0.0) crass (~> 1.0.2) nokogiri (>= 1.16.8) - scenic (1.8.0) + scenic (1.9.0) activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) From 70cd1fdc6302d8527c8da9dd2c9ff324a7153a67 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:32:51 +0200 Subject: [PATCH 004/660] fix(deps): update dependency vite-plugin-pwa to v1.0.1 (#35223) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 46fb5b98d2f..a1aea84b5e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13774,8 +13774,8 @@ __metadata: linkType: hard "vite-plugin-pwa@npm:^1.0.0": - version: 1.0.0 - resolution: "vite-plugin-pwa@npm:1.0.0" + version: 1.0.1 + resolution: "vite-plugin-pwa@npm:1.0.1" dependencies: debug: "npm:^4.3.6" pretty-bytes: "npm:^6.1.1" @@ -13784,13 +13784,13 @@ __metadata: workbox-window: "npm:^7.3.0" peerDependencies: "@vite-pwa/assets-generator": ^1.0.0 - vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 workbox-build: ^7.3.0 workbox-window: ^7.3.0 peerDependenciesMeta: "@vite-pwa/assets-generator": optional: true - checksum: 10c0/284a7a0be50c6ffb742363767951efe76d751183c6b273b576d3d9ffa070e6d48d292f0a564e58dc6fc73cc1bdcdca6f076b7cd47e2e34ddfc28625eb937f3c7 + checksum: 10c0/ceca04df97877ca97eb30805207d4826bd6340796194c9015afeefeb781931bf9019a630c5a0bdaa6dffcada11ce1fdf8595ac48a08d751dff81601aa0c7db38 languageName: node linkType: hard From 3c0767f543dfdbb2fbb6a35f43f8ac5f7b545e2b Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 3 Jul 2025 09:51:12 +0200 Subject: [PATCH 005/660] fix: Remove focus highlight when status is clicked in light mode (#35251) --- app/javascript/styles/mastodon-light/diff.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 7e11af12477..874c44240a4 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -101,7 +101,7 @@ } // Change the background colors of statuses -.focusable:focus { +.focusable:focus-visible { background: lighten($white, 4%); } From c66c5fd73d602caa5cb48f591866a2e91309205a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 11:47:57 +0200 Subject: [PATCH 006/660] New Crowdin Translations (automated) (#35250) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 10 ++-- app/javascript/mastodon/locales/et.json | 10 ++-- app/javascript/mastodon/locales/fa.json | 20 +++++++ app/javascript/mastodon/locales/fr-CA.json | 15 +++-- app/javascript/mastodon/locales/fr.json | 15 +++-- app/javascript/mastodon/locales/ru.json | 2 +- app/javascript/mastodon/locales/tok.json | 2 + config/locales/et.yml | 70 +++++++++++++++++++--- config/locales/fa.yml | 15 +++++ config/locales/fr-CA.yml | 5 +- config/locales/fr.yml | 5 +- config/locales/ru.yml | 52 ++++++++-------- config/locales/simple_form.et.yml | 9 ++- 13 files changed, 171 insertions(+), 59 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index ea5fbad94a3..c28345b2605 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -208,9 +208,9 @@ "compose_form.publish": "Publicér", "compose_form.reply": "Svar", "compose_form.save_changes": "Opdatér", - "compose_form.spoiler.marked": "Fjern emnefelt", - "compose_form.spoiler.unmarked": "Tilføj emnefelt", - "compose_form.spoiler_placeholder": "Emnefelt (valgfrit)", + "compose_form.spoiler.marked": "Fjern indholdsadvarsel", + "compose_form.spoiler.unmarked": "Tilføj indholdsadvarsel", + "compose_form.spoiler_placeholder": "Indholdsadvarsel (valgfri)", "confirmation_modal.cancel": "Afbryd", "confirmations.block.confirm": "Blokér", "confirmations.delete.confirm": "Slet", @@ -492,9 +492,9 @@ "keyboard_shortcuts.reply": "Besvar indlægget", "keyboard_shortcuts.requests": "Åbn liste over følgeanmodninger", "keyboard_shortcuts.search": "Fokusér søgebjælke", - "keyboard_shortcuts.spoilers": "Vis/skjul emnefelt", + "keyboard_shortcuts.spoilers": "Vis/skjul indholdsadvarsel-felt", "keyboard_shortcuts.start": "Åbn \"komme i gang\"-kolonne", - "keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bag emnefelt", + "keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bag indholdsadvarsel", "keyboard_shortcuts.toggle_sensitivity": "Vis/skjul medier", "keyboard_shortcuts.toot": "Påbegynd nyt indlæg", "keyboard_shortcuts.translate": "for at oversætte et indlæg", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index b4b4c380cd4..ffb33c7e7a6 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -167,7 +167,7 @@ "column.domain_blocks": "Peidetud domeenid", "column.edit_list": "Muuda loendit", "column.favourites": "Lemmikud", - "column.firehose": "Laiv lõimed", + "column.firehose": "Postitused reaalajas", "column.follow_requests": "Jälgimistaotlused", "column.home": "Kodu", "column.list_members": "Halda loendi liikmeid", @@ -337,7 +337,7 @@ "errors.unexpected_crash.copy_stacktrace": "Kopeeri stacktrace lõikelauale", "errors.unexpected_crash.report_issue": "Teavita veast", "explore.suggested_follows": "Inimesed", - "explore.title": "Populaarsust kohuv", + "explore.title": "Populaarsust koguv", "explore.trending_links": "Uudised", "explore.trending_statuses": "Postitused", "explore.trending_tags": "Sildid", @@ -389,7 +389,7 @@ "followed_tags": "Jälgitavad märksõnad", "footer.about": "Teave", "footer.directory": "Profiilikataloog", - "footer.get_app": "Tõmba äpp", + "footer.get_app": "Laadi rakendus", "footer.keyboard_shortcuts": "Kiirklahvid", "footer.privacy_policy": "Isikuandmete kaitse", "footer.source_code": "Lähtekood", @@ -831,7 +831,7 @@ "search_results.statuses": "Postitused", "search_results.title": "Otsi märksõna: {q}", "server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 päeva jooksul (kuu aktiivsed kasutajad)", - "server_banner.active_users": "aktiivsed kasutajad", + "server_banner.active_users": "aktiivset kasutajaid", "server_banner.administered_by": "Administraator:", "server_banner.is_one_of_many": "{domain} on üks paljudest sõltumatutest Mastodoni serveritest, mida saab fediversumis osalemiseks kasutada.", "server_banner.server_stats": "Serveri statistika:", @@ -858,7 +858,7 @@ "status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}", "status.embed": "Hangi manustamiskood", "status.favourite": "Lemmik", - "status.favourites": "{count, plural, one {lemmik} other {lemmikud}}", + "status.favourites": "{count, plural, one {lemmik} other {lemmikut}}", "status.filter": "Filtreeri seda postitust", "status.history.created": "{name} lõi {date}", "status.history.edited": "{name} muutis {date}", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index e73d1625f6f..a6c556ac0aa 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -219,6 +219,13 @@ "confirmations.delete_list.confirm": "حذف", "confirmations.delete_list.message": "مطمئنید می‌خواهید این سیاهه را برای همیشه حذف کنید؟", "confirmations.delete_list.title": "حذف سیاهه؟", + "confirmations.discard_draft.confirm": "دور انداختن و ادامه", + "confirmations.discard_draft.edit.cancel": "ادامهٔ ویرایش", + "confirmations.discard_draft.edit.message": "ادامه دادن هر تغییری که روی فرستهٔ در حال ویرایش داده‌اید را دور خواهد ریخت.", + "confirmations.discard_draft.edit.title": "دور ریختن تغییرات فرسته‌تان؟", + "confirmations.discard_draft.post.cancel": "از سر گیری پیش‌نویس", + "confirmations.discard_draft.post.message": "ادامه دادن باعث دور ریخته شدن تغییرات روی فرسته‌ای که دارید می‌نویسید خواهد شد.", + "confirmations.discard_draft.post.title": "دور ریختن فرستهٔ پیش‌نویستان؟", "confirmations.discard_edit_media.confirm": "دور انداختن", "confirmations.discard_edit_media.message": "تغییرات ذخیره نشده‌ای در توضیحات یا پیش‌نمایش رسانه دارید. همگی نادیده گرفته شوند؟", "confirmations.follow_to_list.confirm": "پی‌گیری و افزودن به سیاهه", @@ -557,6 +564,8 @@ "navigation_bar.follows_and_followers": "پی‌گرفتگان و پی‌گیرندگان", "navigation_bar.import_export": "درون‌ریزی و برون‌بری", "navigation_bar.lists": "سیاهه‌ها", + "navigation_bar.live_feed_local": "خوراک زنده (محلی)", + "navigation_bar.live_feed_public": "خوراک زنده (عمومی)", "navigation_bar.logout": "خروج", "navigation_bar.moderation": "نظارت", "navigation_bar.more": "بیشتر", @@ -566,6 +575,10 @@ "navigation_bar.privacy_and_reach": "محرمانگی و دسترسی", "navigation_bar.search": "جست‌وجو", "navigation_bar.search_trends": "جستجو \\ پرطرفدار", + "navigation_panel.collapse_followed_tags": "جمع کردن فهرست برچسب‌های پی‌گرفته", + "navigation_panel.collapse_lists": "جمع کردن فهرست سیاهه", + "navigation_panel.expand_followed_tags": "گسترش فهرست برچسب‌های پی‌گرفته", + "navigation_panel.expand_lists": "گسترش فهرست سیاهه", "not_signed_in_indicator.not_signed_in": "برای دسترسی به این منبع باید وارد شوید.", "notification.admin.report": "{name}، {target} را گزارش داد", "notification.admin.report_account": "{name} {count, plural, one {یک پست} other {پست}} از {target} برای {category} را گزارش داد", @@ -792,6 +805,7 @@ "report_notification.categories.violation": "تخطّی از قانون", "report_notification.categories.violation_sentence": "تخطّی از قانون", "report_notification.open": "گشودن گزارش", + "search.clear": "پاک‌سازی جست‌وجو", "search.no_recent_searches": "جست‌وجوی اخیری نیست", "search.placeholder": "جست‌وجو", "search.quick_action.account_search": "نمایه‌های جور با {x}", @@ -858,6 +872,12 @@ "status.mute_conversation": "خموشاندن گفت‌وگو", "status.open": "گسترش این فرسته", "status.pin": "سنجاق به نمایه", + "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", + "status.quote_error.not_found": "این فرسته قابل نمایش نیست.", + "status.quote_error.pending_approval": "این فرسته منظر تأیید نگارندهٔ اصلی است.", + "status.quote_error.rejected": "از آن‌جا که نگارندهٔ اصلی فرسته اجازهٔ نقل قولش را نمی‌دهد این فرسته قابل نمایش نیست.", + "status.quote_error.removed": "این فرسته به دست نگارنده‌اش برداشته شده.", + "status.quote_error.unauthorized": "از آن‌جا که اجازهٔ دیدن این فرسته را ندارید قابل نمایش نیست.", "status.quote_post_author": "فرسته توسط {name}", "status.read_more": "بیشتر بخوانید", "status.reblog": "تقویت", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index cbe3ff33163..3a31a5af561 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -68,6 +68,7 @@ "account.open_original_page": "Ouvrir la page d'origine", "account.posts": "Publications", "account.posts_with_replies": "Publications et réponses", + "account.remove_from_followers": "Retirer {name} des suiveurs", "account.report": "Signaler @{name}", "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", @@ -137,8 +138,8 @@ "block_modal.title": "Bloquer le compte ?", "block_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour sauter ceci la prochaine fois", - "boost_modal.reblog": "Amplifier le message ?", - "boost_modal.undo_reblog": "Annuler l'amplification du message ?", + "boost_modal.reblog": "Booster le message ?", + "boost_modal.undo_reblog": "Annuler le boost du message ?", "bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur", "bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela pourrait être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.", "bundle_column_error.error.title": "Oh non!", @@ -218,8 +219,12 @@ "confirmations.delete_list.confirm": "Supprimer", "confirmations.delete_list.message": "Voulez-vous vraiment supprimer définitivement cette liste?", "confirmations.delete_list.title": "Supprimer la liste ?", - "confirmations.discard_draft.confirm": "Efface et continue", + "confirmations.discard_draft.confirm": "Effacer et continuer", "confirmations.discard_draft.edit.cancel": "Retour vers l'éditeur", + "confirmations.discard_draft.edit.title": "Jeter les changements faits au message?", + "confirmations.discard_draft.post.cancel": "Retour au brouillon", + "confirmations.discard_draft.post.message": "En continuant, vous perdez le message que vous êtes en train d'écrire.", + "confirmations.discard_draft.post.title": "Jeter le brouillon de message?", "confirmations.discard_edit_media.confirm": "Rejeter", "confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média, voulez-vous quand même les supprimer?", "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste", @@ -839,8 +844,8 @@ "status.mute_conversation": "Masquer la conversation", "status.open": "Afficher la publication entière", "status.pin": "Épingler sur profil", - "status.quote_error.removed": "Ce message a été retiré par son auteur.", - "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé à le voir.", + "status.quote_error.removed": "Ce message a été retiré par son auteur·ice.", + "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé·e à le voir.", "status.quote_post_author": "Message par {name}", "status.read_more": "En savoir plus", "status.reblog": "Booster", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index ca84ae4ff23..173cae18aea 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -68,6 +68,7 @@ "account.open_original_page": "Ouvrir la page d'origine", "account.posts": "Messages", "account.posts_with_replies": "Messages et réponses", + "account.remove_from_followers": "Retirer {name} des suiveurs", "account.report": "Signaler @{name}", "account.requested": "En attente d’approbation. Cliquez pour annuler la demande", "account.requested_follow": "{name} a demandé à vous suivre", @@ -137,8 +138,8 @@ "block_modal.title": "Bloquer le compte ?", "block_modal.you_wont_see_mentions": "Vous ne verrez pas les messages qui le mentionne.", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois", - "boost_modal.reblog": "Amplifier le message ?", - "boost_modal.undo_reblog": "Annuler l'amplification du message ?", + "boost_modal.reblog": "Booster le message ?", + "boost_modal.undo_reblog": "Annuler le boost du message ?", "bundle_column_error.copy_stacktrace": "Copier le rapport d'erreur", "bundle_column_error.error.body": "La page demandée n'a pas pu être affichée. Cela peut être dû à un bogue dans notre code, ou à un problème de compatibilité avec le navigateur.", "bundle_column_error.error.title": "Oh non !", @@ -218,8 +219,12 @@ "confirmations.delete_list.confirm": "Supprimer", "confirmations.delete_list.message": "Voulez-vous vraiment supprimer définitivement cette liste ?", "confirmations.delete_list.title": "Supprimer la liste ?", - "confirmations.discard_draft.confirm": "Efface et continue", + "confirmations.discard_draft.confirm": "Effacer et continuer", "confirmations.discard_draft.edit.cancel": "Retour vers l'éditeur", + "confirmations.discard_draft.edit.title": "Jeter les changements faits au message?", + "confirmations.discard_draft.post.cancel": "Retour au brouillon", + "confirmations.discard_draft.post.message": "En continuant, vous perdez le message que vous êtes en train d'écrire.", + "confirmations.discard_draft.post.title": "Jeter le brouillon de message?", "confirmations.discard_edit_media.confirm": "Rejeter", "confirmations.discard_edit_media.message": "Vous avez des modifications non enregistrées de la description ou de l'aperçu du média, les supprimer quand même ?", "confirmations.follow_to_list.confirm": "Suivre et ajouter à la liste", @@ -839,8 +844,8 @@ "status.mute_conversation": "Masquer la conversation", "status.open": "Afficher le message entier", "status.pin": "Épingler sur le profil", - "status.quote_error.removed": "Ce message a été retiré par son auteur.", - "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé à le voir.", + "status.quote_error.removed": "Ce message a été retiré par son auteur·ice.", + "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé·e à le voir.", "status.quote_post_author": "Message par {name}", "status.read_more": "En savoir plus", "status.reblog": "Partager", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index c777873db4b..66e5cf4760f 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -264,7 +264,7 @@ "directory.recently_active": "Недавно активные", "disabled_account_banner.account_settings": "Настройки учётной записи", "disabled_account_banner.text": "Ваша учётная запись {disabledAccount} в настоящее время отключена.", - "dismissable_banner.community_timeline": "Это самые новые публичные посты от тех пользователей, чьи учётные записи находятся на сервере {domain}.", + "dismissable_banner.community_timeline": "Это самые новые публичные посты от тех пользователей, чьи учётные записи размещены на сервере {domain}.", "dismissable_banner.dismiss": "Закрыть", "dismissable_banner.public_timeline": "Это самые новые публичные посты от всех тех людей в федивёрсе, на которых подписаны пользователи {domain}.", "domain_block_modal.block": "Заблокировать сервер", diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index 168c3d8e311..c48ffa5fe24 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -458,6 +458,8 @@ "navigation_bar.favourites": "ijo pona", "navigation_bar.filters": "nimi len", "navigation_bar.lists": "kulupu lipu", + "navigation_bar.logout": "o weka tan sijelo", + "navigation_bar.moderation": "nasin lawa", "navigation_bar.mutes": "sina wile ala kute e jan ni", "navigation_bar.preferences": "wile sina", "navigation_bar.search": "o alasa", diff --git a/config/locales/et.yml b/config/locales/et.yml index 9cf46415af8..dba31f4ffd7 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -187,6 +187,7 @@ et: create_domain_block: Domeeni blokeerimine create_email_domain_block: Loo e-posti domeeni blokeering create_ip_block: IP-reegli lisamine + create_relay: Loo sõnumivahendusserver create_unavailable_domain: Kättesaamatu domeeni lisamine create_user_role: Loo roll demote_user: Alandas kasutaja @@ -198,14 +199,17 @@ et: destroy_email_domain_block: Kustuta e-posti domeeni blokeering destroy_instance: Domeeni kustutamine destroy_ip_block: IP-reegli kustutamine + destroy_relay: Kustuta sõnumivahendusserver destroy_status: Kustuta postitus destroy_unavailable_domain: Kättesaamatu domeeni kustutamine destroy_user_role: Rolli kustutamine disable_2fa_user: Keela 2FA disable_custom_emoji: Keelas kohandatud emotikoni + disable_relay: Lülita sõnumivahendusserver välja disable_sign_in_token_auth_user: Keela e-posti võtme abil autentimine kasutajale disable_user: Keelas kasutaja enable_custom_emoji: Lubas kohandatud emotikoni + enable_relay: Lülita sõnumivahendusserver sisse enable_sign_in_token_auth_user: Luba e-posti võtme abil autentimine kasutajale enable_user: Lubas kasutaja memorialize_account: Igaveselt lahkunuks märkimine @@ -248,6 +252,7 @@ et: create_domain_block_html: "%{name} keelas domeeni %{target}" create_email_domain_block_html: "%{name} blokeeris e-posti domeeni %{target}" create_ip_block_html: "%{name} lõi IP-aadressile %{target} reegli" + create_relay_html: "%{name} lõi sõnumivahendusserveri: %{target}" create_unavailable_domain_html: "%{name} lõpetas edastamise domeeni %{target}" create_user_role_html: "%{name} lõi rolli %{target}" demote_user_html: "%{name} alandas kasutajat %{target}" @@ -259,14 +264,17 @@ et: destroy_email_domain_block_html: "%{name} eemaldas blokeeringu e-posti domeenilt %{target}" destroy_instance_html: "%{name} kustutas domeeni %{target}" destroy_ip_block_html: "%{name} kustutas IP-aadressi %{target} reegli" + destroy_relay_html: "%{name} kustutas sõnumivahendusserveri: %{target}" destroy_status_html: "%{name} kustutas %{target} postituse" destroy_unavailable_domain_html: "%{name} taastas edastamise domeeni %{target}" destroy_user_role_html: "%{name} kustutas %{target} rolli" disable_2fa_user_html: "%{name} eemaldas kasutaja %{target} kahe etapise nõude" disable_custom_emoji_html: "%{name} keelas emotikooni %{target}" + disable_relay_html: "%{name} eemaldas sõnumivahendusserveri kasutuselt: %{target}" disable_sign_in_token_auth_user_html: "%{name} keelas e-posti võtme abil autentimise %{target} jaoks" disable_user_html: "%{name} keelas %{target} sisenemise" enable_custom_emoji_html: "%{name} lubas emotikooni %{target}" + enable_relay_html: "%{name} võttis sõnumivahendusserveri kasutusele: %{target}" enable_sign_in_token_auth_user_html: "%{name} lubas e-posti võtme abil autentimise %{target} jaoks" enable_user_html: "%{name} lubas %{target} sisenemise" memorialize_account_html: "%{name} märkis %{target} igaveselt lahkunuks" @@ -354,7 +362,7 @@ et: updated_msg: Emotikoni uuendamine õnnestus! upload: Lae üles dashboard: - active_users: aktiivsed kasutajad + active_users: aktiivseid kasutajaid interactions: interaktsioonid media_storage: Meedia hoidla new_users: uued kasutajad @@ -469,9 +477,32 @@ et: title: Domeenikeeldude import no_file: Faili pole valitud fasp: + debug: + callbacks: + created_at: 'Loodud:' + delete: Kustuta + ip: IP-aadress + request_body: Päringu sisu providers: + active: Aktiivne + callback: Pöördliiklus + delete: Kustuta + edit: Muuda teenusepakkujat + finish_registration: Lõpeta registreerimine + name: Nimi + providers: Teenusepakkujad + public_key_fingerprint: Avaliku võtme sõrmejälg + registration_requested: Registreerimispäring + registrations: + confirm: Kinnita + description: Sa oled saanud FASP-i registreerimispäringu. Kui sa seda ise ei algatanud, siis keeldu. Kui algatasid, siis enne kinnitamist kontrolli nime ja võtme sõrmejälge. + reject: Keeldu + title: Kinnita FASP-i registreerimine + save: Salvesta sign_in: Logi sisse status: Olek + title: Täiendavad teenusepakkujad Födiversumis (FASP - Fediverse Auxiliary Service Providers) + title: FASP follow_recommendations: description_html: "Jälgimissoovitused aitavad uutel kasutajatel kiirelt leida huvipakkuvat sisu. Kui kasutaja pole teistega piisavalt läbi käinud, et saaks luua personaalseid soovitusi, soovitatakse neid kontosid. Need arvutatakse ümber igapäevaselt konkreetse keele populaarseimate postituste ja enim jälgitavate kontode seast." language: Keel @@ -540,6 +571,13 @@ et: all: Kõik limited: Piiratud title: Modereerimine + moderation_notes: + create: Lisa modereerimisteade + created_msg: Serveri modereerimisteate koostamine õnnestus! + description_html: Vaata ja lisa teade teistele moderaatoritele ning endalegi tulevikus + destroyed_msg: Serveri modereerimisteate kustutamine õnnestus! + placeholder: Teave selle koduserveri kohta, tehtud toimingud või mis iganes muu oluline, mis võimaldab sul või teistel serverit tulevikus modereerida. + title: Modereerimisteated private_comment: Privaatne kommentaar public_comment: Avalik kommentaar purge: Kustuta @@ -578,7 +616,7 @@ et: relationships: title: "%{acct}-i suhted" relays: - add_new: Lisa uus vahendaja + add_new: Lisa uus sõnumivahendusserver delete: Kustuta description_html: "Födereerumisvahendaja on vahendav server, mis kannab üle suures koguses avalikke postitusi nende serverite vahel, mis on sellega liitunud ja edastavad sellele oma postitusi. See aitab väikestel ja keskmistel serveritel avastada födiversumi sellist sisu, mis muidu eeldaks kohalikelt kasutajatelt nende serverite kasutajate jälgitavaks märkimist." disable: Keela @@ -586,13 +624,13 @@ et: enable: Luba enable_hint: Kui lubatud, siis server tellib sellelt vahendajalt kõik avalikud postitused ning hakkab ka enda avalikke postitusi sellele saatma. enabled: Lubatud - inbox_url: Vahendaja URL - pending: Ootab vahendaja nõusolekut + inbox_url: Sõnumivahendusserveri võrguaadress + pending: Ootan sõnumivahendusserveri kinnitust save_and_enable: Salvesta ja luba setup: Sea üles vahendav ühendus - signatures_not_enabled: Vahendamine ei tööta korrektselt kuniks turvarežiim või lubatud nimekirja režiim on sisse lülitatud + signatures_not_enabled: Vahendamine ei tööta korrektselt kui turvarežiim või lubatud nimekirja režiim on sisse lülitatud status: Olek - title: Vahendajad + title: Sõnumivahendusserverid report_notes: created_msg: Teade edukalt koostatud! destroyed_msg: Teade edukalt kustutatud! @@ -830,6 +868,7 @@ et: back_to_account: Tagasi konto lehele back_to_report: Tagasi raporti lehele batch: + add_to_report: 'Lisa teatisele #%{id}' remove_from_report: Eemalda raportist report: Raport contents: Sisu @@ -941,6 +980,13 @@ et: explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga. title: Teenuse tingimuste seadistamine history: Ajalugu + notify_users: Teata kasutajatele + preview: + explanation_html: 'See e-kiri saadetakse %{display_count}-le kasutajale, kes olid liitunud enne %{date}. E-kirjas sisaldub järgnev tekst:' + send_preview: Saada eelvaade %{email} e-posti aadressile + send_to_all: + one: Saada %{display_count} e-kiri + other: Saada %{display_count} e-kirja publish: Postita published_on_html: Postitatud %{date} title: Kasutustingimused @@ -1180,7 +1226,7 @@ et: new_confirmation_instructions_sent: Saad mõne minuti pärast uue kinnituslingiga e-kirja! title: Kontrolli sisendkasti sign_in: - preamble_html: Logi sisse oma kasutajakontoga serveris %{domain}. Kui konto asub teises serveris, siis sa ei saa siin sisse logida. + preamble_html: Logi sisse oma kasutajakontoga serverisse %{domain}. Kui konto asub teises serveris, siis sa ei saa siin sisse logida. title: Logi sisse serverisse %{domain} sign_up: manual_review: Liitumised kohas %{domain} vaadatakse meie moderaatorite poolt käsitsi läbi. Aitamaks meil sinu taotlust läbi vaadata, kirjuta palun natuke endast ja miks soovid kontot kohas %{domain}. @@ -1808,6 +1854,8 @@ et: reblog: Jagamist ei saa kinnitada quote_policies: followers: Jälgijad ja mainitud kasutajad + nobody: Vaid mainitud kasutajad + public: Kõik title: '%{name}: "%{quote}"' visibilities: direct: Otsene @@ -1863,6 +1911,11 @@ et: does_not_match_previous_name: ei ühti eelmise nimega terms_of_service: title: Kasutustingimused + terms_of_service_interstitial: + future_preamble_html: Alates %{date} muudame me oma kasutustingimusi. Palun vaata muutunud tingimused üle. + past_preamble_html: Peale sinu viimast külastust oleme muutnud oma kasutustingimusi. Palun vaata muutunud tingimused üle. + review_link: Vaata üle kasutustingimused + title: "%{domain} saidi kasutustingimused muutuvad" themes: contrast: Mastodon (Kõrge kontrast) default: Mastodon (Tume) @@ -1895,6 +1948,7 @@ et: webauthn: Turvavõtmed user_mailer: announcement_published: + description: "%{domain} saidi peakasutajate teadaanne:" subject: Saidi teadaanne teenuste kohta title: "%{domain} saidi teadaanne teenuste kohta" appeal_approved: @@ -1927,6 +1981,8 @@ et: subject: Kontole sisenemine uuelt IP-aadressilt title: Uus sisenemine terms_of_service_changed: + agreement: Jätkates %{domain} teenuse kasutamisega nõustud sa kasutustingimustega. Kui sa pole tingimustega nõus, siis võid oma kasutajakonto kustutamisega lepingu %{domain} saidiga alati lõpetada. + changelog: 'Selle uuenduse ülevaade sinu jaoks:' sign_off: "%{domain} saidi tiim" subject: Meie kasutustingimuste uuendused subtitle: "%{domain} saidi kasutustingimused muutuvad" diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 41f8b7febf0..b05a152dc2f 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -578,6 +578,13 @@ fa: all: همه limited: محدود title: مدیریت + moderation_notes: + create: افزودن یادداشت نظارتی + created_msg: یادداشت نظارتی با موفقیت ساخته شد! + description_html: دیدن و گذاشتن یادداشت‌هایی برای دیگر ناظران و خود آینده‌تان + destroyed_msg: یادداشت نظارتی با موفقیت حذف شد! + placeholder: اطلاعاتی دربارهٔ این نمونه، کنش‌های انجام شده یا هرچیز دیگری که در نظارت این نمونه در آینده کمک خواهد کرد. + title: یادداشت‌های نظارتی private_comment: یادداشت خصوصی public_comment: یادداشت عمومی purge: پاکسازی @@ -791,13 +798,21 @@ fa: description_html: در حالی که اکثر افراد ادعا می‌کنند که شرایط استفاده را خوانده و پذیرفته‌اند، افراد معمولا تا پیش از بروز مشکل این متن را مطالعه نمی‌کنند. پیدا کردن قوانین کارساز‌تان را با تبدیل‌شان به صورت فهرست برای کاربران تسهیل کنید. سعی کنید هر قانون را کوتاه و ساده نگاه دارید اما از آن طرف هم تلاش نکنید که آن‌ها به تعداد زیادی مورد جدا از هم تقسیم کنید. edit: ویرایش قانون empty: هنوز هیچ قانونی برای کارساز تعریف نشده. + move_down: پایین بردن + move_up: بالا بردن title: قوانین کارساز + translation: ترجمه + translations: ترجمه‌ها + translations_explanation: می‌توانید به صورت اختیاری نقش‌ها را ترجمه کنید. در صورت موجود نبودن نگارش ترجمه شده مقدار پیش‌گزیده نشان داده خواهد شد. لطفاً همواره مطمئن شوید که ترجمه‌ها با مقدار پیش‌گزیده هماهنگند. settings: about: manage_rules: مدیریت قانون‌های کارساز preamble: اطلاعات عمیقی در مورد نحوه کارکرد، تعدیل و تأمین مالی سرور ارائه دهید. rules_hint: یک منطقه اختصاصی برای قوانینی وجود دارد که انتظار می رود کاربران شما به آن پایبند باشند. title: درباره + allow_referrer_origin: + desc: ممکن است هنگام زدن کاربرانتان روی پیوند پایگاه‌های خارجی مرورگرشان نشانی کارساز ماستودونتان را به عنوان هدایتگر بفرستد. اگر این کار موجب شناسایی یکتای کاربرانتان می‌شود، برای نمونه در صورت خصوصی بودن کارسازتان از کار بیندازدش. + title: اجازه به پایگاه‌های خارجی برای دیدن کارساز‌ ماستودونتان به عنوان منبع شدآمد appearance: preamble: سفارشی‌سازی رابطس وب ماستودون. title: ظاهر diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 35f74331467..86c03aab7db 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -493,11 +493,14 @@ fr-CA: providers: Fournisseur registrations: confirm: Confirmer + description: Vous avez reçu une souscription d'un FSAF. Rejetez-la si vous ne l'avez pas initiée. Si c'est bien votre intention, comparez le nom et l'empreinte de la clé avant de confirmer la souscription. reject: Rejeter + title: Confirmer la souscription au FSAF save: Enregistrer select_capabilities: Sélectionnez les Capacités sign_in: Se connecter status: État + title: Fournisseurs de Services Auxiliaire du Fedivers follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue @@ -567,7 +570,7 @@ fr-CA: limited: Limité title: Modération moderation_notes: - title: Notes d'arbitrage + title: Notes de modération private_comment: Commentaire privé public_comment: Commentaire public purge: Purge diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c3af77f1ea3..bace64c2132 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -493,11 +493,14 @@ fr: providers: Fournisseur registrations: confirm: Confirmer + description: Vous avez reçu une souscription d'un FSAF. Rejetez-la si vous ne l'avez pas initiée. Si c'est bien votre intention, comparez le nom et l'empreinte de la clé avant de confirmer la souscription. reject: Rejeter + title: Confirmer la souscription au FSAF save: Enregistrer select_capabilities: Sélectionnez les Capacités sign_in: Se connecter status: État + title: Fournisseurs de Services Auxiliaire du Fedivers follow_recommendations: description_html: "Les recommandations d'abonnement aident les nouvelles personnes à trouver rapidement du contenu intéressant. Si un·e utilisateur·rice n'a pas assez interagi avec les autres pour avoir des recommandations personnalisées, ces comptes sont alors recommandés. La sélection est mise à jour quotidiennement depuis un mélange de comptes ayant le plus d'interactions récentes et le plus grand nombre d'abonné·e·s locaux pour une langue donnée." language: Pour la langue @@ -567,7 +570,7 @@ fr: limited: Limité title: Modération moderation_notes: - title: Notes d'arbitrage + title: Notes de modération private_comment: Commentaire privé public_comment: Commentaire public purge: Purge diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ac4f3542b14..7160bb0a2eb 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -401,7 +401,7 @@ ru: software: Программное обеспечение sources: Источники регистрации space: Используемое пространство - title: Дашборд + title: Обзор top_languages: Рейтинг языков по активности top_servers: Рейтинг серверов по активности website: Веб-сайт @@ -1236,49 +1236,49 @@ ru: rules: accept: Принять back: Назад - invited_by: 'Вы можете присоединиться к %{domain} благодаря приглашению полученному от:' - preamble: Они устанавливаются и применяются модераторами %{domain}. + invited_by: 'Вы можете зарегистрироваться на сервере %{domain}, потому что вы получили приглашение от:' + preamble: Модераторы сервера %{domain} установили эти правила и следят за их исполнением. preamble_invited: Прежде чем продолжить, ознакомьтесь с основными правилами, установленными модераторами сервера %{domain}. title: Несколько основных правил. - title_invited: Вы были приглашены. + title_invited: Вы получили приглашение. security: Безопасность - set_new_password: Задать новый пароль + set_new_password: Сменить пароль setup: - email_below_hint_html: Проверьте папку "Спам" или запросите другую. Вы можете исправить свой адрес электронной почты, если он неправильный. - email_settings_hint_html: Чтобы начать пользоваться Mastodon, пройдите по ссылке, которую мы отправили на %{email}. А мы пока подождём тут. - link_not_received: Не получили ссылку? + email_below_hint_html: Проверьте папку «Спам» или нажмите на кнопку ниже, чтобы выслать письмо повторно. Вы можете исправить свой адрес электронной почты, если вы ввели его неверно. + email_settings_hint_html: Чтобы начать пользоваться Mastodon, перейдите по ссылке, которую мы отправили на адрес %{email}. + link_not_received: Не приходит письмо? new_confirmation_instructions_sent: Через несколько минут вы получите новое письмо со ссылкой для подтверждения! - title: Проверьте свой почтовый ящик + title: Проверьте свою почту sign_in: - preamble_html: Войдите, используя ваши учётные данные %{domain}. Если ваша учётная запись размещена на другом сервере, вы не сможете здесь войти. - title: Войти в %{domain} + preamble_html: Введите здесь данные своей учётной записи на сервере %{domain}, чтобы войти. Вы не сможете войти, если ваша учётная запись размещена на другом сервере. + title: Авторизация на %{domain} sign_up: - manual_review: Регистрация на %{domain} проходит через ручную проверку нашими модераторами. Чтобы помочь нам обработать вашу регистрацию, напишите немного о себе и о том, почему вы хотите получить аккаунт на %{domain}. - preamble: С учётной записью на этом сервере Mastodon вы сможете подписываться на всех других людей в федиверсе вне зависимости от того, где находятся их учётные записи. - title: Зарегистрируйтесь в %{domain}. + manual_review: На сервере %{domain} все заявки на регистрацию проверяются модераторами вручную. Чтобы помочь нам принять решение в отношении вашей заявки, напишите немного о себе и о том, почему вы хотите создать учётную запись на сервере %{domain}. + preamble: С учётной записью на этом сервере Mastodon вы сможете подписываться на всех других людей в федивёрсе вне зависимости от того, где размещены их учётные записи. + title: Создайте учётную запись на сервере %{domain}. status: - account_status: Статус учётной записи - confirming: Жду подтверждения по электронной почте. + account_status: Состояние учётной записи + confirming: Ожидание подтверждения e-mail. functional: Ваша учётная запись в полном порядке. - pending: Ваша заявка находится на рассмотрении у наших сотрудников. Это может занять некоторое время. Вы получите электронное письмо, если ваша заявка будет одобрена. + pending: Ваша заявка ожидает одобрения администраторами, это может занять немного времени. Вы получите письмо, как только заявку одобрят. redirecting_to: Ваша учётная запись деактивированна, потому что вы настроили перенаправление на %{acct}. self_destruct: Поскольку %{domain} закрывается, вы получите ограниченный доступ к вашей учетной записи. view_strikes: Просмотр предыдущих замечаний в адрес вашей учетной записи too_fast: Форма отправлена слишком быстро, попробуйте еще раз. use_security_key: Использовать ключ безопасности - user_agreement_html: Мной прочитаны и принятыпользовательское соглашение и политика конфиденциальности + user_agreement_html: Мной прочитаны и приняты пользовательское соглашение и политика конфиденциальности user_privacy_agreement_html: Мной прочитана и принята политика конфиденциальности author_attribution: - example_title: Образец текста - hint_html: Публикуете ли вы свои статьи где-либо ещё кроме Mastodon? Если да, то ваше авторство может быть упомянуто, когда ими делятся в Mastodon. - instructions: 'Добавьте код ниже в HTML ваших статей:' - more_from_html: Больше от %{name} - s_blog: "%{name}'S Блог" - then_instructions: Затем добавьте доменное имя сайта, где вы публикуетесь, в поле ниже. - title: Авторская атрибуция + example_title: Пример текста + hint_html: Вы пишете статьи для новостных сайтов или ведёте блог где-либо ещё помимо Mastodon? Ваше авторство может быть упомянуто всякий раз, когда вашими статьями делятся в Mastodon. + instructions: 'Добавьте следующий код в HTML-разметку ваших статей:' + more_from_html: 'Автор: %{name}' + s_blog: "%{name} ведёт блог" + then_instructions: Затем впишите доменное имя сайта, где вы публикуетесь, в поле ниже. + title: Упоминание авторства challenge: confirm: Продолжить - hint_html: "Подсказка: мы не будем спрашивать пароль повторно в течение часа." + hint_html: "Подсказка: В течение часа вам не придётся снова вводить свой пароль." invalid_password: Неверный пароль prompt: Введите пароль для продолжения crypto: diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index f1a0b40bdd1..f013f454caa 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -19,7 +19,7 @@ et: title: Valikuline. Ei ole nähtav saajale admin_account_action: include_statuses: Kasutaja näeb, millised postitused on põhjustanud moderaatori otsuse või hoiatuse - send_email_notification: Konto omanik saab selgituse selle kohta, mis juhtus nende kontoga + send_email_notification: Kasutajakonto omanik saab selgituse selle kohta, mis juhtus tema kontoga text_html: Valikuline. On võimalik kasutada postituse süntaksi. On võimalik lisada hoiatuste eelseadistusi säästmaks aega type_html: Vali, mida teha kasutajaga %{acct} types: @@ -47,7 +47,7 @@ et: digest: Saadetakse ainult pärast pikka tegevusetuse perioodi ja ainult siis, kui on saadetud otsesõnumeid email: Sulle saadetakse e-posti teel kinnituskiri header: WEBP, PNG, GIF või JPG. Kõige rohkem %{size}. Vähendatakse %{dimensions} pikslini - inbox_url: Kopeeri soovitud vahendaja avalehe URL + inbox_url: Kopeeri soovitud sõnumivahendusserveri avalehe võrguaadress irreversible: Filtreeritud postitused kaovad taastamatult, isegi kui filter on hiljem eemaldatud locale: Kasutajaliidese, e-kirjade ja tõuketeadete keel password: Vajalik on vähemalt 8 märki @@ -196,7 +196,7 @@ et: fields: Veebiviited header: Päis honeypot: "%{label} (ära sisesta)" - inbox_url: Vahendaja sisendkausta URL + inbox_url: Sõnumivahendusserveri sisendkausta võrguaadress irreversible: Kustuta selle asemel, et peita locale: Kasutajaliidese keel max_uses: Maksimum kasutajate arv @@ -212,6 +212,7 @@ et: setting_boost_modal: Näita enne jagamist kinnitusdialoogi setting_default_language: Postituse keel setting_default_privacy: Postituse nähtavus + setting_default_quote_policy: Kes võivad tsiteerida setting_default_sensitive: Alati märgista meedia tundlikuks setting_delete_modal: Näita kinnitusdialoogi enne postituse kustutamist setting_disable_hover_cards: Keela profiili eelvaade kui hõljutada @@ -224,6 +225,7 @@ et: setting_hide_network: Peida oma võrk setting_reduce_motion: Vähenda animatsioonides liikumist setting_system_font_ui: Kasuta süsteemi vaikefonti + setting_system_scrollbars_ui: Kasuta süsteemi vaikimisi kerimisriba setting_theme: Saidi teema setting_trends: Näita tänaseid trende setting_unfollow_modal: Näita kinnitusdialoogi enne jälgimise eemaldamist @@ -242,6 +244,7 @@ et: name: Silt filters: actions: + blur: Peida hoiatusega meedia hide: Peida täielikult warn: Peida hoiatusega form_admin_settings: From e97f43399be6443d6c2fb1dfc959e40ca0795414 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 3 Jul 2025 16:42:48 +0200 Subject: [PATCH 007/660] Fix error handling for blank actions in account moderation action form (#35246) --- .../admin/account_actions_controller.rb | 20 +++++++++++-------- app/models/admin/account_action.rb | 10 ++++++++-- spec/system/admin/account_actions_spec.rb | 7 +++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin/account_actions_controller.rb b/app/controllers/admin/account_actions_controller.rb index 91849811e36..3cfd1e17617 100644 --- a/app/controllers/admin/account_actions_controller.rb +++ b/app/controllers/admin/account_actions_controller.rb @@ -14,16 +14,20 @@ module Admin def create authorize @account, :show? - account_action = Admin::AccountAction.new(resource_params) - account_action.target_account = @account - account_action.current_account = current_account + @account_action = Admin::AccountAction.new(resource_params) + @account_action.target_account = @account + @account_action.current_account = current_account - account_action.save! - - if account_action.with_report? - redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id]) + if @account_action.save + if @account_action.with_report? + redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id]) + else + redirect_to admin_account_path(@account.id) + end else - redirect_to admin_account_path(@account.id) + @warning_presets = AccountWarningPreset.all + + render :new end end diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index e43a50ae634..3cde3c7f8e4 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -32,8 +32,8 @@ class Admin::AccountAction validates :type, :target_account, :current_account, presence: true validates :type, inclusion: { in: TYPES } - def save! - raise ActiveRecord::RecordInvalid, self unless valid? + def save + return false unless valid? ApplicationRecord.transaction do process_action! @@ -43,6 +43,12 @@ class Admin::AccountAction process_notification! process_queue! + + true + end + + def save! + raise ActiveRecord::RecordInvalid, self unless save end def report diff --git a/spec/system/admin/account_actions_spec.rb b/spec/system/admin/account_actions_spec.rb index 787b988a0dc..abfd33dc272 100644 --- a/spec/system/admin/account_actions_spec.rb +++ b/spec/system/admin/account_actions_spec.rb @@ -15,6 +15,13 @@ RSpec.describe 'Admin Account Actions' do expect(page) .to have_title(I18n.t('admin.account_actions.title', acct: account.pretty_acct)) + # Invalid submission + expect { submit_form } + .to_not(change { account.strikes.count }) + expect(page) + .to have_content(/can't be blank/) + + # Valid submission choose(option: 'silence') expect { submit_form } .to change { account.strikes.count }.by(1) From 2af17adc34cda13ad305e51c9d787d4202ca0eec Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 3 Jul 2025 10:43:36 -0400 Subject: [PATCH 008/660] Use `ActiveModel::Attributes` in admin/status_batch_action (#35255) --- app/models/admin/status_batch_action.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index 4a100019351..4d0ee130382 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -2,6 +2,7 @@ class Admin::StatusBatchAction include ActiveModel::Model + include ActiveModel::Attributes include AccountableConcern include Authorization @@ -9,11 +10,7 @@ class Admin::StatusBatchAction :status_ids, :report_id, :text - attr_reader :send_email_notification - - def send_email_notification=(value) - @send_email_notification = ActiveModel::Type::Boolean.new.cast(value) - end + attribute :send_email_notification, :boolean def save! process_action! From e8d2432e6acbb385765e1595915e6b9905ad93e9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 3 Jul 2025 12:28:47 -0400 Subject: [PATCH 009/660] Fix intermittent failure of TOS model spec from effective date collision (#35244) --- spec/models/terms_of_service_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/models/terms_of_service_spec.rb b/spec/models/terms_of_service_spec.rb index 7e68c4eac7c..16cd5dd2ebf 100644 --- a/spec/models/terms_of_service_spec.rb +++ b/spec/models/terms_of_service_spec.rb @@ -44,22 +44,24 @@ RSpec.describe TermsOfService do end describe '.live' do - let!(:not_effective) { Fabricate :terms_of_service } + # The `pre_effective_date` record captures a period before the value was tracked + # The update in the `before` block creates an invalid (but historically plausible) record + let!(:pre_effective_date) { travel_to(10.days.ago) { Fabricate :terms_of_service, effective_date: Time.zone.today } } let!(:effective_past) { travel_to(3.days.ago) { Fabricate :terms_of_service, effective_date: Time.zone.today } } let!(:effective_future) { Fabricate :terms_of_service, effective_date: 3.days.from_now } - before { not_effective.update_attribute(:effective_date, nil) } + before { pre_effective_date.update_attribute(:effective_date, nil) } it 'returns records without effective or with past effective' do expect(described_class.live) - .to include(not_effective) + .to include(pre_effective_date) .and include(effective_past) .and not_include(effective_future) end end describe '.upcoming' do - let!(:unpublished) { Fabricate :terms_of_service, published_at: nil } + let!(:unpublished) { Fabricate :terms_of_service, published_at: nil, effective_date: 10.days.from_now } let!(:effective_past) { travel_to(3.days.ago) { Fabricate :terms_of_service, effective_date: Time.zone.today } } let!(:effective_future_near) { Fabricate :terms_of_service, effective_date: 3.days.from_now } let!(:effective_future_far) { Fabricate :terms_of_service, effective_date: 5.days.from_now } From 5a7c0d42f7f9a6c5dc8579f480395678298f6333 Mon Sep 17 00:00:00 2001 From: Andy Piper Date: Thu, 3 Jul 2025 21:51:32 +0100 Subject: [PATCH 010/660] Add specific language confirming that we test with BrowserStack and Chromatic (#35248) Signed-off-by: Andy Piper --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 552940fbea7..12027289659 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub] - [Redis](https://redis.io/) and [Sidekiq](https://sidekiq.org/) are used for caching and queueing. - [Node.js](https://nodejs.org/) powers the streaming API. - [React.js](https://reactjs.org/) and [Redux](https://redux.js.org/) are used for the dynamic parts of the interface. -- [BrowserStack](https://www.browserstack.com/) supports testing on real devices and browsers. -- [Chromatic](https://www.chromatic.com/) provides visual regression testing. +- [BrowserStack](https://www.browserstack.com/) supports testing on real devices and browsers. (This project is tested with BrowserStack) +- [Chromatic](https://www.chromatic.com/) provides visual regression testing. (This project is tested with Chromatic) ### Requirements From 1fa72d6c4419ee9b209553a1add13ef3da4e318d Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 4 Jul 2025 09:25:42 +0200 Subject: [PATCH 011/660] Raise better exception on FASP error responses (#35262) --- app/lib/fasp/request.rb | 2 ++ spec/lib/fasp/request_spec.rb | 24 +++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/lib/fasp/request.rb b/app/lib/fasp/request.rb index 6ea837b89cc..2002e90bb06 100644 --- a/app/lib/fasp/request.rb +++ b/app/lib/fasp/request.rb @@ -49,6 +49,8 @@ class Fasp::Request end def validate!(response) + raise Mastodon::UnexpectedResponseError, response if response.code >= 400 + content_digest_header = response.headers['content-digest'] raise Mastodon::SignatureVerificationError, 'content-digest missing' if content_digest_header.blank? raise Mastodon::SignatureVerificationError, 'content-digest does not match' if content_digest_header != content_digest(response.body) diff --git a/spec/lib/fasp/request_spec.rb b/spec/lib/fasp/request_spec.rb index 80d061dc61f..9b354c8f44b 100644 --- a/spec/lib/fasp/request_spec.rb +++ b/spec/lib/fasp/request_spec.rb @@ -32,13 +32,27 @@ RSpec.describe Fasp::Request do context 'when the response is not signed' do before do stub_request(method, 'https://reqprov.example.com/fasp/test_path') - .to_return(status: 200) + .to_return(status:) end - it 'raises an error' do - expect do - subject.send(method, '/test_path') - end.to raise_error(Mastodon::SignatureVerificationError) + context 'when the request was successful' do + let(:status) { 200 } + + it 'raises a signature verification error' do + expect do + subject.send(method, '/test_path') + end.to raise_error(Mastodon::SignatureVerificationError) + end + end + + context 'when an error response is received' do + let(:status) { 401 } + + it 'raises an unexpected response error' do + expect do + subject.send(method, '/test_path') + end.to raise_error(Mastodon::UnexpectedResponseError) + end end end end From 83660ee3812516773cb307502acbe6bdab4723f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 07:34:54 +0000 Subject: [PATCH 012/660] New Crowdin Translations (automated) (#35261) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/el.json | 3 ++ app/javascript/mastodon/locales/eu.json | 35 ++++++++++++++++++++++ app/javascript/mastodon/locales/fr-CA.json | 22 ++++++++++++++ app/javascript/mastodon/locales/fr.json | 22 ++++++++++++++ config/locales/da.yml | 8 ++--- 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index ee6de69d41a..4a6de095a8d 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -219,6 +219,8 @@ "confirmations.delete_list.confirm": "Διαγραφή", "confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;", "confirmations.delete_list.title": "Διαγραφή λίστας;", + "confirmations.discard_draft.confirm": "Απόρριψη και συνέχεια", + "confirmations.discard_draft.edit.cancel": "Συνέχιση επεξεργασίας", "confirmations.discard_edit_media.confirm": "Απόρριψη", "confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;", "confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα", @@ -796,6 +798,7 @@ "report_notification.categories.violation": "Παραβίαση κανόνα", "report_notification.categories.violation_sentence": "παραβίαση κανόνα", "report_notification.open": "Ανοιχτή αναφορά", + "search.clear": "Εκκαθάριση αναζήτησης", "search.no_recent_searches": "Καμία πρόσφατη αναζήτηση", "search.placeholder": "Αναζήτηση", "search.quick_action.account_search": "Προφίλ που ταιριάζουν με {x}", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index f3528024c9a..3b2662d6af6 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -1,6 +1,7 @@ { "about.blocks": "Moderatutako zerbitzariak", "about.contact": "Kontaktua:", + "about.default_locale": "Lehenetsia", "about.disclaimer": "Mastodon software libre eta kode irekikoa da, eta Mastodon gGmbH-ren marka erregistratua.", "about.domain_blocks.no_reason_available": "Arrazoia ez dago eskuragarri", "about.domain_blocks.preamble": "Mastodonek orokorrean aukera ematen dizu fedibertsoko beste zerbitzarietako erabiltzaileen edukia ikusi eta haiekin komunikatzeko. Zerbitzari zehatz honi ezarritako salbuespenak hauek dira.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Mugatua", "about.domain_blocks.suspended.explanation": "Ez da zerbitzari honetako daturik prozesatuko, gordeko, edo partekatuko, zerbitzari honetako erabiltzaileekin komunikatzea ezinezkoa eginez.", "about.domain_blocks.suspended.title": "Kanporatua", + "about.language_label": "Hizkuntza", "about.not_available": "Zerbitzari honek ez du informazio hau eskuragarri jarri.", "about.powered_by": "{mastodon} erabiltzen duen sare sozial deszentralizatua", "about.rules": "Zerbitzariaren arauak", @@ -19,13 +21,20 @@ "account.block_domain": "Blokeatu {domain} domeinua", "account.block_short": "Blokeatu", "account.blocked": "Blokeatuta", + "account.blocking": "Eragotzitakoak", "account.cancel_follow_request": "Baztertu jarraitzeko eskaera", "account.copy": "Kopiatu profilerako esteka", "account.direct": "Aipatu pribatuki @{name}", "account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean", + "account.domain_blocking": "Eragotzitako domeinua", "account.edit_profile": "Editatu profila", "account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean", "account.endorse": "Nabarmendu profilean", + "account.familiar_followers_one": "{name1}-k jarraitzen du", + "account.familiar_followers_two": "{name1}-k eta {name2}-k jarraitzen dute", + "account.featured": "Gailenak", + "account.featured.accounts": "Profilak", + "account.featured.hashtags": "Traolak", "account.featured_tags.last_status_at": "Azken bidalketa {date} datan", "account.featured_tags.last_status_never": "Bidalketarik ez", "account.follow": "Jarraitu", @@ -33,9 +42,11 @@ "account.followers": "Jarraitzaileak", "account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.", "account.followers_counter": "{count, plural, one {{counter} jarraitzaile} other {{counter} jarraitzaile}}", + "account.followers_you_know_counter": "{counter} ezagutzen dituzu", "account.following": "Jarraitzen", "account.following_counter": "{count, plural, one {{counter} jarraitzen} other {{counter} jarraitzen}}", "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.", + "account.follows_you": "Jarraitzen zaitu", "account.go_to_profile": "Joan profilera", "account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak", "account.in_memoriam": "Oroimenezkoa.", @@ -50,18 +61,23 @@ "account.mute_notifications_short": "Mututu jakinarazpenak", "account.mute_short": "Mututu", "account.muted": "Mutututa", + "account.muting": "Isilarazitakoak", + "account.mutual": "Elkar jarraitzen duzue", "account.no_bio": "Ez da deskribapenik eman.", "account.open_original_page": "Ireki jatorrizko orria", "account.posts": "Bidalketa", "account.posts_with_replies": "Bidalketak eta erantzunak", + "account.remove_from_followers": "Kendu {name} zure jarraitzaileengandik", "account.report": "Salatu @{name}", "account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko", "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", + "account.requests_to_follow_you": "Zu jarraitzeko eskaera egin du", "account.share": "Partekatu @{name} erabiltzailearen profila", "account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak", "account.statuses_counter": "{count, plural, one {{counter} bidalketa} other {{counter} bidalketa}}", "account.unblock": "Desblokeatu @{name}", "account.unblock_domain": "Berriz erakutsi {domain}", + "account.unblock_domain_short": "Desblokeatu", "account.unblock_short": "Desblokeatu", "account.unendorse": "Ez nabarmendu profilean", "account.unfollow": "Utzi jarraitzeari", @@ -87,10 +103,13 @@ "alt_text_modal.add_text_from_image": "Gehitu testua iruditik", "alt_text_modal.cancel": "Utzi", "alt_text_modal.change_thumbnail": "Aldatu koadro txikia", + "alt_text_modal.describe_for_people_with_hearing_impairments": "Deskribatu hau entzumen arazoak dituzten pertsonentzat…", + "alt_text_modal.describe_for_people_with_visual_impairments": "Deskribatu hau ikusmen arazoak dituzten pertsonentzat…", "alt_text_modal.done": "Egina", "announcement.announcement": "Iragarpena", "annual_report.summary.followers.followers": "jarraitzaileak", "annual_report.summary.followers.total": "{count} guztira", + "annual_report.summary.here_it_is": "Hona hemen zure {year}. urtearen bilduma:", "annual_report.summary.highlighted_post.by_favourites": "egindako bidalketa gogokoena", "annual_report.summary.highlighted_post.by_reblogs": "egindako bidalketa zabalduena", "annual_report.summary.highlighted_post.by_replies": "erantzun gehien izan dituen bidalketa", @@ -192,6 +211,12 @@ "confirmations.delete_list.confirm": "Ezabatu", "confirmations.delete_list.message": "Ziur behin betiko ezabatu nahi duzula zerrenda hau?", "confirmations.delete_list.title": "Ezabatu zerrenda?", + "confirmations.discard_draft.confirm": "Baztertu eta jarraitu", + "confirmations.discard_draft.edit.cancel": "Berrekin edizioari", + "confirmations.discard_draft.edit.message": "Jarraitzeak editatzen ari zaren mezuan egindako aldaketak baztertuko ditu.", + "confirmations.discard_draft.edit.title": "Baztertu zure argitalpenari egindako aldaketak?", + "confirmations.discard_draft.post.cancel": "Zirriborroa berrekin", + "confirmations.discard_draft.post.title": "Zure argitalpenaren zirriborroa baztertu nahi duzu?", "confirmations.discard_edit_media.confirm": "Baztertu", "confirmations.discard_edit_media.message": "Multimediaren deskribapen edo aurrebistan gorde gabeko aldaketak daude, baztertu nahi dituzu?", "confirmations.follow_to_list.confirm": "Jarraitu eta zerrendan sartu", @@ -201,12 +226,16 @@ "confirmations.logout.message": "Ziur saioa amaitu nahi duzula?", "confirmations.logout.title": "Itxi saioa?", "confirmations.missing_alt_text.confirm": "Gehitu testu alternatiboa", + "confirmations.missing_alt_text.message": "Zure argitalpenak \"alt text\"-urik gabeko multimedia edukia dauka. Deskribapenak gehitzeak zure edukia jende gehiagorentzat eskuragarri jartzen laguntzen du.", "confirmations.missing_alt_text.secondary": "Bidali edonola ere", "confirmations.missing_alt_text.title": "Testu alternatiboa gehitu?", "confirmations.mute.confirm": "Mututu", "confirmations.redraft.confirm": "Ezabatu eta berridatzi", "confirmations.redraft.message": "Ziur argitalpen hau ezabatu eta zirriborroa berriro egitea nahi duzula? Gogokoak eta bultzadak galduko dira, eta jatorrizko argitalpenaren erantzunak zurtz geratuko dira.", "confirmations.redraft.title": "Ezabatu eta berridatzi bidalketa?", + "confirmations.remove_from_followers.confirm": "Jarraitzailea Kendu", + "confirmations.remove_from_followers.message": "{name}-k zu jarraitzeari utziko dio. Seguru zaude jarraitu nahi duzula?", + "confirmations.remove_from_followers.title": "Jarraitzailea kendu nahi duzu?", "confirmations.unfollow.confirm": "Utzi jarraitzeari", "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?", "confirmations.unfollow.title": "Erabiltzailea jarraitzeari utzi?", @@ -228,12 +257,14 @@ "disabled_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan.", "dismissable_banner.community_timeline": "Hauek dira {domain} zerbitzarian ostatatutako kontuen bidalketa publiko berrienak.", "dismissable_banner.dismiss": "Baztertu", + "dismissable_banner.public_timeline": "Hauek dira {domain}-eko jendeak jarraitzen dituen fedibertsoko erabiltzaileen azken mezu publikoak.", "domain_block_modal.block": "Blokeatu zerbitzaria", "domain_block_modal.block_account_instead": "Blokeatu @{name} bestela", "domain_block_modal.they_can_interact_with_old_posts": "Zerbitzari honetako jendea zure argitalpen zaharrekin elkarreragin dezake.", "domain_block_modal.they_cant_follow": "Zerbitzari honetako inork ezin zaitu jarraitu.", "domain_block_modal.they_wont_know": "Ez dute jakingo blokeatuak izan direnik.", "domain_block_modal.title": "Domeinua blokeatu nahi duzu?", + "domain_block_modal.you_will_lose_relationships": "Instantzia honetatik jarraitzen dituzun jarraitzaile eta pertsona guztiak galduko dituzu.", "domain_block_modal.you_wont_see_posts": "Ez dituzu zerbitzari honetako erabiltzaileen argitalpenik edota jakinarazpenik ikusiko.", "domain_pill.activitypub_lets_connect": "Mastodon-en ez ezik, beste sare sozialen aplikazioetako jendearekin konektatzea eta harremanetan jartzea uzten dizu.", "domain_pill.activitypub_like_language": "ActivityPub, Mastodon-ek beste sare sozialekin hitz egiteko erabiltzen duen hizkuntza bezalakoxea da.", @@ -265,6 +296,9 @@ "emoji_button.search_results": "Bilaketaren emaitzak", "emoji_button.symbols": "Sinboloak", "emoji_button.travel": "Bidaiak eta tokiak", + "empty_column.account_featured.me": "Oraindik ez duzu ezer nabarmendu. Ba al zenekien gehien erabiltzen dituzun traolak eta baita zure lagunen kontuak ere zure profilean nabarmendu ditzakezula?", + "empty_column.account_featured.other": "{acct}-ek ez du ezer nabarmendu oraindik. Ba al zenekien gehien erabiltzen dituzun traolak eta baita zure lagunen kontuak ere zure profilean nabarmendu ditzakezula?", + "empty_column.account_featured_other.unknown": "Kontu honek ez du ezer nabarmendu oraindik.", "empty_column.account_hides_collections": "Erabiltzaile honek informazio hau erabilgarri ez egotea aukeratu du.", "empty_column.account_suspended": "Kanporatutako kontua", "empty_column.account_timeline": "Ez dago bidalketarik hemen!", @@ -296,6 +330,7 @@ "explore.trending_links": "Berriak", "explore.trending_statuses": "Tutak", "explore.trending_tags": "Traolak", + "featured_carousel.previous": "Aurrekoa", "filter_modal.added.context_mismatch_explanation": "Iragazki-kategoria hau ez zaio aplikatzen bidalketa honetara sartzeko erabili duzun testuinguruari. Bidalketa testuinguru horretan ere iragaztea nahi baduzu, iragazkia editatu beharko duzu.", "filter_modal.added.context_mismatch_title": "Testuingurua ez dator bat!", "filter_modal.added.expired_explanation": "Iragazki kategoria hau iraungi da, eragina izan dezan bere iraungitze-data aldatu beharko duzu.", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 3a31a5af561..0fdd593859a 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -221,6 +221,7 @@ "confirmations.delete_list.title": "Supprimer la liste ?", "confirmations.discard_draft.confirm": "Effacer et continuer", "confirmations.discard_draft.edit.cancel": "Retour vers l'éditeur", + "confirmations.discard_draft.edit.message": "Continued va perdre les changements que vous avez faits dans le message courant.", "confirmations.discard_draft.edit.title": "Jeter les changements faits au message?", "confirmations.discard_draft.post.cancel": "Retour au brouillon", "confirmations.discard_draft.post.message": "En continuant, vous perdez le message que vous êtes en train d'écrire.", @@ -241,6 +242,9 @@ "confirmations.redraft.confirm": "Supprimer et réécrire", "confirmations.redraft.message": "Êtes-vous sûr·e de vouloir effacer cette publication pour la réécrire? Ses ses mises en favori et boosts seront perdus et ses réponses seront orphelines.", "confirmations.redraft.title": "Supprimer et réécrire le message ?", + "confirmations.remove_from_followers.confirm": "Supprimer l'abonné·e", + "confirmations.remove_from_followers.message": "{name} cessera de vous suivre. Êtes-vous sûr de vouloir continuer ?", + "confirmations.remove_from_followers.title": "Supprimer l'abonné·e ?", "confirmations.unfollow.confirm": "Ne plus suivre", "confirmations.unfollow.message": "Voulez-vous vraiment arrêter de suivre {name}?", "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", @@ -302,6 +306,9 @@ "emoji_button.search_results": "Résultats", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", + "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.", "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations", "empty_column.account_suspended": "Compte suspendu", "empty_column.account_timeline": "Aucune publication ici!", @@ -330,6 +337,7 @@ "errors.unexpected_crash.copy_stacktrace": "Copier la trace d'appels dans le presse-papier", "errors.unexpected_crash.report_issue": "Signaler un problème", "explore.suggested_follows": "Personnes", + "explore.title": "Tendances", "explore.trending_links": "Nouvelles", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", @@ -389,6 +397,8 @@ "generic.saved": "Sauvegardé", "getting_started.heading": "Pour commencer", "hashtag.admin_moderation": "Ouvrir l'interface de modération pour #{name}", + "hashtag.browse": "Parcourir les posts dans #{hashtag}", + "hashtag.browse_from_account": "Parcourir les posts de @{name} dans #{hashtag}", "hashtag.column_header.tag_mode.all": "et {additional}", "hashtag.column_header.tag_mode.any": "ou {additional}", "hashtag.column_header.tag_mode.none": "sans {additional}", @@ -401,7 +411,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} message} other {{counter} messages}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} message} other {{counter} messages}} aujourd’hui", + "hashtag.feature": "Mettre en avant sur votre profil", "hashtag.follow": "Suivre ce hashtag", + "hashtag.mute": "Mettre #{hashtag} en sourdine", + "hashtag.unfeature": "Ne plus mettre en avant sur votre profil", "hashtag.unfollow": "Ne plus suivre ce hashtag", "hashtags.and_other": "…et {count, plural, other {# de plus}}", "hints.profiles.followers_may_be_missing": "Les abonné·e·s à ce profil peuvent être manquant·e·s.", @@ -412,6 +425,7 @@ "hints.profiles.see_more_posts": "Voir plus de messages sur {domain}", "hints.threads.replies_may_be_missing": "Les réponses provenant des autres serveurs pourraient être manquantes.", "hints.threads.see_more": "Afficher plus de réponses sur {domain}", + "home.column_settings.show_quotes": "Afficher les citations", "home.column_settings.show_reblogs": "Afficher boosts", "home.column_settings.show_replies": "Afficher réponses", "home.hide_announcements": "Masquer les annonces", @@ -534,8 +548,10 @@ "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.", "mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.", "navigation_bar.about": "À propos", + "navigation_bar.account_settings": "Mot de passe et sécurité", "navigation_bar.administration": "Administration", "navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée", + "navigation_bar.automated_deletion": "Suppression automatique du message", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.bookmarks": "Signets", "navigation_bar.direct": "Mention privée", @@ -545,13 +561,19 @@ "navigation_bar.follow_requests": "Demandes d'abonnements", "navigation_bar.followed_tags": "Hashtags suivis", "navigation_bar.follows_and_followers": "Abonnements et abonnés", + "navigation_bar.import_export": "Import et export", "navigation_bar.lists": "Listes", + "navigation_bar.live_feed_local": "Flux en direct (local)", + "navigation_bar.live_feed_public": "Flux en direct (public)", "navigation_bar.logout": "Se déconnecter", "navigation_bar.moderation": "Modération", + "navigation_bar.more": "Plus", "navigation_bar.mutes": "Utilisateurs masqués", "navigation_bar.opened_in_classic_interface": "Les messages, les comptes et les pages spécifiques sont ouvertes dans l’interface classique.", "navigation_bar.preferences": "Préférences", + "navigation_bar.privacy_and_reach": "Vie privée et visibilité", "navigation_bar.search": "Rechercher", + "navigation_bar.search_trends": "Recherche / Tendance", "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}", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 173cae18aea..6f1bbae61c1 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -221,6 +221,7 @@ "confirmations.delete_list.title": "Supprimer la liste ?", "confirmations.discard_draft.confirm": "Effacer et continuer", "confirmations.discard_draft.edit.cancel": "Retour vers l'éditeur", + "confirmations.discard_draft.edit.message": "Continued va perdre les changements que vous avez faits dans le message courant.", "confirmations.discard_draft.edit.title": "Jeter les changements faits au message?", "confirmations.discard_draft.post.cancel": "Retour au brouillon", "confirmations.discard_draft.post.message": "En continuant, vous perdez le message que vous êtes en train d'écrire.", @@ -241,6 +242,9 @@ "confirmations.redraft.confirm": "Supprimer et ré-écrire", "confirmations.redraft.message": "Voulez-vous vraiment supprimer le message pour le réécrire ? Ses partages ainsi que ses mises en favori seront perdues, et ses réponses seront orphelines.", "confirmations.redraft.title": "Supprimer et réécrire le message ?", + "confirmations.remove_from_followers.confirm": "Supprimer l'abonné·e", + "confirmations.remove_from_followers.message": "{name} cessera de vous suivre. Êtes-vous sûr de vouloir continuer ?", + "confirmations.remove_from_followers.title": "Supprimer l'abonné·e ?", "confirmations.unfollow.confirm": "Ne plus suivre", "confirmations.unfollow.message": "Voulez-vous vraiment vous désabonner de {name} ?", "confirmations.unfollow.title": "Se désabonner de l'utilisateur·rice ?", @@ -302,6 +306,9 @@ "emoji_button.search_results": "Résultats de la recherche", "emoji_button.symbols": "Symboles", "emoji_button.travel": "Voyage et lieux", + "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.", "empty_column.account_hides_collections": "Cet utilisateur·ice préfère ne pas rendre publiques ces informations", "empty_column.account_suspended": "Compte suspendu", "empty_column.account_timeline": "Aucun message ici !", @@ -330,6 +337,7 @@ "errors.unexpected_crash.copy_stacktrace": "Copier la trace d'appels dans le presse-papier", "errors.unexpected_crash.report_issue": "Signaler le problème", "explore.suggested_follows": "Personnes", + "explore.title": "Tendances", "explore.trending_links": "Nouvelles", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", @@ -389,6 +397,8 @@ "generic.saved": "Sauvegardé", "getting_started.heading": "Pour commencer", "hashtag.admin_moderation": "Ouvrir l'interface de modération pour #{name}", + "hashtag.browse": "Parcourir les posts dans #{hashtag}", + "hashtag.browse_from_account": "Parcourir les posts de @{name} dans #{hashtag}", "hashtag.column_header.tag_mode.all": "et {additional}", "hashtag.column_header.tag_mode.any": "ou {additional}", "hashtag.column_header.tag_mode.none": "sans {additional}", @@ -401,7 +411,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} message} other {{counter} messages}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} message} other {{counter} messages}} aujourd’hui", + "hashtag.feature": "Mettre en avant sur votre profil", "hashtag.follow": "Suivre le hashtag", + "hashtag.mute": "Mettre #{hashtag} en sourdine", + "hashtag.unfeature": "Ne plus mettre en avant sur votre profil", "hashtag.unfollow": "Ne plus suivre le hashtag", "hashtags.and_other": "…et {count, plural, other {# de plus}}", "hints.profiles.followers_may_be_missing": "Les abonné·e·s à ce profil peuvent être manquant·e·s.", @@ -412,6 +425,7 @@ "hints.profiles.see_more_posts": "Voir plus de messages sur {domain}", "hints.threads.replies_may_be_missing": "Les réponses provenant des autres serveurs pourraient être manquantes.", "hints.threads.see_more": "Afficher plus de réponses sur {domain}", + "home.column_settings.show_quotes": "Afficher les citations", "home.column_settings.show_reblogs": "Afficher les partages", "home.column_settings.show_replies": "Afficher les réponses", "home.hide_announcements": "Masquer les annonces", @@ -534,8 +548,10 @@ "mute_modal.you_wont_see_mentions": "Vous ne verrez pas les publications qui le mentionne.", "mute_modal.you_wont_see_posts": "Il peut toujours voir vos publications, mais vous ne verrez pas les siennes.", "navigation_bar.about": "À propos", + "navigation_bar.account_settings": "Mot de passe et sécurité", "navigation_bar.administration": "Administration", "navigation_bar.advanced_interface": "Ouvrir dans l’interface avancée", + "navigation_bar.automated_deletion": "Suppression automatique du message", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.bookmarks": "Marque-pages", "navigation_bar.direct": "Mention privée", @@ -545,13 +561,19 @@ "navigation_bar.follow_requests": "Demandes d’abonnement", "navigation_bar.followed_tags": "Hashtags suivis", "navigation_bar.follows_and_followers": "Abonnements et abonnés", + "navigation_bar.import_export": "Import et export", "navigation_bar.lists": "Listes", + "navigation_bar.live_feed_local": "Flux en direct (local)", + "navigation_bar.live_feed_public": "Flux en direct (public)", "navigation_bar.logout": "Déconnexion", "navigation_bar.moderation": "Modération", + "navigation_bar.more": "Plus", "navigation_bar.mutes": "Comptes masqués", "navigation_bar.opened_in_classic_interface": "Les messages, les comptes et les pages spécifiques sont ouvertes dans l’interface classique.", "navigation_bar.preferences": "Préférences", + "navigation_bar.privacy_and_reach": "Vie privée et visibilité", "navigation_bar.search": "Rechercher", + "navigation_bar.search_trends": "Recherche / Tendance", "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}", diff --git a/config/locales/da.yml b/config/locales/da.yml index 189ddea40d4..ca5f1831c9f 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1905,9 +1905,9 @@ da: keep_polls: Behold afstemninger keep_polls_hint: Sletter ingen af dine afstemninger keep_self_bookmark: Behold bogmærkede indlæg - keep_self_bookmark_hint: Sletter ikke egne indlæg, såfremt de er bogmærket + keep_self_bookmark_hint: Sletter ikke dine egne indlæg, såfremt de er bogmærket keep_self_fav: Behold favoritmarkerede indlæg - keep_self_fav_hint: Sletter ikke egne indlæg, såfremt de er favoritmarkeret + keep_self_fav_hint: Sletter ikke dine egne indlæg, såfremt de er favoritmarkeret min_age: '1209600': 2 uger '15778476': 6 måneder @@ -1919,9 +1919,9 @@ da: '7889238': 3 måneder min_age_label: Alderstærskel min_favs: Behold indlæg favoritmarkeret mindst - min_favs_hint: Sletter ingen egne indlæg, som har modtaget minimum dette antal favoritmarkeringer. Lad stå tomt for at slette indlæg uanset favoritmarkeringer + min_favs_hint: Sletter ingen dine egne indlæg, som har modtaget minimum dette antal favoritmarkeringer. Lad stå tomt for at slette indlæg uanset antal favoritmarkeringer min_reblogs: Behold indlæg fremhævet mindst - min_reblogs_hint: Sletter ingen af egne indlæg, som er fremhævet flere end dette antal gange. Lad stå tomt for at ignorere denne tærskel under sletning + min_reblogs_hint: Sletter ingen af dine egne indlæg, som er fremhævet flere end dette antal gange. Lad stå tom for at slette indlæg uanset antallet af fremhævelser stream_entries: sensitive_content: Sensitivt indhold strikes: From 1ebb87a6a819dc4c2d3772994c268dd90a6ef674 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Jul 2025 09:51:01 +0200 Subject: [PATCH 013/660] Fix incorrect name in scheduler configuration (#35263) --- config/sidekiq.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/sidekiq.yml b/config/sidekiq.yml index 97eaa55ce3b..fb85343d942 100644 --- a/config/sidekiq.yml +++ b/config/sidekiq.yml @@ -70,5 +70,5 @@ queue: scheduler fasp_follow_recommendation_cleanup_scheduler: interval: 1 day - class: Scheduler::Fasp::FollowRecommendationsScheduler + class: Scheduler::Fasp::FollowRecommendationCleanupScheduler queue: scheduler From bdffdcb12f092b3a104194015d461f87ab78fe5b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 7 Jul 2025 04:07:01 -0400 Subject: [PATCH 014/660] Remove unused scopes in `Account` model (#35276) --- app/models/account.rb | 3 --- spec/models/account_spec.rb | 13 ------------- 2 files changed, 16 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index c4e54756728..c23549c22d9 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -138,10 +138,7 @@ class Account < ApplicationRecord scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :without_instance_actor, -> { where.not(id: INSTANCE_ACTOR_ID) } scope :recent, -> { reorder(id: :desc) } - scope :bots, -> { where(actor_type: AUTOMATED_ACTOR_TYPES) } scope :non_automated, -> { where.not(actor_type: AUTOMATED_ACTOR_TYPES) } - scope :groups, -> { where(actor_type: 'Group') } - scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :matches_uri_prefix, ->(value) { where(arel_table[:uri].matches("#{sanitize_sql_like(value)}/%", false, true)).or(where(uri: value)) } scope :matches_username, ->(value) { where('lower((username)::text) LIKE lower(?)', "#{value}%") } scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) } diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 994a475dd9d..8ce337f1c74 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -640,19 +640,6 @@ RSpec.describe Account do end end - describe 'alphabetic' do - it 'sorts by alphabetic order of domain and username' do - matches = [ - { username: 'a', domain: 'a' }, - { username: 'b', domain: 'a' }, - { username: 'a', domain: 'b' }, - { username: 'b', domain: 'b' }, - ].map(&method(:Fabricate).curry(2).call(:account)) - - expect(described_class.without_internal.alphabetic).to eq matches - end - end - describe 'matches_display_name' do it 'matches display name which starts with the given string' do match = Fabricate(:account, display_name: 'pattern and suffix') From f6b2609353cddae944c6624db512d1daaa5a47ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:21:51 +0200 Subject: [PATCH 015/660] New Crowdin Translations (automated) (#35269) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ar.json | 2 +- app/javascript/mastodon/locales/bg.json | 34 +++++++++++ app/javascript/mastodon/locales/da.json | 34 +++++------ app/javascript/mastodon/locales/eu.json | 49 +++++++++++++++- app/javascript/mastodon/locales/pt-BR.json | 7 +++ app/javascript/mastodon/locales/ru.json | 2 +- app/javascript/mastodon/locales/sk.json | 1 + app/javascript/mastodon/locales/th.json | 28 ++++++++- config/locales/activerecord.eu.yml | 6 ++ config/locales/bg.yml | 22 +++++++ config/locales/da.yml | 20 +++---- config/locales/devise.vi.yml | 12 ++-- config/locales/doorkeeper.da.yml | 6 +- config/locales/eu.yml | 67 ++++++++++++++++++++++ config/locales/nan.yml | 62 ++++++++++++++++++++ config/locales/pt-BR.yml | 2 + config/locales/simple_form.eu.yml | 9 +++ config/locales/simple_form.vi.yml | 4 +- config/locales/th.yml | 12 ++++ config/locales/vi.yml | 28 ++++----- 20 files changed, 351 insertions(+), 56 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 92c269fc3dd..813a92761d3 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -1,7 +1,7 @@ { "about.blocks": "خوادم تحت الإشراف", "about.contact": "للاتصال:", - "about.default_locale": "الافتراضية", + "about.default_locale": "افتراضيالافتراضية", "about.disclaimer": "ماستدون برنامج حر ومفتوح المصدر وعلامة تجارية لـ Mastodon GmbH.", "about.domain_blocks.no_reason_available": "السبب غير متوفر", "about.domain_blocks.preamble": "يتيح مَستُدون عمومًا لمستخدميه مطالعة المحتوى من المستخدمين من الخواديم الأخرى في الفدرالية والتفاعل معهم. وهذه هي الاستثناءات التي وضعت على هذا الخادوم.", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 80d881c2f12..61951ee2362 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -8,6 +8,7 @@ "about.domain_blocks.silenced.title": "Ограничено", "about.domain_blocks.suspended.explanation": "Никакви данни от този сървър няма да се обработват, съхраняват или обменят, правещи невъзможно всяко взаимодействие или комуникация с потребители от тези сървъри.", "about.domain_blocks.suspended.title": "Спряно", + "about.language_label": "Език", "about.not_available": "Тази информация не е публична на този сървър.", "about.powered_by": "Децентрализирана социална мрежа, захранвана от {mastodon}", "about.rules": "Правила на сървъра", @@ -28,6 +29,7 @@ "account.edit_profile": "Редактиране на профила", "account.enable_notifications": "Известяване при публикуване от @{name}", "account.endorse": "Представи в профила", + "account.familiar_followers_many": "Последвано от {name1}, {name2}, и {othersCount, plural, one {един друг, когото познавате} other {# други, които познавате}}", "account.familiar_followers_one": "Последвано от {name1}", "account.familiar_followers_two": "Последвано от {name1} и {name2}", "account.featured": "Препоръчано", @@ -40,6 +42,7 @@ "account.followers": "Последователи", "account.followers.empty": "Още никой не следва потребителя.", "account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}", + "account.followers_you_know_counter": "{counter} познавате", "account.following": "Последвано", "account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}", "account.follows.empty": "Потребителят още никого не следва.", @@ -215,6 +218,13 @@ "confirmations.delete_list.confirm": "Изтриване", "confirmations.delete_list.message": "Наистина ли искате да изтриете завинаги списъка?", "confirmations.delete_list.title": "Изтривате ли списъка?", + "confirmations.discard_draft.confirm": "Изхвърляне и продължаване", + "confirmations.discard_draft.edit.cancel": "Възобновяване на редактиране", + "confirmations.discard_draft.edit.message": "Продължаването ще изхвърли всякакви промени, които сте правили в публикацията, която сега редактирате.", + "confirmations.discard_draft.edit.title": "Изхвърляте ли промените в публикацията си?", + "confirmations.discard_draft.post.cancel": "Възстановка на чернова", + "confirmations.discard_draft.post.message": "Продължаването ще изхвърли публикацията, която сега съставяте.", + "confirmations.discard_draft.post.title": "Изхвърляте ли черновата си публикация?", "confirmations.discard_edit_media.confirm": "Отхвърляне", "confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?", "confirmations.follow_to_list.confirm": "Последване и добавяне в списък", @@ -324,9 +334,15 @@ "errors.unexpected_crash.copy_stacktrace": "Копиране на трасето на стека в буферната памет", "errors.unexpected_crash.report_issue": "Сигнал за проблем", "explore.suggested_follows": "Хора", + "explore.title": "Вървежно", "explore.trending_links": "Новини", "explore.trending_statuses": "Публикации", "explore.trending_tags": "Хаштагове", + "featured_carousel.header": "{count, plural, one {закачена публикация} other {закачени публикации}}", + "featured_carousel.next": "Напред", + "featured_carousel.post": "Публикация", + "featured_carousel.previous": "Назад", + "featured_carousel.slide": "{index} от {total}", "filter_modal.added.context_mismatch_explanation": "Тази категория филтър не е приложима към контекста, в който достъпвате тази публикация. Ако желаете да филтрирате публикациите в този контекст, трябва да изберете друг филтър.", "filter_modal.added.context_mismatch_title": "Несъвпадащ контекст!", "filter_modal.added.expired_explanation": "Валидността на тази категория филтър е изтекла. Сменете срока на валидност, за да я приложите.", @@ -405,6 +421,7 @@ "hints.profiles.see_more_posts": "Преглед на още публикации на {domain}", "hints.threads.replies_may_be_missing": "Отговори от други сървъри може да липсват.", "hints.threads.see_more": "Преглед на още отговори на {domain}", + "home.column_settings.show_quotes": "Показване на цитираното", "home.column_settings.show_reblogs": "Показване на подсилванията", "home.column_settings.show_replies": "Показване на отговорите", "home.hide_announcements": "Скриване на оповестяванията", @@ -527,8 +544,10 @@ "mute_modal.you_wont_see_mentions": "Няма да виждате споменаващите ги публикации.", "mute_modal.you_wont_see_posts": "Още могат да виждат публикациите ви, но вие техните не.", "navigation_bar.about": "Относно", + "navigation_bar.account_settings": "Парола и сигурност", "navigation_bar.administration": "Администрация", "navigation_bar.advanced_interface": "Отваряне в разширен уебинтерфейс", + "navigation_bar.automated_deletion": "Автоматично изтриване на публикации", "navigation_bar.blocks": "Блокирани потребители", "navigation_bar.bookmarks": "Отметки", "navigation_bar.direct": "Частни споменавания", @@ -538,13 +557,17 @@ "navigation_bar.follow_requests": "Заявки за последване", "navigation_bar.followed_tags": "Последвани хаштагове", "navigation_bar.follows_and_followers": "Последвания и последователи", + "navigation_bar.import_export": "Внасяне и изнасяне", "navigation_bar.lists": "Списъци", "navigation_bar.logout": "Излизане", "navigation_bar.moderation": "Модериране", + "navigation_bar.more": "Още", "navigation_bar.mutes": "Заглушени потребители", "navigation_bar.opened_in_classic_interface": "Публикации, акаунти и други особени страници се отварят по подразбиране в класическия мрежови интерфейс.", "navigation_bar.preferences": "Предпочитания", + "navigation_bar.privacy_and_reach": "Поверителност и обхват", "navigation_bar.search": "Търсене", + "navigation_bar.search_trends": "Търсене / Вървежно", "not_signed_in_indicator.not_signed_in": "Трябва ви вход за достъп до ресурса.", "notification.admin.report": "{name} докладва {target}", "notification.admin.report_account": "{name} докладва {count, plural, one {публикация} other {# публикации}} от {target} за {category}", @@ -771,6 +794,7 @@ "report_notification.categories.violation": "Нарушение на правилото", "report_notification.categories.violation_sentence": "нарушение на правило", "report_notification.open": "Отваряне на доклада", + "search.clear": "Изчистване на търсенето", "search.no_recent_searches": "Няма скорошни търсения", "search.placeholder": "Търсене", "search.quick_action.account_search": "Съвпадение на профили {x}", @@ -837,6 +861,13 @@ "status.mute_conversation": "Заглушаване на разговора", "status.open": "Разширяване на публикацията", "status.pin": "Закачане в профила", + "status.quote_error.filtered": "Скрито поради един от филтрите ви", + "status.quote_error.not_found": "Публикацията не може да се показва.", + "status.quote_error.pending_approval": "Публикацията чака одобрение от първоначалния автор.", + "status.quote_error.rejected": "Публикацията не може да се показва като първоначалния автор не позволява цитирането ѝ.", + "status.quote_error.removed": "Публикацията е премахната от автора ѝ.", + "status.quote_error.unauthorized": "Публикацията не може да се показва, тъй като не сте упълномощени да я гледате.", + "status.quote_post_author": "Публикация от {name}", "status.read_more": "Още за четене", "status.reblog": "Подсилване", "status.reblog_private": "Подсилване с оригиналната видимост", @@ -866,7 +897,10 @@ "subscribed_languages.save": "Запазване на промените", "subscribed_languages.target": "Промяна на абонираните езици за {target}", "tabs_bar.home": "Начало", + "tabs_bar.menu": "Меню", "tabs_bar.notifications": "Известия", + "tabs_bar.publish": "Нова публикация", + "tabs_bar.search": "Търсене", "terms_of_service.effective_as_of": "В сила от {date}", "terms_of_service.title": "Условия на услугата", "terms_of_service.upcoming_changes_on": "Предстоящи промени на {date}", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index c28345b2605..74515fd702e 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -83,7 +83,7 @@ "account.unendorse": "Vis ikke på profil", "account.unfollow": "Følg ikke længere", "account.unmute": "Vis @{name} igen", - "account.unmute_notifications_short": "Tænd for notifikationer", + "account.unmute_notifications_short": "Vis notifikationer igen", "account.unmute_short": "Vis igen", "account_note.placeholder": "Klik for at tilføje notat", "admin.dashboard.daily_retention": "Brugerfastholdelsesrate pr. dag efter tilmelding", @@ -130,7 +130,7 @@ "attachments_list.unprocessed": "(ubehandlet)", "audio.hide": "Skjul lyd", "block_modal.remote_users_caveat": "Serveren {domain} vil blive bedt om at respektere din beslutning. Overholdelse er dog ikke garanteret, da nogle servere kan håndtere blokke forskelligt. Offentlige indlæg kan stadig være synlige for ikke-indloggede brugere.", - "block_modal.show_less": "Vis færre", + "block_modal.show_less": "Vis mindre", "block_modal.show_more": "Vis flere", "block_modal.they_cant_mention": "Vedkommende kan ikke omtale eller følge dig.", "block_modal.they_cant_see_posts": "Vedkommende kan ikke se dine indlæg, og du vil ikke se vedkommendes.", @@ -239,9 +239,9 @@ "confirmations.missing_alt_text.secondary": "Læg op alligevel", "confirmations.missing_alt_text.title": "Tilføj alt-tekst?", "confirmations.mute.confirm": "Skjul", - "confirmations.redraft.confirm": "Slet og omformulér", + "confirmations.redraft.confirm": "Slet og omskriv", "confirmations.redraft.message": "Sikker på, at dette indlæg skal slettes og omskrives? Favoritter og fremhævelser går tabt, og svar til det oprindelige indlæg mister tilknytningen.", - "confirmations.redraft.title": "Slet og omformulér indlæg?", + "confirmations.redraft.title": "Slet og omskriv indlæg?", "confirmations.remove_from_followers.confirm": "Fjern følger", "confirmations.remove_from_followers.message": "{name} vil ikke længere følge dig. Er du sikker på, at du vil fortsætte?", "confirmations.remove_from_followers.title": "Fjern følger?", @@ -279,14 +279,14 @@ "domain_pill.activitypub_lets_connect": "Det muliggører at forbinde og interagere med folk, ikke kun på Mastodon, men også på tværs af forskellige sociale apps.", "domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", som Mastodon taler med andre sociale netværk.", "domain_pill.server": "Server", - "domain_pill.their_handle": "Deres greb:", + "domain_pill.their_handle": "Deres handle:", "domain_pill.their_server": "Det digitale hjem, hvor alle indlæggene findes.", "domain_pill.their_username": "Entydig identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.", "domain_pill.username": "Brugernavn", - "domain_pill.whats_in_a_handle": "Hvad er der i et greb?", + "domain_pill.whats_in_a_handle": "Hvad indeholder et handle?", "domain_pill.who_they_are": "Da et handle fortæller, hvem nogen er, og hvor de er, kan du interagere med folk på tværs af det sociale net af .", "domain_pill.who_you_are": "Fordi dit handle fortæller, hvem du er, og hvor du er, kan du interagere med folk på tværs af det sociale net af .", - "domain_pill.your_handle": "Dit greb:", + "domain_pill.your_handle": "Dit handle:", "domain_pill.your_server": "Dit digitale hjem, hvor alle dine indlæg lever. Synes ikke om den her server? Du kan til enhver tid rykke over på en anden server og beholde dine følgere.", "domain_pill.your_username": "Din entydige identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.", "embed.instructions": "Indlejr dette indlæg på din hjemmeside ved at kopiere nedenstående kode.", @@ -414,7 +414,7 @@ "hashtag.counter_by_uses_today": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}} i dag", "hashtag.feature": "Fremhæv på profil", "hashtag.follow": "Følg hashtag", - "hashtag.mute": "Tavsgør #{hashtag}", + "hashtag.mute": "Skjul #{hashtag}", "hashtag.unfeature": "Fremhæv ikke på profil", "hashtag.unfollow": "Følg ikke længere hashtag", "hashtags.and_other": "…og {count, plural, one {}other {# flere}}", @@ -543,10 +543,10 @@ "mute_modal.hide_options": "Skjul valgmuligheder", "mute_modal.indefinite": "Indtil jeg vælger at se dem igen", "mute_modal.show_options": "Vis valgmuligheder", - "mute_modal.they_can_mention_and_follow": "De kan omtale og følge dig, men du vil ikke se dem.", + "mute_modal.they_can_mention_and_follow": "Vedkommende kan nævne og følge dig, men vil ikke blive vist.", "mute_modal.they_wont_know": "De vil ikke vide, at de er blevet skjult.", "mute_modal.title": "Skjul bruger?", - "mute_modal.you_wont_see_mentions": "Du vil ikke se indlæg som omtaler dem.", + "mute_modal.you_wont_see_mentions": "Indlæg, som nævner vedkommende, vises ikke.", "mute_modal.you_wont_see_posts": "De kan stadig se dine indlæg, men du vil ikke se deres.", "navigation_bar.about": "Om", "navigation_bar.account_settings": "Adgangskode og sikkerhed", @@ -686,11 +686,11 @@ "notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer", "notifications.policy.filter_limited_accounts_title": "Modererede konti", "notifications.policy.filter_new_accounts.hint": "Oprettet indenfor {days, plural, one {den seneste dag} other {de seneste # dage}}", - "notifications.policy.filter_new_accounts_title": "Ny konti", - "notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {færre end # dage}}", - "notifications.policy.filter_not_followers_title": "Folk, som ikke følger dig", + "notifications.policy.filter_new_accounts_title": "Nye konti", + "notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {mindre end # dage}}", + "notifications.policy.filter_not_followers_title": "Personer, som ikke følger dig", "notifications.policy.filter_not_following_hint": "Indtil du manuelt godkender dem", - "notifications.policy.filter_not_following_title": "Folk, du ikke følger", + "notifications.policy.filter_not_following_title": "Personer, du ikke følger", "notifications.policy.filter_private_mentions_hint": "Filtreret, medmindre det er i svar på egen omtale, eller hvis afsenderen følges", "notifications.policy.filter_private_mentions_title": "Uopfordrede private omtaler", "notifications.policy.title": "Håndtér notifikationer fra…", @@ -830,7 +830,7 @@ "search_results.see_all": "Vis alle", "search_results.statuses": "Indlæg", "search_results.title": "Søg efter \"{q}\"", - "server_banner.about_active_users": "Folk, som brugte denne server de seneste 30 dage (månedlige aktive brugere)", + "server_banner.about_active_users": "Personer, som brugte denne server de seneste 30 dage (månedlige aktive brugere)", "server_banner.active_users": "aktive brugere", "server_banner.administered_by": "Håndteres af:", "server_banner.is_one_of_many": "{domain} er en af de mange uafhængige Mastodon-servere, du kan bruge for at deltage i fediverset.", @@ -885,7 +885,7 @@ "status.reblogged_by": "{name} fremhævede", "status.reblogs": "{count, plural, one {# fremhævelse} other {# fremhævelser}}", "status.reblogs.empty": "Ingen har endnu fremhævet dette indlæg. Når nogen gør, vil det fremgå hér.", - "status.redraft": "Slet og omformulér", + "status.redraft": "Slet og omskriv", "status.remove_bookmark": "Fjern bogmærke", "status.remove_favourite": "Fjern fra favoritter", "status.replied_in_thread": "Svaret i tråd", @@ -902,7 +902,7 @@ "status.translate": "Oversæt", "status.translated_from_with": "Oversat fra {lang} ved brug af {provider}", "status.uncached_media_warning": "Ingen forhåndsvisning", - "status.unmute_conversation": "Genaktivér samtale", + "status.unmute_conversation": "Vis samtale", "status.unpin": "Frigør fra profil", "subscribed_languages.lead": "Efter ændringen vises kun indlæg på de valgte sprog på din hjem- og listetidslinje. Vælger du ingen, vil du modtage indlæg på alle sprog.", "subscribed_languages.save": "Gem ændringer", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 3b2662d6af6..e492cf9f807 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -327,10 +327,14 @@ "errors.unexpected_crash.copy_stacktrace": "Kopiatu irteera arbelera", "errors.unexpected_crash.report_issue": "Eman arazoaren berri", "explore.suggested_follows": "Jendea", + "explore.title": "Joerak", "explore.trending_links": "Berriak", "explore.trending_statuses": "Tutak", "explore.trending_tags": "Traolak", + "featured_carousel.next": "Hurrengoa", + "featured_carousel.post": "Argitaratu", "featured_carousel.previous": "Aurrekoa", + "featured_carousel.slide": "{total}-tik {index}", "filter_modal.added.context_mismatch_explanation": "Iragazki-kategoria hau ez zaio aplikatzen bidalketa honetara sartzeko erabili duzun testuinguruari. Bidalketa testuinguru horretan ere iragaztea nahi baduzu, iragazkia editatu beharko duzu.", "filter_modal.added.context_mismatch_title": "Testuingurua ez dator bat!", "filter_modal.added.expired_explanation": "Iragazki kategoria hau iraungi da, eragina izan dezan bere iraungitze-data aldatu beharko duzu.", @@ -383,6 +387,8 @@ "generic.saved": "Gordea", "getting_started.heading": "Menua", "hashtag.admin_moderation": "#{name}-(r)en moderazio-interfazea ireki", + "hashtag.browse": "Arakatu bidalketak #{hashtag}(e)n", + "hashtag.browse_from_account": "Arakatu @{name}(r)en bidalketak #{hashtag}(e)n", "hashtag.column_header.tag_mode.all": "eta {osagarria}", "hashtag.column_header.tag_mode.any": "edo {osagarria}", "hashtag.column_header.tag_mode.none": "gabe {osagarria}", @@ -395,7 +401,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} parte-hartzaile} other {{counter} parte-hartzaile}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} argitalpen} other {{counter} argitalpen}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} argitalpen} other {{counter} argitalpen}} gaur", + "hashtag.feature": "Nabarmendu profilean", "hashtag.follow": "Jarraitu traolari", + "hashtag.mute": "Mututu #{hashtag}", + "hashtag.unfeature": "Ez nabarmendu profilean", "hashtag.unfollow": "Utzi traola jarraitzeari", "hashtags.and_other": "…eta {count, plural, one {}other {# gehiago}}", "hints.profiles.followers_may_be_missing": "Baliteke profil honen jarraitzaile guztiak ez agertzea.", @@ -406,6 +415,7 @@ "hints.profiles.see_more_posts": "Ikusi bidalketa gehiago {domain}-(e)n", "hints.threads.replies_may_be_missing": "Baliteke beste zerbitzari batzuen erantzun batzuk ez erakustea.", "hints.threads.see_more": "Ikusi erantzun gehiago {domain}-(e)n", + "home.column_settings.show_quotes": "Erakutsi aipamenak", "home.column_settings.show_reblogs": "Erakutsi bultzadak", "home.column_settings.show_replies": "Erakutsi erantzunak", "home.hide_announcements": "Ezkutatu iragarpenak", @@ -424,6 +434,11 @@ "ignore_notifications_modal.not_followers_title": "Jarraitzen ez zaituzten pertsonen jakinarazpenei ez ikusiarena egin?", "ignore_notifications_modal.not_following_title": "Jarraitzen ez dituzun pertsonen jakinarazpenei ez ikusiarena egin?", "ignore_notifications_modal.private_mentions_title": "Eskatu gabeko aipamen pribatuen jakinarazpenei ez ikusiarena egin?", + "info_button.label": "Laguntza", + "interaction_modal.action.favourite": "Jarraitzeko, zure kontutik atsegindu behar duzu.", + "interaction_modal.action.follow": "Jarraitzeko zure kontutik jarraitu behar duzu.", + "interaction_modal.action.reply": "Jarraitzeko zure kontutik erantzun behar duzu.", + "interaction_modal.action.vote": "Jarraitzeko, zure kontutik bozkatu behar duzu.", "interaction_modal.go": "Joan", "interaction_modal.no_account_yet": "Ez al duzu konturik oraindik?", "interaction_modal.on_another_server": "Beste zerbitzari batean", @@ -468,11 +483,14 @@ "keyboard_shortcuts.toggle_hidden": "testua erakustea/ezkutatzea abisu baten atzean", "keyboard_shortcuts.toggle_sensitivity": "multimedia erakutsi/ezkutatzeko", "keyboard_shortcuts.toot": "Hasi bidalketa berri bat", + "keyboard_shortcuts.translate": "bidalketa itzultzeko", "keyboard_shortcuts.unfocus": "testua konposatzeko area / bilaketatik fokua kentzea", "keyboard_shortcuts.up": "zerrendan gora mugitzea", "lightbox.close": "Itxi", "lightbox.next": "Hurrengoa", "lightbox.previous": "Aurrekoa", + "lightbox.zoom_in": "Zooma egungo tamainara", + "lightbox.zoom_out": "Zooma egokitzeko", "limited_account_hint.action": "Erakutsi profila hala ere", "limited_account_hint.title": "Profil hau ezkutatu egin dute {domain} zerbitzariko moderatzaileek.", "link_preview.author": "Egilea: {name}", @@ -482,13 +500,24 @@ "lists.add_to_list": "Gehitu zerrendara", "lists.add_to_lists": "Gehitu {name} zerrendetara", "lists.create": "Sortu", + "lists.create_a_list_to_organize": "Sortu zerrenda berria zure Hasierako jarioa antolatzeko", "lists.create_list": "Sortu zerrenda", "lists.delete": "Ezabatu zerrenda", "lists.done": "Egina", "lists.edit": "Editatu zerrenda", + "lists.exclusive": "Ezkutatu kideak Hasieran", + "lists.exclusive_hint": "Norbait zerrenda honetan badago, ezkutatu zure Hasierako jariotik mezuak bi aldiz ez ikusteko.", + "lists.find_users_to_add": "Bilatu erabiltzaileak gehitzeko", + "lists.list_name": "Zerrenda izena", + "lists.new_list_name": "Zerrenda izen berria", + "lists.no_lists_yet": "Ez duzu zerrendarik oraindik.", + "lists.no_members_yet": "Ez duzu kiderik oraindik.", + "lists.no_results_found": "Ez da emaitzarik aurkitu.", + "lists.remove_member": "Ezabatu", "lists.replies_policy.followed": "Jarraitutako edozein erabiltzaile", "lists.replies_policy.list": "Zerrendako kideak", "lists.replies_policy.none": "Bat ere ez", + "lists.save": "Gorde", "lists.search": "Bilatu", "load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}", "loading_indicator.label": "Kargatzen…", @@ -504,8 +533,10 @@ "mute_modal.you_wont_see_mentions": "Ez duzu ikusiko bera aipatzen duen argitalpenik.", "mute_modal.you_wont_see_posts": "Zure argitalpenak ikus ditzake, baina ez dituzu bereak ikusiko.", "navigation_bar.about": "Honi buruz", + "navigation_bar.account_settings": "Pasahitza eta segurtasuna", "navigation_bar.administration": "Administrazioa", "navigation_bar.advanced_interface": "Ireki web interfaze aurreratuan", + "navigation_bar.automated_deletion": "Bidalketa automatikoaren ezabaketa", "navigation_bar.blocks": "Blokeatutako erabiltzaileak", "navigation_bar.bookmarks": "Laster-markak", "navigation_bar.direct": "Aipamen pribatuak", @@ -515,13 +546,19 @@ "navigation_bar.follow_requests": "Jarraitzeko eskaerak", "navigation_bar.followed_tags": "Jarraitutako traolak", "navigation_bar.follows_and_followers": "Jarraitutakoak eta jarraitzaileak", + "navigation_bar.import_export": "Inportazioa eta esportazioa", "navigation_bar.lists": "Zerrendak", + "navigation_bar.live_feed_local": "Zuzeneko jarioa (lokala)", + "navigation_bar.live_feed_public": "Zuzeneko jarioa (publikoa)", "navigation_bar.logout": "Amaitu saioa", "navigation_bar.moderation": "Moderazioa", + "navigation_bar.more": "Gehiago", "navigation_bar.mutes": "Mutututako erabiltzaileak", "navigation_bar.opened_in_classic_interface": "Argitalpenak, kontuak eta beste orri jakin batzuk lehenespenez irekitzen dira web-interfaze klasikoan.", "navigation_bar.preferences": "Hobespenak", + "navigation_bar.privacy_and_reach": "Pribatutasuna eta irismena", "navigation_bar.search": "Bilatu", + "navigation_bar.search_trends": "Bilatu / Joera", "not_signed_in_indicator.not_signed_in": "Baliabide honetara sarbidea izateko saioa hasi behar duzu.", "notification.admin.report": "{name} erabiltzaileak {target} salatu du", "notification.admin.report_account": "{name}-(e)k {target}-ren {count, plural, one {bidalketa bat} other {# bidalketa}} salatu zituen {category} delakoagatik", @@ -540,6 +577,7 @@ "notification.label.private_reply": "Erantzun pribatua", "notification.label.reply": "Erantzuna", "notification.mention": "Aipamena", + "notification.mentioned_you": "{name}(e)k aipatu zaitu", "notification.moderation-warning.learn_more": "Informazio gehiago", "notification.moderation_warning": "Moderazio-abisu bat jaso duzu", "notification.moderation_warning.action_delete_statuses": "Argitalpen batzuk kendu dira.", @@ -586,6 +624,7 @@ "notifications.column_settings.filter_bar.category": "Iragazki-barra bizkorra", "notifications.column_settings.follow": "Jarraitzaile berriak:", "notifications.column_settings.follow_request": "Jarraitzeko eskaera berriak:", + "notifications.column_settings.group": "Taldea", "notifications.column_settings.mention": "Aipamenak:", "notifications.column_settings.poll": "Inkestaren emaitzak:", "notifications.column_settings.push": "Push jakinarazpenak", @@ -736,6 +775,7 @@ "report_notification.categories.violation": "Arau haustea", "report_notification.categories.violation_sentence": "arau haustea", "report_notification.open": "Ireki salaketa", + "search.clear": "Garbitu bilaketa", "search.no_recent_searches": "Duela gutxiko bilaketarik ez", "search.placeholder": "Bilatu", "search.quick_action.account_search": "{x}-(r)ekin bat datozen profilak", @@ -800,6 +840,8 @@ "status.mute_conversation": "Mututu elkarrizketa", "status.open": "Hedatu bidalketa hau", "status.pin": "Finkatu profilean", + "status.quote_error.not_found": "Bidalketa hau ezin da erakutsi.", + "status.quote_error.pending_approval": "Bidalketa hau egile originalak onartzeko zain dago.", "status.read_more": "Irakurri gehiago", "status.reblog": "Bultzada", "status.reblog_private": "Bultzada jatorrizko hartzaileei", @@ -828,7 +870,11 @@ "subscribed_languages.save": "Gorde aldaketak", "subscribed_languages.target": "Aldatu {target}(e)n harpidetutako hizkuntzak", "tabs_bar.home": "Hasiera", + "tabs_bar.menu": "Menua", "tabs_bar.notifications": "Jakinarazpenak", + "tabs_bar.publish": "Bidalketa berria", + "tabs_bar.search": "Bilatu", + "terms_of_service.title": "Zerbitzuaren baldintzak", "time_remaining.days": "{number, plural, one {egun #} other {# egun}} amaitzeko", "time_remaining.hours": "{number, plural, one {ordu #} other {# ordu}} amaitzeko", "time_remaining.minutes": "{number, plural, one {minutu #} other {# minutu}} amaitzeko", @@ -855,5 +901,6 @@ "video.fullscreen": "Pantaila osoa", "video.hide": "Ezkutatu bideoa", "video.pause": "Pausatu", - "video.play": "Jo" + "video.play": "Jo", + "video.volume_up": "Bolumena Igo" } diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1a18d4339f1..d4797936877 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -1,6 +1,7 @@ { "about.blocks": "Servidores moderados", "about.contact": "Contato:", + "about.default_locale": "Padrão", "about.disclaimer": "Mastodon é um software de código aberto e livre, e uma marca registrada de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Razão não disponível", "about.domain_blocks.preamble": "O Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no fediverso. Estas são as exceções deste servidor em específico.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Limitado", "about.domain_blocks.suspended.explanation": "Nenhum dado desse servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os usuários deste servidor.", "about.domain_blocks.suspended.title": "Suspenso", + "about.language_label": "Idioma", "about.not_available": "Esta informação não foi disponibilizada neste servidor.", "about.powered_by": "Redes sociais descentralizadas alimentadas por {mastodon}", "about.rules": "Regras do servidor", @@ -217,6 +219,11 @@ "confirmations.delete_list.confirm": "Excluir", "confirmations.delete_list.message": "Você tem certeza de que deseja excluir esta lista?", "confirmations.delete_list.title": "Excluir lista?", + "confirmations.discard_draft.confirm": "Descartar e continuar", + "confirmations.discard_draft.edit.cancel": "Continuar editando", + "confirmations.discard_draft.edit.message": "Continuar vai descartar quaisquer mudanças feitas ao post sendo editado.", + "confirmations.discard_draft.edit.title": "Descartar mudanças no seu post?", + "confirmations.discard_draft.post.cancel": "Continuar rascunho", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 66e5cf4760f..31d19931439 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -363,7 +363,7 @@ "filter_modal.select_filter.title": "Фильтровать этот пост", "filter_modal.title.status": "Фильтровать пост", "filter_warning.matches_filter": "Соответствует фильтру «{title}»", - "filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не знакомых вам людей} one {# человека, которого вы можете знать} other {# человек, которых вы можете знать}}", + "filtered_notifications_banner.pending_requests": "От {count, plural, =0 {незнакомых вам людей} one {# человека, которого вы можете знать} other {# человек, которых вы можете знать}}", "filtered_notifications_banner.title": "Отфильтрованные уведомления", "firehose.all": "Всё вместе", "firehose.local": "Этот сервер", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index d82c4cb94d0..63fd556f79b 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -27,6 +27,7 @@ "account.edit_profile": "Upraviť profil", "account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}", "account.endorse": "Zobraziť na vlastnom profile", + "account.featured": "Zviditeľnené", "account.featured.accounts": "Profily", "account.featured.hashtags": "Hashtagy", "account.featured_tags.last_status_at": "Posledný príspevok dňa {date}", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index a6adca289ac..603c1ab61ff 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -1,6 +1,7 @@ { "about.blocks": "เซิร์ฟเวอร์ที่ได้รับการกลั่นกรอง", "about.contact": "ติดต่อ:", + "about.default_locale": "ค่าเริ่มต้น", "about.disclaimer": "Mastodon เป็นซอฟต์แวร์เสรี โอเพนซอร์ส และเครื่องหมายการค้าของ Mastodon gGmbH", "about.domain_blocks.no_reason_available": "เหตุผลไม่พร้อมใช้งาน", "about.domain_blocks.preamble": "โดยทั่วไป Mastodon อนุญาตให้คุณดูเนื้อหาจากและโต้ตอบกับผู้ใช้จากเซิร์ฟเวอร์อื่นใดในจักรวาลสหพันธ์ นี่คือข้อยกเว้นที่ทำขึ้นในเซิร์ฟเวอร์นี้โดยเฉพาะ", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "จำกัดอยู่", "about.domain_blocks.suspended.explanation": "จะไม่ประมวลผล จัดเก็บ หรือแลกเปลี่ยนข้อมูลจากเซิร์ฟเวอร์นี้ ทำให้การโต้ตอบหรือการสื่อสารใด ๆ กับผู้ใช้จากเซิร์ฟเวอร์นี้เป็นไปไม่ได้", "about.domain_blocks.suspended.title": "ระงับอยู่", + "about.language_label": "ภาษา", "about.not_available": "ไม่ได้ทำให้ข้อมูลนี้พร้อมใช้งานในเซิร์ฟเวอร์นี้", "about.powered_by": "สื่อสังคมแบบกระจายศูนย์ที่ขับเคลื่อนโดย {mastodon}", "about.rules": "กฎของเซิร์ฟเวอร์", @@ -19,14 +21,18 @@ "account.block_domain": "ปิดกั้นโดเมน {domain}", "account.block_short": "ปิดกั้น", "account.blocked": "ปิดกั้นอยู่", + "account.blocking": "กำลังปิดกั้น", "account.cancel_follow_request": "ยกเลิกการติดตาม", "account.copy": "คัดลอกลิงก์ไปยังโปรไฟล์", "account.direct": "กล่าวถึง @{name} แบบส่วนตัว", "account.disable_notifications": "หยุดแจ้งเตือนฉันเมื่อ @{name} โพสต์", - "account.domain_blocking": "โดเมน", + "account.domain_blocking": "กำลังปิดกั้นโดเมน", "account.edit_profile": "แก้ไขโปรไฟล์", "account.enable_notifications": "แจ้งเตือนฉันเมื่อ @{name} โพสต์", "account.endorse": "แสดงในโปรไฟล์", + "account.featured": "น่าสนใจ", + "account.featured.accounts": "โปรไฟล์", + "account.featured.hashtags": "แฮชแท็ก", "account.featured_tags.last_status_at": "โพสต์ล่าสุดเมื่อ {date}", "account.featured_tags.last_status_never": "ไม่มีโพสต์", "account.follow": "ติดตาม", @@ -37,6 +43,7 @@ "account.following": "กำลังติดตาม", "account.following_counter": "{count, plural, other {{counter} กำลังติดตาม}}", "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร", + "account.follows_you": "ติดตามคุณ", "account.go_to_profile": "ไปยังโปรไฟล์", "account.hide_reblogs": "ซ่อนการดันจาก @{name}", "account.in_memoriam": "เพื่อระลึกถึง", @@ -51,6 +58,7 @@ "account.mute_notifications_short": "ซ่อนการแจ้งเตือน", "account.mute_short": "ซ่อน", "account.muted": "ซ่อนอยู่", + "account.muting": "กำลังซ่อน", "account.no_bio": "ไม่ได้ให้คำอธิบาย", "account.open_original_page": "เปิดหน้าดั้งเดิม", "account.posts": "โพสต์", @@ -305,9 +313,14 @@ "errors.unexpected_crash.copy_stacktrace": "คัดลอกการติดตามสแตกไปยังคลิปบอร์ด", "errors.unexpected_crash.report_issue": "รายงานปัญหา", "explore.suggested_follows": "ผู้คน", + "explore.title": "กำลังนิยม", "explore.trending_links": "ข่าว", "explore.trending_statuses": "โพสต์", "explore.trending_tags": "แฮชแท็ก", + "featured_carousel.next": "ถัดไป", + "featured_carousel.post": "โพสต์", + "featured_carousel.previous": "ก่อนหน้า", + "featured_carousel.slide": "{index} จาก {total}", "filter_modal.added.context_mismatch_explanation": "หมวดหมู่ตัวกรองนี้ไม่นำไปใช้กับบริบทที่คุณได้เข้าถึงโพสต์นี้ หากคุณต้องการกรองโพสต์ในบริบทนี้ด้วย คุณจะต้องแก้ไขตัวกรอง", "filter_modal.added.context_mismatch_title": "บริบทไม่ตรงกัน!", "filter_modal.added.expired_explanation": "หมวดหมู่ตัวกรองนี้หมดอายุแล้ว คุณจะต้องเปลี่ยนวันหมดอายุสำหรับหมวดหมู่เพื่อนำไปใช้", @@ -372,7 +385,10 @@ "hashtag.counter_by_accounts": "{count, plural, other {{counter} ผู้มีส่วนร่วม}}", "hashtag.counter_by_uses": "{count, plural, other {{counter} โพสต์}}", "hashtag.counter_by_uses_today": "{count, plural, other {{counter} โพสต์}}วันนี้", + "hashtag.feature": "แสดงในโปรไฟล์", "hashtag.follow": "ติดตามแฮชแท็ก", + "hashtag.mute": "ซ่อน #{hashtag}", + "hashtag.unfeature": "ไม่แสดงในโปรไฟล์", "hashtag.unfollow": "เลิกติดตามแฮชแท็ก", "hashtags.and_other": "…และอีก {count, plural, other {# เพิ่มเติม}}", "hints.profiles.followers_may_be_missing": "ผู้ติดตามสำหรับโปรไฟล์นี้อาจขาดหายไป", @@ -504,8 +520,10 @@ "mute_modal.you_wont_see_mentions": "คุณจะไม่เห็นโพสต์ที่กล่าวถึงเขา", "mute_modal.you_wont_see_posts": "เขายังคงสามารถเห็นโพสต์ของคุณ แต่คุณจะไม่เห็นโพสต์ของเขา", "navigation_bar.about": "เกี่ยวกับ", + "navigation_bar.account_settings": "รหัสผ่านและความปลอดภัย", "navigation_bar.administration": "การดูแล", "navigation_bar.advanced_interface": "เปิดในส่วนติดต่อเว็บขั้นสูง", + "navigation_bar.automated_deletion": "การลบโพสต์แบบอัตโนมัติ", "navigation_bar.blocks": "ผู้ใช้ที่ปิดกั้นอยู่", "navigation_bar.bookmarks": "ที่คั่นหน้า", "navigation_bar.direct": "การกล่าวถึงแบบส่วนตัว", @@ -515,12 +533,15 @@ "navigation_bar.follow_requests": "คำขอติดตาม", "navigation_bar.followed_tags": "แฮชแท็กที่ติดตาม", "navigation_bar.follows_and_followers": "การติดตามและผู้ติดตาม", + "navigation_bar.import_export": "การนำเข้าและการส่งออก", "navigation_bar.lists": "รายการ", "navigation_bar.logout": "ออกจากระบบ", "navigation_bar.moderation": "การกลั่นกรอง", + "navigation_bar.more": "เพิ่มเติม", "navigation_bar.mutes": "ผู้ใช้ที่ซ่อนอยู่", "navigation_bar.opened_in_classic_interface": "จะเปิดโพสต์, บัญชี และหน้าที่เฉพาะเจาะจงอื่น ๆ เป็นค่าเริ่มต้นในส่วนติดต่อเว็บแบบคลาสสิก", "navigation_bar.preferences": "การกำหนดลักษณะ", + "navigation_bar.privacy_and_reach": "ความเป็นส่วนตัวและการเข้าถึง", "navigation_bar.search": "ค้นหา", "not_signed_in_indicator.not_signed_in": "คุณจำเป็นต้องเข้าสู่ระบบเพื่อเข้าถึงทรัพยากรนี้", "notification.admin.report": "{name} ได้รายงาน {target}", @@ -747,6 +768,7 @@ "report_notification.categories.violation": "การละเมิดกฎ", "report_notification.categories.violation_sentence": "การละเมิดกฎ", "report_notification.open": "รายงานที่เปิด", + "search.clear": "ล้างการค้นหา", "search.no_recent_searches": "ไม่มีการค้นหาล่าสุด", "search.placeholder": "ค้นหา", "search.quick_action.account_search": "โปรไฟล์ที่ตรงกับ {x}", @@ -812,6 +834,7 @@ "status.mute_conversation": "ซ่อนการสนทนา", "status.open": "ขยายโพสต์นี้", "status.pin": "ปักหมุดในโปรไฟล์", + "status.quote_post_author": "โพสต์โดย {name}", "status.read_more": "อ่านเพิ่มเติม", "status.reblog": "ดัน", "status.reblog_private": "ดันด้วยการมองเห็นดั้งเดิม", @@ -841,7 +864,10 @@ "subscribed_languages.save": "บันทึกการเปลี่ยนแปลง", "subscribed_languages.target": "เปลี่ยนภาษาที่บอกรับสำหรับ {target}", "tabs_bar.home": "หน้าแรก", + "tabs_bar.menu": "เมนู", "tabs_bar.notifications": "การแจ้งเตือน", + "tabs_bar.publish": "โพสต์ใหม่", + "tabs_bar.search": "ค้นหา", "terms_of_service.title": "เงื่อนไขการให้บริการ", "time_remaining.days": "เหลืออีก {number, plural, other {# วัน}}", "time_remaining.hours": "เหลืออีก {number, plural, other {# ชั่วโมง}}", diff --git a/config/locales/activerecord.eu.yml b/config/locales/activerecord.eu.yml index 8b4235eb993..c0ac1bb338b 100644 --- a/config/locales/activerecord.eu.yml +++ b/config/locales/activerecord.eu.yml @@ -49,8 +49,14 @@ eu: attributes: reblog: taken: mezu honentzat bazegoen aurretik + terms_of_service: + attributes: + effective_date: + too_soon: goizegi da, %{date} baino geroagokoa izan behar da user: attributes: + date_of_birth: + below_limit: adinaren mugaren azpitik dago email: blocked: onartu gabeko e-posta hornitzaile bat erabiltzen du unreachable: dirudienez ez da existitzen diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 5a41164c065..137f9c711f2 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -566,6 +566,13 @@ bg: all: Всичко limited: Ограничено title: Mодериране + moderation_notes: + create: Добавяне на бележка за модериране + created_msg: Успешно създадена бележка за модериране на екземпляр! + description_html: Прегледайте и оставете бележки за други модератори и за вас в бъдеще + destroyed_msg: Успешно изтрита бележка за модериране на екземпляр! + placeholder: Сведенията за този екземпляр, предприети действия, или всичко друго, което ще ви помогне да модерирате този екземпляр в бъдеще. + title: Бележки за модериране private_comment: Личен коментар public_comment: Публичен коментар purge: Чистка @@ -774,11 +781,16 @@ bg: title: Роли rules: add_new: Добавяне на правило + add_translation: Добавяне на превод delete: Изтриване description_html: Дори повечето хора да отбелязват, че са прочели и са съгласни с условията на услугата, обикновено хората не ги четат, докато не се сблъскат с проблем. Улеснете четенето на правилата за сървъра си, представяйки ги като списък с точки. Опитайте да се придържате към кратки и прости правила, но не ги разпилявайте в премного точки. edit: Промяна на правило empty: Още няма определени правила на сървъра. + move_down: Преместване надолу + move_up: Преместване нагоре title: Сървърни правила + translation: Превод + translations: Преводи settings: about: manage_rules: Управление на правилата на сървъра @@ -804,6 +816,7 @@ bg: discovery: follow_recommendations: Препоръки за следване preamble: За потребители, които са нови и не познават никого в Mastodon, показването на интересно съдържание е ключово. Настройте начина, по който различни функции по откриване на съдържание работят на вашия сървър. + privacy: Поверителност profile_directory: Указател на профила public_timelines: Публични хронологии publish_statistics: Публикуване на статистиката @@ -890,6 +903,8 @@ bg: system_checks: database_schema_check: message_html: Има миграции на базата данни, които чакат да бъдат изпълнени. Моля, изпълнете ги, за да осигурите изправността на приложението + elasticsearch_analysis_index_mismatch: + message_html: Настройките за анализиращия индекс Elasticsearch са остарели. Пуснете tootctl search deploy --only-mapping --only=%{value} elasticsearch_health_red: message_html: Клъстерът Elasticsearch е нездрав (червено състояние), функциите за търсене не са налични elasticsearch_health_yellow: @@ -1841,6 +1856,10 @@ bg: limit: Вече сте закачили максималния брой публикации ownership: Публикация на някого другиго не може да бъде закачена reblog: Раздуване не може да бъде закачано + quote_policies: + followers: Последователи и споменати потребители + nobody: Само споменатите потребители + public: Всеки title: "%{name}: „%{quote}“" visibilities: direct: Директно @@ -1894,6 +1913,9 @@ bg: does_not_match_previous_name: не съвпада с предишното име terms_of_service: title: Условия на услугата + terms_of_service_interstitial: + future_preamble_html: Правим някои промени в условията на услугата ни, което ще влезе в сила на %{date}. Насърчаваме ви да разгледате обновените условия. + past_preamble_html: Променихме условията на услугата ни от последното ви посещение. Насърчаваме ви да разгледате обновените условия. themes: contrast: Mastodon (висок контраст) default: Mastodon (тъмно) diff --git a/config/locales/da.yml b/config/locales/da.yml index ca5f1831c9f..db3bb547fe4 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1485,8 +1485,8 @@ da: one: Man er ved at erstatte sine lister med indhold fra %{filename}. %{count} konto føjes til nye lister. other: Man er ved at erstatte sine lister med indhold fra %{filename}. Op til %{count} konti føjes til nye lister. muting_html: - one: Man er ved at sin liste over en tavsgjort konto med %{count} konto fra %{filename}. - other: Du er ved at erstatte din liste over skjulte kontoer med op til %{count} kontoer fra %{filename}. + one: Du er ved at erstatte din liste over en skjult konto med op til %{count} konto fra %{filename}. + other: Du er ved at erstatte din liste over skjulte konti med op til %{count} konti fra %{filename}. preambles: blocking_html: one: Man er ved at blokere %{count} konto fra %{filename}. @@ -1504,8 +1504,8 @@ da: one: Man er ved at tilføje %{count} konto fra %{filename} til sine lister. Nye lister oprettes, hvis der ikke findes nogen liste at tilføje til. other: Man er ved at tilføje %{count} konti fra %{filename} til sine lister. Nye lister oprettes, hvis der ikke findes nogen liste at tilføje til. muting_html: - one: Man er ved at tavsgøre %{count} konto fra %{filename}. - other: Du er ved at skjule op til %{count} kontoer fra %{filename}. + one: Du er ved at skjule op til %{count} konto fra %{filename}. + other: Du er ved at skjule op til %{count} konti fra %{filename}. preface: Du kan importere data, du har eksporteret fra en anden server, såsom en liste over folk du følger eller blokerer. recent_imports: Seneste importer states: @@ -1522,11 +1522,11 @@ da: domain_blocking: Importerer blokerede konti following: Importerer fulgte konti lists: Import af lister - muting: Importerer skjulte kontoer + muting: Importerer skjulte konti type: Importtype type_groups: constructive: Følger og Bogmærker - destructive: Blokerede og skjulte kontoer + destructive: Blokerede og skjulte konti types: blocking: Blokeringsliste bookmarks: Bogmærker @@ -1628,7 +1628,7 @@ da: title: Moderation move_handler: carry_blocks_over_text: Denne bruger er flyttet fra %{acct}, som du har haft blokeret. - carry_mutes_over_text: Denne bruger er flyttet fra %{acct}, som du har haft skjult. + carry_mutes_over_text: Denne bruger flyttede fra %{acct}, som du havde skjult. copy_account_note_text: 'Denne bruger er flyttet fra %{acct}, hvor dine tidligere noter om dem var:' navigation: toggle_menu: Åbn/luk menu @@ -1832,7 +1832,7 @@ da: profile: Offentlig profil relationships: Følger og følgere severed_relationships: Afbrudte forhold - statuses_cleanup: Auto-indlægssletning + statuses_cleanup: Automatiseret sletning af indlæg strikes: Moderationsadvarsler two_factor_authentication: Tofaktorgodkendelse webauthn_authentication: Sikkerhedsnøgler @@ -1905,9 +1905,9 @@ da: keep_polls: Behold afstemninger keep_polls_hint: Sletter ingen af dine afstemninger keep_self_bookmark: Behold bogmærkede indlæg - keep_self_bookmark_hint: Sletter ikke dine egne indlæg, såfremt de er bogmærket + keep_self_bookmark_hint: Sletter ikke dine egne indlæg, hvis du har bogmærket dem keep_self_fav: Behold favoritmarkerede indlæg - keep_self_fav_hint: Sletter ikke dine egne indlæg, såfremt de er favoritmarkeret + keep_self_fav_hint: Sletter ikke dine egne indlæg, hvis du har favoritmarkeret dem min_age: '1209600': 2 uger '15778476': 6 måneder diff --git a/config/locales/devise.vi.yml b/config/locales/devise.vi.yml index 04107b28972..5b4bbf38396 100644 --- a/config/locales/devise.vi.yml +++ b/config/locales/devise.vi.yml @@ -49,14 +49,14 @@ vi: title: Đổi lại mật khẩu two_factor_disabled: explanation: Đăng nhập bây giờ chỉ có thể sử dụng địa chỉ email và mật khẩu. - subject: 'Mastodon: Xác minh 2 bước đã bị vô hiệu hóa' - subtitle: Xác minh hai bước cho tài khoản của bạn đã bị vô hiệu hóa. - title: Vô hiệu hóa xác minh 2 bước + subject: 'Mastodon: Xác thực 2 bước đã bị vô hiệu hóa' + subtitle: Xác thực 2 bước cho tài khoản của bạn đã bị vô hiệu hóa. + title: Vô hiệu hóa xác thực 2 bước two_factor_enabled: explanation: Cần có mã token được tạo bởi ứng dụng TOTP được ghép nối để đăng nhập. - subject: 'Mastodon: Kích hoạt xác minh 2 bước' - subtitle: Xác minh hai bước đã được bật cho tài khoản của bạn. - title: Kích hoạt xác minh 2 bước + subject: 'Mastodon: Kích hoạt xác thực 2 bước' + subtitle: Xác thực 2 bước đã được bật cho tài khoản của bạn. + title: Kích hoạt xác thực 2 bước two_factor_recovery_codes_changed: explanation: Các mã khôi phục trước đó đã bị vô hiệu hóa và thay bằng mã mới. subject: 'Mastodon: Mã khôi phục xác thực hai yếu tố đã được tạo lại' diff --git a/config/locales/doorkeeper.da.yml b/config/locales/doorkeeper.da.yml index 5be66d6c2a2..49917c8ebc5 100644 --- a/config/locales/doorkeeper.da.yml +++ b/config/locales/doorkeeper.da.yml @@ -130,11 +130,11 @@ da: crypto: Ende-til-ende kryptering favourites: Favoritter filters: Filtre - follow: Fulgte, skjjulte og blokerede kontoer + follow: Fulgte, skjulte og blokerede konti follows: Følger lists: Lister media: Medievedhæftninger - mutes: Skjulte kontoer + mutes: Skjulte notifications: Notifikationer profile: Din Mastodon-profil push: Push-notifikationer @@ -177,7 +177,7 @@ da: read:filters: se dine filtre read:follows: se dine følger read:lists: se dine lister - read:mutes: se dine skjulte kontoer + read:mutes: se dine skjulte konti read:notifications: se dine notifikationer read:reports: se dine anmeldelser read:search: søg på dine vegne diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 79080c7bfbf..19a6be4a9ce 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -450,6 +450,27 @@ eu: new: title: Domeinu-blokeoak inportatu no_file: Ez da fitxategirik hautatu + fasp: + debug: + callbacks: + delete: Ezabatu + ip: IP helbidea + providers: + active: Aktibo + base_url: Oinarrizko URL-a + delete: Ezabatu + edit: Editatu hornitzailea + finish_registration: Izen-ematea bukatu + name: Izena + providers: Hornitzaileak + public_key_fingerprint: Gako publikoaren hatz-marka + registration_requested: Izen-ematea eskatuta + registrations: + confirm: Berretsi + reject: Ukatu + save: Gorde + sign_in: Hasi saioa + status: Egoera follow_recommendations: description_html: "Jarraitzeko gomendioek erabiltzaile berriei eduki interesgarria azkar aurkitzen laguntzen diete. Erabiltzaile batek jarraitzeko gomendio pertsonalizatuak jasotzeko adina interakzio izan ez duenean, kontu hauek gomendatzen zaizkio. Egunero birkalkulatzen dira hizkuntza bakoitzerako, azken aldian parte-hartze handiena izan duten eta jarraitzaile lokal gehien dituzten kontuak nahasiz." language: Hizkuntza @@ -717,11 +738,14 @@ eu: title: Rolak rules: add_new: Gehitu araua + add_translation: Gehitu itzulpena delete: Ezabatu description_html: Gehienek erabilera baldintzak irakurri eta onartu dituztela baieztatzen badute ere, orokorrean arazoren bat dagoen arte ez dituzte irakurtzen. Zerbitzariaren arauak begirada batean ikustea errazteko buletadun zerrenda batean bildu. Saiatu arauak labur eta sinple idazten, baina elementu askotan banatu gabe. edit: Editatu araua empty: Ez da zerbitzariko araurik definitu oraindik. title: Zerbitzariaren arauak + translation: Itzulpena + translations: Itzulpenak settings: about: manage_rules: Kudeatu zerbitzariaren arauak @@ -747,6 +771,7 @@ eu: discovery: follow_recommendations: Jarraitzeko gomendioak preamble: Eduki interesgarria aurkitzea garrantzitsua da Mastodoneko erabiltzaile berrientzat, behar bada inor ez dutelako ezagutuko. Kontrolatu zure zerbitzariko aurkikuntza-ezaugarriek nola funtzionatzen duten. + privacy: Pribatutasuna profile_directory: Profil-direktorioa public_timelines: Denbora-lerro publikoak publish_statistics: Argitaratu estatistikak @@ -794,6 +819,7 @@ eu: batch: remove_from_report: Kendu txostenetik report: Salatu + contents: Edukiak deleted: Ezabatuta favourites: Gogokoak history: Bertsio-historia @@ -802,6 +828,7 @@ eu: media: title: Multimedia metadata: Metadatuak + no_history: Bidalketa hau ez da editatu no_status_selected: Ez da bidalketarik aldatu ez delako bat ere hautatu open: Ireki bidalketa original_status: Jatorrizko bidalketa @@ -849,6 +876,8 @@ eu: message_html: Ez duzu zerbitzariaren araurik definitu. sidekiq_process_check: message_html: Ez da ari Sidekiq prozesurik exekutatzen %{value} ilad(et)an. Egiaztatu Sidekiq konfigurazioa + software_version_check: + message_html: Mastodon eguneratze bat eskuragarri dago. software_version_critical_check: action: Ikusi eguneraketa eskuragarriak message_html: Mastodon eguneraketa kritikoa eskuragarri, mesedez eguneratu ahal bezain azkar. @@ -862,8 +891,35 @@ eu: action: Ikus hemen informazio gehiagorako message_html: "Zure objektuen biltegiratzea ez dago ongi konfiguratua. Zure erabiltzaileen pribatutasuna arriskuan dago." tags: + moderation: + not_usable: Ez erabilgarri + pending_review: Berrikusketaren zain + reviewed: Berrikusita + title: Egoera + unreviewed: Berrikusi gabe + usable: Erabilgarri + name: Izena + newest: Berriena + oldest: Zaharrena + open: Publikoki ikusi + reset: Berrezarri review: Berrikusketaren egoera + search: Bilatu + title: Traolak updated_msg: Traola-ezarpenak ongi eguneratu dira + terms_of_service: + changelog: Zer aldatu da + current: Oraingoa + draft: Zirriborroa + generate: Txantiloila erabili + generates: + action: Sortu + history: Historia + live: Zuzenean + publish: Argitaratu + published_on_html: "%{date}(e)an argitaratua" + save_draft: Gorde zirriborroa + title: Zerbitzuaren baldintzak title: Administrazioa trends: allow: Onartu @@ -1056,6 +1112,7 @@ eu: migrate_account_html: Kontu hau beste batera birbideratu nahi baduzu, hemen konfiguratu dezakezu. or_log_in_with: Edo hasi saioa honekin progress: + confirm: Berretsi e-mail helbidea details: Zure xehetasunak review: Gure berrikuspena rules: Onartu arauak @@ -1077,6 +1134,7 @@ eu: security: Segurtasuna set_new_password: Ezarri pasahitza berria setup: + email_below_hint_html: Begiratu zure spameko karpetan, edo eskatu beste bat. Zure helbide elektronikoa zuzen dezakezu oker badago. link_not_received: Ez duzu estekarik jaso? title: Begiratu zure sarrera-ontzia sign_in: @@ -1587,10 +1645,12 @@ eu: delete: Kontuaren ezabaketa development: Garapena edit_profile: Editatu profila + export: Esportatu featured_tags: Nabarmendutako traolak import: Inportazioa import_and_export: Inportatu eta esportatu migrate: Kontuaren migrazioa + notifications: Posta bidezko jakinarazpenak preferences: Hobespenak profile: Profila relationships: Jarraitutakoak eta jarraitzaileak @@ -1637,6 +1697,10 @@ eu: limit: Gehienez finkatu daitekeen bidalketa kopurua finkatu duzu jada ownership: Ezin duzu beste norbaiten bidalketa bat finkatu reblog: Bultzada bat ezin da finkatu + quote_policies: + followers: Jarraitzaileak eta aipatutako erabiltzaileak + nobody: Aipatutako erabiltzaileak soilik + public: Guztiak title: '%{name}: "%{quote}"' visibilities: direct: Zuzena @@ -1688,6 +1752,8 @@ eu: too_late: Beranduegi da neurri hau apelatzeko tags: does_not_match_previous_name: ez dator aurreko izenarekin bat + terms_of_service: + title: Erabilera baldintzak themes: contrast: Mastodon (Kontraste altua) default: Mastodon (Iluna) @@ -1833,6 +1899,7 @@ eu: instructions_html: Kopiatu eta itsatsi ondoko kodea zure webguneko HTMLan. Ondoren, gehitu zure webgunearen helbidea zure profileko eremu gehigarrietako batean, "Editatu profila" fitxatik eta gorde aldaketak. verification: Egiaztaketa verified_links: Zure lotura egiaztatuak + website_verification: Web orriaren egiaztapena webauthn_credentials: add: Gehitu segurtasun gako berria create: diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 810a6f69578..317900e56ba 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -346,6 +346,68 @@ nan: enabled: 啟用ê enabled_msg: Hit ê emoji成功啟用ah image_hint: Sài-suh無超過 %{size} ê PNG á是 GIF + list: 列單 + listed: 加入列單ah + new: + title: 加添新ê自訂emoji + no_emoji_selected: 因為無揀任何emoji,所以lóng無改變 + not_permitted: Lí無允准行tsit ê動作 + overwrite: Khàm掉 + shortcode: 短碼 + shortcode_hint: 字元上少2 ê,kan-ta接受字母、數字kap底線(_) + title: 自訂emoji + uncategorized: Iáu無分類 + unlist: Tuì列單the̍h掉 + unlisted: The̍h掉ah + update_failed_msg: Bē當更新hit ê emoji + updated_msg: Emoji成功更新ah! + upload: 傳上去 + dashboard: + active_users: 活動ê用者 + interactions: 互動 + media_storage: 媒體儲存 + new_users: 新用者 + opened_reports: 拍開ê報告 + pending_appeals_html: + other: "%{count} ê投訴愛處理" + pending_reports_html: + other: "%{count} ê檢舉愛處理" + pending_tags_html: + other: "%{count} ê hashtag愛處理" + pending_users_html: + other: "%{count} ê用者愛處理" + resolved_reports: 解決ê報告 + software: 軟體 + sources: 註冊ê源頭 + space: 空間ê使用 + title: Le-jí-páng (dashboard) + top_languages: 上tsia̍p出現ê語言 + top_servers: 上tsia̍p活動ê服侍器 + website: 網站 + disputes: + appeals: + empty: Tshuē無投訴。 + title: 投訴 + domain_allows: + add_new: 允准kap tsit ê域名相連 + created_msg: 域名已經成功允准相連 + destroyed_msg: 域名已經成功取消相連 + export: 輸出 + import: 輸入 + undo: 禁止kap tsit ê域名相連 + domain_blocks: + add_new: 加添新ê封鎖域名 + confirm_suspension: + cancel: 取消 + confirm: 中止權限 + permanent_action: 取消中止權限,bē當復原任何資料á是關係。 + preamble_html: Lí teh beh停止 %{domain} kap伊ê kiánn域名ê權限。 + remove_all_data: Tse ē tī lí ê服侍器內底,kā tuì tsit ê域名ê口座來ê所有內容、媒體kap個人資料lóng thâi掉。 + stop_communication: Lí ê服侍器ē停止kap hia ê服侍器聯絡。 + title: 確認封鎖域名 %{domain} + edit: 編輯域名封鎖 + export: 輸出 + import: 輸入 instances: dashboard: instance_languages_dimension: Tsia̍p用ê語言 diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 27d2c860e18..1e630275ce8 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -578,6 +578,8 @@ pt-BR: all: Todos limited: Limitados title: Moderação + moderation_notes: + title: Notas de Moderação private_comment: Comentário privado public_comment: Comentário público purge: Limpar diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index dfe6c0b6d77..7706db6637d 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -56,6 +56,7 @@ eu: scopes: Zeintzuk API atzitu ditzakeen aplikazioak. Goi mailako arloa aukeratzen baduzu, ez dituzu azpikoak aukeratu behar. setting_aggregate_reblogs: Ez erakutsi bultzada berriak berriki bultzada jaso duten tootentzat (berriki jasotako bultzadei eragiten die bakarrik) setting_always_send_emails: Normalean eposta jakinarazpenak ez dira bidaliko Mastodon aktiboki erabiltzen ari zaren bitartean + setting_default_quote_policy: Aipaturiko erabiltzaileek beti dute aipatzeko baimena. Ezarpen honek Mastodon-en hurrengo bertsioarekin sortutako argitalpenetan bakarrik izango du eragina, baina prestatzean lehentasuna hauta dezakezu setting_default_sensitive: Multimedia hunkigarria lehenetsita ezkutatzen da, eta sakatuz ikusi daiteke setting_display_media_default: Ezkutatu hunkigarri gisa markatutako multimedia setting_display_media_hide_all: Ezkutatu multimedia guztia beti @@ -75,6 +76,7 @@ eu: filters: action: Aukeratu ze ekintza burutu behar den bidalketa bat iragazkiarekin bat datorrenean actions: + blur: Ezkutatu edukia ohar baten atzean, testua bera ezkutatu gabe hide: Ezkutatu erabat iragazitako edukia, existituko ez balitz bezala warn: Ezkutatu iragazitako edukia iragazkiaren izenburua duen abisu batekin form_admin_settings: @@ -88,6 +90,7 @@ eu: favicon: WEBP, PNG, GIF or JPG. Mastodon-en favicon-a gainidazten du ikono pertsonalizatu batekin. mascot: Web interfaze aurreratuko ilustrazioa gainidazten du. media_cache_retention_period: Multimedia-fitxategiak dituzten urruneko erabiltzaileen argitalpenak zure zerbitzarian gordetzen dira cachean. Balio positiboa ezartzean, multimedia zehazturiko egunen buruan ezabatuko da. Multimedia-datuak eskatzen badira ezabatu ostean, berriro deskargatuko dira, iturburuko edukia oraindik erabilgarri badago. Estekaren aurrebistako txartelek hirugarrenen guneei zenbatetan dei diezaieketen mugatzen dieten murrizketak direla eta, balio honi, gutxienez, 14 egunen balioa ezartzea gomendatzen da, bestela, esteken aurrebistako txartelak ez dira eguneratuko eskatu ahala denbora horren aurretik. + min_age: Erabiltzaileei jaiotze-data berresteko eskatuko zaie izen-ematean peers_api_enabled: Zerbitzari honek fedibertsoan ikusi dituen zerbitzarien domeinu-izenen zerrenda. Ez da daturik ematen zerbitzari jakin batekin federatzearen ala ez federatzearen inguruan, zerbitzariak haien berri duela soilik. Federazioari buruzko estatistika orokorrak biltzen dituzten zerbitzuek erabiltzen dute hau. profile_directory: Profilen direktorioan ikusgai egotea aukeratu duten erabiltzaile guztiak zerrendatzen dira. require_invite_text: Izen emateak eskuz onartu behar direnean, "Zergatik elkartu nahi duzu?" testu sarrera derrigorrezko bezala ezarri, ez hautazko @@ -132,9 +135,12 @@ eu: name: Letrak maiuskula/minuskulara aldatu ditzakezu besterik ez, adibidez irakurterrazago egiteko terms_of_service: changelog: Markdown-en sintaxiarekin egitura daiteke. + effective_date: Zentzuzko epe bat 10 eta 30 egun bitartekoa izan daiteke, erabiltzaileei jakinarazten diezun egunetik kontatzen hasita. text: Markdown-en sintaxiarekin egitura daiteke. terms_of_service_generator: admin_email: Legezko abisuak, kontraindikazioak, agindu judizialak, erretiratzeko eskaerak eta legea betetzeko eskaerak barne. + arbitration_address: Goiko helbide fisikoa edo "N/A" bera izan daiteke posta elektronikoa erabiliz gero. + arbitration_website: Web formularioa izan daiteke, edo "N/A" posta elektronikoa erabiliz gero. user: chosen_languages: Markatzean, hautatutako hizkuntzetan dauden tutak besterik ez dira erakutsiko. user_role: @@ -327,6 +333,9 @@ eu: terms_of_service_generator: domain: Domeinua user: + date_of_birth_1i: Eguna + date_of_birth_2i: Hilabetea + date_of_birth_3i: Urtea role: Rola time_zone: Ordu zona user_role: diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index b9f8d5d9c37..9e794028c58 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -126,7 +126,7 @@ vi: hint: Tùy chọn. Cung cấp chi tiết hơn về nội quy text: Mô tả một nội quy bắt buộc trên máy chủ này. Nên để ngắn và đơn giản sessions: - otp: 'Nhập mã xác minh 2 bước được tạo bởi ứng dụng điện thoại của bạn hoặc dùng một trong các mã khôi phục của bạn:' + otp: 'Nhập mã xác thực 2 bước được tạo bởi ứng dụng điện thoại của bạn hoặc dùng một trong các mã khôi phục của bạn:' webauthn: Nếu đây là USB key, hãy cắm vào và thử xoay chiều. settings: indexable: Trang của bạn có thể xuất hiện trong kết quả tìm kiếm trên Google, Bing và các nơi khác. @@ -221,7 +221,7 @@ vi: max_uses: Lượt dùng tối đa new_password: Mật khẩu mới note: Giới thiệu - otp_attempt: Mã xác minh 2 bước + otp_attempt: Mã xác thực 2 bước password: Mật khẩu phrase: Từ khóa hoặc cụm từ setting_advanced_layout: Bố cục nhiều cột diff --git a/config/locales/th.yml b/config/locales/th.yml index 558e95249b7..51db141fc54 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -472,6 +472,15 @@ th: created_at: สร้างเมื่อ delete: ลบ ip: ที่อยู่ IP + providers: + delete: ลบ + name: ชื่อ + registrations: + confirm: ยืนยัน + reject: ปฏิเสธ + save: บันทึก + sign_in: ลงชื่อเข้า + status: สถานะ follow_recommendations: description_html: "คำแนะนำการติดตามช่วยให้ผู้ใช้ใหม่ค้นหาเนื้อหาที่น่าสนใจได้อย่างรวดเร็ว เมื่อผู้ใช้ไม่ได้โต้ตอบกับผู้อื่นมากพอที่จะสร้างคำแนะนำการติดตามเฉพาะบุคคล จะแนะนำบัญชีเหล่านี้แทน จะคำนวณคำแนะนำใหม่เป็นประจำทุกวันจากบัญชีต่าง ๆ ที่มีการมีส่วนร่วมล่าสุดสูงสุดและจำนวนผู้ติดตามในเซิร์ฟเวอร์สูงสุดสำหรับภาษาที่กำหนด" language: สำหรับภาษา @@ -746,6 +755,8 @@ th: description_html: ขณะที่ส่วนใหญ่อ้างว่าได้อ่านและยอมรับเงื่อนไขการให้บริการ ผู้คนมักจะไม่อ่านจนกว่าหลังจากปัญหาเกิดขึ้น ทำให้การดูกฎของเซิร์ฟเวอร์ของคุณอย่างรวดเร็วง่ายขึ้นโดยการระบุกฎเหล่านั้นในรายการสัญลักษณ์แสดงหัวข้อย่อยแบบแบน พยายามทำให้กฎแต่ละข้อสั้นและเรียบง่าย แต่พยายามอย่าแบ่งกฎเหล่านั้นเป็นหลายรายการแยกเช่นกัน edit: แก้ไขกฎ empty: ยังไม่ได้กำหนดกฎของเซิร์ฟเวอร์ + move_down: ย้ายลง + move_up: ย้ายขึ้น title: กฎของเซิร์ฟเวอร์ settings: about: @@ -772,6 +783,7 @@ th: discovery: follow_recommendations: คำแนะนำการติดตาม preamble: การแสดงเนื้อหาที่น่าสนใจเป็นเครื่องมือในการเตรียมความพร้อมให้ผู้ใช้ใหม่ที่อาจไม่รู้จักใครก็ตามใน Mastodon ควบคุมวิธีที่คุณลักษณะการค้นพบต่าง ๆ ทำงานในเซิร์ฟเวอร์ของคุณ + privacy: ความเป็นส่วนตัว profile_directory: ไดเรกทอรีโปรไฟล์ public_timelines: เส้นเวลาสาธารณะ publish_statistics: เผยแพร่สถิติ diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 271683c2389..93ab1fb811d 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -60,7 +60,7 @@ vi: destroyed_msg: Dữ liệu %{username} sẽ được lên lịch xóa ngay bây giờ disable: Khóa disable_sign_in_token_auth: Tắt xác minh bằng email - disable_two_factor_authentication: Vô hiệu hóa xác minh 2 bước + disable_two_factor_authentication: Vô hiệu hóa xác thực 2 bước disabled: Tạm khóa display_name: Biệt danh domain: Máy chủ @@ -265,7 +265,7 @@ vi: destroy_status_html: "%{name} đã xóa tút của %{target}" destroy_unavailable_domain_html: "%{name} tiếp tục liên hợp với máy chủ %{target}" destroy_user_role_html: "%{name} đã xóa vai trò %{target}" - disable_2fa_user_html: "%{name} đã vô hiệu hóa xác minh hai bước của %{target}" + disable_2fa_user_html: "%{name} đã vô hiệu hóa xác thực 2 bước của %{target}" disable_custom_emoji_html: "%{name} đã ẩn emoji %{target}" disable_relay_html: "%{name} đã tắt relay %{target}" disable_sign_in_token_auth_user_html: "%{name} đã tắt xác minh email của %{target}" @@ -765,7 +765,7 @@ vi: manage_taxonomies: Quản lý phân loại manage_taxonomies_description: Cho phép đánh giá nội dung xu hướng và cập nhật cài đặt hashtag manage_user_access: Quản lý người truy cập - manage_user_access_description: Cho phép người dùng tắt xác minh hai bước của người khác, đổi địa chỉ email và đặt lại mật khẩu của họ + manage_user_access_description: Cho phép người dùng tắt xác thực 2 bước của người khác, đổi địa chỉ email và đặt lại mật khẩu của họ manage_users: Quản lý người manage_users_description: Cho phép xem thông tin chi tiết của người khác và thực hiện các hành động kiểm duyệt manage_webhooks: Quản lý Webhook @@ -1524,11 +1524,11 @@ vi: limit: Bạn đã đạt đến số lượng danh sách tối đa login_activities: authentication_methods: - otp: xác minh 2 bước + otp: xác thực 2 bước password: mật khẩu sign_in_token: mã an toàn email webauthn: khóa bảo mật - description_html: Nếu có lần đăng nhập đáng ngờ, hãy đổi ngay mật khẩu và bật xác minh 2 bước. + description_html: Nếu có lần đăng nhập đáng ngờ, hãy đổi ngay mật khẩu và bật xác thực 2 bước. empty: Không có lịch sử đăng nhập failed_sign_in_html: Đăng nhập thất bại bằng %{method} từ %{ip} (%{browser}) successful_sign_in_html: Đăng nhập bằng %{method} từ %{ip} (%{browser}) @@ -1795,7 +1795,7 @@ vi: severed_relationships: Quan hệ đứt gãy statuses_cleanup: Tự động xóa tút cũ strikes: Lần cảnh cáo - two_factor_authentication: Xác minh 2 bước + two_factor_authentication: Xác thực 2 bước webauthn_authentication: Khóa bảo mật severed_relationships: download: Tải xuống (%{count}) @@ -1911,10 +1911,10 @@ vi: two_factor_authentication: add: Thêm disable: Vô hiệu hóa - disabled_success: Đã vô hiệu hóa xác minh 2 bước + disabled_success: Đã vô hiệu hóa xác thực 2 bước edit: Sửa - enabled: Đã kích hoạt xác minh 2 bước - enabled_success: Xác minh 2 bước được kích hoạt thành công + enabled: Đã kích hoạt xác thực 2 bước + enabled_success: Xác thực 2 bước được kích hoạt thành công generate_recovery_codes: Tạo mã khôi phục lost_recovery_codes: Mã khôi phục cho phép bạn lấy lại quyền truy cập vào tài khoản của mình nếu bạn mất điện thoại. Nếu bạn bị mất mã khôi phục, bạn có thể tạo lại chúng ở đây. Mã khôi phục cũ của bạn sẽ bị vô hiệu. methods: Phương pháp xác minh @@ -1948,13 +1948,13 @@ vi: details: 'Chi tiết thông tin đăng nhập:' explanation: Ai đó đã cố đăng nhập vào tài khoản của bạn nhưng cung cấp yếu tố xác thực thứ hai không hợp lệ. further_actions_html: Nếu không phải bạn, hãy lập tức %{action} vì có thể có rủi ro. - subject: Xác minh hai bước thất bại - title: Xác minh hai bước thất bại + subject: Xác thực 2 bước thất bại + title: Xác thực 2 bước thất bại suspicious_sign_in: change_password: đổi mật khẩu của bạn details: 'Chi tiết thông tin đăng nhập:' explanation: Chúng tôi phát hiện tài khoản của bạn đăng nhập bất thường từ một địa chỉ IP mới. - further_actions_html: Nếu đây không phải là bạn, hãy %{action} lập tức và bật xác minh hai bước để giữ tài khoản được an toàn. + further_actions_html: Nếu đây không phải là bạn, hãy %{action} lập tức và bật xác thực 2 bước để giữ tài khoản được an toàn. subject: Đăng nhập tài khoản từ địa chỉ IP mới title: Lần đăng nhập mới terms_of_service_changed: @@ -2039,7 +2039,7 @@ vi: users: follow_limit_reached: Bạn chỉ có thể theo dõi tối đa %{limit} người go_to_sso_account_settings: Thiết lập tài khoản nhà cung cấp danh tính - invalid_otp_token: Mã xác minh 2 bước không hợp lệ + invalid_otp_token: Mã xác thực 2 bước không hợp lệ otp_lost_help_html: Nếu bạn mất quyền truy cập vào cả hai, bạn có thể đăng nhập bằng %{email} rate_limited: Quá nhiều lần thử, vui lòng thử lại sau. seamless_external_login: Bạn đã đăng nhập thông qua một dịch vụ bên ngoài, vì vậy mật khẩu và email không khả dụng. @@ -2067,5 +2067,5 @@ vi: nickname_hint: Nhập tên mới cho khóa bảo mật của bạn not_enabled: Bạn chưa kích hoạt WebAuthn not_supported: Trình duyệt của bạn không hỗ trợ khóa bảo mật - otp_required: Để dùng khóa bảo mật, trước tiên hãy kích hoạt xác minh 2 bước. + otp_required: Để dùng khóa bảo mật, trước tiên hãy kích hoạt xác thực 2 bước. registered_on: Đăng ký vào %{date} From fdefc4d2b4b955729b0ff0b2c5ca4ffb10a5e70b Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 7 Jul 2025 11:22:22 +0200 Subject: [PATCH 016/660] Add ability to manually trigger i18n uploads (#35279) --- .github/workflows/crowdin-upload.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 4f4d917d15a..d0d79d91996 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -14,6 +14,7 @@ on: - config/locales/devise.en.yml - config/locales/doorkeeper.en.yml - .github/workflows/crowdin-upload.yml + workflow_dispatch: jobs: upload-translations: From b5eebd4d2ba22fe5ae763fd6cc0fe24649d282a4 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 7 Jul 2025 17:10:51 +0200 Subject: [PATCH 017/660] fix: Fix can't skip search field by tabbing (#35281) --- .../features/compose/components/search.tsx | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/search.tsx b/app/javascript/mastodon/features/compose/components/search.tsx index ae242190e8a..2d44772ba2d 100644 --- a/app/javascript/mastodon/features/compose/components/search.tsx +++ b/app/javascript/mastodon/features/compose/components/search.tsx @@ -47,10 +47,6 @@ const labelForRecentSearch = (search: RecentSearch) => { } }; -const unfocus = () => { - document.querySelector('.ui')?.parentElement?.focus(); -}; - const ClearButton: React.FC<{ onClick: () => void; hasValue: boolean; @@ -107,6 +103,11 @@ export const Search: React.FC<{ }, [initialValue]); const searchOptions: SearchOption[] = []; + const unfocus = useCallback(() => { + document.querySelector('.ui')?.parentElement?.focus(); + setExpanded(false); + }, []); + if (searchEnabled) { searchOptions.push( { @@ -282,7 +283,7 @@ export const Search: React.FC<{ history.push({ pathname: '/search', search: queryParams.toString() }); unfocus(); }, - [dispatch, history], + [dispatch, history, unfocus], ); const handleChange = useCallback( @@ -402,7 +403,7 @@ export const Search: React.FC<{ setQuickActions(newQuickActions); }, - [dispatch, history, signedIn, setValue, setQuickActions, submit], + [signedIn, dispatch, unfocus, history, submit], ); const handleClear = useCallback(() => { @@ -410,7 +411,7 @@ export const Search: React.FC<{ setQuickActions([]); setSelectedOption(-1); unfocus(); - }, [setValue, setQuickActions, setSelectedOption]); + }, [unfocus]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -461,7 +462,7 @@ export const Search: React.FC<{ break; } }, - [navigableOptions, value, selectedOption, setSelectedOption, submit], + [unfocus, navigableOptions, selectedOption, submit, value], ); const handleFocus = useCallback(() => { @@ -481,12 +482,38 @@ export const Search: React.FC<{ }, [setExpanded, setSelectedOption, singleColumn]); const handleBlur = useCallback(() => { - setExpanded(false); setSelectedOption(-1); - }, [setExpanded, setSelectedOption]); + }, [setSelectedOption]); + + const formRef = useRef(null); + + useEffect(() => { + // If the search popover is expanded, close it when tabbing or + // clicking outside of it or the search form, while allowing + // tabbing or clicking inside of the popover + if (expanded) { + function closeOnLeave(event: FocusEvent | MouseEvent) { + const form = formRef.current; + const isClickInsideForm = + form && + (form === event.target || form.contains(event.target as Node)); + if (!isClickInsideForm) { + setExpanded(false); + } + } + document.addEventListener('focusin', closeOnLeave); + document.addEventListener('click', closeOnLeave); + + return () => { + document.removeEventListener('focusin', closeOnLeave); + document.removeEventListener('click', closeOnLeave); + }; + } + return () => null; + }, [expanded]); return ( -
+ -
+
{!hasValue && ( <>

From b10078633c1d34d626ed48af19f16cd2f81253cc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:35:54 +0200 Subject: [PATCH 018/660] chore(deps): update dependency libvips to v8.17.1 (#35283) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6cb4a2a1c0a..b7d1159dfcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -186,7 +186,7 @@ FROM build AS libvips # libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"] # renovate: datasource=github-releases depName=libvips packageName=libvips/libvips -ARG VIPS_VERSION=8.17.0 +ARG VIPS_VERSION=8.17.1 # libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"] ARG VIPS_URL=https://github.com/libvips/libvips/releases/download From 71b2120e5c51c1c0e2d41f5a04f9d8956e1eb317 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:22:38 +0200 Subject: [PATCH 019/660] New Crowdin Translations (automated) (#35286) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 2 +- config/locales/bg.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 74515fd702e..f88f04afedd 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -888,7 +888,7 @@ "status.redraft": "Slet og omskriv", "status.remove_bookmark": "Fjern bogmærke", "status.remove_favourite": "Fjern fra favoritter", - "status.replied_in_thread": "Svaret i tråd", + "status.replied_in_thread": "Svarede i tråd", "status.replied_to": "Svarede {name}", "status.reply": "Besvar", "status.replyAll": "Svar alle", diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 137f9c711f2..cbb0b682fd3 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1916,6 +1916,8 @@ bg: terms_of_service_interstitial: future_preamble_html: Правим някои промени в условията на услугата ни, което ще влезе в сила на %{date}. Насърчаваме ви да разгледате обновените условия. past_preamble_html: Променихме условията на услугата ни от последното ви посещение. Насърчаваме ви да разгледате обновените условия. + review_link: Разглеждане на условията на услугата + title: Условията на услугата на %{domain} се променят themes: contrast: Mastodon (висок контраст) default: Mastodon (тъмно) From 8ee6cee36e313fcc39763210dfcd3b871b47cacb Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Tue, 8 Jul 2025 11:31:04 +0200 Subject: [PATCH 020/660] Better error response to malformed headers (#35278) --- .../concerns/signature_verification.rb | 3 +++ app/lib/signed_request.rb | 2 ++ lib/exceptions.rb | 1 + spec/requests/signature_verification_spec.rb | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 902feef6838..b61a569860f 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -64,6 +64,9 @@ module SignatureVerification return (@signed_request_actor = actor) if signed_request.verified?(actor) fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}" + rescue Mastodon::MalformedHeaderError => e + @signature_verification_failure_code = 400 + fail_with! e.message rescue Mastodon::SignatureVerificationError => e fail_with! e.message rescue *Mastodon::HTTP_CONNECTION_ERRORS => e diff --git a/app/lib/signed_request.rb b/app/lib/signed_request.rb index 066411c680f..0ee47ddae12 100644 --- a/app/lib/signed_request.rb +++ b/app/lib/signed_request.rb @@ -196,6 +196,8 @@ class SignedRequest return if body_digest == received_digest raise Mastodon::SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{received_digest}" + rescue Starry::ParseError + raise Mastodon::MalformedHeaderError, 'Content-Digest could not be parsed. It does not contain a valid RFC8941 dictionary.' end def created_time diff --git a/lib/exceptions.rb b/lib/exceptions.rb index 93fcc38dce8..c8c81983825 100644 --- a/lib/exceptions.rb +++ b/lib/exceptions.rb @@ -13,6 +13,7 @@ module Mastodon class SyntaxError < Error; end class InvalidParameterError < Error; end class SignatureVerificationError < Error; end + class MalformedHeaderError < Error; end class UnexpectedResponseError < Error attr_reader :response diff --git a/spec/requests/signature_verification_spec.rb b/spec/requests/signature_verification_spec.rb index edcc2b673df..eccb2babc98 100644 --- a/spec/requests/signature_verification_spec.rb +++ b/spec/requests/signature_verification_spec.rb @@ -621,6 +621,30 @@ RSpec.describe 'signature verification concern' do ) end end + + context 'with a malformed `Content-Digest` header' do + let(:digest_header) { 'SHA-256=:ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=:' } + let(:signature_input) do + 'sig1=("@method" "@target-uri" "content-digest");created=1703066400;keyid="https://remote.domain/users/bob#main-key"' + end + let(:signature_header) do + 'sig1=:aXua24cIlBi8akNXg/Vc5pU8fNGXo0f4U2qQk42iWoIaCcH3G+z2edPMQTNM/aZmD0bULqvb/yi6ZXgRls1ereq3OqnvA4JBLKx15O/jLayS/FhR4d/2vaeXuBOYXM7EGXItKkFxEXn3J+FCQPb5wY31GlbljrESjsiZ6gtrSmwryBluQCwMJ59LACzocxbWo42Kv3cpSig2aCu9CYXKC4sCH3eSKjwPtjdlpmX1VkYX5ge+JaZMn7A218ZgZOc9xpPawESOuIF9axcKW5PDEhOwmswFd2G65c8H9kJY6zEnqbArP9lRQMmjuAb011NILClaaRZOOupz2HZUdm+91Q==:' # rubocop:disable Layout/LineLength + end + + it 'returns `400` (Bad Request)', :aggregate_failures do + post '/activitypub/signature_required', params: 'Hello world', headers: { + 'Host' => 'www.example.com', + 'Content-Digest' => digest_header, + 'Signature-Input' => signature_input, + 'Signature' => signature_header, + } + + expect(response).to have_http_status(400) + expect(response.parsed_body).to match( + error: 'Content-Digest could not be parsed. It does not contain a valid RFC8941 dictionary.' + ) + end + end end context 'with an inaccessible key' do From ef66d8379c7c2343e6b14263b302cfa502e2fe82 Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 8 Jul 2025 12:51:11 +0200 Subject: [PATCH 021/660] Add option to set emoji preferences behind feature flag (#35282) --- app/models/user_settings.rb | 1 + .../settings/preferences/appearance/show.html.haml | 11 +++++++++++ config/locales/en.yml | 4 ++++ config/locales/simple_form.en.yml | 2 ++ 4 files changed, 18 insertions(+) diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 590eed713ca..fd8659dc970 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -35,6 +35,7 @@ class UserSettings setting :expand_content_warnings, default: false setting :display_media, default: 'default', in: %w(default show_all hide_all) setting :auto_play, default: false + setting :emoji_style, default: 'auto', in: %w(auto native twemoji) end namespace :notification_emails do diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 8fd7a85defa..72e2575a8ea 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -31,6 +31,17 @@ label: I18n.t('simple_form.labels.defaults.setting_theme'), wrapper: :with_label + - if Mastodon::Feature.modern_emojis_enabled? + .fields-group + = f.simple_fields_for :settings, current_user.settings do |ff| + = ff.input :'web.emoji_style', + collection: %w(auto twemoji native), + include_blank: false, + hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'), + label: I18n.t('simple_form.labels.defaults.setting_emoji_style'), + label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) }, + wrapper: :with_label + - unless I18n.locale == :en .flash-message.translation-prompt #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: '_blank', rel: 'noopener')} diff --git a/config/locales/en.yml b/config/locales/en.yml index 6633ffa4a95..4df63f4c738 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1349,6 +1349,10 @@ en: basic_information: Basic information hint_html: "Customize what people see on your public profile and next to your posts. Other people are more likely to follow you back and interact with you when you have a filled out profile and a profile picture." other: Other + emoji_styles: + auto: Auto + native: Native + twemoji: Twemoji errors: '400': The request you submitted was invalid or malformed. '403': You don't have permission to view this page. diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index bb48cddff54..74614d1af62 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -61,6 +61,7 @@ en: setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media + setting_emoji_style: How to display emojis. "Auto" will try using native emoji, but falls back to Twemoji for legacy browsers. setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed @@ -241,6 +242,7 @@ en: setting_display_media_default: Default setting_display_media_hide_all: Hide all setting_display_media_show_all: Show all + setting_emoji_style: Emoji style setting_expand_spoilers: Always expand posts marked with content warnings setting_hide_network: Hide your social graph setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text From d8fa807998b105d2b3dce2db693082bedbf8ac6a Mon Sep 17 00:00:00 2001 From: Miguel Landaeta Date: Tue, 8 Jul 2025 14:04:16 +0100 Subject: [PATCH 022/660] Bump linzer to 0.7.7 (#35258) --- Gemfile | 2 +- Gemfile.lock | 4 ++-- config/initializers/linzer.rb | 22 ++++++---------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index ffd5371b06d..ce775fc57bc 100644 --- a/Gemfile +++ b/Gemfile @@ -62,7 +62,7 @@ gem 'inline_svg' gem 'irb', '~> 1.8' gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' -gem 'linzer', '~> 0.7.2' +gem 'linzer', '~> 0.7.7' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar' gem 'mutex_m' diff --git a/Gemfile.lock b/Gemfile.lock index c59f26c44db..f306eb8619f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -403,7 +403,7 @@ GEM rexml link_header (0.0.8) lint_roller (1.1.0) - linzer (0.7.3) + linzer (0.7.7) cgi (~> 0.4.2) forwardable (~> 1.3, >= 1.3.3) logger (~> 1.7, >= 1.7.0) @@ -1008,7 +1008,7 @@ DEPENDENCIES letter_opener (~> 1.8) letter_opener_web (~> 3.0) link_header (~> 0.0) - linzer (~> 0.7.2) + linzer (~> 0.7.7) lograge (~> 0.12) mail (~> 2.8) mario-redis-lock (~> 1.2) diff --git a/config/initializers/linzer.rb b/config/initializers/linzer.rb index 119f65af17b..b21d405cd1b 100644 --- a/config/initializers/linzer.rb +++ b/config/initializers/linzer.rb @@ -5,24 +5,14 @@ require 'linzer/message/adapter/http_gem/response' module Linzer::Message::Adapter module ActionDispatch - class Response < Linzer::Message::Adapter::Abstract - def initialize(operation, **_options) # rubocop:disable Lint/MissingSuper - @operation = operation - end - - def header(name) - @operation.headers[name] - end - - def attach!(signature) - signature.to_h.each { |h, v| @operation.headers[h] = v } - end + class Response < Linzer::Message::Adapter::Generic::Response + private # Incomplete, but sufficient for FASP - def [](field_name) - return @operation.status if field_name == '@status' - - @operation.headers[field_name] + def derived(name) + case name.value + when '@status' then @operation.status + end end end end From b628a98d323902cdcc08fb8d612887dff4dbcd89 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 8 Jul 2025 16:26:43 +0200 Subject: [PATCH 023/660] Bump version to v4.4.0 (#35291) --- CHANGELOG.md | 11 ++++++----- SECURITY.md | 11 ++++++----- docker-compose.yml | 6 +++--- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efdd3adf120..59a8a926827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [4.4.0] - UNRELEASED +## [4.4.0] - 2025-07-08 ### Added @@ -38,7 +38,7 @@ All notable changes to this project will be documented in this file. Server administrators can now chose to opt in to transmit referrer information when following an external link. Only the domain name is transmitted, not the referrer path. - Add double tap to zoom and swipe to dismiss to media modal in web UI (#34210 by @Gargron) - Add link from Web UI for Hashtags to the Moderation UI (#31448 by @ThisIsMissEm) -- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126 and #35127 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\ +- **Add terms of service** (#33055, #33233, #33230, #33703, #33699, #33994, #33993, #34105, #34122, #34200, #34527, #35053, #35115, #35126, #35127 and #35233 by @ClearlyClaire, @Gargron, @mjankowski, and @oneiros)\ Server administrators can now fill in Terms of Service and notify their users of upcoming changes. - Add optional bulk mailer settings (#35191 and #35203 by @oneiros)\ This adds the optional environment variables `BULK_SMTP_PORT`, `BULK_SMTP_SERVER`, `BULK_SMTP_LOGIN` and so on analogous to `SMTP_PORT`, `SMTP_SERVER`, `SMTP_LOGIN` and related SMTP configuration environment variables.\ @@ -51,7 +51,7 @@ All notable changes to this project will be documented in this file. - Add ability to dismiss alt text badge by tapping it in web UI (#33737 by @Gargron) - Add loading indicator to timeline gap indicators in web UI (#33762 by @Gargron) - Add interaction modal when trying to interact with a poll while logged out (#32609 by @ThisIsMissEm) -- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033 and #35218 by @oneiros)\ +- **Add experimental FASP support** (#34031, #34415, #34765, #34965, #34964, #34033, #35218, #35262 and #35263 by @oneiros)\ This is a first step towards supporting “Fediverse Auxiliary Service Providers” (https://github.com/mastodon/fediverse_auxiliary_service_provider_specifications). This is mostly interesting to developers who would like to implement their own FASP, but also includes the capability to share data with a discovery provider (see https://www.fediscovery.org). - Add ability for admins to send announcements to all users via email (#33928 and #34411 by @ClearlyClaire)\ This is meant for critical announcements only, as this will potentially send a lot of emails and cannot be opted out of by users. @@ -64,7 +64,7 @@ All notable changes to this project will be documented in this file. - Add dropdown menu with quick actions to lists of accounts in web UI (#34391, #34709, and #34767 by @Gargron, @diondiondion, and @mkljczk) - Add support for displaying “year in review” notification in web UI (#32710, #32765, #32709, #32807, #32914, #33148, and #33882 by @Gargron and @mjankowski)\ Note that the notification is currently not generated automatically, and at the moment requires a manual undocumented administrator action. -- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033 and #35109 by @oneiros)\ +- Add experimental support for receiving HTTP Message Signatures (RFC9421) (#34814, #35033, #35109 and #35278 by @oneiros)\ For now, this needs to be explicitly enabled through the `http_message_signatures` feature flag (`EXPERIMENTAL_FEATURES=http_message_signatures`). This currently only covers verifying such signatures (inbound HTTP requests), not issuing them (outbound HTTP requests). - Add experimental Async Refreshes API (#34918 by @oneiros) - Add experimental server-side feature to fetch remote replies (#32615, #34147, #34149, #34151, #34615, #34682, and #34702 by @ClearlyClaire and @sneakers-the-rat)\ @@ -218,6 +218,7 @@ All notable changes to this project will be documented in this file. - Fix admin dashboard crash on specific Elasticsearch connection errors (#34683 by @ClearlyClaire) - Fix OIDC account creation failing for long display names (#34639 by @defnull) - Fix use of the deprecated `/api/v1/instance` endpoint in the moderation interface (#34613 by @renchap) +- Fix inaccessible “Clear search” button (#35152 and #35281 by @diondiondion) - Fix search operators sometimes getting lost (#35190 by @ClearlyClaire) - Fix directory scroll position reset (#34560 by @przucidlo) - Fix needlessly complex SVG paths for oEmbed and logo (#34538 by @edent) @@ -232,7 +233,7 @@ All notable changes to this project will be documented in this file. - Fix extra space under left-indented vertical videos (#34313 by @ClearlyClaire) - Fix glitchy iOS media attachment drag interactions (#35057 by @diondiondion) - Fix zoomed images being blurry in Safari (#35052 by @diondiondion) -- Fix redundant focus stop within status component in Web UI and make focus style more noticeable (#35037, #35051, #35096 and #35150 by @diondiondion) +- Fix redundant focus stop within status component in Web UI and make focus style more noticeable (#35037, #35051, #35096, #35150 and #35251 by @diondiondion) - Fix digits in media player time readout not having a consistent width (#35038 by @diondiondion) - Fix wrong text color for “Open in advanced web interface” banner in high-contrast theme (#35032 by @diondiondion) - Fix hover card for limited accounts not hiding information as expected (#35024 by @diondiondion) diff --git a/SECURITY.md b/SECURITY.md index 26c06e67f83..19f431fac59 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,8 +13,9 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through ## Supported Versions -| Version | Supported | -| ------- | --------- | -| 4.3.x | Yes | -| 4.2.x | Yes | -| < 4.2 | No | +| Version | Supported | +| ------- | ---------------- | +| 4.4.x | Yes | +| 4.3.x | Yes | +| 4.2.x | Until 2026-01-08 | +| < 4.2 | No | diff --git a/docker-compose.yml b/docker-compose.yml index 454ef91f9fb..4926101b954 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.3.8 + image: ghcr.io/mastodon/mastodon:v4.4.0 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/mastodon/mastodon-streaming:v4.3.8 + image: ghcr.io/mastodon/mastodon-streaming:v4.4.0 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.3.8 + image: ghcr.io/mastodon/mastodon:v4.4.0 restart: always env_file: .env.production command: bundle exec sidekiq From f7259f625feb8e19d337d122ff6c8c61ec4b488e Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 9 Jul 2025 04:03:39 -0400 Subject: [PATCH 024/660] Prefer `on: :update` in Tag validation declaration (#35297) --- app/models/tag.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index a3ccdd8ac6b..8e21ddca82b 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -49,8 +49,8 @@ class Tag < ApplicationRecord validates :name, presence: true, format: { with: HASHTAG_NAME_RE } validates :display_name, format: { with: HASHTAG_NAME_RE } - validate :validate_name_change, if: -> { !new_record? && name_changed? } - validate :validate_display_name_change, if: -> { !new_record? && display_name_changed? } + validate :validate_name_change, on: :update, if: :name_changed? + validate :validate_display_name_change, on: :update, if: :display_name_changed? scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) } scope :usable, -> { where(usable: [true, nil]) } From fb6c22f5c275685aa644d84c003e1d6922e15d40 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 9 Jul 2025 04:04:00 -0400 Subject: [PATCH 025/660] Use `touch` to record viewing annual report (#35296) --- app/models/generated_annual_report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/generated_annual_report.rb b/app/models/generated_annual_report.rb index 43c97d7108b..aba0712fe40 100644 --- a/app/models/generated_annual_report.rb +++ b/app/models/generated_annual_report.rb @@ -24,7 +24,7 @@ class GeneratedAnnualReport < ApplicationRecord end def view! - update!(viewed_at: Time.now.utc) + touch(:viewed_at) end def account_ids From 1e2d77f2c72c373bf1800a8faa62a8b2966b7afc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 9 Jul 2025 04:45:29 -0400 Subject: [PATCH 026/660] Use `if_exists: true` when removing duplicate indexes (#35309) --- db/migrate/20241014010506_remove_duplicate_indexes.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/db/migrate/20241014010506_remove_duplicate_indexes.rb b/db/migrate/20241014010506_remove_duplicate_indexes.rb index 50e0e6ffcfc..0c71936c431 100644 --- a/db/migrate/20241014010506_remove_duplicate_indexes.rb +++ b/db/migrate/20241014010506_remove_duplicate_indexes.rb @@ -2,9 +2,11 @@ class RemoveDuplicateIndexes < ActiveRecord::Migration[7.1] def change - remove_index :account_aliases, :account_id - remove_index :account_relationship_severance_events, :account_id - remove_index :custom_filter_statuses, :status_id - remove_index :webauthn_credentials, :user_id + with_options if_exists: true do + remove_index :account_aliases, :account_id + remove_index :account_relationship_severance_events, :account_id + remove_index :custom_filter_statuses, :status_id + remove_index :webauthn_credentials, :user_id + end end end From 8bd2c87399eebdb3c345ba1cb1c6a7b6d5b69a9d Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 9 Jul 2025 10:58:41 +0200 Subject: [PATCH 027/660] Fix support for special characters in various environment variables (#35314) Co-authored-by: Matt Jankowski --- config/cache_buster.yml | 4 ++-- config/email.yml | 18 +++++++++--------- config/mastodon.yml | 10 +++++----- config/translation.yml | 6 +++--- config/vapid.yml | 4 ++-- spec/configuration/email_spec.rb | 22 ++++++++++++++++++++++ 6 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 spec/configuration/email_spec.rb diff --git a/config/cache_buster.yml b/config/cache_buster.yml index 709c0eba887..09d6cfc6eaf 100644 --- a/config/cache_buster.yml +++ b/config/cache_buster.yml @@ -1,5 +1,5 @@ shared: enabled: <%= ENV.fetch('CACHE_BUSTER_ENABLED', 'false') == 'true' %> - secret_header: <%= ENV.fetch('CACHE_BUSTER_SECRET_HEADER', nil) %> - secret: <%= ENV.fetch('CACHE_BUSTER_SECRET', nil) %> + secret_header: <%= ENV.fetch('CACHE_BUSTER_SECRET_HEADER', nil)&.to_json %> + secret: <%= ENV.fetch('CACHE_BUSTER_SECRET', nil)&.to_json %> http_method: <%= ENV.fetch('CACHE_BUSTER_HTTP_METHOD', 'GET') %> diff --git a/config/email.yml b/config/email.yml index a6d34c30068..f39aa2545ae 100644 --- a/config/email.yml +++ b/config/email.yml @@ -2,14 +2,14 @@ # keys are added here. production: delivery_method: <%= ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp') %> - from_address: <%= ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost') %> - reply_to: <%= ENV.fetch('SMTP_REPLY_TO', nil) %> - return_path: <%= ENV.fetch('SMTP_RETURN_PATH', nil) %> + from_address: <%= ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost')&.to_json %> + reply_to: <%= ENV.fetch('SMTP_REPLY_TO', nil)&.to_json %> + return_path: <%= ENV.fetch('SMTP_RETURN_PATH', nil)&.to_json %> smtp_settings: port: <%= ENV.fetch('SMTP_PORT', nil) %> - address: <%= ENV.fetch('SMTP_SERVER', nil) %> - user_name: <%= ENV.fetch('SMTP_LOGIN', nil) %> - password: <%= ENV.fetch('SMTP_PASSWORD', nil) %> + address: <%= ENV.fetch('SMTP_SERVER', nil)&.to_json %> + user_name: <%= ENV.fetch('SMTP_LOGIN', nil)&.to_json %> + password: <%= ENV.fetch('SMTP_PASSWORD', nil)&.to_json %> domain: <%= ENV.fetch('SMTP_DOMAIN', ENV.fetch('LOCAL_DOMAIN', nil)) %> authentication: <%= ENV.fetch('SMTP_AUTH_METHOD', 'plain') %> ca_file: <%= ENV.fetch('SMTP_CA_FILE', '/etc/ssl/certs/ca-certificates.crt') %> @@ -22,9 +22,9 @@ production: bulk_mail: smtp_settings: port: <%= ENV.fetch('BULK_SMTP_PORT', nil) %> - address: <%= ENV.fetch('BULK_SMTP_SERVER', nil) %> - user_name: <%= ENV.fetch('BULK_SMTP_LOGIN', nil) %> - password: <%= ENV.fetch('BULK_SMTP_PASSWORD', nil) %> + address: <%= ENV.fetch('BULK_SMTP_SERVER', nil)&.to_json %> + user_name: <%= ENV.fetch('BULK_SMTP_LOGIN', nil)&.to_json %> + password: <%= ENV.fetch('BULK_SMTP_PASSWORD', nil)&.to_json %> domain: <%= ENV.fetch('BULK_SMTP_DOMAIN', ENV.fetch('LOCAL_DOMAIN', nil)) %> authentication: <%= ENV.fetch('BULK_SMTP_AUTH_METHOD', 'plain') %> ca_file: <%= ENV.fetch('BULK_SMTP_CA_FILE', '/etc/ssl/certs/ca-certificates.crt') %> diff --git a/config/mastodon.yml b/config/mastodon.yml index 31c2b2b7854..4585e1f2aee 100644 --- a/config/mastodon.yml +++ b/config/mastodon.yml @@ -2,14 +2,14 @@ shared: experimental_features: <%= ENV.fetch('EXPERIMENTAL_FEATURES', nil) %> limited_federation_mode: <%= (ENV.fetch('LIMITED_FEDERATION_MODE', nil) || ENV.fetch('WHITELIST_MODE', nil)) == 'true' %> - self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %> - software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %> + self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil)&.to_json %> + software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check')&.to_json %> source: - base_url: <%= ENV.fetch('SOURCE_BASE_URL', nil) %> + base_url: <%= ENV.fetch('SOURCE_BASE_URL', nil)&.to_json %> repository: <%= ENV.fetch('GITHUB_REPOSITORY', 'mastodon/mastodon') %> tag: <%= ENV.fetch('SOURCE_TAG', nil) %> version: - metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil) %> - prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %> + metadata: <%= ENV.fetch('MASTODON_VERSION_METADATA', nil)&.to_json %> + prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil)&.to_json %> test: experimental_features: <%= [ENV.fetch('EXPERIMENTAL_FEATURES', nil), 'testing_only'].compact.join(',') %> diff --git a/config/translation.yml b/config/translation.yml index e074c5d0f22..75754928ee8 100644 --- a/config/translation.yml +++ b/config/translation.yml @@ -1,7 +1,7 @@ shared: deepl: - api_key: <%= ENV.fetch('DEEPL_API_KEY', nil) %> + api_key: <%= ENV.fetch('DEEPL_API_KEY', nil)&.to_json %> plan: <%= ENV.fetch('DEEPL_PLAN', 'free') %> libre_translate: - api_key: <%= ENV.fetch('LIBRE_TRANSLATE_API_KEY', nil) %> - endpoint: <%= ENV.fetch('LIBRE_TRANSLATE_ENDPOINT', nil) %> + api_key: <%= ENV.fetch('LIBRE_TRANSLATE_API_KEY', nil)&.to_json %> + endpoint: <%= ENV.fetch('LIBRE_TRANSLATE_ENDPOINT', nil)&.to_json %> diff --git a/config/vapid.yml b/config/vapid.yml index c3ee806fd6a..49d8cd0de80 100644 --- a/config/vapid.yml +++ b/config/vapid.yml @@ -13,5 +13,5 @@ # https://rossta.net/blog/using-the-web-push-api-with-vapid.html # shared: - private_key: <%= ENV.fetch('VAPID_PRIVATE_KEY', nil) %> - public_key: <%= ENV.fetch('VAPID_PUBLIC_KEY', nil) %> + private_key: <%= ENV.fetch('VAPID_PRIVATE_KEY', nil)&.to_json %> + public_key: <%= ENV.fetch('VAPID_PUBLIC_KEY', nil)&.to_json %> diff --git a/spec/configuration/email_spec.rb b/spec/configuration/email_spec.rb new file mode 100644 index 00000000000..2838beb6450 --- /dev/null +++ b/spec/configuration/email_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Configuration for email', type: :feature do + context 'with special characters in SMTP_PASSWORD env variable' do + let(:password) { ']]123456789[["!:@<>/\\=' } + + around do |example| + ClimateControl.modify SMTP_PASSWORD: password do + example.run + end + end + + it 'parses value correctly' do + expect(Rails.application.config_for(:email, env: :production)) + .to include( + smtp_settings: include(password: password) + ) + end + end +end From 76c14464161fd7ca295b82b76aeaca4186e17474 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:10:55 +0200 Subject: [PATCH 028/660] New Crowdin Translations (automated) (#35310) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/bg.json | 2 +- app/javascript/mastodon/locales/en-GB.json | 24 +++++++ app/javascript/mastodon/locales/fy.json | 3 + app/javascript/mastodon/locales/kab.json | 11 ++++ app/javascript/mastodon/locales/zh-CN.json | 3 + config/locales/activerecord.en-GB.yml | 6 ++ config/locales/br.yml | 3 + config/locales/cs.yml | 4 ++ config/locales/da.yml | 6 +- config/locales/de.yml | 4 ++ config/locales/doorkeeper.cs.yml | 2 +- config/locales/en-GB.yml | 74 ++++++++++++++++++++++ config/locales/es-AR.yml | 4 ++ config/locales/es-MX.yml | 4 ++ config/locales/es.yml | 4 ++ config/locales/eu.yml | 20 ++++++ config/locales/fi.yml | 4 ++ config/locales/fo.yml | 4 ++ config/locales/fy.yml | 11 ++++ config/locales/gl.yml | 4 ++ config/locales/he.yml | 4 ++ config/locales/is.yml | 4 ++ config/locales/kab.yml | 16 +++++ config/locales/ko.yml | 4 ++ config/locales/nl.yml | 4 ++ config/locales/simple_form.br.yml | 1 + config/locales/simple_form.cs.yml | 2 + config/locales/simple_form.da.yml | 2 + config/locales/simple_form.de.yml | 2 + config/locales/simple_form.en-GB.yml | 23 +++++++ config/locales/simple_form.es-AR.yml | 2 + config/locales/simple_form.es-MX.yml | 2 + config/locales/simple_form.es.yml | 2 + config/locales/simple_form.fi.yml | 1 + config/locales/simple_form.fo.yml | 2 + config/locales/simple_form.fy.yml | 2 + config/locales/simple_form.gl.yml | 2 + config/locales/simple_form.he.yml | 2 + config/locales/simple_form.is.yml | 2 + config/locales/simple_form.ko.yml | 2 + config/locales/simple_form.nl.yml | 2 + config/locales/simple_form.tr.yml | 2 + config/locales/simple_form.vi.yml | 2 + config/locales/simple_form.zh-CN.yml | 1 + config/locales/simple_form.zh-TW.yml | 2 + config/locales/tr.yml | 4 ++ config/locales/vi.yml | 4 ++ config/locales/zh-CN.yml | 4 ++ config/locales/zh-TW.yml | 4 ++ 49 files changed, 300 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 61951ee2362..8628b68954c 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -188,7 +188,7 @@ "community.column_settings.remote_only": "Само отдалечено", "compose.language.change": "Смяна на езика", "compose.language.search": "Търсене на езици...", - "compose.published.body": "Публикувана публикация.", + "compose.published.body": "Публикувано.", "compose.published.open": "Отваряне", "compose.saved.body": "Запазена публикация.", "compose_form.direct_message_warning_learn_more": "Още информация", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 588576ad0a5..1702076b3fb 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -219,6 +219,13 @@ "confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.title": "Delete list?", + "confirmations.discard_draft.confirm": "Discard and continue", + "confirmations.discard_draft.edit.cancel": "Resume editing", + "confirmations.discard_draft.edit.message": "Continuing will discard any changes you have made to the post you are currently editing.", + "confirmations.discard_draft.edit.title": "Discard changes to your post?", + "confirmations.discard_draft.post.cancel": "Resume draft", + "confirmations.discard_draft.post.message": "Continuing will discard the post you are currently composing.", + "confirmations.discard_draft.post.title": "Discard your draft post?", "confirmations.discard_edit_media.confirm": "Discard", "confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?", "confirmations.follow_to_list.confirm": "Follow and add to list", @@ -330,6 +337,7 @@ "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard", "errors.unexpected_crash.report_issue": "Report issue", "explore.suggested_follows": "People", + "explore.title": "Trending", "explore.trending_links": "News", "explore.trending_statuses": "Posts", "explore.trending_tags": "Hashtags", @@ -541,8 +549,10 @@ "mute_modal.you_wont_see_mentions": "You won't see posts that mention them.", "mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.", "navigation_bar.about": "About", + "navigation_bar.account_settings": "Password and security", "navigation_bar.administration": "Administration", "navigation_bar.advanced_interface": "Open in advanced web interface", + "navigation_bar.automated_deletion": "Automated post deletion", "navigation_bar.blocks": "Blocked users", "navigation_bar.bookmarks": "Bookmarks", "navigation_bar.direct": "Private mentions", @@ -552,13 +562,23 @@ "navigation_bar.follow_requests": "Follow requests", "navigation_bar.followed_tags": "Followed hashtags", "navigation_bar.follows_and_followers": "Follows and followers", + "navigation_bar.import_export": "Import and export", "navigation_bar.lists": "Lists", + "navigation_bar.live_feed_local": "Live feed (local)", + "navigation_bar.live_feed_public": "Live feed (public)", "navigation_bar.logout": "Logout", "navigation_bar.moderation": "Moderation", + "navigation_bar.more": "More", "navigation_bar.mutes": "Muted users", "navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.", "navigation_bar.preferences": "Preferences", + "navigation_bar.privacy_and_reach": "Privacy and reach", "navigation_bar.search": "Search", + "navigation_bar.search_trends": "Search / Trending", + "navigation_panel.collapse_followed_tags": "Collapse followed hashtags menu", + "navigation_panel.collapse_lists": "Collapse list menu", + "navigation_panel.expand_followed_tags": "Expand followed hashtags menu", + "navigation_panel.expand_lists": "Expand list menu", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", "notification.admin.report": "{name} reported {target}", "notification.admin.report_account": "{name} reported {count, plural, one {one post} other {# posts}} from {target} for {category}", @@ -785,6 +805,7 @@ "report_notification.categories.violation": "Rule violation", "report_notification.categories.violation_sentence": "rule violation", "report_notification.open": "Open report", + "search.clear": "Clear search", "search.no_recent_searches": "No recent searches", "search.placeholder": "Search", "search.quick_action.account_search": "Profiles matching {x}", @@ -887,7 +908,10 @@ "subscribed_languages.save": "Save changes", "subscribed_languages.target": "Change subscribed languages for {target}", "tabs_bar.home": "Home", + "tabs_bar.menu": "Menu", "tabs_bar.notifications": "Notifications", + "tabs_bar.publish": "New Post", + "tabs_bar.search": "Search", "terms_of_service.effective_as_of": "Effective as of {date}", "terms_of_service.title": "Terms of Service", "terms_of_service.upcoming_changes_on": "Upcoming changes on {date}", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 85d06d063cd..33c6d89d195 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -564,6 +564,8 @@ "navigation_bar.follows_and_followers": "Folgers en folgjenden", "navigation_bar.import_export": "Ymportearje en eksportearje", "navigation_bar.lists": "Listen", + "navigation_bar.live_feed_local": "Livefeed (lokaal)", + "navigation_bar.live_feed_public": "Livefeed (iepenbier)", "navigation_bar.logout": "Ofmelde", "navigation_bar.moderation": "Moderaasje", "navigation_bar.more": "Mear", @@ -803,6 +805,7 @@ "report_notification.categories.violation": "Skeinde regels", "report_notification.categories.violation_sentence": "skeinde regels", "report_notification.open": "Rapport iepenje", + "search.clear": "Sykopdracht wiskje", "search.no_recent_searches": "Gjin resinte sykopdrachten", "search.placeholder": "Sykje", "search.quick_action.account_search": "Accounts dy’t oerienkomme mei {x}", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index d825c878946..dce8fc460a1 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -6,6 +6,7 @@ "about.domain_blocks.preamble": "Maṣṭudun s umata yeḍmen-ak ad teẓreḍ agbur, ad tesdemreḍ akked yimseqdacen-nniḍen seg yal aqeddac deg fedivers. Ha-tent-an ɣur-k tsuraf i yellan deg uqeddac-agi.", "about.domain_blocks.silenced.title": "Ɣur-s talast", "about.domain_blocks.suspended.title": "Yettwaḥbes", + "about.language_label": "Tutlayt", "about.not_available": "Talɣut-a ur tettwabder ara deg uqeddac-a.", "about.powered_by": "Azeṭṭa inmetti yettwasɣelsen sɣur {mastodon}", "about.rules": "Ilugan n uqeddac", @@ -237,6 +238,10 @@ "explore.trending_links": "Isallen", "explore.trending_statuses": "Tisuffaɣ", "explore.trending_tags": "Ihacṭagen", + "featured_carousel.next": "Uḍfiṛ", + "featured_carousel.post": "Tasuffeɣt", + "featured_carousel.previous": "Uzwir", + "featured_carousel.slide": "{index} ɣef {total}", "filter_modal.added.review_and_configure_title": "Iɣewwaṛen n imzizdig", "filter_modal.added.settings_link": "asebter n yiɣewwaṛen", "filter_modal.added.short_explanation": "Tasuffeɣt-a tettwarna ɣer taggayt-a n yimsizdegen: {title}.", @@ -357,6 +362,7 @@ "lists.add_to_list": "Rnu ɣer tebdart", "lists.add_to_lists": "Rnu {name} ɣer tebdarin", "lists.create": "Snulfu-d", + "lists.create_list": "Snulfu-d tabdart", "lists.delete": "Kkes tabdart", "lists.done": "Immed", "lists.edit": "Ẓreg tabdart", @@ -397,6 +403,7 @@ "navigation_bar.lists": "Tibdarin", "navigation_bar.logout": "Ffeɣ", "navigation_bar.moderation": "Aseɣyed", + "navigation_bar.more": "Ugar", "navigation_bar.mutes": "Iseqdacen yettwasusmen", "navigation_bar.opened_in_classic_interface": "Tisuffaɣ, imiḍanen akked isebtar-nniḍen igejdanen ldin-d s wudem amezwer deg ugrudem web aklasiki.", "navigation_bar.preferences": "Imenyafen", @@ -610,6 +617,7 @@ "status.mute_conversation": "Sgugem adiwenni", "status.open": "Semɣeṛ tasuffeɣt-ayi", "status.pin": "Senteḍ-itt deg umaɣnu", + "status.quote_post_author": "Izen sɣur {name}", "status.read_more": "Issin ugar", "status.reblog": "Bḍu", "status.reblogged_by": "Yebḍa-tt {name}", @@ -635,7 +643,10 @@ "status.unpin": "Kkes asenteḍ seg umaɣnu", "subscribed_languages.save": "Sekles ibeddilen", "tabs_bar.home": "Agejdan", + "tabs_bar.menu": "Umuɣ", "tabs_bar.notifications": "Ilɣa", + "tabs_bar.publish": "Tasuffeɣt tamaynut", + "tabs_bar.search": "Nadi", "terms_of_service.title": "Tiwtilin n useqdec", "time_remaining.days": "Mazal {number, plural, one {# wass} other {# wussan}}", "time_remaining.hours": "Mazal {number, plural, one {# usarag} other {# yisragen}}", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index dc34246fb20..57958076a7b 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -218,6 +218,9 @@ "confirmations.delete_list.confirm": "删除", "confirmations.delete_list.message": "确定要永久删除此列表吗?", "confirmations.delete_list.title": "确定要删除列表?", + "confirmations.discard_draft.confirm": "放弃并继续", + "confirmations.discard_draft.edit.cancel": "恢复编辑", + "confirmations.discard_draft.post.cancel": "恢复草稿", "confirmations.discard_edit_media.confirm": "丢弃", "confirmations.discard_edit_media.message": "你还有未保存的媒体描述或预览修改,仍要丢弃吗?", "confirmations.follow_to_list.confirm": "关注并添加到列表", diff --git a/config/locales/activerecord.en-GB.yml b/config/locales/activerecord.en-GB.yml index 4e7f7b3e203..fe10c5c8a75 100644 --- a/config/locales/activerecord.en-GB.yml +++ b/config/locales/activerecord.en-GB.yml @@ -49,8 +49,14 @@ en-GB: attributes: reblog: taken: of post already exists + terms_of_service: + attributes: + effective_date: + too_soon: is too soon, must be later than %{date} user: attributes: + date_of_birth: + below_limit: is below the age limit email: blocked: uses a disallowed e-mail provider unreachable: does not seem to exist diff --git a/config/locales/br.yml b/config/locales/br.yml index 9f0cf07ab95..e5b9ff2559c 100644 --- a/config/locales/br.yml +++ b/config/locales/br.yml @@ -396,6 +396,9 @@ br: created_at: Deiziad title_actions: none: Diwall + emoji_styles: + auto: Emgefreek + twemoji: Twemoji exports: archive_takeout: date: Deiziad diff --git a/config/locales/cs.yml b/config/locales/cs.yml index e4c083bf707..1ad8df6e71f 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1387,6 +1387,10 @@ cs: basic_information: Základní informace hint_html: "Nastavte si, co lidé uvidí na vašem veřejném profilu a vedle vašich příspěvků. Ostatní lidé vás budou spíše sledovat a komunikovat s vámi, když budete mít vyplněný profil a profilový obrázek." other: Další + emoji_styles: + auto: Auto + native: Výchozí + twemoji: Twemoji errors: '400': Žádost, kterou jste odeslali, byla neplatná nebo poškozená. '403': Nejste oprávněni tuto stránku zobrazit. diff --git a/config/locales/da.yml b/config/locales/da.yml index db3bb547fe4..adb59158deb 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1006,7 +1006,7 @@ da: one: Send %{display_count} e-mail other: Send %{display_count} e-mails title: Forhåndsvis Tjenestevilkår-underretning - publish: Udgiv + publish: Publicér published_on_html: Udgivet pr. %{date} save_draft: Gem udkast title: Tjenestevilkår @@ -1349,6 +1349,10 @@ da: basic_information: Oplysninger hint_html: "Tilpas hvad folk ser på din offentlige profil og ved siden af dine indlæg. Andre personer vil mere sandsynligt følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede." other: Andre + emoji_styles: + auto: Auto + native: Indbygget + twemoji: Twemoji errors: '400': Din indsendte anmodning er ugyldig eller fejlbehæftet. '403': Du har ikke tilladelse til at se denne side. diff --git a/config/locales/de.yml b/config/locales/de.yml index 39530f6ee30..59ceea8e79c 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1349,6 +1349,10 @@ de: basic_information: Allgemeine Informationen hint_html: "Bestimme, was andere auf deinem öffentlichen Profil und neben deinen Beiträgen sehen können. Wenn du ein Profilbild festlegst und dein Profil vervollständigst, werden andere eher mit dir interagieren und dir folgen." other: Andere + emoji_styles: + auto: Automatisch + native: Nativ + twemoji: Twemoji errors: '400': Die Anfrage, die du gestellt hast, war ungültig oder fehlerhaft. '403': Dir fehlt die Berechtigung, diese Seite aufzurufen. diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml index 1ee73c2cb95..b435624334f 100644 --- a/config/locales/doorkeeper.cs.yml +++ b/config/locales/doorkeeper.cs.yml @@ -29,7 +29,7 @@ cs: edit: title: Upravit aplikaci form: - error: A jéje! Zkontrolujte ve formuláři případné chyby + error: A jéje! Zkontrolujte formulář pro případné chyby help: native_redirect_uri: Pro místní testy použijte %{native_redirect_uri} redirect_uri: Jedno URI na každý řádek diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index f44404502e2..a65ee021318 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -309,6 +309,7 @@ en-GB: title: Audit log unavailable_instance: "(domain name unavailable)" announcements: + back: Back to announcements destroyed_msg: Announcement successfully deleted! edit: title: Edit announcement @@ -317,6 +318,10 @@ en-GB: new: create: Create announcement title: New announcement + preview: + disclaimer: As users cannot opt out of them, email notifications should be limited to important announcements such as personal data breach or server closure notifications. + explanation_html: 'The email will be sent to %{display_count} users. The following text will be included in the e-mail:' + title: Preview announcement notification publish: Publish published_msg: Announcement successfully published! scheduled_for: Scheduled for %{time} @@ -475,6 +480,36 @@ en-GB: new: title: Import domain blocks no_file: No file selected + fasp: + debug: + callbacks: + created_at: Created at + delete: Delete + ip: IP address + request_body: Request body + title: Debug Callbacks + providers: + active: Active + base_url: Base URL + callback: Callback + delete: Delete + edit: Edit Provider + finish_registration: Finish registration + name: Name + providers: Providers + public_key_fingerprint: Public key fingerprint + registration_requested: Registration requested + registrations: + confirm: Confirm + description: You received a registration from a FASP. Reject it if you did not initiate this. If you initiated this, carefully compare name and key fingerprint before confirming the registration. + reject: Reject + title: Confirm FASP Registration + save: Save + select_capabilities: Select Capabilities + sign_in: Sign In + status: Status + title: Fediverse Auxiliary Service Providers + title: FASP follow_recommendations: description_html: "Follow recommendations help new users quickly find interesting content. When a user has not interacted with others enough to form personalised follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language." language: For language @@ -543,6 +578,13 @@ en-GB: all: All limited: Limited title: Moderation + moderation_notes: + create: Add Moderation Note + created_msg: Instance moderation note successfully created! + description_html: View and leave notes for other moderators and your future self + destroyed_msg: Instance moderation note successfully deleted! + placeholder: Information about this instance, actions taken, or anything else that will help you moderate this instance in the future. + title: Moderation Notes private_comment: Private comment public_comment: Public comment purge: Purge @@ -751,17 +793,26 @@ en-GB: title: Roles rules: add_new: Add rule + add_translation: Add translation delete: Delete description_html: While most claim to have read and agree to the terms of service, usually people do not read through until after a problem arises. Make it easier to see your server's rules at a glance by providing them in a flat bullet point list. Try to keep individual rules short and simple, but try not to split them up into many separate items either. edit: Edit rule empty: No server rules have been defined yet. + move_down: Move down + move_up: Move up title: Server rules + translation: Translation + translations: Translations + translations_explanation: You can optionally add translations for the rules. The default value will be shown if no translated version is available. Please always ensure any provided translation is in sync with the default value. settings: about: manage_rules: Manage server rules preamble: Provide in-depth information about how the server is operated, moderated, funded. rules_hint: There is a dedicated area for rules that your users are expected to adhere to. title: About + allow_referrer_origin: + desc: When your users click links to external sites, their browser may send the address of your Mastodon server as the referrer. Disable this if this would uniquely identify your users, e.g. if this is a personal Mastodon server. + title: Allow external sites to see your Mastodon server as a traffic source appearance: preamble: Customise Mastodon's web interface. title: Appearance @@ -781,6 +832,7 @@ en-GB: discovery: follow_recommendations: Follow recommendations preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server. + privacy: Privacy profile_directory: Profile directory public_timelines: Public timelines publish_statistics: Publish statistics @@ -867,6 +919,8 @@ en-GB: system_checks: database_schema_check: message_html: There are pending database migrations. Please run them to ensure the application behaves as expected + elasticsearch_analysis_index_mismatch: + message_html: Elasticsearch index analyser settings are outdated. Please run tootctl search deploy --only-mapping --only=%{value} elasticsearch_health_red: message_html: Elasticsearch cluster is unhealthy (red status), search features are unavailable elasticsearch_health_yellow: @@ -938,6 +992,7 @@ en-GB: chance_to_review_html: "The generated terms of service will not be published automatically. You will have a chance to review the results. Please fill in the necessary details to proceed." explanation_html: The terms of service template provided is for informational purposes only, and should not be construed as legal advice on any subject matter. Please consult with your own legal counsel on your situation and specific legal questions you have. title: Terms of Service Setup + going_live_on_html: Live, effective %{date} history: History live: Live no_history: There are no recorded changes of the terms of service yet. @@ -1294,6 +1349,10 @@ en-GB: basic_information: Basic information hint_html: "Customise what people see on your public profile and next to your posts. Other people are more likely to follow you back and interact with you when you have a filled out profile and a profile picture." other: Other + emoji_styles: + auto: Auto + native: Native + twemoji: Twemoji errors: '400': The request you submitted was invalid or malformed. '403': You don't have permission to view this page. @@ -1819,6 +1878,10 @@ en-GB: limit: You have already pinned the maximum number of posts ownership: Someone else's post cannot be pinned reblog: A boost cannot be pinned + quote_policies: + followers: Followers and mentioned users + nobody: Only mentioned users + public: Everyone title: '%{name}: "%{quote}"' visibilities: direct: Direct @@ -1872,6 +1935,11 @@ en-GB: does_not_match_previous_name: does not match the previous name terms_of_service: title: Terms of Service + terms_of_service_interstitial: + future_preamble_html: We're making some changes to our terms of service, which will be effective on %{date}. We encourage you to review the updated terms. + past_preamble_html: We have changed our terms of service since your last visit. We encourage you to review the updated terms. + review_link: Review terms of service + title: The terms of service of %{domain} are changing themes: contrast: Mastodon (High contrast) default: Mastodon (Dark) @@ -1903,6 +1971,10 @@ en-GB: recovery_instructions_html: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe. For example, you may print them and store them with other important documents. webauthn: Security keys user_mailer: + announcement_published: + description: 'The administrators of %{domain} are making an announcement:' + subject: Service announcement + title: "%{domain} service announcement" appeal_approved: action: Account Settings explanation: The appeal of the strike against your account on %{strike_date} that you submitted on %{appeal_date} has been approved. Your account is once again in good standing. @@ -1935,6 +2007,8 @@ en-GB: terms_of_service_changed: agreement: By continuing to use %{domain}, you are agreeing to these terms. If you disagree with the updated terms, you may terminate your agreement with %{domain} at any time by deleting your account. changelog: 'At a glance, here is what this update means for you:' + description: 'You are receiving this e-mail because we''re making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here:' + description_html: You are receiving this e-mail because we're making some changes to our terms of service at %{domain}. These updates will take effect on %{date}. We encourage you to review the updated terms in full here. sign_off: The %{domain} team subject: Updates to our terms of service subtitle: The terms of service of %{domain} are changing diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 9ed16616162..9ed961db348 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1349,6 +1349,10 @@ es-AR: basic_information: Información básica hint_html: "Personalizá lo que la gente ve en tu perfil público y junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen con vos cuando tengas un perfil completo y una foto de perfil." other: Otros + emoji_styles: + auto: Automático + native: Nativo + twemoji: Twemoji errors: '400': La solicitud que enviaste no era válida o estaba corrompida. '403': No tenés permiso para ver esta página. diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index e50f2a7f007..cfc573fab8d 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1349,6 +1349,10 @@ es-MX: basic_information: Información básica hint_html: "Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen contigo cuando completes tu perfil y agregues una foto." other: Otro + emoji_styles: + auto: Automático + native: Nativo + twemoji: Twemoji errors: '400': La solicitud que has enviado no es valida o estaba malformada. '403': No tienes permiso para acceder a esta página. diff --git a/config/locales/es.yml b/config/locales/es.yml index 19f340cec21..4b10f23b132 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1349,6 +1349,10 @@ es: basic_information: Información básica hint_html: "Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen contigo cuando completas tu perfil y foto." other: Otros + emoji_styles: + auto: Automático + native: Nativo + twemoji: Twemoji errors: '400': La solicitud que has enviado no es valida o estaba malformada. '403': No tienes permiso para acceder a esta página. diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 19a6be4a9ce..bf52c83bc94 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -187,6 +187,7 @@ eu: create_domain_block: Sortu domeinu blokeoa create_email_domain_block: Sortu email domeinu blokeoa create_ip_block: Sortu IP araua + create_relay: Erreleboa sortu create_unavailable_domain: Sortu eskuragarri ez dagoen domeinua create_user_role: Sortu rola demote_user: Jaitsi erabiltzailearen maila @@ -198,11 +199,13 @@ eu: destroy_email_domain_block: Ezabatu email domeinu blokeoa destroy_instance: Ezabatu betiko domeinua destroy_ip_block: Ezabatu IP araua + destroy_relay: Erreleboa ezabatu destroy_status: Ezabatu bidalketa destroy_unavailable_domain: Ezabatu eskuragarri ez dagoen domeinua destroy_user_role: Ezabatu rola disable_2fa_user: Desgaitu 2FA disable_custom_emoji: Desgaitu emoji pertsonalizatua + disable_relay: Erreleboa desaktibatu disable_sign_in_token_auth_user: Desgaitu email token autentifikazioa erabiltzailearentzat disable_user: Desgaitu erabiltzailea enable_custom_emoji: Gaitu emoji pertsonalizatua @@ -210,6 +213,7 @@ eu: enable_user: Gaitu erabiltzailea memorialize_account: Bihurtu kontua oroigarri promote_user: Igo erabiltzailea mailaz + publish_terms_of_service: Zerbitzu-baldintzak argitaratu reject_appeal: Baztertu apelazioa reject_user: Baztertu erabiltzailea remove_avatar_user: Kendu abatarra @@ -229,23 +233,29 @@ eu: update_custom_emoji: Eguneratu emoji pertsonalizatua update_domain_block: Eguneratu domeinu-blokeoa update_ip_block: Eguneratu IP araua + update_report: Txostena eguneratu update_status: Eguneratu bidalketa update_user_role: Eguneratu rola actions: approve_appeal_html: "%{name} erabiltzaileak %{target} erabiltzailearen moderazio erabakiaren apelazioa onartu du" approve_user_html: "%{name} erabiltzaileak %{target} erabiltzailearen izen-ematea onartu du" assigned_to_self_report_html: "%{name} erabiltzaileak %{target} salaketa bere buruari esleitu dio" + change_email_user_html: "%{name}(e)k %{target} erabiltzailearen e-posta helbidea aldatu du" change_role_user_html: "%{name} erabiltzaileak %{target} kontuaren rola aldatu du" + confirm_user_html: "%{name}(e)k %{target} erabiltzailearen e-posta helbidea berretsi du" create_account_warning_html: "%{name} erabiltzaileak abisua bidali dio %{target} erabiltzaileari" create_announcement_html: "%{name} erabiltzaileak %{target} iragarpen berria sortu du" + create_canonical_email_block_html: "%{name}(e)k %{target} hash-a duen helbide elektronikoa blokeatu du" create_custom_emoji_html: "%{name} erabiltzaileak %{target} emoji berria kargatu du" create_domain_allow_html: "%{name} erabiltzaileak %{target} domeinuarekin federazioa onartu du" create_domain_block_html: "%{name} erabiltzaileak %{target} domeinua blokeatu du" + create_email_domain_block_html: "%{name}(e)k %{target} e-posta helbideen domeinua blokeatu du" create_ip_block_html: "%{name} kontuak %{target} IParen araua sortu du" create_unavailable_domain_html: "%{name}(e)k %{target} domeinurako banaketa gelditu du" create_user_role_html: "%{name} erabiltzaileak %{target} rola sortu du" demote_user_html: "%{name} erabiltzaileak %{target} erabiltzailea mailaz jaitsi du" destroy_announcement_html: "%{name} erabiltzaileak %{target} iragarpena ezabatu du" + destroy_canonical_email_block_html: "%{name}(e)k %{target} hash-a duen helbide elektronikoa desblokeatu du" destroy_custom_emoji_html: "%{name} erabiltzaileak %{target} emoji-a ezabatu du" destroy_domain_allow_html: "%{name} erabiltzaileak %{target} domeinuarekin federatzea debekatu du" destroy_domain_block_html: "%{name} erabiltzaileak %{target} domeinua desblokeatu du" @@ -286,7 +296,9 @@ eu: filter_by_action: Iragazi ekintzen arabera filter_by_user: Iragazi erabiltzaileen arabera title: Auditoria-egunkaria + unavailable_instance: "(domeinu izena ez dago erabilgarri)" announcements: + back: Oharretara bueltatu destroyed_msg: Iragarpena ongi ezabatu da! edit: title: Editatu iragarpena @@ -433,8 +445,10 @@ eu: new: create: Gehitu domeinua resolve: Ebatzi domeinua + title: Posta domeinu berria blokeatu not_permitted: Baimendu gabea resolved_through_html: "%{domain} domeinuaren bidez ebatzia" + title: Email domeinua blokeatuta export_domain_allows: new: title: Baimendutako domeinuak inportatu @@ -471,6 +485,8 @@ eu: save: Gorde sign_in: Hasi saioa status: Egoera + title: Fedibertsoko zerbitzu osagarrien hornitzaileak + title: FZOH follow_recommendations: description_html: "Jarraitzeko gomendioek erabiltzaile berriei eduki interesgarria azkar aurkitzen laguntzen diete. Erabiltzaile batek jarraitzeko gomendio pertsonalizatuak jasotzeko adina interakzio izan ez duenean, kontu hauek gomendatzen zaizkio. Egunero birkalkulatzen dira hizkuntza bakoitzerako, azken aldian parte-hartze handiena izan duten eta jarraitzaile lokal gehien dituzten kontuak nahasiz." language: Hizkuntza @@ -536,6 +552,10 @@ eu: all: Denak limited: Mugatua title: Moderazioa + moderation_notes: + create: Gehitu Moderazio Oharra + created_msg: Instantziako moderazio oharra ongi sortu da! + description_html: Ikusi eta idatzi oharrak beste moderatzaileentzat eta zuretzat etorkizunerako private_comment: Iruzkin pribatua public_comment: Iruzkin publikoa purge: Ezabatu betiko diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 4de6815a00e..54aadf60f32 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1347,6 +1347,10 @@ fi: basic_information: Perustiedot hint_html: "Mukauta, mitä ihmiset näkevät julkisessa profiilissasi ja julkaisujesi vieressä. Sinua seurataan takaisin ja kanssasi ollaan vuorovaikutuksessa todennäköisemmin, kun sinulla on täytetty profiili ja profiilikuva." other: Muut + emoji_styles: + auto: Automaattinen + native: Natiivi + twemoji: Twemoji errors: '400': Lähettämäsi pyyntö oli virheellinen tai muotoiltu virheellisesti. '403': Sinulla ei ole oikeutta nähdä tätä sivua. diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 05427192526..23a6b589765 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1349,6 +1349,10 @@ fo: basic_information: Grundleggjandi upplýsingar hint_html: "Tillaga tað, sum fólk síggja á tínum almenna vanga og við síðna av tínum postum. Sannlíkindini fyri, at onnur fylgja tær og virka saman við tær eru størri, tá tú hevur fylt út vangan og eina vangamynd." other: Onnur + emoji_styles: + auto: Sjálvvirkandi + native: Upprunalig + twemoji: Twitter kenslutekn errors: '400': Umbønin, sum tú sendi inn, var ógildug ella hevði skeivt skap. '403': Tú hevur ikki loyvi at síggja hesa síðuna. diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 561b50079bc..38752869da1 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -578,6 +578,13 @@ fy: all: Alle limited: Beheind title: Moderaasje + moderation_notes: + create: Moderaasje-opmerking oanmeitsje + created_msg: Oanmeitsjen fan servermoderaasje-opmerking slagge! + description_html: Opmerkingen besjen en foar josele en oare moderatoaren efterlitte + destroyed_msg: Fuortsmiten fan servermoderaasje-opmerking slagge! + placeholder: Ynformaasje oer dizze server, nommen aksjes of wat oars dy’t jo helpe kinne om dizze server yn de takomst te moderearjen. + title: Moderaasje-opmerkingen private_comment: Priveeopmerking public_comment: Iepenbiere opmerking purge: Folslein fuortsmite @@ -1342,6 +1349,10 @@ fy: basic_information: Algemiene ynformaasje hint_html: "Pas oan wat minsken op jo iepenbiere profyl en njonken jo berjochten sjogge. Oare minsken sille jo earder folgje en mei jo kommunisearje wannear’t jo profyl ynfolle is en jo in profylfoto hawwe." other: Oars + emoji_styles: + auto: Automatysk + native: Systeemeigen + twemoji: Twemoji errors: '400': De oanfraach dy’t jo yntsjinne hawwe wie ûnjildich of fout. '403': Jo hawwe gjin tastimming om dizze side te besjen. diff --git a/config/locales/gl.yml b/config/locales/gl.yml index cfc72681a92..d19d47ab26b 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1349,6 +1349,10 @@ gl: basic_information: Información básica hint_html: "Personaliza o que van ver no teu perfil público e ao lado das túas publicacións. As outras persoas estarán máis animadas a seguirte e interactuar contigo se engades algún dato sobre ti así como unha imaxe de perfil." other: Outros + emoji_styles: + auto: Auto + native: Nativo + twemoji: Twemoji errors: '400': A solicitude que enviou non é válida ou ten formato incorrecto. '403': Non ten permiso para ver esta páxina. diff --git a/config/locales/he.yml b/config/locales/he.yml index 5a562f81167..50711387649 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1387,6 +1387,10 @@ he: basic_information: מידע בסיסי hint_html: "התאמה אישית של מה שיראו אחרים בפרופיל הציבורי שלך וליד הודעותיך. אחרים עשויים יותר להחזיר עוקב וליצור אתך שיחה אם הפרופיל והתמונה יהיו מלאים." other: אחר + emoji_styles: + auto: אוטומטי + native: מקומי + twemoji: Twemoji errors: '400': הבקשה שהגשת לא תקינה. '403': חסרות לך הרשאות לצפיה בעמוד זה. diff --git a/config/locales/is.yml b/config/locales/is.yml index e997be21b2a..5e33189cfb2 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1353,6 +1353,10 @@ is: basic_information: Grunnupplýsingar hint_html: "Sérsníddu hvað fólk sér á opinbera notandasniðinu þínu og næst færslunum þínum. Annað fólk er líklegra til að fylgjast með þér og eiga í samskiptum við þig ef þú fyllir út notandasniðið og setur auðkennismynd." other: Annað + emoji_styles: + auto: Sjálfvirkt + native: Innbyggt + twemoji: Twemoji errors: '400': Beiðnin sem þú sendir er ógild eða rangt uppsett. '403': Þú hefur ekki heimildir til að skoða þessari síðu. diff --git a/config/locales/kab.yml b/config/locales/kab.yml index a7ad9b356f6..e68c99e2f81 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -280,6 +280,7 @@ kab: registrations: confirm: Sentem save: Sekles + sign_in: Qqen title: FASP follow_recommendations: language: I tutlayt @@ -383,6 +384,9 @@ kab: updated_at: Yettwaleqqem view_profile: Wali amaɣnu roles: + assigned_users: + one: "%{count} n useqdac" + other: "%{count} n iseqdacen" categories: administration: Tadbelt invites: Iɛeṛṛuḍen @@ -395,16 +399,20 @@ kab: view_dashboard: Timẓriwt n tfelwit rules: add_new: Rnu alugen + add_translation: Ad yernu tasuqilt delete: Kkes edit: Ẓreg alugen empty: Mazal ur ttwasbadun ara yilugan n uqeddac. title: Ilugan n uqeddac + translation: Tasuqilt + translations: Tisuqilin settings: about: title: Ɣef appearance: title: Udem discovery: + privacy: Tabaḍnit profile_directory: Akaram n imaɣnuten title: Asnirem trends: Ayen mucaɛen @@ -440,6 +448,10 @@ kab: visibility: Abani with_media: S umidya system_checks: + elasticsearch_preset: + action: Wali tasemlit + elasticsearch_preset_single_node: + action: Wali tasemlit rules_check: action: Sefrek ilugan n uqeddac software_version_critical_check: @@ -601,6 +613,8 @@ kab: basic_information: Talɣut tamatut hint_html: "Mudd udem i wayen ttwalin medden deg umaɣnu-inek azayez ɣer yidis n yiznan-ik. Imdanen niḍen zemren ad k-ḍefren yernu ad gen assaɣ yid-k mi ara tesɛuḍ amaɣnu yeccuṛen ed tugna n umaɣnu." other: Ayen nniḍen + emoji_styles: + auto: Awurman errors: '404': Asebter i tettnadiḍ ulac-it da. '500': @@ -835,6 +849,8 @@ kab: one: "%{count} n tbidyutt" other: "%{count} n tbidyutin" edited_at_html: Tettwaẓreg ass n %{date} + quote_policies: + public: Yal yiwen title: '%{name} : "%{quote}"' visibilities: direct: Usrid diff --git a/config/locales/ko.yml b/config/locales/ko.yml index cc6665c1cb7..66c16c899df 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1332,6 +1332,10 @@ ko: basic_information: 기본 정보 hint_html: "사람들이 공개 프로필을 보고서 게시물을 볼 때를 위한 프로필을 꾸밉니다. 프로필과 프로필 사진을 채우면 다른 사람들이 나를 팔로우하고 나와 교류할 기회가 더 많아집니다." other: 기타 + emoji_styles: + auto: 자동 + native: 시스템 기본 + twemoji: 트웨모지 errors: '400': 제출한 요청이 올바르지 않습니다. '403': 이 페이지를 표시할 권한이 없습니다. diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 92f2f4ab345..e38d80b1001 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1349,6 +1349,10 @@ nl: basic_information: Algemene informatie hint_html: "Wat mensen op jouw openbare profiel en naast je berichten zien aanpassen. Andere mensen gaan je waarschijnlijk eerder volgen en hebben vaker interactie met je, wanneer je profiel is ingevuld en je een profielfoto hebt." other: Overige + emoji_styles: + auto: Auto + native: Systeemeigen + twemoji: Twemoji errors: '400': De aanvraag die je hebt ingediend was ongeldig of foutief. '403': Jij hebt geen toestemming om deze pagina te bekijken. diff --git a/config/locales/simple_form.br.yml b/config/locales/simple_form.br.yml index f4d442cfdab..b415640eb09 100644 --- a/config/locales/simple_form.br.yml +++ b/config/locales/simple_form.br.yml @@ -49,6 +49,7 @@ br: setting_display_media_default: Dre ziouer setting_display_media_hide_all: Kuzhat pep tra setting_display_media_show_all: Diskouez pep tra + setting_emoji_style: Stil an emojioù setting_theme: Neuz al lec'hienn setting_use_pending_items: Mod gorrek title: Titl diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 7af8c1ce7cb..eb8f7f2f3a6 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -61,6 +61,7 @@ cs: setting_display_media_default: Skrývat média označená jako citlivá setting_display_media_hide_all: Vždy skrývat média setting_display_media_show_all: Vždy zobrazovat média + setting_emoji_style: Jak se budou zobrazovat emoji. "Auto" zkusí použít výchozí emoji, ale pro starší prohlížeče použije Twemoji. setting_system_scrollbars_ui: Platí pouze pro desktopové prohlížeče založené na Safari nebo Chrome setting_use_blurhash: Gradienty jsou vytvořeny na základě barvev skrytých médií, ale zakrývají veškeré detaily setting_use_pending_items: Aktualizovat časovou osu až po kliknutí namísto automatického rolování kanálu @@ -243,6 +244,7 @@ cs: setting_display_media_default: Výchozí setting_display_media_hide_all: Skrýt vše setting_display_media_show_all: Zobrazit vše + setting_emoji_style: Styl emoji setting_expand_spoilers: Vždy rozbalit příspěvky označené varováními o obsahu setting_hide_network: Skrýt mou síť setting_missing_alt_text_modal: Zobrazit potvrzovací dialog před odesláním médií bez alt textu diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 7ac8816d914..07d36ae1a15 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -61,6 +61,7 @@ da: setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_hide_all: Skjul altid medier setting_display_media_show_all: Vis altid medier + setting_emoji_style: Hvordan emojis skal vises. "Auto" vil forsøge at bruge indbyggede emojis, men skifter tilbage til Twemoji for ældre browsere. setting_system_scrollbars_ui: Gælder kun for computerwebbrowsere baseret på Safari og Chrome setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning @@ -241,6 +242,7 @@ da: setting_display_media_default: Standard setting_display_media_hide_all: Skjul alle setting_display_media_show_all: Vis alle + setting_emoji_style: Emoji-stil setting_expand_spoilers: Udvid altid indlæg markeret med indholdsadvarsler setting_hide_network: Skjul din sociale graf setting_missing_alt_text_modal: Vis bekræftelsesdialog inden medier uden alt-tekst lægges op diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 9f65a474b04..07862f3d880 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -61,6 +61,7 @@ de: setting_display_media_default: Medien mit Inhaltswarnung ausblenden setting_display_media_hide_all: Medien immer ausblenden setting_display_media_show_all: Medien mit Inhaltswarnung immer anzeigen + setting_emoji_style: Darstellung von Emojis. „Automatisch“ verwendet native Emojis, für ältere Browser jedoch Twemoji. setting_system_scrollbars_ui: Betrifft nur Desktop-Browser, die auf Chrome oder Safari basieren setting_use_blurhash: Der Farbverlauf basiert auf den Farben der ausgeblendeten Medien, verschleiert aber jegliche Details setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt automatisch zu scrollen @@ -241,6 +242,7 @@ de: setting_display_media_default: Standard setting_display_media_hide_all: Alle Medien ausblenden setting_display_media_show_all: Alle Medien anzeigen + setting_emoji_style: Emoji-Stil setting_expand_spoilers: Beiträge mit Inhaltswarnung immer ausklappen setting_hide_network: Follower und „Folge ich“ nicht anzeigen setting_missing_alt_text_modal: Bestätigungsdialog anzeigen, bevor Medien ohne Bildbeschreibung veröffentlicht werden diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index 5f5453edc35..fe4160f3864 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -56,10 +56,12 @@ en-GB: scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon + setting_default_quote_policy: Mentioned users are always allowed to quote. This setting will only take effect for posts created with the next Mastodon version, but you can select your preference in preparation setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media + setting_emoji_style: How to display emojis. "Auto" will try using native emoji, but falls back to Twemoji for legacy browsers. setting_system_scrollbars_ui: Applies only to desktop browsers based on Safari and Chrome setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed @@ -75,6 +77,7 @@ en-GB: filters: action: Chose which action to perform when a post matches the filter actions: + blur: Hide media behind a warning, without hiding the text itself hide: Completely hide the filtered content, behaving as if it did not exist warn: Hide the filtered content behind a warning mentioning the filter's title form_admin_settings: @@ -88,6 +91,7 @@ en-GB: favicon: WEBP, PNG, GIF or JPG. Overrides the default Mastodon favicon with a custom icon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Media files from posts made by remote users are cached on your server. When set to a positive value, media will be deleted after the specified number of days. If the media data is requested after it is deleted, it will be re-downloaded, if the source content is still available. Due to restrictions on how often link preview cards poll third-party sites, it is recommended to set this value to at least 14 days, or link preview cards will not be updated on demand before that time. + min_age: Users will be asked to confirm their date of birth during sign-up peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense. profile_directory: The profile directory lists all users who have opted-in to be discoverable. require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional @@ -132,14 +136,23 @@ en-GB: name: You can only change the casing of the letters, for example, to make it more readable terms_of_service: changelog: Can be structured with Markdown syntax. + effective_date: A reasonable timeframe can range anywhere from 10 to 30 days from the date you notify your users. text: Can be structured with Markdown syntax. terms_of_service_generator: admin_email: Legal notices include counternotices, court orders, takedown requests, and law enforcement requests. + arbitration_address: Can be the same as Physical address above, or “N/A” if using email. + arbitration_website: Can be a web form, or “N/A” if using email. + choice_of_law: City, region, territory or state the internal substantive laws of which shall govern any and all claims. dmca_address: For US operators, use the address registered in the DMCA Designated Agent Directory. A P.O. Box listing is available upon direct request, use the DMCA Designated Agent Post Office Box Waiver Request to email the Copyright Office and describe that you are a home-based content moderator who fears revenge or retribution for your actions and need to use a P.O. Box to remove your home address from public view. + dmca_email: Can be the same email used for “Email address for legal notices” above. domain: Unique identification of the online service you are providing. jurisdiction: List the country where whoever pays the bills lives. If it’s a company or other entity, list the country where it’s incorporated, and the city, region, territory or state as appropriate. + min_age: Should not be below the minimum age required by the laws of your jurisdiction. user: chosen_languages: When checked, only posts in selected languages will be displayed in public timelines + date_of_birth: + one: We have to make sure you're at least %{count} to use Mastodon. We won't store this. + other: We have to make sure you're at least %{count} to use Mastodon. We won't store this. role: The role controls which permissions the user has. user_role: color: Color to be used for the role throughout the UI, as RGB in hex format @@ -220,6 +233,7 @@ en-GB: setting_boost_modal: Show confirmation dialogue before boosting setting_default_language: Posting language setting_default_privacy: Posting privacy + setting_default_quote_policy: Who can quote setting_default_sensitive: Always mark media as sensitive setting_delete_modal: Show confirmation dialogue before deleting a post setting_disable_hover_cards: Disable profile preview on hover @@ -228,6 +242,7 @@ en-GB: setting_display_media_default: Default setting_display_media_hide_all: Hide all setting_display_media_show_all: Show all + setting_emoji_style: Emoji style setting_expand_spoilers: Always expand posts marked with content warnings setting_hide_network: Hide your social graph setting_missing_alt_text_modal: Show confirmation dialogue before posting media without alt text @@ -252,6 +267,7 @@ en-GB: name: Hashtag filters: actions: + blur: Hide media with a warning hide: Hide completely warn: Hide with a warning form_admin_settings: @@ -265,6 +281,7 @@ en-GB: favicon: Favicon mascot: Custom mascot (legacy) media_cache_retention_period: Media cache retention period + min_age: Minimum age requirement peers_api_enabled: Publish list of discovered servers in the API profile_directory: Enable profile directory registrations_mode: Who can sign-up @@ -330,16 +347,22 @@ en-GB: usable: Allow posts to use this hashtag locally terms_of_service: changelog: What's changed? + effective_date: Effective date text: Terms of Service terms_of_service_generator: admin_email: Email address for legal notices arbitration_address: Physical address for arbitration notices arbitration_website: Website for submitting arbitration notices + choice_of_law: Choice of Law dmca_address: Physical address for DMCA/copyright notices dmca_email: Email address for DMCA/copyright notices domain: Domain jurisdiction: Legal jurisdiction + min_age: Minimum age user: + date_of_birth_1i: Day + date_of_birth_2i: Month + date_of_birth_3i: Year role: Role time_zone: Time Zone user_role: diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index 3b68cf009f3..8364741080d 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -61,6 +61,7 @@ es-AR: setting_display_media_default: Ocultar medios marcados como sensibles setting_display_media_hide_all: Siempre ocultar todos los medios setting_display_media_show_all: Siempre mostrar todos los medios + setting_emoji_style: Cómo se mostrarán los emojis. "Automático" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. setting_system_scrollbars_ui: Solo aplica para navegadores web de escritorio basados en Safari y Chrome setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar actualizaciones de la línea temporal detrás de un clic en lugar de desplazar automáticamente el flujo @@ -241,6 +242,7 @@ es-AR: setting_display_media_default: Predeterminada setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo + setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir los mensajes marcados con advertencias de contenido setting_hide_network: Ocultá tu gráfica social setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de enviar medios sin texto alternativo diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 30614992fce..883f7070e00 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -61,6 +61,7 @@ es-MX: setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible + setting_emoji_style: Cómo se mostrarán los emojis. "Auto" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. setting_system_scrollbars_ui: Solo se aplica a los navegadores de escritorio basados en Safari y Chrome setting_use_blurhash: Los degradados se basan en los colores de los elementos visuales ocultos, pero ocultan cualquier detalle setting_use_pending_items: Ocultar las publicaciones de la línea de tiempo tras un clic en lugar de desplazar automáticamente el feed @@ -241,6 +242,7 @@ es-MX: setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo + setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red setting_missing_alt_text_modal: Mostrar cuadro de diálogo de confirmación antes de publicar contenido multimedia sin texto alternativo diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index 56eea49d25e..9c929a8c678 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -61,6 +61,7 @@ es: setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible + setting_emoji_style: Cómo se mostrarán los emojis. "Auto" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. setting_system_scrollbars_ui: Solo aplica para navegadores de escritorio basados en Safari y Chrome setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar nuevas publicaciones detrás de un clic en lugar de desplazar automáticamente el feed @@ -241,6 +242,7 @@ es: setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo + setting_emoji_style: Estilo de emoji setting_expand_spoilers: Siempre expandir las publicaciones marcadas con advertencias de contenido setting_hide_network: Ocultar tu red setting_missing_alt_text_modal: Mostrar diálogo de confirmación antes de publicar medios sin texto alternativo diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 82f47cd6f32..cafc9e0c7e4 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -240,6 +240,7 @@ fi: setting_display_media_default: Oletus setting_display_media_hide_all: Piilota kaikki setting_display_media_show_all: Näytä kaikki + setting_emoji_style: Emojityyli setting_expand_spoilers: Laajenna aina sisältövaroituksilla merkityt julkaisut setting_hide_network: Piilota verkostotietosi setting_missing_alt_text_modal: Näytä vahvistusikkuna ennen kuin julkaistaan mediaa ilman vaihtoehtoista tekstiä diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index c3f1dad678a..1b5931135c1 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -61,6 +61,7 @@ fo: setting_display_media_default: Fjal miðlafílur, sum eru merktar sum viðkvæmar setting_display_media_hide_all: Fjal altíð miðlafílur setting_display_media_show_all: Vís altíð miðlafílur + setting_emoji_style: Hvussu kenslutekn vera víst. "Sjálvvirkandi" roynir at brúka upprunalig kenslutekn, men fellir aftur á Twitter kenslutekn í eldri kagum. setting_system_scrollbars_ui: Er einans viðkomandi fyri skriviborðskagar grundaðir á Safari og Chrome setting_use_blurhash: Gradientar eru grundaðir á litirnar av fjaldu myndunum, men grugga allar smálutir setting_use_pending_items: Fjal tíðarlinjudagføringar aftan fyri eitt klikk heldur enn at skrulla tilføringina sjálvvirkandi @@ -241,6 +242,7 @@ fo: setting_display_media_default: Sjálvvirði setting_display_media_hide_all: Fjal alt setting_display_media_show_all: Vís alt + setting_emoji_style: Kensluteknsstílur setting_expand_spoilers: Víðka altíð postar, sum eru merktir við innihaldsávaringum setting_hide_network: Fjal sosiala grafin hjá tær setting_missing_alt_text_modal: Spyr um góðkenning áðrenn miðlar uttan alternativan tekst verða postaðir diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index eea259ef757..ddbac08b736 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -61,6 +61,7 @@ fy: setting_display_media_default: As gefoelich markearre media ferstopje setting_display_media_hide_all: Media altyd ferstopje setting_display_media_show_all: Media altyd toane + setting_emoji_style: Wêrmei moatte emojis werjûn wurde. ‘Automatysk’ probearret de systeemeigen emojis te brûken, mar falt werom op Twemoji foar âldere browsers. setting_system_scrollbars_ui: Allinnich fan tapassing op desktopbrowsers basearre op Safari en Chromium setting_use_blurhash: Dizige kleuroergongen binne basearre op de kleuren fan de ferstoppe media, wêrmei elk detail ferdwynt setting_use_pending_items: De tiidline wurdt bywurke troch op it oantal nije items te klikken, yn stee fan dat dizze automatysk bywurke wurdt @@ -241,6 +242,7 @@ fy: setting_display_media_default: Standert setting_display_media_hide_all: Alles ferstopje setting_display_media_show_all: Alles toane + setting_emoji_style: Emojistyl setting_expand_spoilers: Berjochten mei ynhâldswarskôgingen altyd útklappe setting_hide_network: Jo folgers en wa’t jo folget ferstopje setting_missing_alt_text_modal: Befêstigingsfinster toane foar it pleatsen fan media sûnder alt-tekst diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index a425a103847..40354650cca 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -61,6 +61,7 @@ gl: setting_display_media_default: Ocultar medios marcados como sensibles setting_display_media_hide_all: Ocultar sempre os medios setting_display_media_show_all: Mostrar sempre os medios marcados como sensibles + setting_emoji_style: Forma de mostrar emojis. «Auto» intentará usar os emojis nativos, e se falla recurrirase a Twemoji en navegadores antigos. setting_system_scrollbars_ui: Aplícase só en navegadores de escritorio baseados en Safari e Chrome setting_use_blurhash: Os gradientes toman as cores da imaxe oculta pero esvaecendo tódolos detalles setting_use_pending_items: Agochar actualizacións da cronoloxía tras un click no lugar de desprazar automáticamente os comentarios @@ -241,6 +242,7 @@ gl: setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo + setting_emoji_style: Estilo dos emojis setting_expand_spoilers: Despregar sempre as publicacións marcadas con avisos de contido setting_hide_network: Non mostrar contactos setting_missing_alt_text_modal: Mostrar mensaxe de confirmación antes de publicar multimedia sen texto descritivo diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index 4bceaa7e8f4..69699c44293 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -61,6 +61,7 @@ he: setting_display_media_default: הסתרת מדיה המסומנת כרגישה setting_display_media_hide_all: הסתר מדיה תמיד setting_display_media_show_all: גלה מדיה תמיד + setting_emoji_style: כיצד להציג רגישונים. "אוטומטי" ינסה להציג מסט האימוג'י המקומי, אבל נופל לערכת Twemoji כברירת מחדל עבור דפדפנים ישנים. setting_system_scrollbars_ui: נוגע רק לגבי דפדפני דסקטופ מבוססים ספארי וכרום setting_use_blurhash: הגראדיינטים מבוססים על תוכן התמונה המוסתרת, אבל מסתירים את כל הפרטים setting_use_pending_items: הסתר עדכוני פיד מאחורי קליק במקום לגלול את הפיד אוטומטית @@ -243,6 +244,7 @@ he: setting_display_media_default: ברירת מחדל setting_display_media_hide_all: להסתיר הכל setting_display_media_show_all: להציג הכול + setting_emoji_style: סגנון רגישונים (אמוג'י) setting_expand_spoilers: להרחיב תמיד הודעות מסומנות באזהרת תוכן setting_hide_network: להחביא את הגרף החברתי שלך setting_missing_alt_text_modal: הצג כרטיס אישור לפני פרסום קובץ גרפי ללא תיאור מילולי diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index 778d2d8f64f..0313bd378b8 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -61,6 +61,7 @@ is: setting_display_media_default: Fela myndefni sem merkt er viðkvæmt setting_display_media_hide_all: Alltaf fela allt myndefni setting_display_media_show_all: Alltaf birta myndefni sem merkt er viðkvæmt + setting_emoji_style: Hvernig birta skal tjáningartákn (emoji). "Sjálfvirkt" mun reyna að nota innbyggð tjáningartákn, en til vara verða notuð Twemoji-tákn fyrir eldri vafra. setting_system_scrollbars_ui: Á einungis við um vafra fyrir vinnutölvur sem byggjast á Safari og Chrome setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt @@ -241,6 +242,7 @@ is: setting_display_media_default: Sjálfgefið setting_display_media_hide_all: Fela allt setting_display_media_show_all: Birta allt + setting_emoji_style: Stíll tjáningartákna setting_expand_spoilers: Alltaf útfella færslur sem eru með aðvörun vegna efnisins setting_hide_network: Fela félagsnetið þitt setting_missing_alt_text_modal: Birta staðfestingarglugga áður en myndefni án ALT-hjálpartexta er birt diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index b198ddd0d95..e425bab9fb7 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -61,6 +61,7 @@ ko: setting_display_media_default: 민감함으로 표시된 미디어 가리기 setting_display_media_hide_all: 모든 미디어를 가리기 setting_display_media_show_all: 모든 미디어를 보이기 + setting_emoji_style: 에모지 표현 방식. "자동"은 시스템 기본 에모지를 적용하고 그렇지 못하는 오래된 브라우저의 경우 트웨모지를 사용합니다. setting_system_scrollbars_ui: 사파리와 크롬 기반의 데스크탑 브라우저만 적용됩니다 setting_use_blurhash: 그라디언트는 숨겨진 내용의 색상을 기반으로 하지만 상세 내용은 보이지 않게 합니다 setting_use_pending_items: 타임라인의 새 게시물을 자동으로 보여 주는 대신, 클릭해서 나타내도록 합니다 @@ -240,6 +241,7 @@ ko: setting_display_media_default: 기본 setting_display_media_hide_all: 모두 가리기 setting_display_media_show_all: 모두 보이기 + setting_emoji_style: 에모지 스타일 setting_expand_spoilers: 내용 경고로 표시된 게시물을 항상 펼치기 setting_hide_network: 내 인맥 숨기기 setting_missing_alt_text_modal: 대체 텍스트 없이 미디어를 게시하려고 할 때 확인창을 띄웁니다 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index dd9e2f78ca7..ef5b2a00b62 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -61,6 +61,7 @@ nl: setting_display_media_default: Als gevoelig gemarkeerde media verbergen setting_display_media_hide_all: Media altijd verbergen setting_display_media_show_all: Media altijd tonen + setting_emoji_style: Waarmee moeten emojis worden weergegeven. "Auto" probeert de systeemeigen emojis te gebruiken, maar valt terug op Twemoji voor oudere webbrowsers. setting_system_scrollbars_ui: Alleen van toepassing op desktopbrowsers gebaseerd op Safari en Chrome setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt @@ -241,6 +242,7 @@ nl: setting_display_media_default: Standaard setting_display_media_hide_all: Alles verbergen setting_display_media_show_all: Alles tonen + setting_emoji_style: Emoji-stijl setting_expand_spoilers: Berichten met inhoudswaarschuwingen altijd uitklappen setting_hide_network: Jouw volgers en wie je volgt verbergen setting_missing_alt_text_modal: Bevestigingsvenster tonen voor het plaatsen van media zonder alt-tekst diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 80bc56d033e..e5205412730 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -61,6 +61,7 @@ tr: setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle setting_display_media_hide_all: Medyayı her zaman gizle setting_display_media_show_all: Medyayı her zaman göster + setting_emoji_style: Emojiler nasıl görüntülensin. "Otomatik" seçeneği yerel emojileri kullanmaya çalışır, ancak eski tarayıcılar için Twemoji'yi kullanır. setting_system_scrollbars_ui: Yalnızca Safari ve Chrome tabanlı masaüstü tarayıcılar için geçerlidir setting_use_blurhash: Gradyenler gizli görsellerin renklerine dayanır, ancak detayları gizler setting_use_pending_items: Akışı otomatik olarak kaydırmak yerine, zaman çizelgesi güncellemelerini tek bir tıklamayla gizleyin @@ -241,6 +242,7 @@ tr: setting_display_media_default: Varsayılan setting_display_media_hide_all: Tümünü gizle setting_display_media_show_all: Tümünü göster + setting_emoji_style: Emoji stili setting_expand_spoilers: İçerik uyarılarıyla işaretli gönderileri her zaman genişlet setting_hide_network: Sosyal grafiğini gizle setting_missing_alt_text_modal: Alternatif metni olmayan medya göndermeden önce onay sorusu göster diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index 9e794028c58..556bcb2e90f 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -61,6 +61,7 @@ vi: setting_display_media_default: Click để xem setting_display_media_hide_all: Luôn ẩn setting_display_media_show_all: Luôn hiện + setting_emoji_style: Cách hiển thị Emoji. "Tự động" sẽ dùng biểu tượng cảm xúc nguyên bản, nhưng đối với các trình duyệt cũ sẽ chuyển thành Twemoji. setting_system_scrollbars_ui: Chỉ áp dụng trình duyệt Chrome và Safari bản desktop setting_use_blurhash: Phủ lớp màu làm nhòe đi hình ảnh nhạy cảm setting_use_pending_items: Dồn lại toàn bộ tút mới và chỉ hiển thị khi nhấn vào @@ -240,6 +241,7 @@ vi: setting_display_media_default: Mặc định setting_display_media_hide_all: Ẩn toàn bộ setting_display_media_show_all: Hiện toàn bộ + setting_emoji_style: Phong cách Emoji setting_expand_spoilers: Luôn mở rộng tút chứa nội dung ẩn setting_hide_network: Ẩn quan hệ của bạn setting_missing_alt_text_modal: Hỏi trước khi đăng media không có văn bản thay thế diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index 7716c2232f1..3c9fc974715 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -240,6 +240,7 @@ zh-CN: setting_display_media_default: 默认 setting_display_media_hide_all: 隐藏全部 setting_display_media_show_all: 显示全部 + setting_emoji_style: 表情符号样式 setting_expand_spoilers: 一律展开具有内容警告的嘟文 setting_hide_network: 隐藏你的社交网络 setting_missing_alt_text_modal: 发布媒体时若未为其设置替代文本,则显示确认对话框 diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index fb51f2ed3e9..83feb30eba0 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -61,6 +61,7 @@ zh-TW: setting_display_media_default: 隱藏標為敏感內容的媒體 setting_display_media_hide_all: 總是隱藏所有媒體 setting_display_media_show_all: 總是顯示標為敏感內容的媒體 + setting_emoji_style: 如何顯示 emoji 表情符號。「自動」將嘗試使用原生 emoji ,但於老式瀏覽器使用 Twemoji。 setting_system_scrollbars_ui: 僅套用至基於 Safari 或 Chrome 之桌面瀏覽器 setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊 setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新 @@ -240,6 +241,7 @@ zh-TW: setting_display_media_default: 預設 setting_display_media_hide_all: 全部隱藏 setting_display_media_show_all: 全部顯示 + setting_emoji_style: emoji 風格 setting_expand_spoilers: 永遠展開標有內容警告的嘟文 setting_hide_network: 隱藏您的社交網路 setting_missing_alt_text_modal: 發表未包含說明文字之多媒體嘟文前先詢問我 diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 7e681c90318..89c51ca6e08 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1349,6 +1349,10 @@ tr: basic_information: Temel bilgiler hint_html: "İnsanlara herkese açık profilinizde ve gönderilerinizin yanında ne göstermek istediğinizi düzenleyin. Dolu bir profile ve bir profil resmine sahip olduğunuzda diğer insanlar daha yüksek ihtimalle sizi takip etmek ve sizinle etkileşime geçmek isteyeceklerdir." other: Diğer + emoji_styles: + auto: Otomatik + native: Yerel + twemoji: Twemoji errors: '400': Gönderdiğiniz istek geçersiz veya hatalı biçimlendirilmiş. '403': Bu sayfayı görmek için izniniz yok. diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 93ab1fb811d..b8e2b9ef728 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1330,6 +1330,10 @@ vi: basic_information: Thông tin cơ bản hint_html: Mọi người sẽ muốn theo dõi và tương tác với bạn hơn nếu bạn có ảnh đại diện và hồ sơ hoàn chỉnh. other: Khác + emoji_styles: + auto: Tự động + native: Nguyên bản + twemoji: Twemoji errors: '400': Yêu cầu bạn gửi không hợp lệ hoặc sai hình thức. '403': Bạn không có quyền xem trang này. diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index ddd2c68113b..c62dc8e3191 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1321,6 +1321,10 @@ zh-CN: basic_information: 基本信息 hint_html: "自定义公开资料和嘟文旁边显示的内容。当你填写完整的个人资料并设置了头像时,其他人更有可能关注你并与你互动。" other: 其他 + emoji_styles: + auto: 自动 + native: 原生 + twemoji: Twemoji errors: '400': 你提交的请求无效或格式不正确。 '403': 你没有访问此页面的权限。 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 7c895d6794d..546ac7d989a 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1332,6 +1332,10 @@ zh-TW: basic_information: 基本資訊 hint_html: "自訂人們能於您個人檔案及嘟文旁所見之內容。當您完成填寫個人檔案及設定大頭貼後,其他人們比較願意跟隨您並與您互動。" other: 其他 + emoji_styles: + auto: 自動 + native: 原生風格 + twemoji: Twemoji errors: '400': 您所送出的請求無效或格式不正確。 '403': 您沒有檢視這個頁面的權限。 From a1e88135225753544197d8f1dd57fbb58cead43f Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 9 Jul 2025 11:55:41 +0200 Subject: [PATCH 029/660] Emoji Indexing and Search (#35253) --- .../mastodon/features/emoji/constants.ts | 110 ++++++++++++++ .../mastodon/features/emoji/database.ts | 102 +++++++++++++ .../mastodon/features/emoji/index.ts | 38 +++++ .../mastodon/features/emoji/loader.ts | 77 ++++++++++ .../mastodon/features/emoji/locale.test.ts | 61 ++------ .../mastodon/features/emoji/locale.ts | 56 ++------ .../mastodon/features/emoji/normalize.test.ts | 135 +++++++++++------- .../mastodon/features/emoji/normalize.ts | 78 +++++++--- .../mastodon/features/emoji/worker.ts | 13 ++ package.json | 2 + vite.config.mts | 25 +++- yarn.lock | 106 +++++++++++++- 12 files changed, 632 insertions(+), 171 deletions(-) create mode 100644 app/javascript/mastodon/features/emoji/constants.ts create mode 100644 app/javascript/mastodon/features/emoji/database.ts create mode 100644 app/javascript/mastodon/features/emoji/index.ts create mode 100644 app/javascript/mastodon/features/emoji/loader.ts create mode 100644 app/javascript/mastodon/features/emoji/worker.ts diff --git a/app/javascript/mastodon/features/emoji/constants.ts b/app/javascript/mastodon/features/emoji/constants.ts new file mode 100644 index 00000000000..d38f17f2160 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/constants.ts @@ -0,0 +1,110 @@ +// Utility codes +export const VARIATION_SELECTOR_CODE = 0xfe0f; +export const KEYCAP_CODE = 0x20e3; + +// Gender codes +export const GENDER_FEMALE_CODE = 0x2640; +export const GENDER_MALE_CODE = 0x2642; + +// Skin tone codes +export const SKIN_TONE_CODES = [ + 0x1f3fb, // Light skin tone + 0x1f3fc, // Medium-light skin tone + 0x1f3fd, // Medium skin tone + 0x1f3fe, // Medium-dark skin tone + 0x1f3ff, // Dark skin tone +] as const; + +export const EMOJIS_WITH_DARK_BORDER = [ + '🎱', // 1F3B1 + '🐜', // 1F41C + '⚫', // 26AB + '🖤', // 1F5A4 + '⬛', // 2B1B + '◼️', // 25FC-FE0F + '◾', // 25FE + '◼️', // 25FC-FE0F + '✒️', // 2712-FE0F + '▪️', // 25AA-FE0F + '💣', // 1F4A3 + '🎳', // 1F3B3 + '📷', // 1F4F7 + '📸', // 1F4F8 + '♣️', // 2663-FE0F + '🕶️', // 1F576-FE0F + '✴️', // 2734-FE0F + '🔌', // 1F50C + '💂‍♀️', // 1F482-200D-2640-FE0F + '📽️', // 1F4FD-FE0F + '🍳', // 1F373 + '🦍', // 1F98D + '💂', // 1F482 + '🔪', // 1F52A + '🕳️', // 1F573-FE0F + '🕹️', // 1F579-FE0F + '🕋', // 1F54B + '🖊️', // 1F58A-FE0F + '🖋️', // 1F58B-FE0F + '💂‍♂️', // 1F482-200D-2642-FE0F + '🎤', // 1F3A4 + '🎓', // 1F393 + '🎥', // 1F3A5 + '🎼', // 1F3BC + '♠️', // 2660-FE0F + '🎩', // 1F3A9 + '🦃', // 1F983 + '📼', // 1F4FC + '📹', // 1F4F9 + '🎮', // 1F3AE + '🐃', // 1F403 + '🏴', // 1F3F4 + '🐞', // 1F41E + '🕺', // 1F57A + '📱', // 1F4F1 + '📲', // 1F4F2 + '🚲', // 1F6B2 + '🪮', // 1FAA6 + '🐦‍⬛', // 1F426-200D-2B1B +]; + +export const EMOJIS_WITH_LIGHT_BORDER = [ + '👽', // 1F47D + '⚾', // 26BE + '🐔', // 1F414 + '☁️', // 2601-FE0F + '💨', // 1F4A8 + '🕊️', // 1F54A-FE0F + '👀', // 1F440 + '🍥', // 1F365 + '👻', // 1F47B + '🐐', // 1F410 + '❕', // 2755 + '❔', // 2754 + '⛸️', // 26F8-FE0F + '🌩️', // 1F329-FE0F + '🔊', // 1F50A + '🔇', // 1F507 + '📃', // 1F4C3 + '🌧️', // 1F327-FE0F + '🐏', // 1F40F + '🍚', // 1F35A + '🍙', // 1F359 + '🐓', // 1F413 + '🐑', // 1F411 + '💀', // 1F480 + '☠️', // 2620-FE0F + '🌨️', // 1F328-FE0F + '🔉', // 1F509 + '🔈', // 1F508 + '💬', // 1F4AC + '💭', // 1F4AD + '🏐', // 1F3D0 + '🏳️', // 1F3F3-FE0F + '⚪', // 26AA + '⬜', // 2B1C + '◽', // 25FD + '◻️', // 25FB-FE0F + '▫️', // 25AB-FE0F + '🪽', // 1FAE8 + '🪿', // 1FABF +]; diff --git a/app/javascript/mastodon/features/emoji/database.ts b/app/javascript/mastodon/features/emoji/database.ts new file mode 100644 index 00000000000..618f0108509 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/database.ts @@ -0,0 +1,102 @@ +import { SUPPORTED_LOCALES } from 'emojibase'; +import type { FlatCompactEmoji, Locale } from 'emojibase'; +import type { DBSchema } from 'idb'; +import { openDB } from 'idb'; + +import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; + +import type { LocaleOrCustom } from './locale'; +import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; + +interface EmojiDB extends LocaleTables, DBSchema { + custom: { + key: string; + value: ApiCustomEmojiJSON; + indexes: { + category: string; + }; + }; + etags: { + key: LocaleOrCustom; + value: string; + }; +} + +interface LocaleTable { + key: string; + value: FlatCompactEmoji; + indexes: { + group: number; + label: string; + order: number; + tags: string[]; + }; +} +type LocaleTables = Record; + +const SCHEMA_VERSION = 1; + +const db = await openDB('mastodon-emoji', SCHEMA_VERSION, { + upgrade(database) { + const customTable = database.createObjectStore('custom', { + keyPath: 'shortcode', + autoIncrement: false, + }); + customTable.createIndex('category', 'category'); + + database.createObjectStore('etags'); + + for (const locale of SUPPORTED_LOCALES) { + const localeTable = database.createObjectStore(locale, { + keyPath: 'hexcode', + autoIncrement: false, + }); + localeTable.createIndex('group', 'group'); + localeTable.createIndex('label', 'label'); + localeTable.createIndex('order', 'order'); + localeTable.createIndex('tags', 'tags', { multiEntry: true }); + } + }, +}); + +export async function putEmojiData(emojis: FlatCompactEmoji[], locale: Locale) { + const trx = db.transaction(locale, 'readwrite'); + await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); + await trx.done; +} + +export async function putCustomEmojiData(emojis: ApiCustomEmojiJSON[]) { + const trx = db.transaction('custom', 'readwrite'); + await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); + await trx.done; +} + +export function putLatestEtag(etag: string, localeString: string) { + const locale = toSupportedLocaleOrCustom(localeString); + return db.put('etags', etag, locale); +} + +export function searchEmojiByHexcode(hexcode: string, localeString: string) { + const locale = toSupportedLocale(localeString); + return db.get(locale, hexcode); +} + +export function searchEmojiByTag(tag: string, localeString: string) { + const locale = toSupportedLocale(localeString); + const range = IDBKeyRange.only(tag.toLowerCase()); + return db.getAllFromIndex(locale, 'tags', range); +} + +export function searchCustomEmojiByShortcode(shortcode: string) { + return db.get('custom', shortcode); +} + +export async function loadLatestEtag(localeString: string) { + const locale = toSupportedLocaleOrCustom(localeString); + const rowCount = await db.count(locale); + if (!rowCount) { + return null; // No data for this locale, return null even if there is an etag. + } + const etag = await db.get('etags', locale); + return etag ?? null; +} diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts new file mode 100644 index 00000000000..6975465b55f --- /dev/null +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -0,0 +1,38 @@ +import initialState from '@/mastodon/initial_state'; + +import { toSupportedLocale } from './locale'; + +const serverLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); + +const worker = + 'Worker' in window + ? new Worker(new URL('./worker', import.meta.url), { + type: 'module', + }) + : null; + +export async function initializeEmoji() { + if (worker) { + worker.addEventListener('message', (event: MessageEvent) => { + const { data: message } = event; + if (message === 'ready') { + worker.postMessage(serverLocale); + worker.postMessage('custom'); + } + }); + } else { + const { importCustomEmojiData, importEmojiData } = await import('./loader'); + await Promise.all([importCustomEmojiData(), importEmojiData(serverLocale)]); + } +} + +export async function loadEmojiLocale(localeString: string) { + const locale = toSupportedLocale(localeString); + + if (worker) { + worker.postMessage(locale); + } else { + const { importEmojiData } = await import('./loader'); + await importEmojiData(locale); + } +} diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts new file mode 100644 index 00000000000..f9c69713515 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -0,0 +1,77 @@ +import { flattenEmojiData } from 'emojibase'; +import type { CompactEmoji, FlatCompactEmoji } from 'emojibase'; + +import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; +import { isDevelopment } from '@/mastodon/utils/environment'; + +import { + putEmojiData, + putCustomEmojiData, + loadLatestEtag, + putLatestEtag, +} from './database'; +import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; +import type { LocaleOrCustom } from './locale'; + +export async function importEmojiData(localeString: string) { + const locale = toSupportedLocale(localeString); + const emojis = await fetchAndCheckEtag(locale); + if (!emojis) { + return; + } + const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); + await putEmojiData(flattenedEmojis, locale); +} + +export async function importCustomEmojiData() { + const emojis = await fetchAndCheckEtag('custom'); + if (!emojis) { + return; + } + await putCustomEmojiData(emojis); +} + +async function fetchAndCheckEtag( + localeOrCustom: LocaleOrCustom, +): Promise { + const locale = toSupportedLocaleOrCustom(localeOrCustom); + + let uri: string; + if (locale === 'custom') { + uri = '/api/v1/custom_emojis'; + } else { + uri = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`; + } + + const oldEtag = await loadLatestEtag(locale); + const response = await fetch(uri, { + headers: { + 'Content-Type': 'application/json', + 'If-None-Match': oldEtag ?? '', // Send the old ETag to check for modifications + }, + }); + // If not modified, return null + if (response.status === 304) { + return null; + } + if (!response.ok) { + throw new Error( + `Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`, + ); + } + + const data = (await response.json()) as ResultType; + if (!Array.isArray(data)) { + throw new Error( + `Unexpected data format for ${localeOrCustom}: expected an array`, + ); + } + + // Store the ETag for future requests + const etag = response.headers.get('ETag'); + if (etag) { + await putLatestEtag(etag, localeOrCustom); + } + + return data; +} diff --git a/app/javascript/mastodon/features/emoji/locale.test.ts b/app/javascript/mastodon/features/emoji/locale.test.ts index 0e098b2d447..5a474e94284 100644 --- a/app/javascript/mastodon/features/emoji/locale.test.ts +++ b/app/javascript/mastodon/features/emoji/locale.test.ts @@ -1,52 +1,6 @@ -import { flattenEmojiData, SUPPORTED_LOCALES } from 'emojibase'; -import emojiEnData from 'emojibase-data/en/compact.json'; -import emojiFrData from 'emojibase-data/fr/compact.json'; +import { SUPPORTED_LOCALES } from 'emojibase'; -import { toSupportedLocale, unicodeToLocaleLabel } from './locale'; - -describe('unicodeToLocaleLabel', () => { - const emojiTestCases = [ - '1F3CB-1F3FF-200D-2640-FE0F', // 🏋🏿‍♀️ Woman weightlifter, dark skin - '1F468-1F3FB', // 👨🏻 Man, light skin - '1F469-1F3FB-200D-2695-FE0F', // 👩🏻‍⚕️ Woman health worker, light skin - '1F468-1F3FD-200D-1F692', // 👨🏽‍🚒 Man firefighter, medium skin - '1F469-1F3FE', // 👩🏾 Woman, medium-dark skin - '1F469-1F3FF-200D-1F4BB', // 👩🏿‍💻 Woman technologist, dark skin - '1F478-1F3FF', // 👸🏿 Princess with dark skin tone - '1F935-1F3FC-200D-2640-FE0F', // 🤵🏼‍♀️ Woman in tuxedo, medium-light skin - '1F9D1-1F3FC', // 🧑🏼 Person, medium-light skin - '1F9D4-1F3FE', // 🧔🏾 Person with beard, medium-dark skin - ]; - - const flattenedEnData = flattenEmojiData(emojiEnData); - const flattenedFrData = flattenEmojiData(emojiFrData); - - const emojiTestEnLabels = new Map( - emojiTestCases.map((code) => [ - code, - flattenedEnData.find((emoji) => emoji.hexcode === code)?.label, - ]), - ); - const emojiTestFrLabels = new Map( - emojiTestCases.map((code) => [ - code, - flattenedFrData.find((emoji) => emoji.hexcode === code)?.label, - ]), - ); - - test.for( - emojiTestCases.flatMap((code) => [ - [code, 'en', emojiTestEnLabels.get(code)], - [code, 'fr', emojiTestFrLabels.get(code)], - ]) satisfies [string, string, string | undefined][], - )( - 'returns correct label for %s for %s locale', - async ([unicodeHex, locale, expectedLabel]) => { - const label = await unicodeToLocaleLabel(unicodeHex, locale); - expect(label).toBe(expectedLabel); - }, - ); -}); +import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; describe('toSupportedLocale', () => { test('returns the same locale if it is supported', () => { @@ -62,3 +16,14 @@ describe('toSupportedLocale', () => { } }); }); + +describe('toSupportedLocaleOrCustom', () => { + test('returns custom for "custom" locale', () => { + expect(toSupportedLocaleOrCustom('custom')).toBe('custom'); + }); + test('returns supported locale for valid locales', () => { + for (const locale of SUPPORTED_LOCALES) { + expect(toSupportedLocaleOrCustom(locale)).toBe(locale); + } + }); +}); diff --git a/app/javascript/mastodon/features/emoji/locale.ts b/app/javascript/mastodon/features/emoji/locale.ts index aac6c376b03..561c94afb0a 100644 --- a/app/javascript/mastodon/features/emoji/locale.ts +++ b/app/javascript/mastodon/features/emoji/locale.ts @@ -1,51 +1,23 @@ -import type { CompactEmoji, Locale } from 'emojibase'; -import { flattenEmojiData, SUPPORTED_LOCALES } from 'emojibase'; +import type { Locale } from 'emojibase'; +import { SUPPORTED_LOCALES } from 'emojibase'; -// Simple cache. This will be replaced with an IndexedDB cache in the future. -const localeCache = new Map>(); +export type LocaleOrCustom = Locale | 'custom'; -export async function unicodeToLocaleLabel( - unicodeHex: string, - localeString: string, -) { - const locale = toSupportedLocale(localeString); - let hexMap = localeCache.get(locale); - if (!hexMap) { - hexMap = await loadLocaleLabels(locale); - localeCache.set(locale, hexMap); - } - - const label = hexMap.get(unicodeHex)?.label; - if (!label) { - throw new Error( - `Label for unicode hex ${unicodeHex} not found in locale ${locale}`, - ); - } - return label; -} - -async function loadLocaleLabels( - locale: Locale, -): Promise> { - const { default: localeEmoji } = ((await import( - `emojibase-data/${locale}/compact.json` - )) ?? { default: [] }) as { default: CompactEmoji[] }; - if (!Array.isArray(localeEmoji)) { - throw new Error(`Locale data for ${locale} not found`); - } - const hexMapEntries = flattenEmojiData(localeEmoji).map( - (emoji) => [emoji.hexcode, emoji] satisfies [string, CompactEmoji], - ); - return new Map(hexMapEntries); -} - -export function toSupportedLocale(locale: string): Locale { +export function toSupportedLocale(localeBase: string): Locale { + const locale = localeBase.toLowerCase(); if (isSupportedLocale(locale)) { return locale; } return 'en'; // Default to English if unsupported } -function isSupportedLocale(locale: string): locale is Locale { - return SUPPORTED_LOCALES.includes(locale as Locale); +export function toSupportedLocaleOrCustom(locale: string): LocaleOrCustom { + if (locale.toLowerCase() === 'custom') { + return 'custom'; + } + return toSupportedLocale(locale); +} + +function isSupportedLocale(locale: string): locale is Locale { + return SUPPORTED_LOCALES.includes(locale.toLowerCase() as Locale); } diff --git a/app/javascript/mastodon/features/emoji/normalize.test.ts b/app/javascript/mastodon/features/emoji/normalize.test.ts index 29255d52916..ee9cd89487f 100644 --- a/app/javascript/mastodon/features/emoji/normalize.test.ts +++ b/app/javascript/mastodon/features/emoji/normalize.test.ts @@ -1,9 +1,17 @@ import { readdir } from 'fs/promises'; import { basename, resolve } from 'path'; -import unicodeEmojis from 'emojibase-data/en/data.json'; +import { flattenEmojiData } from 'emojibase'; +import unicodeRawEmojis from 'emojibase-data/en/data.json'; -import { twemojiToUnicodeInfo, unicodeToTwemojiHex } from './normalize'; +import { + twemojiHasBorder, + twemojiToUnicodeInfo, + unicodeToTwemojiHex, + CODES_WITH_DARK_BORDER, + CODES_WITH_LIGHT_BORDER, + emojiToUnicodeHex, +} from './normalize'; const emojiSVGFiles = await readdir( // This assumes tests are run from project root @@ -13,60 +21,81 @@ const emojiSVGFiles = await readdir( }, ); const svgFileNames = emojiSVGFiles - .filter( - (file) => - file.isFile() && - file.name.endsWith('.svg') && - !file.name.endsWith('_border.svg'), - ) + .filter((file) => file.isFile() && file.name.endsWith('.svg')) .map((file) => basename(file.name, '.svg').toUpperCase()); +const svgFileNamesWithoutBorder = svgFileNames.filter( + (fileName) => !fileName.endsWith('_BORDER'), +); -describe('normalizeEmoji', () => { - describe('unicodeToSVGName', () => { - test.concurrent.for( - unicodeEmojis - // Our version of Twemoji only supports up to version 15.1 - .filter((emoji) => emoji.version < 16) - .map((emoji) => [emoji.hexcode, emoji.label] as [string, string]), - )('verifying an emoji exists for %s (%s)', ([hexcode], { expect }) => { - const result = unicodeToTwemojiHex(hexcode); - expect(svgFileNames).toContain(result); - }); - }); +const unicodeEmojis = flattenEmojiData(unicodeRawEmojis); - describe('twemojiToUnicodeInfo', () => { - const unicodeMap = new Map( - unicodeEmojis.flatMap((emoji) => { - const base: [string, string][] = [[emoji.hexcode, emoji.label]]; - if (emoji.skins) { - base.push( - ...emoji.skins.map( - (skin) => [skin.hexcode, skin.label] as [string, string], - ), - ); - } - return base; - }), - ); +describe('emojiToUnicodeHex', () => { + test.concurrent.for([ + ['🎱', '1F3B1'], + ['🐜', '1F41C'], + ['⚫', '26AB'], + ['🖤', '1F5A4'], + ['💀', '1F480'], + ['💂‍♂️', '1F482-200D-2642-FE0F'], + ] as const)( + 'emojiToUnicodeHex converts %s to %s', + ([emoji, hexcode], { expect }) => { + expect(emojiToUnicodeHex(emoji)).toBe(hexcode); + }, + ); +}); - test.concurrent.for(svgFileNames)( - 'verifying SVG file %s maps to Unicode emoji', - (svgFileName, { expect }) => { - assert(!!svgFileName); - const result = twemojiToUnicodeInfo(svgFileName); - const hexcode = - typeof result === 'string' ? result : result.unqualified; - if (!hexcode) { - // No hexcode means this is a special case like the Shibuya 109 emoji - expect(result).toHaveProperty('label'); - return; - } - assert(!!hexcode); - expect( - unicodeMap.has(hexcode), - `${hexcode} (${svgFileName}) not found`, - ).toBeTruthy(); - }, - ); +describe('unicodeToTwemojiHex', () => { + test.concurrent.for( + unicodeEmojis + // Our version of Twemoji only supports up to version 15.1 + .filter((emoji) => emoji.version < 16) + .map((emoji) => [emoji.hexcode, emoji.label] as [string, string]), + )('verifying an emoji exists for %s (%s)', ([hexcode], { expect }) => { + const result = unicodeToTwemojiHex(hexcode); + expect(svgFileNamesWithoutBorder).toContain(result); }); }); + +describe('twemojiHasBorder', () => { + test.concurrent.for( + svgFileNames + .filter((file) => file.endsWith('_BORDER')) + .map((file) => { + const hexCode = file.replace('_BORDER', ''); + return [ + hexCode, + CODES_WITH_LIGHT_BORDER.includes(hexCode), + CODES_WITH_DARK_BORDER.includes(hexCode), + ] as const; + }), + )('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => { + const result = twemojiHasBorder(hexCode); + expect(result).toHaveProperty('hexCode', hexCode); + expect(result).toHaveProperty('hasLightBorder', isLight); + expect(result).toHaveProperty('hasDarkBorder', isDark); + }); +}); + +describe('twemojiToUnicodeInfo', () => { + const unicodeCodeSet = new Set(unicodeEmojis.map((emoji) => emoji.hexcode)); + + test.concurrent.for(svgFileNamesWithoutBorder)( + 'verifying SVG file %s maps to Unicode emoji', + (svgFileName, { expect }) => { + assert(!!svgFileName); + const result = twemojiToUnicodeInfo(svgFileName); + const hexcode = typeof result === 'string' ? result : result.unqualified; + if (!hexcode) { + // No hexcode means this is a special case like the Shibuya 109 emoji + expect(result).toHaveProperty('label'); + return; + } + assert(!!hexcode); + expect( + unicodeCodeSet.has(hexcode), + `${hexcode} (${svgFileName}) not found`, + ).toBeTruthy(); + }, + ); +}); diff --git a/app/javascript/mastodon/features/emoji/normalize.ts b/app/javascript/mastodon/features/emoji/normalize.ts index 024cd536252..94dc33a6ea2 100644 --- a/app/javascript/mastodon/features/emoji/normalize.ts +++ b/app/javascript/mastodon/features/emoji/normalize.ts @@ -1,19 +1,12 @@ -// Utility codes -const VARIATION_SELECTOR_CODE = 0xfe0f; -const KEYCAP_CODE = 0x20e3; - -// Gender codes -const GENDER_FEMALE_CODE = 0x2640; -const GENDER_MALE_CODE = 0x2642; - -// Skin tone codes -const SKIN_TONE_CODES = [ - 0x1f3fb, // Light skin tone - 0x1f3fc, // Medium-light skin tone - 0x1f3fd, // Medium skin tone - 0x1f3fe, // Medium-dark skin tone - 0x1f3ff, // Dark skin tone -] as const; +import { + VARIATION_SELECTOR_CODE, + KEYCAP_CODE, + GENDER_FEMALE_CODE, + GENDER_MALE_CODE, + SKIN_TONE_CODES, + EMOJIS_WITH_DARK_BORDER, + EMOJIS_WITH_LIGHT_BORDER, +} from './constants'; // Misc codes that have special handling const SKIER_CODE = 0x26f7; @@ -24,6 +17,17 @@ const LEVITATING_PERSON_CODE = 0x1f574; const SPEECH_BUBBLE_CODE = 0x1f5e8; const MS_CLAUS_CODE = 0x1f936; +export function emojiToUnicodeHex(emoji: string): string { + const codes: number[] = []; + for (const char of emoji) { + const code = char.codePointAt(0); + if (code !== undefined) { + codes.push(code); + } + } + return hexNumbersToString(codes); +} + export function unicodeToTwemojiHex(unicodeHex: string): string { const codes = hexStringToNumbers(unicodeHex); const normalizedCodes: number[] = []; @@ -50,6 +54,35 @@ export function unicodeToTwemojiHex(unicodeHex: string): string { return hexNumbersToString(normalizedCodes, 0); } +interface TwemojiBorderInfo { + hexCode: string; + hasLightBorder: boolean; + hasDarkBorder: boolean; +} + +export const CODES_WITH_DARK_BORDER = + EMOJIS_WITH_DARK_BORDER.map(emojiToUnicodeHex); + +export const CODES_WITH_LIGHT_BORDER = + EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex); + +export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo { + const normalizedHex = twemojiHex.toUpperCase(); + let hasLightBorder = false; + let hasDarkBorder = false; + if (CODES_WITH_LIGHT_BORDER.includes(normalizedHex)) { + hasLightBorder = true; + } + if (CODES_WITH_DARK_BORDER.includes(normalizedHex)) { + hasDarkBorder = true; + } + return { + hexCode: normalizedHex, + hasLightBorder, + hasDarkBorder, + }; +} + interface TwemojiSpecificEmoji { unqualified?: string; gender?: number; @@ -84,11 +117,16 @@ export function twemojiToUnicodeInfo( let gender: undefined | number; let skin: undefined | number; for (const code of codes) { - if (code in GENDER_CODES_MAP) { + if (!gender && code in GENDER_CODES_MAP) { gender = GENDER_CODES_MAP[code]; - } else if (code in SKIN_TONE_CODES) { + } else if (!skin && code in SKIN_TONE_CODES) { skin = code; } + + // Exit if we have both skin and gender + if (skin && gender) { + break; + } } let mappedCodes: unknown[] = codes; @@ -103,8 +141,8 @@ export function twemojiToUnicodeInfo( // For key emoji, insert the variation selector mappedCodes = [codes[0], VARIATION_SELECTOR_CODE, KEYCAP_CODE]; } else if ( - codes.at(0) === SKIER_CODE || - codes.at(0) === LEVITATING_PERSON_CODE + (codes.at(0) === SKIER_CODE || codes.at(0) === LEVITATING_PERSON_CODE) && + codes.length > 1 ) { // Twemoji offers more gender and skin options for the skier and levitating person emoji. return { diff --git a/app/javascript/mastodon/features/emoji/worker.ts b/app/javascript/mastodon/features/emoji/worker.ts new file mode 100644 index 00000000000..1c48a077730 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/worker.ts @@ -0,0 +1,13 @@ +import { importEmojiData, importCustomEmojiData } from './loader'; + +addEventListener('message', handleMessage); +self.postMessage('ready'); // After the worker is ready, notify the main thread + +function handleMessage(event: MessageEvent) { + const { data: locale } = event; + if (locale !== 'custom') { + void importEmojiData(locale); + } else { + void importCustomEmojiData(); + } +} diff --git a/package.json b/package.json index 26704223340..ace593ab01b 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "history": "^4.10.1", "hoist-non-react-statics": "^3.3.2", "http-link-header": "^1.1.1", + "idb": "^8.0.3", "immutable": "^4.3.0", "intl-messageformat": "^10.7.16", "js-yaml": "^4.1.0", @@ -117,6 +118,7 @@ "vite-plugin-pwa": "^1.0.0", "vite-plugin-rails": "^0.5.0", "vite-plugin-ruby": "^5.1.1", + "vite-plugin-static-copy": "^3.1.0", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^5.1.4", "wicg-inert": "^3.1.2", diff --git a/vite.config.mts b/vite.config.mts index 484211eaa0c..6429f97c594 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -1,14 +1,15 @@ import path from 'node:path'; import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin'; +import legacy from '@vitejs/plugin-legacy'; import react from '@vitejs/plugin-react'; import { PluginOption } from 'vite'; -import svgr from 'vite-plugin-svgr'; import { visualizer } from 'rollup-plugin-visualizer'; -import RailsPlugin from 'vite-plugin-rails'; import { VitePWA } from 'vite-plugin-pwa'; +import RailsPlugin from 'vite-plugin-rails'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; +import svgr from 'vite-plugin-svgr'; import tsconfigPaths from 'vite-tsconfig-paths'; -import legacy from '@vitejs/plugin-legacy'; import { defineConfig, UserConfigFnPromise, UserConfig } from 'vite'; import postcssPresetEnv from 'postcss-preset-env'; @@ -78,6 +79,9 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }, }, }, + worker: { + format: 'es', + }, plugins: [ tsconfigPaths(), RailsPlugin({ @@ -92,6 +96,21 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { plugins: ['formatjs', 'transform-react-remove-prop-types'], }, }), + viteStaticCopy({ + targets: [ + { + src: path.resolve( + __dirname, + 'node_modules/emojibase-data/**/compact.json', + ), + dest: 'emoji', + rename(_name, ext, dir) { + const locale = path.basename(path.dirname(dir)); + return `${locale}.${ext}`; + }, + }, + ], + }), MastodonServiceWorkerLocales(), MastodonEmojiCompressed(), legacy({ diff --git a/yarn.lock b/yarn.lock index a1aea84b5e1..a0814a27af4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2686,6 +2686,7 @@ __metadata: hoist-non-react-statics: "npm:^3.3.2" http-link-header: "npm:^1.1.1" husky: "npm:^9.0.11" + idb: "npm:^8.0.3" immutable: "npm:^4.3.0" intl-messageformat: "npm:^10.7.16" js-yaml: "npm:^4.1.0" @@ -2742,6 +2743,7 @@ __metadata: vite-plugin-pwa: "npm:^1.0.0" vite-plugin-rails: "npm:^0.5.0" vite-plugin-ruby: "npm:^5.1.1" + vite-plugin-static-copy: "npm:^3.1.0" vite-plugin-svgr: "npm:^4.2.0" vite-tsconfig-paths: "npm:^5.1.4" vitest: "npm:^3.2.1" @@ -5083,6 +5085,16 @@ __metadata: languageName: node linkType: hard +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + "are-docs-informative@npm:^0.0.2": version: 0.0.2 resolution: "are-docs-informative@npm:0.0.2" @@ -5471,6 +5483,13 @@ __metadata: languageName: node linkType: hard +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + "bintrees@npm:1.0.2": version: 1.0.2 resolution: "bintrees@npm:1.0.2" @@ -5531,7 +5550,7 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.3": +"braces@npm:^3.0.3, braces@npm:~3.0.2": version: 3.0.3 resolution: "braces@npm:3.0.3" dependencies: @@ -5745,6 +5764,25 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^3.5.3": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 + languageName: node + linkType: hard + "chokidar@npm:^4.0.0": version: 4.0.0 resolution: "chokidar@npm:4.0.0" @@ -7563,6 +7601,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.3.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a + languageName: node + linkType: hard + "fs-extra@npm:^9.0.1": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" @@ -7749,7 +7798,7 @@ __metadata: languageName: node linkType: hard -"glob-parent@npm:^5.1.2": +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" dependencies: @@ -8116,6 +8165,13 @@ __metadata: languageName: node linkType: hard +"idb@npm:^8.0.3": + version: 8.0.3 + resolution: "idb@npm:8.0.3" + checksum: 10c0/421cd9a3281b7564528857031cc33fd9e95753f8191e483054cb25d1ceea7303a0d1462f4f69f5b41606f0f066156999e067478abf2460dfcf9cab80dae2a2b2 + languageName: node + linkType: hard + "ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -8319,6 +8375,15 @@ __metadata: languageName: node linkType: hard +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 + languageName: node + linkType: hard + "is-boolean-object@npm:^1.2.1": version: 1.2.2 resolution: "is-boolean-object@npm:1.2.2" @@ -8432,7 +8497,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -9712,7 +9777,7 @@ __metadata: languageName: node linkType: hard -"normalize-path@npm:^3.0.0": +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 @@ -9927,6 +9992,13 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^7.0.3": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.0 resolution: "package-json-from-dist@npm:1.0.0" @@ -10165,7 +10237,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be @@ -11394,6 +11466,15 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b + languageName: node + linkType: hard + "real-require@npm:^0.2.0": version: 0.2.0 resolution: "real-require@npm:0.2.0" @@ -13822,6 +13903,21 @@ __metadata: languageName: node linkType: hard +"vite-plugin-static-copy@npm:^3.1.0": + version: 3.1.0 + resolution: "vite-plugin-static-copy@npm:3.1.0" + dependencies: + chokidar: "npm:^3.5.3" + fs-extra: "npm:^11.3.0" + p-map: "npm:^7.0.3" + picocolors: "npm:^1.1.1" + tinyglobby: "npm:^0.2.14" + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/dce43f12ecc71417f1afd530d15b316774fe0441c2502e48e2bfafcd07fd4ae90a5782621f932d8d12a8c8213bed6746e80d5452e2fb216ece2bcf7e80309f82 + languageName: node + linkType: hard + "vite-plugin-stimulus-hmr@npm:^3.0.0": version: 3.0.0 resolution: "vite-plugin-stimulus-hmr@npm:3.0.0" From e7c5c25de8c1bd4e32a4bf599c759303420c47e3 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 9 Jul 2025 14:13:51 +0200 Subject: [PATCH 030/660] Fix replying from media modal or pop-in-player tagging user `@undefined` (#35317) --- .../picture_in_picture/components/footer.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx index 26cb2172a94..080aaca4512 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; @@ -21,6 +21,9 @@ import { openModal } from 'mastodon/actions/modal'; import { IconButton } from 'mastodon/components/icon_button'; import { useIdentity } from 'mastodon/identity_context'; import { me } from 'mastodon/initial_state'; +import type { Status } from 'mastodon/models/status'; +import { makeGetStatus } from 'mastodon/selectors'; +import type { RootState } from 'mastodon/store'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; const messages = defineMessages({ @@ -47,6 +50,11 @@ const messages = defineMessages({ open: { id: 'status.open', defaultMessage: 'Expand this status' }, }); +type GetStatusSelector = ( + state: RootState, + props: { id?: string | null; contextType?: string }, +) => Status | null; + export const Footer: React.FC<{ statusId: string; withOpenButton?: boolean; @@ -56,7 +64,8 @@ export const Footer: React.FC<{ const intl = useIntl(); const history = useHistory(); const dispatch = useAppDispatch(); - const status = useAppSelector((state) => state.statuses.get(statusId)); + const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector; + const status = useAppSelector((state) => getStatus(state, { id: statusId })); const accountId = status?.get('account') as string | undefined; const account = useAppSelector((state) => accountId ? state.accounts.get(accountId) : undefined, From 786b12e3799ba40e40b584a2daf4c9bc5cf4da60 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 9 Jul 2025 16:22:47 +0200 Subject: [PATCH 031/660] Relax error restriction in initializer (#35321) --- config/initializers/settings_digests.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/settings_digests.rb b/config/initializers/settings_digests.rb index 2a5d925c70d..85599ee1b0b 100644 --- a/config/initializers/settings_digests.rb +++ b/config/initializers/settings_digests.rb @@ -3,7 +3,7 @@ Rails.application.config.to_prepare do custom_css = begin Setting.custom_css - rescue ActiveRecord::AdapterError # Running without a database, not migrated, no connection, etc + rescue # Running without a cache, database, not migrated, no connection, etc nil end From 5cfc1fabcf6d95756571d3f31d66b4947bf21665 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 9 Jul 2025 16:34:16 +0200 Subject: [PATCH 032/660] Fix nearly every sub-directory being crawled as part of Vite build (#35323) --- vite.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.mts b/vite.config.mts index 6429f97c594..8d0fdfde51c 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -83,7 +83,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { format: 'es', }, plugins: [ - tsconfigPaths(), + tsconfigPaths({ projects: [path.resolve(__dirname, 'tsconfig.json')] }), RailsPlugin({ compress: mode === 'production' && command === 'build', sri: { From e9170e2de1d75e02f04ad39f2af131c70189fb50 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 9 Jul 2025 18:22:25 +0200 Subject: [PATCH 033/660] Bump version to v4.4.1 (#35329) --- CHANGELOG.md | 10 ++++++++++ docker-compose.yml | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59a8a926827..19be8ea68e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented in this file. +## [4.4.1] - 2025-07-09 + +### Fixed + +- Fix nearly every sub-directory being crawled as part of Vite build (#35323 by @ClearlyClaire) +- Fix assets not building when Redis is unavailable (#35321 by @oneiros) +- Fix replying from media modal or pop-in-player tagging user `@undefined` (#35317 by @ClearlyClaire) +- Fix support for special characters in various environment variables (#35314 by @mjankowski and @ClearlyClaire) +- Fix some database migrations failing for indexes manually removed by admins (#35309 by @mjankowski) + ## [4.4.0] - 2025-07-08 ### Added diff --git a/docker-compose.yml b/docker-compose.yml index 4926101b954..e5d94b390f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.0 + image: ghcr.io/mastodon/mastodon:v4.4.1 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/mastodon/mastodon-streaming:v4.4.0 + image: ghcr.io/mastodon/mastodon-streaming:v4.4.1 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.0 + image: ghcr.io/mastodon/mastodon:v4.4.1 restart: always env_file: .env.production command: bundle exec sidekiq From a315934314a9fdadcab7353d5654acc02018643a Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 10 Jul 2025 08:56:40 +0200 Subject: [PATCH 034/660] Fix styling of external log-in button (#35320) --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5a5ee055321..33d4bf6d022 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -66,7 +66,7 @@ module ApplicationHelper def provider_sign_in_link(provider) label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize) - link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post + link_to label, omniauth_authorize_path(:user, provider), class: "btn button-#{provider}", method: :post end def locale_direction From 4ecfbd392083631d773fff950b281de038112e84 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 10 Jul 2025 03:13:22 -0400 Subject: [PATCH 035/660] Add `Status.only_polls` (and without polls) scope (#35330) --- app/lib/annual_report/archetype.rb | 2 +- app/models/account_statuses_cleanup_policy.rb | 2 +- app/models/status.rb | 2 ++ spec/models/status_spec.rb | 22 +++++++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/lib/annual_report/archetype.rb b/app/lib/annual_report/archetype.rb index ff7e14e2a04..a02dbf7db71 100644 --- a/app/lib/annual_report/archetype.rb +++ b/app/lib/annual_report/archetype.rb @@ -28,7 +28,7 @@ class AnnualReport::Archetype < AnnualReport::Source end def polls_count - @polls_count ||= report_statuses.where.not(poll_id: nil).count + @polls_count ||= report_statuses.only_polls.count end def reblogs_count diff --git a/app/models/account_statuses_cleanup_policy.rb b/app/models/account_statuses_cleanup_policy.rb index ef41bb3ee3a..0915003c8b7 100644 --- a/app/models/account_statuses_cleanup_policy.rb +++ b/app/models/account_statuses_cleanup_policy.rb @@ -161,7 +161,7 @@ class AccountStatusesCleanupPolicy < ApplicationRecord end def without_poll_scope - Status.where(poll_id: nil) + Status.without_polls end def without_popular_scope diff --git a/app/models/status.rb b/app/models/status.rb index 4aac3b62d7f..bf392317a5a 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -121,6 +121,8 @@ class Status < ApplicationRecord scope :without_replies, -> { not_reply.or(reply_to_account) } scope :not_reply, -> { where(reply: false) } scope :only_reblogs, -> { where.not(reblog_of_id: nil) } + scope :only_polls, -> { where.not(poll_id: nil) } + scope :without_polls, -> { where(poll_id: nil) } scope :reply_to_account, -> { where(arel_table[:in_reply_to_account_id].eq arel_table[:account_id]) } scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index e6c49f1013d..f3453b3b674 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -363,6 +363,28 @@ RSpec.describe Status do end end + describe '.only_polls' do + let!(:poll_status) { Fabricate :status, poll: Fabricate(:poll) } + let!(:no_poll_status) { Fabricate :status } + + it 'returns the expected statuses' do + expect(described_class.only_polls) + .to include(poll_status) + .and not_include(no_poll_status) + end + end + + describe '.without_polls' do + let!(:poll_status) { Fabricate :status, poll: Fabricate(:poll) } + let!(:no_poll_status) { Fabricate :status } + + it 'returns the expected statuses' do + expect(described_class.without_polls) + .to not_include(poll_status) + .and include(no_poll_status) + end + end + describe '.tagged_with' do let(:tag_cats) { Fabricate(:tag, name: 'cats') } let(:tag_dogs) { Fabricate(:tag, name: 'dogs') } From f9beecb3433742880d69236563f692340ac539a9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 10 Jul 2025 03:23:09 -0400 Subject: [PATCH 036/660] Improve Accounts CLI `prune` spec (#35302) --- spec/lib/mastodon/cli/accounts_spec.rb | 81 +++++++++++++++----------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 238f72f834f..e60f5a0cf71 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -1288,49 +1288,64 @@ RSpec.describe Mastodon::CLI::Accounts do describe '#prune' do let(:action) { :prune } - let!(:local_account) { Fabricate(:account) } - let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') } - let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') } - let!(:mentioned_account) { Fabricate(:account, domain: 'example.com') } - let!(:prunable_accounts) do - Fabricate.times(2, :account, domain: 'example.com', bot: false, suspended_at: nil, silenced_at: nil) - end + let(:viable_attrs) { { domain: 'example.com', bot: false, suspended: false, silenced: false } } + let!(:local_account) { Fabricate(:account) } + let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') } + let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') } + let!(:account_mentioned) { Fabricate(:account, viable_attrs) } + let!(:account_with_favourite) { Fabricate(:account, viable_attrs) } + let!(:account_with_status) { Fabricate(:account, viable_attrs) } + let!(:account_with_follow) { Fabricate(:account, viable_attrs) } + let!(:account_targeted_follow) { Fabricate(:account, viable_attrs) } + let!(:account_with_block) { Fabricate(:account, viable_attrs) } + let!(:account_targeted_block) { Fabricate(:account, viable_attrs) } + let!(:account_targeted_mute) { Fabricate(:account, viable_attrs) } + let!(:account_targeted_report) { Fabricate(:account, viable_attrs) } + let!(:account_with_follow_request) { Fabricate(:account, viable_attrs) } + let!(:account_targeted_follow_request) { Fabricate(:account, viable_attrs) } + let!(:prunable_accounts) { Fabricate.times(2, :account, viable_attrs) } before do - Fabricate(:mention, account: mentioned_account, status: Fabricate(:status, account: Fabricate(:account))) + Fabricate :mention, account: account_mentioned, status: Fabricate(:status, account: Fabricate(:account)) + Fabricate :favourite, account: account_with_favourite + Fabricate :status, account: account_with_status + Fabricate :follow, account: account_with_follow + Fabricate :follow, target_account: account_targeted_follow + Fabricate :block, account: account_with_block + Fabricate :block, target_account: account_targeted_block + Fabricate :mute, target_account: account_targeted_mute + Fabricate :report, target_account: account_targeted_report + Fabricate :follow_request, account: account_with_follow_request + Fabricate :follow_request, target_account: account_targeted_follow_request stub_parallelize_with_progress! end - def expect_prune_remote_accounts_without_interaction - prunable_account_ids = prunable_accounts.pluck(:id) - - expect(Account.where(id: prunable_account_ids).count).to eq(0) - end - it 'displays a successful message and handles accounts correctly' do expect { subject } .to output_results("OK, pruned #{prunable_accounts.size} accounts") - expect_prune_remote_accounts_without_interaction - expect_not_prune_local_accounts - expect_not_prune_bot_accounts - expect_not_prune_group_accounts - expect_not_prune_mentioned_accounts + expect(prunable_account_records) + .to have_attributes(count: eq(0)) + expect(Account.all) + .to include(local_account) + .and include(bot_account) + .and include(group_account) + .and include(account_mentioned) + .and include(account_with_favourite) + .and include(account_with_status) + .and include(account_with_follow) + .and include(account_targeted_follow) + .and include(account_with_block) + .and include(account_targeted_block) + .and include(account_targeted_mute) + .and include(account_targeted_report) + .and include(account_with_follow_request) + .and include(account_targeted_follow_request) + .and not_include(prunable_accounts.first) + .and not_include(prunable_accounts.last) end - def expect_not_prune_local_accounts - expect(Account.exists?(id: local_account.id)).to be(true) - end - - def expect_not_prune_bot_accounts - expect(Account.exists?(id: bot_account.id)).to be(true) - end - - def expect_not_prune_group_accounts - expect(Account.exists?(id: group_account.id)).to be(true) - end - - def expect_not_prune_mentioned_accounts - expect(Account.exists?(id: mentioned_account.id)).to be true + def prunable_account_records + Account.where(id: prunable_accounts.pluck(:id)) end context 'with --dry-run option' do From 86645fc14ce483e536685aecf465d3e38c94deb0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:23:23 +0200 Subject: [PATCH 037/660] chore(deps): update dependency rubocop to v1.78.0 (#35289) 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 f306eb8619f..769d67cd94a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -761,7 +761,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.4) - rubocop (1.77.0) + rubocop (1.78.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) From b363a3651d2f539894f5f45d584f047e1d8db849 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:23:27 +0200 Subject: [PATCH 038/660] New Crowdin Translations (automated) (#35335) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/da.json | 4 ++-- app/javascript/mastodon/locales/eu.json | 3 +++ app/javascript/mastodon/locales/lad.json | 5 +++++ config/locales/da.yml | 2 +- config/locales/de.yml | 2 +- config/locales/eu.yml | 4 ++++ config/locales/fr-CA.yml | 4 ++++ config/locales/fr.yml | 4 ++++ config/locales/ga.yml | 4 ++++ config/locales/it.yml | 4 ++++ config/locales/lad.yml | 21 +++++++++++++++++++++ config/locales/nl.yml | 2 +- config/locales/simple_form.da.yml | 2 +- config/locales/simple_form.de.yml | 4 ++-- config/locales/simple_form.ga.yml | 2 ++ config/locales/simple_form.it.yml | 2 ++ config/locales/simple_form.nl.yml | 2 +- config/locales/simple_form.uk.yml | 1 + config/locales/uk.yml | 3 +++ 19 files changed, 66 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index f88f04afedd..07e763283cf 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -386,7 +386,7 @@ "follow_suggestions.similar_to_recently_followed_longer": "Minder om profiler, du har fulgt for nylig", "follow_suggestions.view_all": "Vis alle", "follow_suggestions.who_to_follow": "Hvem, som skal følges", - "followed_tags": "Hashtag, som følges", + "followed_tags": "Hashtags, som følges", "footer.about": "Om", "footer.directory": "Profiloversigt", "footer.get_app": "Hent appen", @@ -560,7 +560,7 @@ "navigation_bar.favourites": "Favoritter", "navigation_bar.filters": "Skjulte ord", "navigation_bar.follow_requests": "Følgeanmodninger", - "navigation_bar.followed_tags": "Hashtag, som følges", + "navigation_bar.followed_tags": "Hashtags, som følges", "navigation_bar.follows_and_followers": "Følges og følgere", "navigation_bar.import_export": "Import og eksport", "navigation_bar.lists": "Lister", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index e492cf9f807..b757fb0cdaa 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -569,6 +569,7 @@ "notification.admin.sign_up.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiago} other {# erabiltzaile gehiago}} erregistratu dira", "notification.favourite": "{name}(e)k zure bidalketa gogoko du", "notification.favourite.name_and_others_with_link": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zure bidalketa gogoko dute", + "notification.favourite_pm": "{name}-ek zure aipamen pribatua gogokoetan jarri du", "notification.follow": "{name}(e)k jarraitzen dizu", "notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du", "notification.follow_request.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zu jarraitzeko eskaera egin dute", @@ -902,5 +903,7 @@ "video.hide": "Ezkutatu bideoa", "video.pause": "Pausatu", "video.play": "Jo", + "video.unmute": "Soinua ezarri", + "video.volume_down": "Bolumena jaitsi", "video.volume_up": "Bolumena Igo" } diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 2f34c7aaedc..83ffe1834be 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -356,6 +356,7 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} partisipante} other {{counter} partisipantes}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}} oy", + "hashtag.feature": "Avalia en profil", "hashtag.follow": "Sige etiketa", "hashtag.mute": "Silensia #{hashtag}", "hashtag.unfeature": "No avalia en profil", @@ -390,6 +391,7 @@ "interaction_modal.title.reblog": "Repartaja publikasyon de {name}", "interaction_modal.title.reply": "Arisponde a publikasyon de {name}", "interaction_modal.title.vote": "Vota en la anketa de {name}", + "interaction_modal.username_prompt": "Por enshemplo {example}", "intervals.full.days": "{number, plural, one {# diya} other {# diyas}}", "intervals.full.hours": "{number, plural, one {# ora} other {# oras}}", "intervals.full.minutes": "{number, plural, one {# minuto} other {# minutos}}", @@ -442,6 +444,7 @@ "lists.delete": "Efasa lista", "lists.done": "Fecho", "lists.edit": "Edita lista", + "lists.list_name": "Nombre de lista", "lists.new_list_name": "Nombre de mueva lista", "lists.replies_policy.followed": "Kualseker utilizador segido", "lists.replies_policy.list": "Miembros de la lista", @@ -738,6 +741,7 @@ "status.reblogs.empty": "Ainda nadie tiene repartajado esta publikasyon. Kuando algien lo aga, se amostrara aki.", "status.redraft": "Efasa i eskrive de muevo", "status.remove_bookmark": "Kita markador", + "status.remove_favourite": "Kita de los favoritos", "status.replied_in_thread": "Arispondo en filo", "status.replied_to": "Arispondio a {name}", "status.reply": "Arisponde", @@ -758,6 +762,7 @@ "subscribed_languages.save": "Guadra trokamientos", "subscribed_languages.target": "Troka linguas abonadas para {target}", "tabs_bar.home": "Linya prinsipala", + "tabs_bar.menu": "Menu", "tabs_bar.notifications": "Avizos", "tabs_bar.publish": "Mueva publikasyon", "tabs_bar.search": "Bushkeda", diff --git a/config/locales/da.yml b/config/locales/da.yml index adb59158deb..9783fecbf0d 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1347,7 +1347,7 @@ da: your_appeal_rejected: Din appel er afvist edit_profile: basic_information: Oplysninger - hint_html: "Tilpas hvad folk ser på din offentlige profil og ved siden af dine indlæg. Andre personer vil mere sandsynligt følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede." + hint_html: "Tilpas, hvad folk ser på din offentlige profil og ved siden af dine indlæg. Andre personer er mere tilbøjelige til at følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede." other: Andre emoji_styles: auto: Auto diff --git a/config/locales/de.yml b/config/locales/de.yml index 59ceea8e79c..1eb5ef1c4f9 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1944,7 +1944,7 @@ de: contrast: Mastodon (Hoher Kontrast) default: Mastodon (Dunkel) mastodon-light: Mastodon (Hell) - system: Automatisch (mit System synchronisieren) + system: Automatisch (wie Betriebssystem) time: formats: default: "%d. %b %Y, %H:%M Uhr" diff --git a/config/locales/eu.yml b/config/locales/eu.yml index bf52c83bc94..42fb04a65a0 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -467,8 +467,11 @@ eu: fasp: debug: callbacks: + created_at: Sortua hemen delete: Ezabatu ip: IP helbidea + request_body: Eskaeraren edukia + title: Atzera-deiak araztu providers: active: Aktibo base_url: Oinarrizko URL-a @@ -822,6 +825,7 @@ eu: destroyed_msg: Guneko igoera ongi ezabatu da! software_updates: critical_update: Kritikoa — mesedez, eguneratu azkar + description: Gomendagarria da Mastodon instalazioa eguneratuta mantentzea azken konponketa eta funtzioez baliatzeko. Gainera, batzuetan ezinbestekoa da Mastodon garaiz eguneratzea segurtasun arazoak saihesteko. Arrazoi hauengatik, Mastodonek 30 minuturo eguneratzeak egiaztatzen ditu, eta zure posta elektroniko bidezko jakinarazpenen lehentasunen arabera jakinaraziko dizu. documentation_link: Informazio gehiago release_notes: Bertsio oharrak title: Eguneraketak eskuragarri diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 86c03aab7db..bb68494118f 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1851,6 +1851,8 @@ fr-CA: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_policies: + followers: Abonné·e·s et utilisateur·trice·s mentionné·e·s title: "%{name} : « %{quote} »" visibilities: direct: Direct @@ -1904,6 +1906,8 @@ fr-CA: does_not_match_previous_name: ne correspond pas au nom précédent terms_of_service: title: Conditions d'utilisation + terms_of_service_interstitial: + title: Les conditions d'utilisation de %{domain} ont changées themes: contrast: Mastodon (Contraste élevé) default: Mastodon (Sombre) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index bace64c2132..6fcdb5b9722 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1851,6 +1851,8 @@ fr: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_policies: + followers: Abonné·e·s et utilisateur·trice·s mentionné·e·s title: "%{name} : « %{quote} »" visibilities: direct: Direct @@ -1904,6 +1906,8 @@ fr: does_not_match_previous_name: ne correspond pas au nom précédent terms_of_service: title: Conditions d'utilisation + terms_of_service_interstitial: + title: Les conditions d'utilisation de %{domain} ont changées themes: contrast: Mastodon (Contraste élevé) default: Mastodon (Sombre) diff --git a/config/locales/ga.yml b/config/locales/ga.yml index a38a3cfc7a2..c9e943c91c1 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1406,6 +1406,10 @@ ga: basic_information: Eolas bunúsach hint_html: "Saincheap a bhfeiceann daoine ar do phróifíl phoiblí agus in aice le do phostálacha. Is dóichí go leanfaidh daoine eile ar ais tú agus go n-idirghníomhóidh siad leat nuair a bhíonn próifíl líonta agus pictiúr próifíle agat." other: Eile + emoji_styles: + auto: Uath + native: Dúchasach + twemoji: Twemoji errors: '400': Bhí an t-iarratas a chuir tú isteach neamhbhailí nó míchumtha. '403': Níl cead agat an leathanach seo a fheiceáil. diff --git a/config/locales/it.yml b/config/locales/it.yml index 595c2fddb2e..2bf9f656ba0 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1351,6 +1351,10 @@ it: basic_information: Informazioni di base hint_html: "Personalizza ciò che le persone vedono sul tuo profilo pubblico e accanto ai tuoi post. È più probabile che altre persone ti seguano e interagiscano con te quando hai un profilo compilato e un'immagine del profilo." other: Altro + emoji_styles: + auto: Automatico + native: Nativo + twemoji: Twemoji errors: '400': La richiesta che hai inviato non è valida o non è corretta. '403': Non sei autorizzato a visualizzare questa pagina. diff --git a/config/locales/lad.yml b/config/locales/lad.yml index e3c14367b78..2a5f8cabd25 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -186,6 +186,7 @@ lad: create_domain_block: Kriya bloko de domeno create_email_domain_block: Kriya bloko de domeno de posta create_ip_block: Kriya regla de IP + create_relay: Kriya relevo create_unavailable_domain: Kriya domeno no desponivle create_user_role: Kriya rolo demote_user: Degrada utilizador @@ -197,6 +198,7 @@ lad: destroy_email_domain_block: Efasa bloko de domeno de posta destroy_instance: Efasa domeno destroy_ip_block: Efasa regla de IP + destroy_relay: Efasa relevo destroy_status: Efasa publikasyon destroy_unavailable_domain: Efasa domeno no desponivle destroy_user_role: Efasa rolo @@ -205,6 +207,7 @@ lad: disable_sign_in_token_auth_user: Inkapasita la autentifikasyon por token de posta elektronika para el utilizador disable_user: Inkapasita utilizador enable_custom_emoji: Kapasita emoji personalizados + enable_relay: Aktiva relevo enable_sign_in_token_auth_user: Kapasita la autentifikasyon por token de posta para el utilizador enable_user: Kapasita utilizador memorialize_account: Transforma en kuento komemorativo @@ -229,6 +232,7 @@ lad: update_custom_emoji: Aktualiza emoji personalizado update_domain_block: Aktualiza bloko de domeno update_ip_block: Aktualiza regla de IP + update_report: Aktualiza raporto update_status: Aktualiza publikasyon update_user_role: Aktualiza rolo actions: @@ -466,10 +470,13 @@ lad: fasp: debug: callbacks: + created_at: Kriyado en delete: Efasa + ip: Adreso IP providers: active: Aktivo delete: Efasa + finish_registration: Finaliza enrejistrasyon name: Nombre registrations: confirm: Konfirma @@ -542,6 +549,12 @@ lad: all: Todos limited: Limitado title: Moderasyon + moderation_notes: + create: Adjusta nota de moderasyon + created_msg: Nota de moderasyon de sirvidor kriyada kon sukseso! + description_html: Ve i desha notas a otros moderadores i a tu yo futuro + destroyed_msg: Nota de moderasyon de sirvidor efasada kon sukseso! + title: Notas de moderasyon private_comment: Komento privado public_comment: Komento publiko purge: Purga @@ -748,6 +761,7 @@ lad: title: Rolos rules: add_new: Adjusta regla + add_translation: Adjusta traduksyon delete: Efasa description_html: Aunke la majorita afirma aver meldado i estar de akodro kon los terminos de servisyo, la djente normalmente no los melda asta dempues de ke surja algun problema. Az ke sea mas kolay ver las normas de tu sirvidor de un vistazo estipulándolas en una lista de puntos. Aprova ke kada norma sea corta i kolay, ama sin estar divididas en munchos puntos. edit: Edita regla @@ -920,6 +934,9 @@ lad: updated_msg: Konfigurasyon de etiketas aktualizada kon sukseso terms_of_service: changelog: Ke troko + current: Aktual + generates: + action: Djenera history: Istorya live: En bivo publish: Publika @@ -1245,6 +1262,10 @@ lad: basic_information: Enformasyon bazika hint_html: "Personaliza lo ke la djente ve en tu profil publiko i kon tus publikasyones. Es mas probavle ke otras personas te sigan i enteraktuen kontigo kuando kompletas tu profil i foto." other: Otros + emoji_styles: + auto: Otomatiko + native: Nativo + twemoji: Twemoji errors: '400': La solisitasyon ke enviates no fue valida o fue malformada. '403': No tienes permiso para ver esta pajina. diff --git a/config/locales/nl.yml b/config/locales/nl.yml index e38d80b1001..b53aa68a652 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -583,7 +583,7 @@ nl: created_msg: Aanmaken van servermoderatie-opmerking geslaagd! description_html: Opmerkingen bekijken, en voor jezelf en andere moderatoren achterlaten destroyed_msg: Verwijderen van servermoderatie-opmerking geslaagd! - placeholder: Informatie over deze server, genomen acties of iets anders die jou kunnen helpen om deze server in de toekomst te moderen. + placeholder: Informatie over deze server, genomen acties of iets anders die jou kunnen helpen om deze server in de toekomst te modereren. title: Moderatie-opmerkingen private_comment: Privé-opmerking public_comment: Openbare opmerking diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 07d36ae1a15..6ce3669527c 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -61,7 +61,7 @@ da: setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_hide_all: Skjul altid medier setting_display_media_show_all: Vis altid medier - setting_emoji_style: Hvordan emojis skal vises. "Auto" vil forsøge at bruge indbyggede emojis, men skifter tilbage til Twemoji for ældre browsere. + setting_emoji_style: Hvordan emojis skal vises. "Auto" vil forsøge at bruge indbyggede emojis, men skifter tilbage til Twemoji i ældre webbrowsere. setting_system_scrollbars_ui: Gælder kun for computerwebbrowsere baseret på Safari og Chrome setting_use_blurhash: Gradienter er baseret på de skjulte grafikelementers farver, men slører alle detaljer setting_use_pending_items: Skjul tidslinjeopdateringer bag et klik i stedet for brug af auto-feedrulning diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 07862f3d880..a7cb83eb13b 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -61,7 +61,7 @@ de: setting_display_media_default: Medien mit Inhaltswarnung ausblenden setting_display_media_hide_all: Medien immer ausblenden setting_display_media_show_all: Medien mit Inhaltswarnung immer anzeigen - setting_emoji_style: Darstellung von Emojis. „Automatisch“ verwendet native Emojis, für ältere Browser jedoch Twemoji. + setting_emoji_style: 'Wie Emojis dargestellt werden: „Automatisch“ verwendet native Emojis, für veraltete Browser wird jedoch Twemoji verwendet.' setting_system_scrollbars_ui: Betrifft nur Desktop-Browser, die auf Chrome oder Safari basieren setting_use_blurhash: Der Farbverlauf basiert auf den Farben der ausgeblendeten Medien, verschleiert aber jegliche Details setting_use_pending_items: Neue Beiträge hinter einem Klick verstecken, anstatt automatisch zu scrollen @@ -248,7 +248,7 @@ de: setting_missing_alt_text_modal: Bestätigungsdialog anzeigen, bevor Medien ohne Bildbeschreibung veröffentlicht werden setting_reduce_motion: Bewegung in Animationen verringern setting_system_font_ui: Standardschriftart des Browsers verwenden - setting_system_scrollbars_ui: Bildlaufleiste des Systems verwenden + setting_system_scrollbars_ui: Bildlaufleiste des Betriebssystems verwenden setting_theme: Design setting_trends: Heutige Trends anzeigen setting_unfollow_modal: Bestätigungsdialog beim Entfolgen eines Profils anzeigen diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index b26cd854fc5..9c7b9fe28eb 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -61,6 +61,7 @@ ga: setting_display_media_default: Folaigh meáin atá marcáilte mar íogair setting_display_media_hide_all: Folaigh meáin i gcónaí setting_display_media_show_all: Taispeáin meáin i gcónaí + setting_emoji_style: Conas emojis a thaispeáint. Déanfaidh "Auto" iarracht emoji dúchasacha a úsáid, ach titeann sé ar ais go Twemoji le haghaidh seanbhrabhsálaithe. setting_system_scrollbars_ui: Ní bhaineann sé ach le brabhsálaithe deisce bunaithe ar Safari agus Chrome setting_use_blurhash: Tá grádáin bunaithe ar dhathanna na n-amharcanna ceilte ach cuireann siad salach ar aon mhionsonraí setting_use_pending_items: Folaigh nuashonruithe amlíne taobh thiar de chlic seachas an fotha a scrollú go huathoibríoch @@ -244,6 +245,7 @@ ga: setting_display_media_default: Réamhshocrú setting_display_media_hide_all: Cuir uile i bhfolach setting_display_media_show_all: Taispeáin uile + setting_emoji_style: Stíl Emoji setting_expand_spoilers: Méadaigh postálacha atá marcáilte le rabhaidh inneachair i gcónaí setting_hide_network: Folaigh do ghraf sóisialta setting_missing_alt_text_modal: Taispeáin dialóg deimhnithe sula bpostálann tú meán gan alt téacs diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 93c0858ed68..9428f389962 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -61,6 +61,7 @@ it: setting_display_media_default: Nascondi media segnati come sensibili setting_display_media_hide_all: Nascondi sempre tutti i media setting_display_media_show_all: Mostra sempre i media segnati come sensibili + setting_emoji_style: Come visualizzare gli emoji. "Automatico" proverà a usare gli emoji nativi, ma per i browser più vecchi ricorrerà a Twemoji. setting_system_scrollbars_ui: Si applica solo ai browser desktop basati su Safari e Chrome setting_use_blurhash: I gradienti sono basati sui colori delle immagini nascoste ma offuscano tutti i dettagli setting_use_pending_items: Fare clic per mostrare i nuovi messaggi invece di aggiornare la timeline automaticamente @@ -241,6 +242,7 @@ it: setting_display_media_default: Predefinita setting_display_media_hide_all: Nascondi tutti setting_display_media_show_all: Mostra tutti + setting_emoji_style: Stile emoji setting_expand_spoilers: Espandi sempre post con content warning setting_hide_network: Nascondi la tua rete setting_missing_alt_text_modal: Chiedi di confermare prima di pubblicare media senza testo alternativo diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index ef5b2a00b62..78ac35b46d5 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -61,7 +61,7 @@ nl: setting_display_media_default: Als gevoelig gemarkeerde media verbergen setting_display_media_hide_all: Media altijd verbergen setting_display_media_show_all: Media altijd tonen - setting_emoji_style: Waarmee moeten emojis worden weergegeven. "Auto" probeert de systeemeigen emojis te gebruiken, maar valt terug op Twemoji voor oudere webbrowsers. + setting_emoji_style: Waarmee moeten emojis worden weergegeven. ‘Auto’ probeert de systeemeigen emojis te gebruiken, maar valt terug op Twemoji voor oudere webbrowsers. setting_system_scrollbars_ui: Alleen van toepassing op desktopbrowsers gebaseerd op Safari en Chrome setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml index 2561eeb2ca9..8a0e9012811 100644 --- a/config/locales/simple_form.uk.yml +++ b/config/locales/simple_form.uk.yml @@ -234,6 +234,7 @@ uk: setting_display_media_default: За промовчанням setting_display_media_hide_all: Сховати всі setting_display_media_show_all: Показати всі + setting_emoji_style: Стиль емодзі setting_expand_spoilers: Завжди розгортати дописи з попередженнями про вміст setting_hide_network: Сховати вашу мережу setting_missing_alt_text_modal: Запитувати перед розміщенням медіа без альтернативного тексту diff --git a/config/locales/uk.yml b/config/locales/uk.yml index dd9e8e826ec..ceadfc34664 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1338,6 +1338,9 @@ uk: basic_information: Основна інформація hint_html: "Налаштуйте те, що люди бачитимуть у вашому загальнодоступному профілі та поруч із вашими дописами. Інші люди з більшою ймовірністю підпишуться на вас та взаємодіятимуть з вами, якщо у вас є заповнений профіль та зображення профілю." other: Інше + emoji_styles: + auto: Авто + native: Рідний errors: '400': Ваш запит був недійсним або неправильним. '403': У Вас немає доступу до перегляду даної сторінки. From dd3d958e754887588db45736cab9d08f57aa00e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 07:23:54 +0000 Subject: [PATCH 039/660] fix(deps): update dependency core-js to v3.44.0 (#35284) 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 a0814a27af4..ea96821fbed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6032,9 +6032,9 @@ __metadata: linkType: hard "core-js@npm:^3.30.2, core-js@npm:^3.41.0": - version: 3.43.0 - resolution: "core-js@npm:3.43.0" - checksum: 10c0/9d4ad66296e60380777de51d019b5c3e6cce023b7999750a5094f9a4b0ea53bf3600beb4ef11c56548f2c8791d43d4056e270d1cf55ba87273011aa7d4597871 + version: 3.44.0 + resolution: "core-js@npm:3.44.0" + checksum: 10c0/759bf3dc5f75068e9425dddf895fd5531c38794a11ea1c2b65e5ef7c527fe3652d59e8c287e574a211af9bab3c057c5c3fa6f6a773f4e142af895106efad38a4 languageName: node linkType: hard From 149648877193d410cea159029c5a3269189ab5db Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 10 Jul 2025 03:35:04 -0400 Subject: [PATCH 040/660] Add `Status#not_replying_to_account` scope for annual report classes (#35257) --- app/lib/annual_report/archetype.rb | 2 +- .../commonly_interacted_with_accounts.rb | 2 +- app/lib/annual_report/type_distribution.rb | 2 +- app/models/status.rb | 1 + spec/models/status_spec.rb | 13 +++++++++++++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/lib/annual_report/archetype.rb b/app/lib/annual_report/archetype.rb index a02dbf7db71..41973294a62 100644 --- a/app/lib/annual_report/archetype.rb +++ b/app/lib/annual_report/archetype.rb @@ -36,7 +36,7 @@ class AnnualReport::Archetype < AnnualReport::Source end def replies_count - @replies_count ||= report_statuses.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count + @replies_count ||= report_statuses.where.not(in_reply_to_id: nil).not_replying_to_account(@account).count end def standalone_count diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb index c2aee44dea4..219c30063a4 100644 --- a/app/lib/annual_report/commonly_interacted_with_accounts.rb +++ b/app/lib/annual_report/commonly_interacted_with_accounts.rb @@ -18,7 +18,7 @@ class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source private def commonly_interacted_with_accounts - report_statuses.where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count + report_statuses.not_replying_to_account(@account).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count end def minimum_interaction_count diff --git a/app/lib/annual_report/type_distribution.rb b/app/lib/annual_report/type_distribution.rb index bdafe34c13d..0534055c28e 100644 --- a/app/lib/annual_report/type_distribution.rb +++ b/app/lib/annual_report/type_distribution.rb @@ -6,7 +6,7 @@ class AnnualReport::TypeDistribution < AnnualReport::Source type_distribution: { total: report_statuses.count, reblogs: report_statuses.only_reblogs.count, - replies: report_statuses.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count, + replies: report_statuses.where.not(in_reply_to_id: nil).not_replying_to_account(@account).count, standalone: report_statuses.without_replies.without_reblogs.count, }, } diff --git a/app/models/status.rb b/app/models/status.rb index bf392317a5a..e6e9450264b 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -124,6 +124,7 @@ class Status < ApplicationRecord scope :only_polls, -> { where.not(poll_id: nil) } scope :without_polls, -> { where(poll_id: nil) } scope :reply_to_account, -> { where(arel_table[:in_reply_to_account_id].eq arel_table[:account_id]) } + scope :not_replying_to_account, ->(account) { where.not(in_reply_to_account: account) } scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) } diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index f3453b3b674..e0a2c391046 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -191,6 +191,19 @@ RSpec.describe Status do end end + describe '.not_replying_to_account' do + let(:account) { Fabricate :account } + let!(:status_from_account) { Fabricate :status, account: account } + let!(:reply_to_account_status) { Fabricate :status, thread: status_from_account } + let!(:reply_to_other) { Fabricate :status, thread: Fabricate(:status) } + + it 'returns records not in reply to provided account' do + expect(described_class.not_replying_to_account(account)) + .to not_include(reply_to_account_status) + .and include(reply_to_other) + end + end + describe '#untrusted_favourites_count' do before do alice.update(domain: 'example.com') From ad78701b6f2932868f5db01838ab26d5f488769f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 10 Jul 2025 03:35:40 -0400 Subject: [PATCH 041/660] Mark `private` methods in `AnnualReport::TopStatuses` (#35256) --- app/lib/annual_report/top_statuses.rb | 40 +++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb index 74b129595ac..f32bd09a15a 100644 --- a/app/lib/annual_report/top_statuses.rb +++ b/app/lib/annual_report/top_statuses.rb @@ -2,20 +2,44 @@ class AnnualReport::TopStatuses < AnnualReport::Source def generate - top_reblogs = base_scope.order(reblogs_count: :desc).first&.id - top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id - top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id - { top_statuses: { - by_reblogs: top_reblogs&.to_s, - by_favourites: top_favourites&.to_s, - by_replies: top_replies&.to_s, + by_reblogs: status_identifier(most_reblogged_status), + by_favourites: status_identifier(most_favourited_status), + by_replies: status_identifier(most_replied_status), }, } end + private + + def status_identifier(status) + status.id.to_s if status.present? + end + + def most_reblogged_status + base_scope + .order(reblogs_count: :desc) + .first + end + + def most_favourited_status + base_scope + .excluding(most_reblogged_status) + .order(favourites_count: :desc) + .first + end + + def most_replied_status + base_scope + .excluding(most_reblogged_status, most_favourited_status) + .order(replies_count: :desc) + .first + end + def base_scope - report_statuses.public_visibility.joins(:status_stat) + report_statuses + .public_visibility + .joins(:status_stat) end end From 28633a504aed2eb8b2447ec60abbdbb46a5d963d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 09:38:07 +0200 Subject: [PATCH 042/660] chore(deps): update dependency json-schema to v5.2.1 (#35337) 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 769d67cd94a..9695abadcdf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -365,7 +365,7 @@ GEM json-ld-preloaded (3.3.1) json-ld (~> 3.3) rdf (~> 3.3) - json-schema (5.1.1) + json-schema (5.2.1) addressable (~> 2.8) bigdecimal (~> 3.1) jsonapi-renderer (0.2.2) From c442589593ac39f1f36c972890850c30efd3ca1b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 10 Jul 2025 03:40:56 -0400 Subject: [PATCH 043/660] Use `ActiveModel::Attributes` in `FollowLimitable` concern (#35327) --- app/models/concerns/follow_limitable.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/models/concerns/follow_limitable.rb b/app/models/concerns/follow_limitable.rb index c64060d6e56..fb8f76e9bfa 100644 --- a/app/models/concerns/follow_limitable.rb +++ b/app/models/concerns/follow_limitable.rb @@ -4,14 +4,8 @@ module FollowLimitable extend ActiveSupport::Concern included do - validates_with FollowLimitValidator, on: :create, unless: :bypass_follow_limit? - end + validates_with FollowLimitValidator, on: :create, unless: :bypass_follow_limit - def bypass_follow_limit=(value) - @bypass_follow_limit = value - end - - def bypass_follow_limit? - @bypass_follow_limit + attribute :bypass_follow_limit, :boolean, default: false end end From b69b5ba775e47cce983487f9669883ef58c5043e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:14:39 +0200 Subject: [PATCH 044/660] New Crowdin Translations (automated) (#35344) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ca.json | 4 ++++ app/javascript/mastodon/locales/da.json | 2 +- config/locales/ca.yml | 9 +++++++++ config/locales/da.yml | 16 ++++++++-------- config/locales/hu.yml | 4 ++++ config/locales/simple_form.ca.yml | 2 ++ config/locales/simple_form.hu.yml | 2 ++ 7 files changed, 30 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 5c29a0ca3a6..53b432152bb 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -219,6 +219,9 @@ "confirmations.delete_list.confirm": "Elimina", "confirmations.delete_list.message": "Segur que vols suprimir permanentment aquesta llista?", "confirmations.delete_list.title": "Eliminar la llista?", + "confirmations.discard_draft.confirm": "Descarta i continua", + "confirmations.discard_draft.edit.cancel": "Continua l'edició", + "confirmations.discard_draft.post.cancel": "Reprendre l'esborrany", "confirmations.discard_edit_media.confirm": "Descarta", "confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?", "confirmations.follow_to_list.confirm": "Seguir i afegir a una llista", @@ -792,6 +795,7 @@ "report_notification.categories.violation": "Violació de norma", "report_notification.categories.violation_sentence": "violació de normes", "report_notification.open": "Obre l'informe", + "search.clear": "Esborra la cerca", "search.no_recent_searches": "No hi ha cerques recents", "search.placeholder": "Cerca", "search.quick_action.account_search": "Perfils coincidint amb {x}", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 07e763283cf..27bbf5131d8 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -572,7 +572,7 @@ "navigation_bar.mutes": "Skjulte brugere", "navigation_bar.opened_in_classic_interface": "Indlæg, konti og visse andre sider åbnes som standard i den klassiske webgrænseflade.", "navigation_bar.preferences": "Præferencer", - "navigation_bar.privacy_and_reach": "Fortrolighed og udbredelse", + "navigation_bar.privacy_and_reach": "Fortrolighed og rækkevidde", "navigation_bar.search": "Søg", "navigation_bar.search_trends": "Søg/Trender", "navigation_panel.collapse_followed_tags": "Sammenfold menuen Fulgte hashtags", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 0d0d60cf520..1f64cb82fe2 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -578,6 +578,11 @@ ca: all: Totes limited: Limitades title: Moderació + moderation_notes: + create: Afegeix una nota de moderació + created_msg: S'ha creat la nota de moderació d'instància. + destroyed_msg: S'ha esborrat la nota de moderació d'instància. + title: Notes de moderació private_comment: Comentari privat public_comment: Comentari públic purge: Purga @@ -1339,6 +1344,10 @@ ca: basic_information: Informació bàsica hint_html: "Personalitza el que la gent veu en el teu perfil públic i a prop dels teus tuts.. És més probable que altres persones et segueixin i interaccionin amb tu quan tens emplenat el teu perfil i amb la teva imatge." other: Altres + emoji_styles: + auto: Automàtic + native: Nadiu + twemoji: Twemoji errors: '400': La sol·licitud que vas emetre no era vàlida o no era correcta. '403': No tens permís per a veure aquesta pàgina. diff --git a/config/locales/da.yml b/config/locales/da.yml index 9783fecbf0d..432308d5912 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -653,7 +653,7 @@ da: mark_as_sensitive_description_html: Medierne i det anmeldte indlæg markeres som sensitive, og en advarsel (strike) registreres mhp. eskalering ved evt. fremtidige overtrædelser fra samme konto. other_description_html: Se flere muligheder for at kontrollere kontoens adfærd og tilpasse kommunikationen til den anmeldte konto. resolve_description_html: Ingen foranstaltninger træffes mod den anmeldte konto, ingen advarsel (strike) registreres og anmeldelsen lukkes. - silence_description_html: Kontoen vil kun være synlig for følgerene eller dem, som manuelt slå den op, hvilket markant begrænser dens udbredelse. Kan altid omgøres. Lukker alle indrapporteringer af kontoen. + silence_description_html: Kontoen vil kun være synlig for dem, der allerede følger den eller manuelt slår den op, hvilket alvorligt begrænser dens rækkevidde. Kan altid omgøres. Lukker alle indrapporteringer af denne konto. suspend_description_html: Kontoen inkl. alt indhold utilgængeliggøres og interaktion umuliggøres, og den slettes på et tidspunkt. Kan omgøres inden for 30 dage. Lukker alle indrapporteringer af kontoen. actions_description_html: Afgør, hvilke foranstaltning, der skal træffes for at løse denne anmeldelse. Ved en straffende foranstaltning mod den anmeldte konto, fremsendes en e-mailnotifikation, undtagen når kategorien Spam er valgt. actions_description_remote_html: Fastslå en nødvendig handling mhp. at løse denne anmeldelse. Dette vil kun påvirke din servers kommunikation med, og indholdshåndtering for, fjernkontoen. @@ -1266,8 +1266,8 @@ da: user_privacy_agreement_html: Jeg accepterer fortrolighedspolitikken author_attribution: example_title: Eksempeltekst - hint_html: Skriver du nyheder eller blogartikler uden for Mastodon? Styr, hvordan man bliver krediteret, når disse deles på Mastodon. - instructions: 'Sørg for, at denne kode er i artikelens HTML:' + hint_html: Skriver du nyheder eller blogartikler uden for Mastodon? Styr, hvordan du bliver krediteret, når de bliver delt på Mastodon. + instructions: 'Sørg for, at denne kode er i din artikels HTML:' more_from_html: Flere fra %{name} s_blog: "%{name}s blog" then_instructions: Tilføj dernæst publikationsdomænenavnet i feltet nedenfor. @@ -1718,11 +1718,11 @@ da: hint_html: "Tilpas hvordan din profil og dine indlæg kan findes. En række funktioner i Mastodon kan hjælpe dig med at nå ud til et bredere publikum, hvis du aktiverer dem. Tjek indstillingerne herunder for at sikre, at de passer til dit brugsscenarie." privacy: Privatliv privacy_hint_html: Styr, hvor meget der ønskes synliggjort til gavn for andre. Folk finder interessante profiler og apps ved at tjekke andres følgere ud, samt se hvilke apps de sender fra, men dine præferencer ønskes muligvis ikke synliggjort. - reach: Udbredelse + reach: Rækkevidde reach_hint_html: Indstil om du vil blive opdaget og fulgt af nye mennesker. Ønsker du, at dine indlæg skal vises på Udforsk-siden? Ønsker du, at andre skal se dig i deres følg-anbefalinger? Ønsker du at acceptere alle nye følgere automatisk, eller vil du have detaljeret kontrol over hver og en? - search: Søg + search: Søgning search_hint_html: Indstil hvordan du vil findes. Ønsker du, at folk skal finde dig gennem hvad du har skrevet offentligt? Vil du have folk udenfor Mastodon til at finde din profil, når de søger på nettet? Vær opmærksom på, at det ikke kan garanteres at dine offentlige indlæg er udelukket fra alle søgemaskiner. - title: Fortrolighed og udbredelse + title: Fortrolighed og rækkevidde privacy_policy: title: Privatlivspolitik reactions: @@ -1923,7 +1923,7 @@ da: '7889238': 3 måneder min_age_label: Alderstærskel min_favs: Behold indlæg favoritmarkeret mindst - min_favs_hint: Sletter ingen dine egne indlæg, som har modtaget minimum dette antal favoritmarkeringer. Lad stå tomt for at slette indlæg uanset antal favoritmarkeringer + min_favs_hint: Sletter ingen af dine egne indlæg, som har modtaget minimum dette antal favoritmarkeringer. Lad stå tom for at slette indlæg uanset antal favoritmarkeringer min_reblogs: Behold indlæg fremhævet mindst min_reblogs_hint: Sletter ingen af dine egne indlæg, som er fremhævet flere end dette antal gange. Lad stå tom for at slette indlæg uanset antallet af fremhævelser stream_entries: @@ -2095,7 +2095,7 @@ da: verification: extra_instructions_html: Tip: Linket på din hjemmeside kan være usynligt. Den vigtige del er rel="me" , som forhindrer impersonation på websteder med brugergenereret indhold. Du kan endda bruge et link tag i overskriften på siden i stedet for a, men HTML skal være tilgængelig uden at udføre JavaScript. here_is_how: Sådan gør du - hint_html: "Bekræftelse af din identitet på Mastodon er for alle. Baseret på åbne webstandarder, nu og for evigt gratis. Alt du behøver er en personlig hjemmeside, som folk genkende dig ved. Når du linker til denne hjemmeside fra din profil, vi vil kontrollere, at hjemmesiden linker tilbage til din profil og vise en visuel indikator på det." + hint_html: "Verificering af din identitet på Mastodon er for alle. Baseret på åbne webstandarder, nu og for altid gratis. Alt, hvad du behøver, er en personlig hjemmeside, som folk kender dig fra. Når du linker til denne hjemmeside fra din profil, kontrollerer vi, at hjemmesiden linker tilbage til din profil, og viser en visuel indikator på den." instructions_html: Kopier og indsæt koden nedenfor i HTML på din hjemmeside. Tilføj derefter adressen på din hjemmeside i et af de ekstra felter på din profil på fanen "Redigér profil" og gem ændringer. verification: Bekræftelse verified_links: Dine bekræftede links diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 1bf0194043e..b46062b2c7b 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1349,6 +1349,10 @@ hu: basic_information: Általános információk hint_html: "Tedd egyedivé, mi látnak mások a profilodon és a bejegyzéseid mellett. Mások nagyobb eséllyel követnek vissza és lépnek veled kapcsolatba, ha van kitöltött profilod és profilképed." other: Egyéb + emoji_styles: + auto: Automatikus + native: Natív + twemoji: Twemoji errors: '400': A küldött kérés érvénytelen vagy hibás volt. '403': Nincs jogosultságod az oldal megtekintéséhez. diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 26205e3826c..04bfe7c6983 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -61,6 +61,7 @@ ca: setting_display_media_default: Amaga el contingut gràfic marcat com a sensible setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia setting_display_media_show_all: Mostra sempre el contingut gràfic + setting_emoji_style: Com mostrar els emojis. "Automàtic" provarà de fer servir els emojis nadius, però revertirà a twemojis en els navegadors antics. setting_system_scrollbars_ui: S'aplica només als navegadors d'escriptori basats en Safari i Chrome setting_use_blurhash: Els degradats es basen en els colors de les imatges ocultes, però n'enfosqueixen els detalls setting_use_pending_items: Amaga les actualitzacions de la línia de temps després de fer un clic, en lloc de desplaçar-les automàticament @@ -240,6 +241,7 @@ ca: setting_display_media_default: Per defecte setting_display_media_hide_all: Amaga-ho tot setting_display_media_show_all: Mostra-ho tot + setting_emoji_style: Estil d'emojis setting_expand_spoilers: Desplega sempre els tuts marcats amb advertències de contingut setting_hide_network: Amaga la teva xarxa setting_missing_alt_text_modal: Mostra un diàleg de confirmació abans de publicar contingut sense text alternatiu diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index e54adb8ca28..540da347c11 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -61,6 +61,7 @@ hu: setting_display_media_default: Kényes tartalomnak jelölt média elrejtése setting_display_media_hide_all: Média elrejtése mindig setting_display_media_show_all: Média megjelenítése mindig + setting_emoji_style: Az emodzsik megjelenítési módja. Az „Automatikus” megpróbálja a natív emodzsikat használni, de az örökölt böngészők esetén a Twemojira vált vissza. setting_system_scrollbars_ui: Csak Chrome és Safari alapú asztali böngészőkre vonatkozik setting_use_blurhash: A kihomályosítás az eredeti képből történik, de minden részletet elrejt setting_use_pending_items: Idővonal frissítése csak kattintásra automatikus görgetés helyett @@ -241,6 +242,7 @@ hu: setting_display_media_default: Alapértelmezés setting_display_media_hide_all: Mindent elrejt setting_display_media_show_all: Mindent mutat + setting_emoji_style: Emodzsistílus setting_expand_spoilers: Tartalmi figyelmeztetéssel ellátott bejegyzések automatikus kinyitása setting_hide_network: Hálózatod elrejtése setting_missing_alt_text_modal: Megerősítési párbeszédablak megjelenítése a helyettesítő szöveg nélküli média közzététele előtt From 88b0f3a1727201d90308da719e3e238a697f9914 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 11 Jul 2025 10:36:05 +0200 Subject: [PATCH 045/660] Simplify `DatabaseViewRecord.refresh` (#35252) --- app/models/concerns/database_view_record.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/concerns/database_view_record.rb b/app/models/concerns/database_view_record.rb index 8b6672e2991..b6709e1e419 100644 --- a/app/models/concerns/database_view_record.rb +++ b/app/models/concerns/database_view_record.rb @@ -10,12 +10,6 @@ module DatabaseViewRecord concurrently: true, cascade: false ) - rescue ActiveRecord::StatementInvalid - Scenic.database.refresh_materialized_view( - table_name, - concurrently: false, - cascade: false - ) end end From 94bceb8683e1993842bdb0344c8c70c6fdabc652 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 11 Jul 2025 15:15:22 +0200 Subject: [PATCH 046/660] Expose enabled features to the frontend (#35348) --- app/javascript/mastodon/initial_state.js | 10 +++++++++- app/serializers/initial_state_serializer.rb | 6 +++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 093422ac87b..590c4c8d2b4 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -1,6 +1,5 @@ // @ts-check - /** * @typedef {[code: string, name: string, localName: string]} InitialStateLanguage */ @@ -64,6 +63,7 @@ * @property {boolean=} critical_updates_pending * @property {InitialStateMeta} meta * @property {Role?} role + * @property {string[]} features */ const element = document.getElementById('initial-state'); @@ -140,4 +140,12 @@ export function getAccessToken() { return getMeta('access_token'); } +/** + * @param {string} feature + * @returns {boolean} + */ +export function isFeatureEnabled(feature) { + return initialState?.features?.includes(feature) || false; +} + export default initialState; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 1c83eff4b23..ddcb214a472 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -5,7 +5,7 @@ class InitialStateSerializer < ActiveModel::Serializer attributes :meta, :compose, :accounts, :media_attachments, :settings, - :languages + :languages, :features attribute :critical_updates_pending, if: -> { object&.role&.can?(:view_devops) && SoftwareUpdate.check_enabled? } @@ -85,6 +85,10 @@ class InitialStateSerializer < ActiveModel::Serializer LanguagesHelper::SUPPORTED_LOCALES.map { |(key, value)| [key, value[0], value[1]] } end + def features + Mastodon::Feature.enabled_features + end + private def default_meta_store From 853a0c466e60ea52b4a52d918cf04b3370cf6ca2 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 11 Jul 2025 17:18:34 +0200 Subject: [PATCH 047/660] Make bio hashtags open the local page instead of the remote instance (#35349) --- .../mastodon/components/account_bio.tsx | 48 +++++++++++++++++-- .../components/account_header.tsx | 13 +++-- app/javascript/mastodon/hooks/useLinks.ts | 9 ++-- app/javascript/mastodon/models/account.ts | 7 ++- 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index 301ffcbb247..e0127f20923 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -1,12 +1,30 @@ +import { useCallback } from 'react'; + import { useLinks } from 'mastodon/hooks/useLinks'; -export const AccountBio: React.FC<{ +interface AccountBioProps { note: string; className: string; -}> = ({ note, className }) => { - const handleClick = useLinks(); + dropdownAccountId?: string; +} - if (note.length === 0 || note === '

') { +export const AccountBio: React.FC = ({ + note, + className, + dropdownAccountId, +}) => { + const handleClick = useLinks(!!dropdownAccountId); + const handleNodeChange = useCallback( + (node: HTMLDivElement | null) => { + if (!dropdownAccountId || !node || node.childNodes.length === 0) { + return; + } + addDropdownToHashtags(node, dropdownAccountId); + }, + [dropdownAccountId], + ); + + if (note.length === 0) { return null; } @@ -15,6 +33,28 @@ export const AccountBio: React.FC<{ className={`${className} translate`} dangerouslySetInnerHTML={{ __html: note }} onClickCapture={handleClick} + ref={handleNodeChange} /> ); }; + +function addDropdownToHashtags(node: HTMLElement | null, accountId: string) { + if (!node) { + return; + } + for (const childNode of node.childNodes) { + if (!(childNode instanceof HTMLElement)) { + continue; + } + if ( + childNode instanceof HTMLAnchorElement && + (childNode.classList.contains('hashtag') || + childNode.innerText.startsWith('#')) && + !childNode.dataset.menuHashtag + ) { + childNode.dataset.menuHashtag = accountId; + } else if (childNode.childNodes.length > 0) { + addDropdownToHashtags(childNode, accountId); + } + } +} 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 30433151c6f..b9f83bebaaa 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames'; import { Helmet } from 'react-helmet'; import { NavLink } from 'react-router-dom'; +import { AccountBio } from '@/mastodon/components/account_bio'; import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import LockIcon from '@/material-icons/400-24px/lock.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; @@ -773,7 +774,6 @@ export const AccountHeader: React.FC<{ ); } - const content = { __html: account.note_emojified }; const displayNameHtml = { __html: account.display_name_html }; const fields = account.fields; const isLocal = !account.acct.includes('@'); @@ -897,12 +897,11 @@ export const AccountHeader: React.FC<{ )} - {account.note.length > 0 && account.note !== '

' && ( -
- )} +
diff --git a/app/javascript/mastodon/hooks/useLinks.ts b/app/javascript/mastodon/hooks/useLinks.ts index c99f3f41991..160fe18503f 100644 --- a/app/javascript/mastodon/hooks/useLinks.ts +++ b/app/javascript/mastodon/hooks/useLinks.ts @@ -8,13 +8,14 @@ import { openURL } from 'mastodon/actions/search'; import { useAppDispatch } from 'mastodon/store'; const isMentionClick = (element: HTMLAnchorElement) => - element.classList.contains('mention'); + element.classList.contains('mention') && + !element.classList.contains('hashtag'); const isHashtagClick = (element: HTMLAnchorElement) => element.textContent?.[0] === '#' || element.previousSibling?.textContent?.endsWith('#'); -export const useLinks = () => { +export const useLinks = (skipHashtags?: boolean) => { const history = useHistory(); const dispatch = useAppDispatch(); @@ -61,12 +62,12 @@ export const useLinks = () => { if (isMentionClick(target)) { e.preventDefault(); void handleMentionClick(target); - } else if (isHashtagClick(target)) { + } else if (isHashtagClick(target) && !skipHashtags) { e.preventDefault(); handleHashtagClick(target); } }, - [handleMentionClick, handleHashtagClick], + [skipHashtags, handleMentionClick, handleHashtagClick], ); return handleClick; diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index 2666059b40c..75a5c09b9d8 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -126,6 +126,9 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { ? accountJSON.username : accountJSON.display_name; + const accountNote = + accountJSON.note && accountJSON.note !== '

' ? accountJSON.note : ''; + return AccountFactory({ ...accountJSON, moved: moved?.id, @@ -142,8 +145,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { escapeTextContentForBrowser(displayName), emojiMap, ), - note_emojified: emojify(accountJSON.note, emojiMap), - note_plain: unescapeHTML(accountJSON.note), + note_emojified: emojify(accountNote, emojiMap), + note_plain: unescapeHTML(accountNote), url: accountJSON.url.startsWith('http://') || accountJSON.url.startsWith('https://') From 3b52dca4057560c58b260433722d91650fcd5040 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 11 Jul 2025 18:35:06 +0200 Subject: [PATCH 048/660] Fix quote attributes missing from Mastodon's context (#35354) --- app/helpers/context_helper.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 33d7267905b..c8e871a2414 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -26,6 +26,12 @@ module ContextHelper suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, attribution_domains: { 'toot' => 'http://joinmastodon.org/ns#', 'attributionDomains' => { '@id' => 'toot:attributionDomains', '@type' => '@id' } }, quote_requests: { 'QuoteRequest' => 'https://w3id.org/fep/044f#QuoteRequest' }, + quotes: { + 'quote' => 'https://w3id.org/fep/044f#quote', + 'quoteUri' => 'http://fedibird.com/ns#quoteUri', + '_misskey_quote' => 'https://misskey-hub.net/ns#_misskey_quote', + 'quoteAuthorization' => 'https://w3id.org/fep/044f#quoteAuthorization', + }, interaction_policies: { 'gts' => 'https://gotosocial.org/ns#', 'interactionPolicy' => { '@id' => 'gts:interactionPolicy', '@type' => '@id' }, From a643d9d498690991637eb03567dde7abc6fa711c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 08:21:56 +0200 Subject: [PATCH 049/660] New Crowdin Translations (automated) (#35358) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 178 +++++++++++----- app/javascript/mastodon/locales/ca.json | 9 + app/javascript/mastodon/locales/el.json | 65 +++--- app/javascript/mastodon/locales/es-AR.json | 2 +- app/javascript/mastodon/locales/ru.json | 2 +- app/javascript/mastodon/locales/sv.json | 9 + app/javascript/mastodon/locales/vi.json | 4 +- config/locales/activerecord.be.yml | 5 +- config/locales/be.yml | 235 +++++++++++++-------- config/locales/ca.yml | 25 +++ config/locales/devise.be.yml | 18 +- config/locales/devise.el.yml | 2 +- config/locales/devise.vi.yml | 8 +- config/locales/doorkeeper.be.yml | 4 +- config/locales/doorkeeper.eu.yml | 1 + config/locales/el.yml | 31 ++- config/locales/eu.yml | 22 ++ config/locales/nan.yml | 60 ++++++ config/locales/simple_form.be.yml | 41 ++-- config/locales/simple_form.el.yml | 18 +- config/locales/simple_form.eu.yml | 1 + config/locales/simple_form.sv.yml | 2 + config/locales/sv.yml | 4 + config/locales/vi.yml | 6 +- 24 files changed, 523 insertions(+), 229 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index b1567a8b6b8..213ec61aca6 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -1,6 +1,7 @@ { "about.blocks": "Мадэраваныя серверы", "about.contact": "Кантакт:", + "about.default_locale": "Прадвызначаная", "about.disclaimer": "Mastodon - свабоднае праграмнае забеспячэнне, з адкрытым зыходным кодам, і гандлёвай маркай Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Прычына недаступная", "about.domain_blocks.preamble": "Mastodon, у асноўным, дазваляе вам праглядаць кантэнт і ўзаемадзейнічаць з карыстальнікамі з іншых сервераў у федэсвету. Гэтыя выключэнні былі зроблены дакладна на гэтым серверы.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Абмежаваны", "about.domain_blocks.suspended.explanation": "Ніякая інфармацыя з гэтага сервера не будзе апрацавана, захавана або абменена, узаемадзеянне або камунікацыя з карыстальнікамі гэтага сервера немагчымы.", "about.domain_blocks.suspended.title": "Прыпынены", + "about.language_label": "Мова", "about.not_available": "Дадзеная інфармацыя не дасяжная на гэтым серверы.", "about.powered_by": "Дэцэнтралізаваная сацыяльная сетка, створаная {mastodon}", "about.rules": "Правілы сервера", @@ -19,13 +21,21 @@ "account.block_domain": "Заблакіраваць дамен {domain}", "account.block_short": "Заблакіраваць", "account.blocked": "Заблакіраваны", + "account.blocking": "Блакіраванне", "account.cancel_follow_request": "Скасаваць запыт на падпіску", "account.copy": "Скапіраваць спасылку на профіль", "account.direct": "Згадаць асабіста @{name}", "account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}", + "account.domain_blocking": "Блакіраванне дамена", "account.edit_profile": "Рэдагаваць профіль", "account.enable_notifications": "Апавяшчаць мяне пра допісы @{name}", "account.endorse": "Паказваць у профілі", + "account.familiar_followers_many": "Мае сярод падпісчыкаў {name1}, {name2}, і {othersCount, plural, one {яшчэ # чалавека, знаёмага вам} few {яшчэ # чалавекі, знаёмыя вам} many {яшчэ # чалавек, знаёмых вам} other {яшчэ # чалавекі, знаёмыя вам}}", + "account.familiar_followers_one": "Мае сярод падпісчыкаў {name1}", + "account.familiar_followers_two": "Мае сярод падпісчыкаў {name1} і {name2}", + "account.featured": "Рэкамендаванае", + "account.featured.accounts": "Профілі", + "account.featured.hashtags": "Хэштэгі", "account.featured_tags.last_status_at": "Апошні допіс ад {date}", "account.featured_tags.last_status_never": "Няма допісаў", "account.follow": "Падпісацца", @@ -33,9 +43,11 @@ "account.followers": "Падпісчыкі", "account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.", "account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}", + "account.followers_you_know_counter": "{count, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}", "account.following": "Падпіскі", "account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}", "account.follows.empty": "Карыстальнік ні на каго не падпісаны.", + "account.follows_you": "Падпісаны на вас", "account.go_to_profile": "Перайсці да профілю", "account.hide_reblogs": "Схаваць пашырэнні ад @{name}", "account.in_memoriam": "У памяць.", @@ -50,18 +62,22 @@ "account.mute_notifications_short": "Не апавяшчаць", "account.mute_short": "Ігнараваць", "account.muted": "Ігнаруецца", + "account.mutual": "Вы падпісаны адно на аднаго", "account.no_bio": "Апісанне адсутнічае.", "account.open_original_page": "Адкрыць арыгінальную старонку", "account.posts": "Допісы", "account.posts_with_replies": "Допісы і адказы", + "account.remove_from_followers": "Выдаліць карыстальніка {name} з падпісчыкаў", "account.report": "Паскардзіцца на @{name}", "account.requested": "Чакаецца ўхваленне. Націсніце, каб скасаваць запыт на падпіску", "account.requested_follow": "{name} адправіў запыт на падпіску", + "account.requests_to_follow_you": "Хоча падпісацца на вас", "account.share": "Абагуліць профіль @{name}", "account.show_reblogs": "Паказаць падштурхоўванні ад @{name}", "account.statuses_counter": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", "account.unblock": "Разблакіраваць @{name}", "account.unblock_domain": "Разблакіраваць дамен {domain}", + "account.unblock_domain_short": "Разблакіраваць", "account.unblock_short": "Разблакіраваць", "account.unendorse": "Не паказваць у профілі", "account.unfollow": "Адпісацца", @@ -83,26 +99,30 @@ "alert.unexpected.message": "Узнікла нечаканая памылка.", "alert.unexpected.title": "Вой!", "alt_text_badge.title": "Альтэрнатыўны тэкст", + "alt_text_modal.add_alt_text": "Дадаць альтэрнатыўны тэкст", + "alt_text_modal.add_text_from_image": "Дадаць тэкст з відарыса", + "alt_text_modal.cancel": "Скасаваць", + "alt_text_modal.change_thumbnail": "Змяніць мініяцюру", "alt_text_modal.done": "Гатова", "announcement.announcement": "Аб'ява", - "annual_report.summary.archetype.booster": "Трэнда-сьледнік", + "annual_report.summary.archetype.booster": "Паляўнічы на трэнды", "annual_report.summary.archetype.lurker": "Назіральнік", "annual_report.summary.archetype.oracle": "Аракул", "annual_report.summary.archetype.pollster": "Апытвальнік", - "annual_report.summary.archetype.replier": "Душа кампанійі", - "annual_report.summary.followers.followers": "падпісанты", - "annual_report.summary.followers.total": "Усяго {count}", - "annual_report.summary.here_it_is": "Вось вашыя вынікі {year} году:", + "annual_report.summary.archetype.replier": "Душа кампаніі", + "annual_report.summary.followers.followers": "падпісчыкі", + "annual_report.summary.followers.total": "Агулам {count}", + "annual_report.summary.here_it_is": "Вось вашы вынікі {year} за год:", "annual_report.summary.highlighted_post.by_favourites": "самы ўпадабаны допіс", "annual_report.summary.highlighted_post.by_reblogs": "самы пашыраны допіс", "annual_report.summary.highlighted_post.by_replies": "самы каментаваны допіс", "annual_report.summary.highlighted_post.possessive": "{name}", - "annual_report.summary.most_used_app.most_used_app": "самая выкарыстоўваная аплікацыя", - "annual_report.summary.most_used_hashtag.most_used_hashtag": "самы выкарыстоўваны гэштаґ", + "annual_report.summary.most_used_app.most_used_app": "праграма, якой карысталіся часцей", + "annual_report.summary.most_used_hashtag.most_used_hashtag": "хэштэг, якім карысталіся часцей", "annual_report.summary.most_used_hashtag.none": "Няма", "annual_report.summary.new_posts.new_posts": "новыя допісы", - "annual_report.summary.percentile.text": "Мэта месьціць вас у топ карыстальнікаў {domain}.", - "annual_report.summary.percentile.we_wont_tell_bernie": "Мы ня скажам аб гэтым Сіняпальцаму.", + "annual_report.summary.percentile.text": "З-за гэтага, вы знаходзіцеся ў топе карыстальнікаў {domain}.", + "annual_report.summary.percentile.we_wont_tell_bernie": "КДБ пра гэта не даведаецца.", "annual_report.summary.thanks": "Дзякуй за ўдзел у Mastodon!", "attachments_list.unprocessed": "(неапрацаваны)", "audio.hide": "Схаваць аўдыя", @@ -127,7 +147,7 @@ "bundle_column_error.routing.body": "Запытаная старонка не знойдзена. Вы ўпэўнены, што URL у адрасным радку правільны?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Закрыць", - "bundle_modal_error.message": "Падчас загрузкі гэтага экрана штосьці пайшло ня так.", + "bundle_modal_error.message": "Падчас загрузкі гэтага экрана штосьці пайшло не так.", "bundle_modal_error.retry": "Паспрабуйце зноў", "closed_registrations.other_server_instructions": "Паколькі Mastodon дэцэнтралізаваны, вы можаце стварыць уліковы запіс на іншым серверы і працягваць узаемадзейнічаць з ім.", "closed_registrations_modal.description": "Стварэнне ўліковага запісу на {domain} цяпер немагчыма. Заўважце, што няма неабходнасці мець уліковы запіс менавіта на {domain}, каб выкарыстоўваць Mastodon.", @@ -147,7 +167,7 @@ "column.firehose": "Стужкі", "column.follow_requests": "Запыты на падпіску", "column.home": "Галоўная", - "column.list_members": "Кіраванне ўдзельнікамі спісу", + "column.list_members": "Кіраванне ўдзельнікамі спіса", "column.lists": "Спісы", "column.mutes": "Ігнараваныя карыстальнікі", "column.notifications": "Апавяшчэнні", @@ -196,19 +216,24 @@ "confirmations.delete_list.confirm": "Выдаліць", "confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?", "confirmations.delete_list.title": "Выдаліць спіс?", + "confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне", "confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", - "confirmations.follow_to_list.confirm": "Падпісацца й дадаць у сьпіс", - "confirmations.follow_to_list.message": "Вы мусіце быць падпісаныя на {name} каб дадаць яго ў сьпіс.", + "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", + "confirmations.follow_to_list.message": "Вам трэба падпісацца на карыстальніка {name}, каб дадаць яго ў спіс.", "confirmations.follow_to_list.title": "Падпісацца на карыстальніка?", "confirmations.logout.confirm": "Выйсці", "confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?", "confirmations.logout.title": "Выйсці?", - "confirmations.missing_alt_text.title": "Дадаць апісаньне?", + "confirmations.missing_alt_text.confirm": "Дадаць альтэрнатыўны тэкст", + "confirmations.missing_alt_text.secondary": "Усё адно апублікаваць", + "confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?", "confirmations.mute.confirm": "Ігнараваць", "confirmations.redraft.confirm": "Выдаліць і перапісаць", "confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.", "confirmations.redraft.title": "Выдаліць і перапісаць допіс?", + "confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка", + "confirmations.remove_from_followers.title": "Выдаліць падпісчыка?", "confirmations.unfollow.confirm": "Адпісацца", "confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?", "confirmations.unfollow.title": "Адпісацца ад карыстальніка?", @@ -221,7 +246,7 @@ "conversation.with": "З {names}", "copy_icon_button.copied": "Скапіявана ў буфер абмену", "copypaste.copied": "Скапіравана", - "copypaste.copy_to_clipboard": "Капіраваць у буфер абмену", + "copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену", "directory.federated": "З вядомага федэсвету", "directory.local": "Толькі з {domain}", "directory.new_arrivals": "Новыя карыстальнікі", @@ -230,7 +255,7 @@ "disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.", "dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.", "dismissable_banner.dismiss": "Адхіліць", - "dismissable_banner.public_timeline": "Гэта самыя новыя публічныя допісы ад карыстальнікаў фэдывёрсу на якіх падпісаныя карыстальнікі {domain}.", + "dismissable_banner.public_timeline": "Вось апошнія публічныя допісы ад карыстальнікаў fediverse на якіх падпісаны карыстальнікі {domain}.", "domain_block_modal.block": "Заблакіраваць сервер", "domain_block_modal.block_account_instead": "Заблакіраваць @{name} замест гэтага", "domain_block_modal.they_can_interact_with_old_posts": "Людзі з гэтага сервера змогуць узаемадзейнічаць з вашымі старымі допісамі.", @@ -275,15 +300,15 @@ "empty_column.account_timeline": "Тут няма допісаў!", "empty_column.account_unavailable": "Профіль недаступны", "empty_column.blocks": "Вы яшчэ нікога не заблакіравалі.", - "empty_column.bookmarked_statuses": "У вас яшчэ няма паведамленняў з закладкамі. Калі вы дадасце закладку, яна з'явіцца тут.", - "empty_column.community": "Мясцовая стужка пустая. Напішыце што-небудзь публічна, каб зрушыць з месца!", - "empty_column.direct": "Пакуль у вас няма асабістых згадак. Калі вы дашляце або атрымаеце штось, яно з'явіцца тут.", + "empty_column.bookmarked_statuses": "У вашых закладках яшчэ няма допісаў. Калі вы дадасце закладку, яна з’явіцца тут.", + "empty_column.community": "Мясцовая стужка пустая. Напішыце нешта публічнае, каб разварушыць справу!", + "empty_column.direct": "Пакуль у вас няма асабістых згадванняў. Калі вы дашляце або атрымаеце штосьці, яно з’явіцца тут.", "empty_column.domain_blocks": "Заблакіраваных даменаў пакуль няма.", "empty_column.explore_statuses": "Зараз не ў трэндзе. Праверце пазней", "empty_column.favourited_statuses": "Вы яшчэ не ўпадабалі ніводны допіс. Калі гэта адбудзецца, вы ўбачыце яго тут.", "empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, вы ўбачыце гэтых людзей тут.", - "empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіскуі. Калі вы атрымаеце запыт, ён з'явяцца тут.", - "empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны з'явяцца тут.", + "empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіску. Калі вы атрымаеце запыт, ён з’явіцца тут.", + "empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны з’явяцца тут.", "empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.", "empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}", "empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.", @@ -294,13 +319,18 @@ "error.unexpected_crash.explanation": "Гэта старонка не можа быць адлюстравана карэктна з-за памылкі ў нашым кодзе, або праблемы з сумяшчальнасцю браўзера.", "error.unexpected_crash.explanation_addons": "Гэтая старонка не можа быць адлюстравана карэктна. Верагодна, гэтая памылка выклікана дадатковым кампанентам браўзера або інструментамі аўтаматычнага перакладу", "error.unexpected_crash.next_steps": "Паспрабуйце абнавіць старонку. Калі гэта не дапаможа, вы можаце паспрабаваць іншы браўзер, альбо выкарыстаць усталяваную праграму.", - "error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і аднавіць старонку. Калі гэта не дапаможа, вы можаце карыстацца Мастадонт праз другі браўзер ці аплікацыю.", + "error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і абнавіць старонку. Калі гэта не дапамагае, вы ўсё яшчэ можаце карыстацца Mastodon праз іншы браўзер ці натыўную праграму.", "errors.unexpected_crash.copy_stacktrace": "Дадаць дыягнастычны стэк у буфер абмену", "errors.unexpected_crash.report_issue": "Паведаміць аб праблеме", "explore.suggested_follows": "Людзі", + "explore.title": "Трэндавае", "explore.trending_links": "Навіны", "explore.trending_statuses": "Допісы", "explore.trending_tags": "Хэштэгі", + "featured_carousel.next": "Далей", + "featured_carousel.post": "Допіс", + "featured_carousel.previous": "Назад", + "featured_carousel.slide": "{index} з {total}", "filter_modal.added.context_mismatch_explanation": "Гэтая катэгорыя фільтра не прымяняецца да кантэксту, у якім вы адкрылі гэты пост. Калі вы хочаце, каб паведамленне таксама было адфільтравана ў гэтым кантэксце, вам трэба будзе адрэдагаваць фільтр", "filter_modal.added.context_mismatch_title": "Неадпаведны кантэкст!", "filter_modal.added.expired_explanation": "Тэрмін дзеяння гэтай катэгорыі фільтраў скончыўся, вам трэба будзе змяніць дату заканчэння тэрміну дзеяння, каб яна прымянялася", @@ -349,10 +379,10 @@ "footer.privacy_policy": "Палітыка прыватнасці", "footer.source_code": "Прагледзець зыходны код", "footer.status": "Статус", - "footer.terms_of_service": "Умовы абслугоўваньня", + "footer.terms_of_service": "Умовы выкарыстання", "generic.saved": "Захавана", "getting_started.heading": "Пачатак працы", - "hashtag.admin_moderation": "Адкрыць інтэрфэйс мадаратара для #{name}", + "hashtag.admin_moderation": "Адкрыць інтэрфейс мадэратара для #{name}", "hashtag.column_header.tag_mode.all": "і {additional}", "hashtag.column_header.tag_mode.any": "або {additional}", "hashtag.column_header.tag_mode.none": "без {additional}", @@ -374,35 +404,39 @@ "hints.profiles.see_more_followers": "Глядзець больш падпісаных на {domain}", "hints.profiles.see_more_follows": "Глядзець больш падпісак на {domain}", "hints.profiles.see_more_posts": "Глядзець больш допісаў на {domain}", - "hints.threads.replies_may_be_missing": "Адказы зь іншых сэрвэраў могуць адсутнічаць.", + "hints.threads.replies_may_be_missing": "Адказы з іншых сервераў могуць адсутнічаць.", "hints.threads.see_more": "Глядзіце больш адказаў на {domain}", + "home.column_settings.show_quotes": "Паказаць цытаты", "home.column_settings.show_reblogs": "Паказваць пашырэнні", "home.column_settings.show_replies": "Паказваць адказы", "home.hide_announcements": "Схаваць аб'явы", - "home.pending_critical_update.body": "Калі ласка, абнавіце свой сервер Mastodon як мага хутчэй!", + "home.pending_critical_update.body": "Абнавіце свой сервер Mastodon як мага хутчэй!", "home.pending_critical_update.link": "Прагледзець абнаўленні", "home.pending_critical_update.title": "Даступна крытычнае абнаўленне бяспекі!", "home.show_announcements": "Паказаць аб'явы", - "ignore_notifications_modal.disclaimer": "Mastodon ня можа йнфармаваць карыстальнікаў аб тым, што вы прайігнаравалі йх паведамленьні. Ігнараваньне паведамленьняў не спыніць іх адпраўку.", + "ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.", "ignore_notifications_modal.filter_instead": "Замест гэтага адфільтраваць", "ignore_notifications_modal.filter_to_act_users": "Вы па-ранейшаму зможаце прымаць, адхіляць ці скардзіцца на карыстальнікаў", - "ignore_notifications_modal.filter_to_avoid_confusion": "Фільтраваньне дапамагае пазьбегнуць патэнцыйнай блытаніны", - "ignore_notifications_modal.filter_to_review_separately": "Вы можаце прагледзець адфільтраваныя паведамленьні асобна", - "ignore_notifications_modal.ignore": "Ігнараваць паведамленьні", - "ignore_notifications_modal.limited_accounts_title": "Ігнараваць паведамленьні ад абмежаваных уліковых запісаў?", - "ignore_notifications_modal.new_accounts_title": "Ігнараваць паведамленьні ад новых уліковых запісаў?", - "ignore_notifications_modal.not_followers_title": "Ігнараваць паведамленьні ад людзей, якія ня падпісаныя на вас?", + "ignore_notifications_modal.filter_to_avoid_confusion": "Выкарыстанне фільтраў дапамагае пазбягаць патэнцыйнай блытаніны", + "ignore_notifications_modal.filter_to_review_separately": "Вы можаце прагледзець адфільтраваныя апавяшчэнні асобна", + "ignore_notifications_modal.ignore": "Ігнараваць апавяшчэнні", + "ignore_notifications_modal.limited_accounts_title": "Ігнараваць апавяшчэнні ад уліковых запісаў пад мадэрацыяй?", + "ignore_notifications_modal.new_accounts_title": "Ігнараваць апавяшчэнні ад новых уліковых запісаў?", + "ignore_notifications_modal.not_followers_title": "Ігнараваць апавяшчэнні ад людзей, якія не падпісаныя на вас?", "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?", - "ignore_notifications_modal.private_mentions_title": "Ігнараваць паведамленьні аб непажаданых прыватных згадках?", + "ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?", "info_button.label": "Даведка", - "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць з вашага ўліковага запісу.", - "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца з вашага ўліковага запісу.", + "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.", + "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.", + "interaction_modal.go": "Перайсці", + "interaction_modal.no_account_yet": "Не маеце ўліковага запісу?", "interaction_modal.on_another_server": "На іншым серверы", "interaction_modal.on_this_server": "На гэтым серверы", "interaction_modal.title.favourite": "Упадабаць допіс {name}", "interaction_modal.title.follow": "Падпісацца на {name}", "interaction_modal.title.reblog": "Пашырыць допіс ад {name}", "interaction_modal.title.reply": "Адказаць на допіс {name}", + "interaction_modal.username_prompt": "Напр., {example}", "intervals.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}", "intervals.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}}", "intervals.full.minutes": "{number, plural, one {# хвіліна} few {# хвіліны} many {# хвілін} other {# хвіліны}}", @@ -438,21 +472,32 @@ "keyboard_shortcuts.toggle_hidden": "Паказаць/схаваць тэкст за папярэджаннем пра кантэнт", "keyboard_shortcuts.toggle_sensitivity": "Паказаць/схаваць медыя", "keyboard_shortcuts.toot": "Стварыць новы допіс", + "keyboard_shortcuts.translate": "каб перакласці допіс", "keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок", "keyboard_shortcuts.up": "Перамясціцца ўверх па спісе", "lightbox.close": "Закрыць", "lightbox.next": "Далей", "lightbox.previous": "Назад", + "lightbox.zoom_in": "Маштабаваць да фактычнага памеру", + "lightbox.zoom_out": "Дапасаваць усё змесціва пад памеры экрана", "limited_account_hint.action": "Усе роўна паказваць профіль", "limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі", "link_preview.author": "Ад {name}", "link_preview.more_from_author": "Больш ад {name}", "link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", "lists.add_member": "Дадаць", + "lists.add_to_list": "Дадаць у спіс", + "lists.add_to_lists": "Дадаць {name} у спісы", "lists.create": "Стварыць", "lists.create_list": "Стварыць спіс", "lists.delete": "Выдаліць спіс", + "lists.done": "Гатова", "lists.edit": "Рэдагаваць спіс", + "lists.list_name": "Назва спіса", + "lists.new_list_name": "Назва новага спіса", + "lists.no_lists_yet": "Пакуль няма спісаў.", + "lists.no_members_yet": "Пакуль няма ўдзельнікаў.", + "lists.no_results_found": "Нічога не знойдзена.", "lists.remove_member": "Выдаліць", "lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся", "lists.replies_policy.list": "Удзельнікі гэтага спісу", @@ -460,7 +505,7 @@ "lists.save": "Захаваць", "lists.search": "Пошук", "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", - "loading_indicator.label": "Загрузка…", + "loading_indicator.label": "Ідзе загрузка…", "media_gallery.hide": "Схаваць", "moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны таму што вы перанесены на {movedToAccount}.", "mute_modal.hide_from_notifications": "Схаваць з апавяшчэнняў", @@ -473,7 +518,10 @@ "mute_modal.you_wont_see_mentions": "Вы не ўбачыце паведамленняў са згадваннем карыстальніка.", "mute_modal.you_wont_see_posts": "Карыстальнік па-ранейшаму будзе бачыць вашыя паведамленні, але вы не будзеце паведамленні карыстальніка.", "navigation_bar.about": "Пра нас", + "navigation_bar.account_settings": "Пароль і бяспека", + "navigation_bar.administration": "Адміністрацыя", "navigation_bar.advanced_interface": "Адкрыць у пашыраным вэб-інтэрфейсе", + "navigation_bar.automated_deletion": "Аўтаматычнае выдаленне допісаў", "navigation_bar.blocks": "Заблакіраваныя карыстальнікі", "navigation_bar.bookmarks": "Закладкі", "navigation_bar.direct": "Асабістыя згадванні", @@ -483,13 +531,21 @@ "navigation_bar.follow_requests": "Запыты на падпіску", "navigation_bar.followed_tags": "Падпіскі", "navigation_bar.follows_and_followers": "Падпіскі і падпісчыкі", + "navigation_bar.import_export": "Імпарт і экспарт", "navigation_bar.lists": "Спісы", + "navigation_bar.live_feed_local": "Жывая стужка (лакальная)", + "navigation_bar.live_feed_public": "Жывая стужка (публічная)", "navigation_bar.logout": "Выйсці", "navigation_bar.moderation": "Мадэрацыя", + "navigation_bar.more": "Больш", "navigation_bar.mutes": "Ігнараваныя карыстальнікі", "navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.", "navigation_bar.preferences": "Налады", + "navigation_bar.privacy_and_reach": "Прыватнасць і пошук", "navigation_bar.search": "Пошук", + "navigation_bar.search_trends": "Пошук / Трэндавае", + "navigation_panel.collapse_followed_tags": "Згарнуць меню падпісак на хэштэгі", + "navigation_panel.collapse_lists": "Згарнуць меню спісаў", "not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.", "notification.admin.report": "{name} паскардзіўся на {target}", "notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}", @@ -497,7 +553,10 @@ "notification.admin.report_statuses": "{name} паскардзіўся на {target} з прычыны {category}", "notification.admin.report_statuses_other": "{name} паскардзіўся на {target}", "notification.admin.sign_up": "{name} зарэгістраваўся", + "notification.annual_report.view": "Перайсці да #Wrapstodon", "notification.favourite": "Ваш допіс упадабаны {name}", + "notification.favourite_pm": "Ваша асабістае згадванне ўпадабана {name}", + "notification.favourite_pm.name_and_others_with_link": "{name} і {count, plural, one {# іншы} few {# іншыя} many {# іншых} other {# іншых}} ўпадабалі ваша асабістае згадванне", "notification.follow": "{name} падпісаўся на вас", "notification.follow_request": "{name} адправіў запыт на падпіску", "notification.follow_request.name_and_others": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} запыталіся падпісацца на вас", @@ -508,7 +567,7 @@ "notification.mention": "Згадванне", "notification.mentioned_you": "{name} згадаў вас", "notification.moderation-warning.learn_more": "Даведацца больш", - "notification.moderation_warning": "Вы атрымалі папярэджанне аб мадэрацыі", + "notification.moderation_warning": "Вы атрымалі папярэджанне ад мадэратараў", "notification.moderation_warning.action_delete_statuses": "Некаторыя вашыя допісы былі выдаленыя.", "notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.", @@ -539,7 +598,7 @@ "notification_requests.title": "Адфільтраваныя апавяшчэнні", "notification_requests.view": "Прагляд апавяшчэнняў", "notifications.clear": "Ачысціць апавяшчэнні", - "notifications.clear_confirmation": "Вы ўпэўнены, што жадаеце назаўсёды сцерці ўсё паведамленні?", + "notifications.clear_confirmation": "Вы ўпэўнены, што хочаце назаўсёды сцерці ўсе свае паведамленні?", "notifications.clear_title": "Ачысціць апавяшчэнні?", "notifications.column_settings.admin.report": "Новыя скаргі:", "notifications.column_settings.admin.sign_up": "Новыя ўваходы:", @@ -549,7 +608,7 @@ "notifications.column_settings.filter_bar.category": "Панэль хуткай фільтрацыі", "notifications.column_settings.follow": "Новыя падпісчыкі:", "notifications.column_settings.follow_request": "Новыя запыты на падпіску:", - "notifications.column_settings.group": "Аб'яднаць апавяшчэнні ад падпісчыкаў", + "notifications.column_settings.group": "Аб’яднаць апавяшчэнні ад падпісчыкаў", "notifications.column_settings.mention": "Згадванні:", "notifications.column_settings.poll": "Вынікі апытання:", "notifications.column_settings.push": "Push-апавяшчэнні", @@ -571,13 +630,13 @@ "notifications.group": "{count} Апавяшчэнняў", "notifications.mark_as_read": "Пазначыць усе апавяшчэнні як прачытаныя", "notifications.permission_denied": "Апавяшчэнні на працоўным стале недаступныя з-за папярэдне адхіленага запыта праў браўзера", - "notifications.permission_denied_alert": "Апавяшчэнні на працоўным стале не могуць быць уключаныя, з-за таго што запыт браўзера быў адхілены", + "notifications.permission_denied_alert": "З-за таго, што запыт браўзера быў раней адхілены, немагчыма ўключыць апавяшчэнні на працоўным стале", "notifications.permission_required": "Апавяшчэнні на працоўным стале недаступныя, з-за таго што неабходны дазвол не быў дадзены.", "notifications.policy.accept": "Прыняць", "notifications.policy.accept_hint": "Паказваць у апавяшчэннях", "notifications.policy.drop": "Iгнараваць", "notifications.policy.filter": "Фільтраваць", - "notifications.policy.filter_limited_accounts_title": "Абмежаваныя ўліковыя запісы", + "notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй", "notifications.policy.filter_new_accounts.hint": "Створаныя на працягу {days, plural, one {апошняга # дня} few {апошніх # дзён} many {апошніх # дзён} other {апошняй # дня}}", "notifications.policy.filter_new_accounts_title": "Новыя ўліковыя запісы", "notifications.policy.filter_not_followers_hint": "Уключаючы людзей, якія падпісаны на вас менш, чым {days, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}", @@ -594,13 +653,13 @@ "onboarding.follows.done": "Гатова", "onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыце спробу пазней.", "onboarding.follows.search": "Пошук", - "onboarding.follows.title": "Падпішыцеся каб пачаць", + "onboarding.follows.title": "Падпішыцеся на некага, каб пачаць", "onboarding.profile.discoverable": "Зрабіць мой профіль бачным", "onboarding.profile.discoverable_hint": "Калі вы звяртаецеся да адкрытасці на Mastodon, вашы паведамленні могуць з'яўляцца ў выніках пошуку і тэндэнцый, а ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.", "onboarding.profile.display_name": "Бачнае імя", "onboarding.profile.display_name_hint": "Ваша поўнае імя або ваш псеўданім…", "onboarding.profile.note": "Біяграфія", - "onboarding.profile.note_hint": "Вы можаце @згадаць іншых людзей або выкарыстоўваць #хэштэгі…", + "onboarding.profile.note_hint": "Вы можаце @згадваць іншых людзей або выкарыстоўваць #хэштэгі…", "onboarding.profile.save_and_continue": "Захаваць і працягнуць", "onboarding.profile.title": "Налады профілю", "onboarding.profile.upload_avatar": "Загрузіць фота профілю", @@ -620,6 +679,7 @@ "poll_button.remove_poll": "Выдаліць апытанне", "privacy.change": "Змяніць прыватнасць допісу", "privacy.direct.long": "Усе згаданыя ў допісе", + "privacy.direct.short": "Асабістае згадванне", "privacy.private.long": "Толькі вашыя падпісчыкі", "privacy.private.short": "Падпісчыкі", "privacy.public.long": "Усе, хто ёсць і каго няма ў Mastodon", @@ -629,10 +689,10 @@ "privacy.unlisted.short": "Ціхі публічны", "privacy_policy.last_updated": "Адноўлена {date}", "privacy_policy.title": "Палітыка канфідэнцыйнасці", - "recommended": "Рэкамендуем", + "recommended": "Рэкамендаванае", "refresh": "Абнавiць", - "regeneration_indicator.please_stand_by": "Калі ласка, пачакайце.", - "regeneration_indicator.preparing_your_home_feed": "Рыхтуем вашую стужку…", + "regeneration_indicator.please_stand_by": "Пачакайце.", + "regeneration_indicator.preparing_your_home_feed": "Рыхтуем вашу галоўную стужку…", "relative_time.days": "{number} д", "relative_time.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}} таму", "relative_time.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}} таму", @@ -649,7 +709,7 @@ "reply_indicator.poll": "Апытанне", "report.block": "Заблакіраваць", "report.block_explanation": "Вы перастанеце бачыць допісы гэтага карыстальніка. Ён не зможа сачыць за вамі і бачыць вашы допісы. Ён зможа зразумець, што яго заблакіравалі.", - "report.categories.legal": "Права", + "report.categories.legal": "Звязанае з правам", "report.categories.other": "Іншае", "report.categories.spam": "Спам", "report.categories.violation": "Змест парушае адно ці некалькі правілаў сервера", @@ -716,8 +776,8 @@ "search_results.accounts": "Профілі", "search_results.all": "Усё", "search_results.hashtags": "Хэштэгі", - "search_results.no_results": "Анічога ня знойдзена.", - "search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або гэштаґі.", + "search_results.no_results": "Няма вынікаў.", + "search_results.no_search_yet": "Паспрабуйце пашукаць допісы, профілі або хэштэгі.", "search_results.see_all": "Праглядзець усе", "search_results.statuses": "Допісы", "search_results.title": "Шукаць \"{q}\"", @@ -738,7 +798,7 @@ "status.bookmark": "Дадаць закладку", "status.cancel_reblog_private": "Прыбраць", "status.cannot_reblog": "Гэты пост нельга пашырыць", - "status.continued_thread": "Працяг тэмы", + "status.continued_thread": "Працяг ланцужка", "status.copy": "Скапіраваць спасылку на допіс", "status.delete": "Выдаліць", "status.detailed_status": "Дэтальны агляд размовы", @@ -763,6 +823,7 @@ "status.mute_conversation": "Ігнараваць размову", "status.open": "Разгарнуць гэты допіс", "status.pin": "Замацаваць у профілі", + "status.quote_post_author": "Допіс карыстальніка @{name}", "status.read_more": "Чытаць болей", "status.reblog": "Пашырыць", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю", @@ -771,7 +832,7 @@ "status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.", "status.redraft": "Выдаліць і паправіць", "status.remove_bookmark": "Выдаліць закладку", - "status.replied_in_thread": "Адказаў у тэме", + "status.replied_in_thread": "Адказаў у ланцужку", "status.replied_to": "Адказаў {name}", "status.reply": "Адказаць", "status.replyAll": "Адказаць у ланцугу", @@ -791,8 +852,11 @@ "subscribed_languages.save": "Захаваць змены", "subscribed_languages.target": "Змяніць мовы падпіскі для {target}", "tabs_bar.home": "Галоўная", + "tabs_bar.menu": "Меню", "tabs_bar.notifications": "Апавяшчэнні", - "terms_of_service.title": "Умовы абслугоўваньня", + "tabs_bar.publish": "Новы допіс", + "tabs_bar.search": "Пошук", + "terms_of_service.title": "Умовы выкарыстання", "time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}", "time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}", "time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}", @@ -818,6 +882,12 @@ "video.expand": "Разгарнуць відэа", "video.fullscreen": "Увесь экран", "video.hide": "Схаваць відэа", + "video.mute": "Выключыць гук", "video.pause": "Паўза", - "video.play": "Прайграць" + "video.play": "Прайграць", + "video.skip_backward": "Праматаць назад", + "video.skip_forward": "Праматаць уперад", + "video.unmute": "Уключыць гук", + "video.volume_down": "Паменшыць гучнасць", + "video.volume_up": "Павялічыць гучнасць" } diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 53b432152bb..7fab405f8e2 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -221,7 +221,11 @@ "confirmations.delete_list.title": "Eliminar la llista?", "confirmations.discard_draft.confirm": "Descarta i continua", "confirmations.discard_draft.edit.cancel": "Continua l'edició", + "confirmations.discard_draft.edit.message": "Continuar descartarà tots els canvis que hàgiu fet a la publicació que editeu.", + "confirmations.discard_draft.edit.title": "Descartar els canvis a la publicació?", "confirmations.discard_draft.post.cancel": "Reprendre l'esborrany", + "confirmations.discard_draft.post.message": "Continuar descartarà la publicació que escriviu.", + "confirmations.discard_draft.post.title": "Descartar l'esborrany?", "confirmations.discard_edit_media.confirm": "Descarta", "confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?", "confirmations.follow_to_list.confirm": "Seguir i afegir a una llista", @@ -559,6 +563,8 @@ "navigation_bar.follows_and_followers": "Seguint i seguidors", "navigation_bar.import_export": "Importació i exportació", "navigation_bar.lists": "Llistes", + "navigation_bar.live_feed_local": "Canal en directe (local)", + "navigation_bar.live_feed_public": "Canal en directe (públic)", "navigation_bar.logout": "Tanca la sessió", "navigation_bar.moderation": "Moderació", "navigation_bar.more": "Més", @@ -567,7 +573,10 @@ "navigation_bar.preferences": "Preferències", "navigation_bar.privacy_and_reach": "Privacitat i abast", "navigation_bar.search": "Cerca", + "navigation_bar.search_trends": "Cerca / En tendència", + "navigation_panel.collapse_followed_tags": "Comprimeix el menú d'etiquetes seguides", "navigation_panel.collapse_lists": "Tanca el menú", + "navigation_panel.expand_followed_tags": "Expandeix el menú d'etiquetes seguides", "navigation_panel.expand_lists": "Expandeix el menú", "not_signed_in_indicator.not_signed_in": "Cal que iniciïs la sessió per a accedir a aquest recurs.", "notification.admin.report": "{name} ha reportat {target}", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 4a6de095a8d..03c4d48d952 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -4,7 +4,7 @@ "about.default_locale": "Προεπιλογή", "about.disclaimer": "Το Mastodon είναι ελεύθερο λογισμικό ανοιχτού κώδικα και εμπορικό σήμα της Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Αιτιολογία μη διαθέσιμη", - "about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σού επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.", + "about.domain_blocks.preamble": "Σε γενικές γραμμές το Mastodon σου επιτρέπει να βλέπεις περιεχόμενο και να αλληλεπιδράς με χρήστες από οποιονδήποτε άλλο διακομιστή σε ένα διασυνδεδεμένο σύμπαν διακομιστών (fediverse). Ακολουθούν οι εξαιρέσεις που ισχύουν για τον συγκεκριμένο διακομιστή.", "about.domain_blocks.silenced.explanation": "Συνήθως δε θα βλέπεις προφίλ και περιεχόμενο απ' αυτόν τον διακομιστή, εκτός αν κάνεις συγκεκριμένη αναζήτηση ή επιλέξεις να τον ακολουθήσεις.", "about.domain_blocks.silenced.title": "Περιορισμένος", "about.domain_blocks.suspended.explanation": "Τα δεδομένα αυτού του διακομιστή, δε θα επεξεργάζονται, δε θα αποθηκεύονται και δε θα ανταλλάσσονται, καθιστώντας οποιαδήποτε αλληλεπίδραση ή επικοινωνία με χρήστες από αυτόν το διακομιστή αδύνατη.", @@ -65,7 +65,7 @@ "account.muting": "Σίγαση", "account.mutual": "Ακολουθείτε ο ένας τον άλλο", "account.no_bio": "Δεν υπάρχει περιγραφή.", - "account.open_original_page": "Ανοικτό", + "account.open_original_page": "Άνοιγμα αυθεντικής σελίδας", "account.posts": "Τουτ", "account.posts_with_replies": "Τουτ και απαντήσεις", "account.remove_from_followers": "Κατάργηση {name} από τους ακόλουθους", @@ -194,7 +194,7 @@ "compose.saved.body": "Η ανάρτηση αποθηκεύτηκε.", "compose_form.direct_message_warning_learn_more": "Μάθε περισσότερα", "compose_form.encryption_warning": "Οι δημοσιεύσεις στο Mastodon δεν είναι κρυπτογραφημένες από άκρο σε άκρο. Μη μοιράζεσαι ευαίσθητες πληροφορίες μέσω του Mastodon.", - "compose_form.hashtag_warning": "Αυτή η δημοσίευση δεν θα εμφανίζεται κάτω από οποιαδήποτε ετικέτα καθώς δεν είναι δημόσια. Μόνο οι δημόσιες δημοσιεύσεις μπορούν να αναζητηθούν με ετικέτα.", + "compose_form.hashtag_warning": "Αυτή η ανάρτηση δεν θα εμφανίζεται κάτω από οποιαδήποτε ετικέτα καθώς δεν είναι δημόσια. Μόνο οι δημόσιες αναρτήσεις μπορούν να αναζητηθούν με ετικέτα.", "compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σου προς τους ακολούθους σου.", "compose_form.lock_disclaimer.lock": "κλειδωμένο", "compose_form.placeholder": "Τι σκέφτεσαι;", @@ -214,13 +214,18 @@ "confirmation_modal.cancel": "Άκυρο", "confirmations.block.confirm": "Αποκλεισμός", "confirmations.delete.confirm": "Διαγραφή", - "confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή τη δημοσίευση;", + "confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή την ανάρτηση;", "confirmations.delete.title": "Διαγραφή ανάρτησης;", "confirmations.delete_list.confirm": "Διαγραφή", "confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;", "confirmations.delete_list.title": "Διαγραφή λίστας;", "confirmations.discard_draft.confirm": "Απόρριψη και συνέχεια", "confirmations.discard_draft.edit.cancel": "Συνέχιση επεξεργασίας", + "confirmations.discard_draft.edit.message": "Συνεχίζοντας θα απορρίψει τυχόν αλλαγές που έχετε κάνει στην ανάρτηση που επεξεργάζεστε.", + "confirmations.discard_draft.edit.title": "Απόρριψη αλλαγών στην ανάρτηση σας;", + "confirmations.discard_draft.post.cancel": "Συνέχιση προχείρου", + "confirmations.discard_draft.post.message": "Συνεχίζοντας θα απορρίψει την ανάρτηση που συνθέτετε.", + "confirmations.discard_draft.post.title": "Απόρριψη της πρόχειρης ανάρτησης σας;", "confirmations.discard_edit_media.confirm": "Απόρριψη", "confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;", "confirmations.follow_to_list.confirm": "Ακολούθησε και πρόσθεσε στη λίστα", @@ -232,7 +237,7 @@ "confirmations.missing_alt_text.confirm": "Προσθήκη εναλ κειμένου", "confirmations.missing_alt_text.message": "Η ανάρτησή σου περιέχει πολυμέσα χωρίς εναλλακτικό κείμενο. Η προσθήκη περιγραφών βοηθά να γίνει το περιεχόμενό σου προσβάσιμο σε περισσότερους ανθρώπους.", "confirmations.missing_alt_text.secondary": "Δημοσίευση όπως και να ΄χει", - "confirmations.missing_alt_text.title": "Προσθήκη alt κειμένου;", + "confirmations.missing_alt_text.title": "Προσθήκη εναλλακτικού κειμένου;", "confirmations.mute.confirm": "Αποσιώπηση", "confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο", "confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.", @@ -271,7 +276,7 @@ "domain_block_modal.you_will_lose_num_followers": "Θα χάσετε {followersCount, plural, one {{followersCountDisplay} ακόλουθο} other {{followersCountDisplay} ακόλουθους}} και {followingCount, plural, one {{followingCountDisplay} άτομο που ακολουθείτε} other {{followingCountDisplay} άτομα που ακολουθείτε}}.", "domain_block_modal.you_will_lose_relationships": "Θα χάσετε όλους τους ακόλουθους και τα άτομα που ακολουθείτε από αυτόν τον διακομιστή.", "domain_block_modal.you_wont_see_posts": "Δεν θα βλέπεις αναρτήσεις ή ειδοποιήσεις από χρήστες σε αυτόν το διακομιστή.", - "domain_pill.activitypub_lets_connect": "Σού επιτρέπει να συνδεθείς και να αλληλεπιδράσεις με τους ανθρώπους όχι μόνο στο Mastodon, αλλά και σε διαφορετικές κοινωνικές εφαρμογές.", + "domain_pill.activitypub_lets_connect": "Σου επιτρέπει να συνδεθείς και να αλληλεπιδράσεις με τους ανθρώπους όχι μόνο στο Mastodon, αλλά και σε διαφορετικές κοινωνικές εφαρμογές.", "domain_pill.activitypub_like_language": "Το ActivityPub είναι σαν τη γλώσσα Mastodon μιλάει με άλλα κοινωνικά δίκτυα.", "domain_pill.server": "Διακομιστής", "domain_pill.their_handle": "Το πλήρες όνομα χρήστη:", @@ -317,7 +322,7 @@ "empty_column.favourited_statuses": "Δεν έχεις καμία αγαπημένη ανάρτηση ακόμα. Μόλις αγαπήσεις κάποια, θα εμφανιστεί εδώ.", "empty_column.favourites": "Κανείς δεν έχει αγαπήσει αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", "empty_column.follow_requests": "Δεν έχεις κανένα αίτημα παρακολούθησης ακόμα. Μόλις λάβεις κάποιο, θα εμφανιστεί εδώ.", - "empty_column.followed_tags": "Δεν έχετε παρακολουθήσει ακόμα καμία ετικέτα. Όταν το κάνετε, θα εμφανιστούν εδώ.", + "empty_column.followed_tags": "Δεν έχετε ακολουθήσει ακόμα καμία ετικέτα. Όταν το κάνετε, θα εμφανιστούν εδώ.", "empty_column.hashtag": "Δεν υπάρχει ακόμα κάτι για αυτή την ετικέτα.", "empty_column.home": "Η τοπική σου ροή είναι κενή! Πήγαινε στο {public} ή κάνε αναζήτηση για να ξεκινήσεις και να γνωρίσεις άλλους χρήστες.", "empty_column.list": "Δεν υπάρχει τίποτα σε αυτή τη λίστα ακόμα. Όταν τα μέλη της δημοσιεύσουν νέες καταστάσεις, θα εμφανιστούν εδώ.", @@ -381,7 +386,7 @@ "follow_suggestions.similar_to_recently_followed_longer": "Παρόμοια με προφίλ που ακολούθησες πρόσφατα", "follow_suggestions.view_all": "Εμφάνιση όλων", "follow_suggestions.who_to_follow": "Ποιον να ακολουθήσεις", - "followed_tags": "Ετικέτες που ακολουθούνται", + "followed_tags": "Ακολουθούμενες ετικέτες", "footer.about": "Σχετικά με", "footer.directory": "Κατάλογος προφίλ", "footer.get_app": "Αποκτήστε την εφαρμογή", @@ -412,7 +417,7 @@ "hashtag.mute": "Σίγαση #{hashtag}", "hashtag.unfeature": "Να μην αναδεικνύεται στο προφίλ", "hashtag.unfollow": "Διακοπή παρακολούθησης ετικέτας", - "hashtags.and_other": "…και {count, plural, one {}other {# ακόμη}}", + "hashtags.and_other": "…και {count, plural, other {# ακόμη}}", "hints.profiles.followers_may_be_missing": "Μπορεί να λείπουν ακόλουθοι για αυτό το προφίλ.", "hints.profiles.follows_may_be_missing": "Άτομα που ακολουθούνται μπορεί να λείπουν απ' αυτό το προφίλ.", "hints.profiles.posts_may_be_missing": "Κάποιες αναρτήσεις από αυτό το προφίλ μπορεί να λείπουν.", @@ -420,7 +425,7 @@ "hints.profiles.see_more_follows": "Δες περισσότερα άτομα που ακολουθούνται στο {domain}", "hints.profiles.see_more_posts": "Δες περισσότερες αναρτήσεις στο {domain}", "hints.threads.replies_may_be_missing": "Απαντήσεις από άλλους διακομιστές μπορεί να λείπουν.", - "hints.threads.see_more": "Δες περισσότερες αναρτήσεις στο {domain}", + "hints.threads.see_more": "Δες περισσότερες απαντήσεις στο {domain}", "home.column_settings.show_quotes": "Εμφάνιση παραθεμάτων", "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων", "home.column_settings.show_replies": "Εμφάνιση απαντήσεων", @@ -451,7 +456,7 @@ "interaction_modal.no_account_yet": "Δεν έχεις ακόμη λογαριασμό;", "interaction_modal.on_another_server": "Σε διαφορετικό διακομιστή", "interaction_modal.on_this_server": "Σε αυτόν τον διακομιστή", - "interaction_modal.title.favourite": "Favorite {name}'s post\nΠροτίμησε την ανάρτηση της/του {name}", + "interaction_modal.title.favourite": "Αγάπησε την ανάρτηση του χρήστη {name}", "interaction_modal.title.follow": "Ακολούθησε {name}", "interaction_modal.title.reblog": "Ενίσχυσε την ανάρτηση του {name}", "interaction_modal.title.reply": "Απάντηση στην ανάρτηση του {name}", @@ -463,13 +468,13 @@ "keyboard_shortcuts.back": "Μετάβαση πίσω", "keyboard_shortcuts.blocked": "Άνοιγμα λίστας αποκλεισμένων χρηστών", "keyboard_shortcuts.boost": "Ενίσχυση ανάρτησης", - "keyboard_shortcuts.column": "Στήλη εστίασης", - "keyboard_shortcuts.compose": "Περιοχή συγγραφής κειμένου εστίασης", + "keyboard_shortcuts.column": "Εστίαση στη στήλη", + "keyboard_shortcuts.compose": "Εστίαση στην περιοχή συγγραφής κειμένου", "keyboard_shortcuts.description": "Περιγραφή", "keyboard_shortcuts.direct": "για το άνοιγμα της στήλης ιδιωτικών επισημάνσεων", - "keyboard_shortcuts.down": "κίνηση προς τα κάτω στη λίστα", - "keyboard_shortcuts.enter": "Εμφάνιση ανάρτησης", - "keyboard_shortcuts.favourite": "Αγαπημένη δημοσίευση", + "keyboard_shortcuts.down": "Μετακίνηση προς τα κάτω στη λίστα", + "keyboard_shortcuts.enter": "Άνοιγμα ανάρτησης", + "keyboard_shortcuts.favourite": "Αγάπησε την ανάρτηση", "keyboard_shortcuts.favourites": "Άνοιγμα λίστας αγαπημένων", "keyboard_shortcuts.federated": "Άνοιγμα ροής συναλλαγών", "keyboard_shortcuts.heading": "Συντομεύσεις πληκτρολογίου", @@ -486,13 +491,13 @@ "keyboard_shortcuts.profile": "Άνοιγμα προφίλ συγγραφέα", "keyboard_shortcuts.reply": "Απάντηση στην ανάρτηση", "keyboard_shortcuts.requests": "Άνοιγμα λίστας αιτημάτων ακολούθησης", - "keyboard_shortcuts.search": "Γραμμή αναζήτησης εστίασης", + "keyboard_shortcuts.search": "Εστίαση στη γραμμή αναζήτησης", "keyboard_shortcuts.spoilers": "Εμφάνιση/απόκρυψη πεδίου CW", "keyboard_shortcuts.start": "Άνοιγμα της στήλης \"Ας ξεκινήσουμε\"", "keyboard_shortcuts.toggle_hidden": "Εμφάνιση/απόκρυψη κειμένου πίσω από το CW", "keyboard_shortcuts.toggle_sensitivity": "Εμφάνιση/απόκρυψη πολυμέσων", "keyboard_shortcuts.toot": "Δημιουργία νέας ανάρτησης", - "keyboard_shortcuts.translate": "να μεταφράσει μια δημοσίευση", + "keyboard_shortcuts.translate": "για να μεταφραστεί μια ανάρτηση", "keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης", "keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα", "lightbox.close": "Κλείσιμο", @@ -555,10 +560,12 @@ "navigation_bar.favourites": "Αγαπημένα", "navigation_bar.filters": "Αποσιωπημένες λέξεις", "navigation_bar.follow_requests": "Αιτήματα ακολούθησης", - "navigation_bar.followed_tags": "Ετικέτες που ακολουθούνται", + "navigation_bar.followed_tags": "Ακολουθούμενες ετικέτες", "navigation_bar.follows_and_followers": "Ακολουθείς και σε ακολουθούν", "navigation_bar.import_export": "Εισαγωγή και εξαγωγή", "navigation_bar.lists": "Λίστες", + "navigation_bar.live_feed_local": "Ζωντανή ροή (τοπική)", + "navigation_bar.live_feed_public": "Ζωντανή ροή (δημόσια)", "navigation_bar.logout": "Αποσύνδεση", "navigation_bar.moderation": "Συντονισμός", "navigation_bar.more": "Περισσότερα", @@ -568,9 +575,9 @@ "navigation_bar.privacy_and_reach": "Ιδιωτικότητα και προσιτότητα", "navigation_bar.search": "Αναζήτηση", "navigation_bar.search_trends": "Αναζήτηση / Τάσεις", - "navigation_panel.collapse_followed_tags": "Σύμπτυξη μενού ετικετών που ακολουθούνται", + "navigation_panel.collapse_followed_tags": "Σύμπτυξη μενού ετικετών που ακολουθείτε", "navigation_panel.collapse_lists": "Σύμπτυξη μενού λίστας", - "navigation_panel.expand_followed_tags": "Επέκταση μενού ετικετών που ακολουθούνται", + "navigation_panel.expand_followed_tags": "Επέκταση μενού ετικετών που ακολουθείτε", "navigation_panel.expand_lists": "Επέκταση μενού λίστας", "not_signed_in_indicator.not_signed_in": "Πρέπει να συνδεθείς για να αποκτήσεις πρόσβαση σε αυτόν τον πόρο.", "notification.admin.report": "Ο/Η {name} ανέφερε τον {target}", @@ -582,11 +589,11 @@ "notification.admin.sign_up.name_and_others": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} έχουν εγγραφεί", "notification.annual_report.message": "Το #Wrapstodon {year} σε περιμένει! Αποκάλυψε τα στιγμιότυπα της χρονιάς και αξέχαστες στιγμές σου στο Mastodon!", "notification.annual_report.view": "Προβολή #Wrapstodon", - "notification.favourite": "{name} favorited your post\n{name} προτίμησε την ανάρτηση σου", + "notification.favourite": "{name} αγάπησε την ανάρτηση σου", "notification.favourite.name_and_others_with_link": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} αγάπησαν την ανάρτησή σου", "notification.favourite_pm": "Ο χρήστης {name} αγάπησε την ιδιωτική σου επισήμανση", "notification.favourite_pm.name_and_others_with_link": "Ο χρήστης {name} και {count, plural, one {# ακόμη} other {# ακόμη}} αγάπησαν την ιδωτική σου επισήμανση", - "notification.follow": "Ο/Η {name} σε ακολούθησε", + "notification.follow": "Ο χρήστης {name} σε ακολούθησε", "notification.follow.name_and_others": "Ο χρήστης {name} και {count, plural, one {# ακόμη} other {# ακόμη}} σε ακολούθησαν", "notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει", "notification.follow_request.name_and_others": "{name} και {count, plural, one {# άλλος} other {# άλλοι}} ζήτησαν να σε ακολουθήσουν", @@ -719,7 +726,7 @@ "poll_button.add_poll": "Προσθήκη δημοσκόπησης", "poll_button.remove_poll": "Αφαίρεση δημοσκόπησης", "privacy.change": "Προσαρμογή ιδιωτικότητας ανάρτησης", - "privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στη δημοσίευση", + "privacy.direct.long": "Όλοι όσοι αναφέρθηκαν στην ανάρτηση", "privacy.direct.short": "Ιδιωτική επισήμανση", "privacy.private.long": "Μόνο οι ακόλουθοί σας", "privacy.private.short": "Ακόλουθοι", @@ -797,7 +804,7 @@ "report_notification.categories.spam_sentence": "ανεπιθύμητα", "report_notification.categories.violation": "Παραβίαση κανόνα", "report_notification.categories.violation_sentence": "παραβίαση κανόνα", - "report_notification.open": "Ανοιχτή αναφορά", + "report_notification.open": "Άνοιγμα αναφοράς", "search.clear": "Εκκαθάριση αναζήτησης", "search.no_recent_searches": "Καμία πρόσφατη αναζήτηση", "search.placeholder": "Αναζήτηση", @@ -851,7 +858,7 @@ "status.edited_x_times": "Επεξεργάστηκε {count, plural, one {{count} φορά} other {{count} φορές}}", "status.embed": "Απόκτηση κώδικα ενσωμάτωσης", "status.favourite": "Αγαπημένα", - "status.favourites": "{count, plural, one {# αγαπημένο} other {# αγαπημένα}}", + "status.favourites": "{count, plural, one {αγαπημένο} other {αγαπημένα}}", "status.filter": "Φιλτράρισμα αυτής της ανάρτησης", "status.history.created": "{name} δημιούργησε στις {date}", "status.history.edited": "{name} επεξεργάστηκε στις {date}", @@ -876,7 +883,7 @@ "status.reblog": "Ενίσχυση", "status.reblog_private": "Ενίσχυση με αρχική ορατότητα", "status.reblogged_by": "{name} προώθησε", - "status.reblogs": "{count, plural, one {# ενίσχυση} other {# ενισχύσεις}}", + "status.reblogs": "{count, plural, one {ενίσχυση} other {ενισχύσεις}}", "status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.", "status.redraft": "Σβήσε & ξαναγράψε", "status.remove_bookmark": "Αφαίρεση σελιδοδείκτη", @@ -888,8 +895,8 @@ "status.report": "Αναφορά @{name}", "status.sensitive_warning": "Ευαίσθητο περιεχόμενο", "status.share": "Κοινοποίηση", - "status.show_less_all": "Δείξε λιγότερα για όλα", - "status.show_more_all": "Δείξε περισσότερα για όλα", + "status.show_less_all": "Δείξε λιγότερο για όλες", + "status.show_more_all": "Δείξε περισσότερο για όλες", "status.show_original": "Εμφάνιση αρχικού", "status.title.with_attachments": "{user} δημοσίευσε {attachmentCount, plural, one {ένα συνημμένο} other {{attachmentCount} συνημμένα}}", "status.translate": "Μετάφραση", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index a3bd2b82448..946a9ecd12c 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -858,7 +858,7 @@ "status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}", "status.embed": "Obtener código para insertar", "status.favourite": "Marcar como favorito", - "status.favourites": "{count, plural, one {# vez marcado como favorito} other {# veces marcado como favorito}}", + "status.favourites": "{count, plural, one {vez marcado como favorito} other {veces marcado como favorito}}", "status.filter": "Filtrar este mensaje", "status.history.created": "Creado por {name}, {date}", "status.history.edited": "Editado por {name}, {date}", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 31d19931439..f8514666ccf 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -150,7 +150,7 @@ "bundle_column_error.routing.body": "Запрошенная страница не найдена. Вы уверены, что в адресной строке указан правильный URL?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Закрыть", - "bundle_modal_error.message": "Что-то пошло не так при загрузке этой страницы.", + "bundle_modal_error.message": "Кое-что пошло не так при загрузке этой страницы.", "bundle_modal_error.retry": "Попробовать снова", "closed_registrations.other_server_instructions": "Поскольку Mastodon децентрализован, вы можете зарегистрироваться на другом сервере и всё равно взаимодействовать с этим сервером.", "closed_registrations_modal.description": "Зарегистрироваться на {domain} сейчас не выйдет, но имейте в виду, что вам не нужна учётная запись именно на {domain}, чтобы использовать Mastodon.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 9eee2e750f9..0808963e5ff 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -220,6 +220,12 @@ "confirmations.delete_list.message": "Är du säker på att du vill radera denna lista permanent?", "confirmations.delete_list.title": "Ta bort listan?", "confirmations.discard_draft.confirm": "Släng bort och fortsätt", + "confirmations.discard_draft.edit.cancel": "Fortsätt redigera", + "confirmations.discard_draft.edit.message": "Fortsätter du, kommer ändringarna till inlägget att förkastas.", + "confirmations.discard_draft.edit.title": "Vill du förkasta ändringarna i inlägget?", + "confirmations.discard_draft.post.cancel": "Återuppta utkast", + "confirmations.discard_draft.post.message": "Fortsätter du, kommer inlägget du skriver att förkastas.", + "confirmations.discard_draft.post.title": "Vill du förkasta utkastet?", "confirmations.discard_edit_media.confirm": "Kasta", "confirmations.discard_edit_media.message": "Du har osparade ändringar till mediabeskrivningen eller förhandsgranskningen, kasta bort dem ändå?", "confirmations.follow_to_list.confirm": "Följ och lägg till i listan", @@ -558,6 +564,8 @@ "navigation_bar.follows_and_followers": "Följer och följare", "navigation_bar.import_export": "Importera och exportera", "navigation_bar.lists": "Listor", + "navigation_bar.live_feed_local": "Direkt flöde (lokalt)", + "navigation_bar.live_feed_public": "Direkt flöde (publikt)", "navigation_bar.logout": "Logga ut", "navigation_bar.moderation": "Moderering", "navigation_bar.more": "Fler", @@ -797,6 +805,7 @@ "report_notification.categories.violation": "Regelöverträdelse", "report_notification.categories.violation_sentence": "regelöverträdelse", "report_notification.open": "Öppna rapport", + "search.clear": "Rensa sökning", "search.no_recent_searches": "Inga sökningar nyligen", "search.placeholder": "Sök", "search.quick_action.account_search": "Profiler som matchar {x}", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 2a843b3dfa2..ec6e7188f22 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -98,7 +98,7 @@ "alert.rate_limited.message": "Vui lòng thử lại sau {retry_time, time, medium}.", "alert.rate_limited.title": "Vượt giới hạn", "alert.unexpected.message": "Đã xảy ra lỗi không mong muốn.", - "alert.unexpected.title": "Ốiii!", + "alert.unexpected.title": "Ốii!", "alt_text_badge.title": "Văn bản thay thế", "alt_text_modal.add_alt_text": "Thêm văn bản thay thế", "alt_text_modal.add_text_from_image": "Thêm văn bản từ ảnh", @@ -276,7 +276,7 @@ "domain_block_modal.you_will_lose_num_followers": "Bạn sẽ mất {followersCount, plural, other {{followersCountDisplay} người theo dõi}} và {followingCount, plural, other {{followingCountDisplay} người bạn theo dõi}}.", "domain_block_modal.you_will_lose_relationships": "Bạn sẽ mất tất cả người theo dõi và những người bạn theo dõi từ máy chủ này.", "domain_block_modal.you_wont_see_posts": "Bạn không còn thấy tút hoặc thông báo từ thành viên máy chủ này.", - "domain_pill.activitypub_lets_connect": "Nó cho phép bạn kết nối và tương tác với mọi người không chỉ trên Mastodon mà còn trên các ứng dụng xã hội khác.", + "domain_pill.activitypub_lets_connect": "Nó cho phép bạn kết nối và tương tác với mọi người, không chỉ trên Mastodon mà còn trên các nền tảng khác.", "domain_pill.activitypub_like_language": "ActivityPub giống như ngôn ngữ Mastodon giao tiếp với các mạng xã hội khác.", "domain_pill.server": "Máy chủ", "domain_pill.their_handle": "Địa chỉ Mastodon:", diff --git a/config/locales/activerecord.be.yml b/config/locales/activerecord.be.yml index f95fbd77d0e..73f6f67f01a 100644 --- a/config/locales/activerecord.be.yml +++ b/config/locales/activerecord.be.yml @@ -15,6 +15,9 @@ be: user/invite_request: text: Прычына errors: + attributes: + domain: + invalid: не з’яўляецца сапраўдным даменным імем models: account: attributes: @@ -37,7 +40,7 @@ be: attributes: account_id: taken: ужо ў спісе - must_be_following: мусіць быць падпісаным уліковым запісам + must_be_following: мусіць быць карыстальнікам у вашых падпісках status: attributes: reblog: diff --git a/config/locales/be.yml b/config/locales/be.yml index 978328ffd6c..601a81b6487 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -18,7 +18,7 @@ be: link_verified_on: Права ўласнасці на гэтую спасылку праверана %{date} nothing_here: Тут нічога няма! pin_errors: - following: Вы павінны быць падпісаны на чалавека, якога жадаеце рэкамендаваць + following: Трэба падпісацца на чалавека, якога вы хочаце рэкамендаваць posts: few: Допісы many: Допісаў @@ -28,6 +28,8 @@ be: admin: account_actions: action: Выканаць дзеянне + already_silenced: Гэты ўліковы запіс ужо абмежаваны. + already_suspended: Гэты ўліковы запіс ужо прыпынены. title: Мадэраваць %{acct} account_moderation_notes: create: Пакінуць нататку @@ -107,7 +109,7 @@ be: no_role_assigned: Роля не прызначана not_subscribed: Не падпісаны pending: Чакае праверкі - perform_full_suspension: Выключыць + perform_full_suspension: Прыпыніць previous_strikes: Ранейшыя скаргі previous_strikes_description_html: few: Гэты ўліковы запіс мае %{count} скаргі. @@ -164,7 +166,7 @@ be: unconfirmed_email: Непацверджаны адрас эл. пошты undo_sensitized: Прыбраць прымусовую пазнаку далікатнасці undo_silenced: Адмяніць ліміт - undo_suspension: Разблакаваць карыстальніка + undo_suspension: Разблакіраваць карыстальніка unsilenced_msg: З уліковага запісу %{username} зняты абмежаванні unsubscribe: Адпісацца unsuspended_msg: Уліковы запіс %{username} адноўлены @@ -189,7 +191,7 @@ be: create_domain_block: Стварыць даменны блок create_email_domain_block: Стварыць даменны блок электроннай пошты create_ip_block: Стварыць IP правіла - create_relay: Стварыць паўтор + create_relay: Стварыць рэтранслятар create_unavailable_domain: Стварыць недаступны Дамен create_user_role: Стварыць ролю demote_user: Панізіць карыстальніка @@ -201,22 +203,22 @@ be: destroy_email_domain_block: Выдаліць даменны блок электроннай пошты destroy_instance: Вычысціць дамен destroy_ip_block: Выдаліць IP правіла - destroy_relay: Выдаліць паўтор + destroy_relay: Выдаліць рэтранслятар destroy_status: Выдаліць допіс destroy_unavailable_domain: Выдаліць недаступны дамен destroy_user_role: Выдаліць ролю disable_2fa_user: Адключыць двухэтапнае спраўджанне disable_custom_emoji: Адключыць адвольныя эмодзі - disable_relay: Выключыць паўтор + disable_relay: Выключыць рэтранслятар disable_sign_in_token_auth_user: Адключыць аўтарызацыю праз электронную пошту для карыстальніка disable_user: Адключыць карыстальніка enable_custom_emoji: Уключыць адвольныя эмодзі - enable_relay: Уключыць паўтор + enable_relay: Уключыць рэтранслятар enable_sign_in_token_auth_user: Уключыць аўтарызацыю праз электронную пошту для карыстальніка enable_user: Уключыць карыстальніка memorialize_account: Запомніць уліковы запіс promote_user: Павысіць правы Карыстальніка - publish_terms_of_service: Апублікаваць Умовы выкарыстаньня + publish_terms_of_service: Апублікаваць умовы выкарыстання reject_appeal: Адхіліць абскарджанне reject_user: Адмовіць карыстальніку remove_avatar_user: Выдаліць аватар @@ -226,13 +228,13 @@ be: resolve_report: Вырашыць скаргу sensitive_account: Уліковы запіс прымусова пазначаны як далікатны silence_account: Абмежаваць уліковы запіс - suspend_account: Выключыць уліковы запіс + suspend_account: Прыпыніць уліковы запіс unassigned_report: Прыбраць прызначэнне скаргі unblock_email_account: Разблакіраваць электронны адрас unsensitive_account: Прымусовая пазнака далікатнасці прыбраная unsilence_account: Уліковы запіс больш не абмежаваны unsuspend_account: Аднавіць уліковы запіс - update_announcement: Абнавіць аб'яву + update_announcement: Абнавіць аб’яву update_custom_emoji: Абнавіць адвольныя эмодзі update_domain_block: Актуалізаваць блакіроўку дамена update_ip_block: Абнавіць IP правіла @@ -254,34 +256,34 @@ be: create_domain_block_html: "%{name} заблакіраваў дамен %{target}" create_email_domain_block_html: "%{name} заблакіраваў дамен эл. пошты %{target}" create_ip_block_html: "%{name} стварыў правіла для IP %{target}" - create_relay_html: "%{name} стварыў(-а) паўтор %{target}" + create_relay_html: "%{name} стварыў(-ла) рэтранслятар %{target}" create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}" create_user_role_html: "%{name} зрабіў ролю %{target}" demote_user_html: "%{name} прыбраў карыстальніка %{target}" destroy_announcement_html: "%{name} выдаліў аб'яву %{target}" destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}" - destroy_custom_emoji_html: "%{name} выдаліў(а) эмоджі %{target}" - destroy_domain_allow_html: "%{name} зняў дазвол на аб'яднанне з даменам %{target}\n" + destroy_custom_emoji_html: "%{name} выдаліў(-ла) эмодзі %{target}" + destroy_domain_allow_html: "%{name} зняў(-ла) дазвол на аб’яднанне з даменам %{target}" destroy_domain_block_html: "%{name} разблакаваў дамен %{target}" destroy_email_domain_block_html: "%{name} разблакіраваў дамен эл. пошты %{target}" destroy_instance_html: "%{name} цалкам прыбраў дамен %{target}" destroy_ip_block_html: "%{name} выдаліў правіла для IP %{target}" - destroy_relay_html: "%{name} выдаліў(-ла) паўтор %{target}" + destroy_relay_html: "%{name} выдаліў(-ла) рэтранслятар %{target}" destroy_status_html: "%{name} выдаліў допіс %{target}" destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}" destroy_user_role_html: "%{name} выдаліў ролю %{target}" disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}" disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}" - disable_relay_html: "%{name} выключыў(-ла) паўтор %{target}" + disable_relay_html: "%{name} выключыў(-ла) рэтранслятар %{target}" disable_sign_in_token_auth_user_html: "%{name} адключыў уваход праз эл. пошту для %{target}" disable_user_html: "%{name} адключыў уваход для карыстальніка %{target}" enable_custom_emoji_html: "%{name} уключыў эмодзі %{target}" - enable_relay_html: "%{name} уключыў(-ла) паўтор %{target}" + enable_relay_html: "%{name} уключыў(-ла) рэтранслятар %{target}" enable_sign_in_token_auth_user_html: "%{name} уключыў уваход праз эл. пошту для %{target}" enable_user_html: "%{name} уключыў уваход для карыстальніка %{target}" memorialize_account_html: Карыстальнік %{name} пераключыў уліковы запіс %{target} у старонку памяці promote_user_html: "%{name} павысіў карыстальніка %{target}" - publish_terms_of_service_html: "%{name} апублікаваў абнаўленьне ўмоваў абслугоўваньня" + publish_terms_of_service_html: "%{name} апублікаваў(-ла) абнаўленні ва ўмовах выкарыстання" reject_appeal_html: "%{name} адхіліў запыт на абскарджанне %{target}" reject_user_html: "%{name} адхіліў рэгістрацыю з %{target}" remove_avatar_user_html: "%{name} выдаліў аватар %{target}" @@ -291,13 +293,13 @@ be: resolve_report_html: "%{name} вырашыў скаргу %{target}" sensitive_account_html: "%{name} адзначыў медыя %{target} як далікатнае" silence_account_html: "%{name} абмежаваў уліковы запіс %{target}" - suspend_account_html: Уліковы запіс %{target} выключаны %{name} + suspend_account_html: Уліковы запіс %{target} прыпынены %{name} unassigned_report_html: "%{name} знял(а) прызначэнне скаргі %{target}" unblock_email_account_html: "%{name} разблакіраваў эл. пошту %{target}" unsensitive_account_html: "%{name} зняў адзнаку далікатнае з медыя %{target}" unsilence_account_html: "%{name} зняў ліміт з уліковага запісу %{target}" unsuspend_account_html: Уліковы запіс %{target} адноўлены %{name} - update_announcement_html: "%{name} абнавіў аб'яву %{target}" + update_announcement_html: "%{name} абнавіў(-ла) аб’яву %{target}" update_custom_emoji_html: "%{name} абнавіў эмодзі %{target}" update_domain_block_html: "%{name} абнавіў блакіроўку дамена для %{target}" update_ip_block_html: "%{name} змяніў правіла для IP %{target}" @@ -311,7 +313,7 @@ be: title: Аўдыт unavailable_instance: "(імя дамена недаступнае)" announcements: - destroyed_msg: Аб'ява паспяхова выдалена! + destroyed_msg: Аб’ява выдалена! edit: title: Рэдагаваць абвестку empty: Не знайдзена ніводнай абвесткі @@ -325,26 +327,26 @@ be: scheduled_msg: Аб'ява запланавана да публікацыі! title: Аб'явы unpublish: Зняць з публікацыі - unpublished_msg: Аб'ява паспяхова схована! - updated_msg: Аб'ява паспяхова адноўлена! + unpublished_msg: Аб’ява схавана! + updated_msg: Аб’ява абноўлена! critical_update_pending: Чакаецца абнаўленне custom_emojis: assign_category: Прызначыць катэгорыю by_domain: Дамен - copied_msg: Лакальная копія эмодзі паспяхова створана + copied_msg: Створана лакальная копія эмодзі copy: Скапіраваць copy_failed_msg: Немагчыма зрабіць лакальную копію гэтага эмодзі create_new_category: Стварыць новую катэгорыю created_msg: Эмодзі паспяхова створана! delete: Выдаліць - destroyed_msg: Смайлік паспяхова прыбраны! + destroyed_msg: Уласнае эмодзі знішчана! disable: Адключыць disabled: Адключана disabled_msg: Смайлік паспяхова адключаны emoji: Эмодзі enable: Уключыць enabled: Уключана - enabled_msg: Смайлік паспяхова ўключаны + enabled_msg: Эмодзі паспяхова ўключана image_hint: PNG або GIF да %{size} list: Паказваць listed: Паказваецца @@ -423,7 +425,7 @@ be: domain: Дамен edit: Рэдагаваць блакіраванне дамену existing_domain_block: Вы ўжо налажылі стражэйшыя абмежаванні на %{name}. - existing_domain_block_html: Вы ўжо налажылі стражэйшыя абмежаванні на %{name}, спачатку вам неабходна разблакаваць гэты акаўнт. + existing_domain_block_html: Вы ўжо наклалі больш строгія абмежаванні на %{name}, спачатку вам трэба разблакіраваць гэты ўліковы запіс. export: Экспарт import: Імпарт new: @@ -433,7 +435,7 @@ be: desc_html: "Абмежаванне зробіць допісы людзей з гэтага дамену нябачнымі для тых, хто на іх не падпісаны. Выключэнне выдаліць усё змесціва, медыя і даныя профіляў дамену з вашага серверу. «Нічога» проста адхіліць медыя файлы." noop: Пуста silence: Абмежаваць - suspend: Выключыць + suspend: Прыпыніць title: Новы блок дамену no_domain_block_selected: Блакіроўкі даменаў не былі змененыя, таму што ні адзін з іх не быў выбраны not_permitted: Вам забаронена выконваць гэта дзеянне @@ -478,7 +480,7 @@ be: no_file: Файл не выбраны export_domain_blocks: import: - description_html: Вы збіраецеся імпартаваць спіс даменных блокаў. Калі ласка, праглядзіце гэты спіс вельмі ўважліва, асабліва калі вы асабіста не з'яўляецеся аўтарам гэтага спісу. + description_html: Вы збіраецеся імпартаваць спіс даменных блокаў. Праглядзіце гэты спіс вельмі ўважліва, асабліва калі вы асабіста не з’яўляецеся аўтарам гэтага спіса. existing_relationships_warning: Наяўныя зносіны private_comment_description_html: 'Каб дапамагчы вам адсочваць, адкуль паходзяць імпартаваныя блокі, імпартаваныя блокі будуць створаны з наступным прыватным каментарыем: %{comment}' private_comment_template: Імпартавана з %{source} %{date} @@ -487,6 +489,26 @@ be: new: title: Імпарт блакіровак дамену no_file: Файл не выбраны + fasp: + debug: + callbacks: + delete: Выдаліць + ip: IP-адрас + providers: + active: Актыўны + base_url: Базавы URL-адрас + delete: Выдаліць + edit: Рэдагаваць пастаўшчыка + finish_registration: Завяршыць рэгістрацыю + name: Назва + providers: Пастаўшчыкі + public_key_fingerprint: Лічбавы адбітак публічнага ключа + registrations: + confirm: Пацвердзіць + reject: Адхіліць + save: Захаваць + sign_in: Увайсці + title: FASP follow_recommendations: description_html: "Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы." language: Для мовы @@ -527,7 +549,7 @@ be: reject_media: Адхіліць мультымедыя reject_reports: Адхіліць справаздачы silence: Ліміт - suspend: Выключыць + suspend: Прыпыніць policy: Палітыка reason: Публічная прычына title: Палітыкі кантэнту @@ -561,6 +583,11 @@ be: all: Усе limited: Абмежаваныя title: Мадэрацыя + moderation_notes: + create: Дадаць нататку мадэратара + created_msg: Нататка мадэратара для экзэмпляра сервера створана! + destroyed_msg: Нататка мадэратара экзэмпляра сервера выдалена! + title: Нататкі мадэратараў private_comment: Прыватны каментарый public_comment: Публічны каментарый purge: Ачысціць @@ -601,7 +628,7 @@ be: relays: add_new: Дадаць новы рэтранслятар delete: Выдаліць - description_html: "Федэрацыйны рэтранслятар - гэта прамежкавы сервер, які абменьваецца вялікімі аб'ёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і публікуюць на ім. Гэта можа дапамагчы малым і сярэднім серверам выяўляць кантэнт з fediverse, для чаго лакальным карыстальнікам у адваротным выпадку трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах." + description_html: "Федэрацыйны рэтранслятар - гэта прамежкавы сервер, які абменьваецца вялікімі аб’ёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і робяць публікацыі на ім. Гэта можа дапамагчы малым і сярэднім серверам выяўляць змесціва з fediverse, бо ў іншым выпадку лакальным карыстальнікам трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах." disable: Адключыць disabled: Адключана enable: Уключыць @@ -635,11 +662,11 @@ be: suspend_description_html: Уліковы запіс і ўсё яго змесціва будзе недаступна і ў далейшым выдалены, узаемадзеянне з ім будзе немагчыма. Магчыма адмяніць на працягу 30 дзён. Закрывае ўсе скаргі на гэты ўліковы запіс. actions_description_html: Вырашыце, якія дзеянні распачаць, каб вырашыць гэтую скаргу. Калі вы прымеце меры пакарання ў дачыненні да ўліковага запісу, пра які паведамляецца, ім будзе адпраўлена апавяшчэнне па электроннай пошце, за выключэннем выпадкаў, калі выбрана катэгорыя Спам. actions_description_remote_html: Вырашыце як паступіць з гэтай скаргай. Гэта паўплывае толькі на тое як ваш сервер звязваецца з аддалёным уліковым запісам і апрацоўвае яго кантэнт. - actions_no_posts: У гэтай скаргі няма зьвязаных паведамленьняў для выдаленьня + actions_no_posts: Да гэтай скаргі не прывязаны допісы да выдалення add_to_report: Дадаць яшчэ дэталяў да скаргі already_suspended_badges: - local: Ужо прыпынена на гэтым сэрвэры - remote: Ужо прыпынена на іх сэрвэры + local: Ужо прыпынена на гэтым серверы + remote: Ужо прыпынена на іх серверы are_you_sure: Вы ўпэўнены? assign_to_self: Прызначыць мне assigned: Прызначаны мадэратар @@ -668,7 +695,7 @@ be: delete: Выдаліць placeholder: Апішыце, якія дзеянні былі зроблены, або любыя іншыя звязаныя абнаўленні... title: Нататкі - notes_description_html: Праглядвайце і пакідайце нататкі іншым мадэратарам і сабе ў будучыні + notes_description_html: Праглядайце і пакідайце нататкі для іншых мадэратараў і сябе ў будучыні processed_msg: 'Скарга #%{id} паспяхова апрацавана' quick_actions_description_html: 'Выканайце хуткае дзеянне або пракруціце ўніз, каб убачыць змесціва, на якое пададзена скарга:' remote_user_placeholder: аддалены карыстальнік з %{instance} @@ -738,12 +765,12 @@ be: delete_user_data_description: Дазваляе карыстальнікам без затрымкі выдаляць даныя іншых карыстальнікаў invite_users: Запрашэнне карыстальнікаў invite_users_description: Дазваляе запрашаць новых людзей на сервер - manage_announcements: Кіраванне аб'явамі - manage_announcements_description: Дазваляе кіраваць аб'явамі на серверы + manage_announcements: Кіраванне аб’явамі + manage_announcements_description: Дазваляе кіраваць аб’явамі на серверы manage_appeals: Кіраванне апеляцыямі manage_appeals_description: Дазваляе карыстальнікам разглядаць апеляцыі на дзеянні мадэратара manage_blocks: Кіраванне блакіроўкамі - manage_blocks_description: Дазваляе блакаваць пэўныя паштовыя правайдэры і IP адрасы + manage_blocks_description: Дазваляе блакіраваць пэўных пастаўшчыкоў паслуг электроннай пошты і IP адрасы manage_custom_emojis: Кіраванне адвольнымі эмодзі manage_custom_emojis_description: Дазваляе кіраваць адвольнымі эмодзі на серверы manage_federation: Кіраваць федэрацыяй @@ -775,11 +802,14 @@ be: title: Ролі rules: add_new: Дадаць правіла + add_translation: Дадаць пераклад delete: Выдаліць description_html: Большасць сцвярджаюць, што прачыталі ўмовы абслугоўвання і згаджаюцца з імі, але звычайна людзі не чытаюць іх да канца, пакуль не ўзнікне праблема. Таму зрабіце правілы вашага сервера простымі з першага погляду, прадставіўшы іх у выглядзе маркіраванага спісу. Старайцеся рабіць правілы кароткімі і простымі, але не разбіваць іх на шмат асобных пунктаў. edit: Рэдагаваць правіла empty: Правілы сервера яшчэ не вызначаны. title: Правілы сервера + translation: Пераклад + translations: Пераклады settings: about: manage_rules: Кіраваць правіламі сервера @@ -793,7 +823,7 @@ be: preamble: Брэндынг вашага сервера адрознівае яго ад іншых сервераў у сетцы. Гэтая інфармацыя можа адлюстроўвацца ў розных асяроддзях, напрыклад, у вэб-інтэрфейсе Mastodon, уласных праграмах, у папярэднім праглядзе спасылак на іншых вэб-сайтах і ў праграмах абмену паведамленнямі і гэтак далей. Па гэтай прычыне лепш трымаць гэтую інфармацыю яснай, кароткай і сціслай. title: Брэндынг captcha_enabled: - desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, гэта можа зрабіць працэс рэгістрацыі значна менш даступным для некаторых людзей, асабліва інвалідаў. Па гэтых прычынах, калі ласка, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння. + desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, гэта можыць зменшыць даступнасць працэсу рэгістрацыі для некаторых людзей (асабліва інвалідаў). Па гэтых прычынах, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння. title: Патрабаваць ад новых карыстальнікаў рашэння CAPTCHA для пацверджання іх уліковага запісу content_retention: danger_zone: Небяспечная зона @@ -805,11 +835,12 @@ be: discovery: follow_recommendations: Выконвайце рэкамендацыі preamble: Прадстаўленне цікавага кантэнту дапамагае прыцягнуць новых карыстальнікаў, якія могуць не ведаць нікога на Mastodon. Кантралюйце працу розных функцый выяўлення на вашым серверы. + privacy: Прыватнасць profile_directory: Дырэкторыя профіляў public_timelines: Публічная паслядоўнасць публікацый publish_statistics: Апублікаваць статыстыку title: Выяўленне - trends: Актуальныя + trends: Трэнды domain_blocks: all: Для ўсіх disabled: Нікому @@ -854,7 +885,7 @@ be: add_to_report: 'Дадаць да скаргі #%{id}' remove_from_report: Выдаліць са справаздачы report: Справаздача - contents: Зьмест + contents: Змест deleted: Выдалены favourites: Упадабаныя history: Гісторыя версій @@ -863,7 +894,7 @@ be: media: title: Медыя metadata: Метаданыя - no_history: Гэты пост не рэдагаваўся + no_history: Гэты допіс не рэдагаваўся no_status_selected: Ніводная публікацыя не была зменена, бо ніводная не была выбрана open: Адкрыць допіс original_status: Зыходны допіс @@ -872,7 +903,7 @@ be: status_changed: Допіс зменены status_title: Допіс карыстальніка @%{name} title: Допісы карыстальніка - @%{name} - trending: Папулярныя + trending: Трэндавае view_publicly: Глядзець публічна visibility: Бачнасць with_media: З медыя @@ -890,13 +921,13 @@ be: appeal_rejected: Абскарджанне адхілена system_checks: database_schema_check: - message_html: Ёсць незавершаныя міграцыі базы дадзеных. Калі ласка, запусціце іх, каб пераканацца, што дадатак паводзіць сябе належным чынам + message_html: Ёсць незавершаныя міграцыі базы даных. Запусціце іх, каб пераканацца, што праграма паводзіць сябе належным чынам elasticsearch_health_red: message_html: Кластар Elasticsearch нездаровы (чырвоны статус), функцыі пошуку недаступныя elasticsearch_health_yellow: message_html: Кластар Elasticsearch нездаровы (жоўты статус), магчыма, неабходна высветліць прычыну elasticsearch_index_mismatch: - message_html: Супастаўленне індэксаў Elasticsearch састарэла. Калі ласка, выканайце каманду tootctl search deploy --only=%{value} + message_html: Супастаўленне індэксаў Elasticsearch састарэла. Выканайце каманду tootctl search deploy --only=%{value} elasticsearch_preset: action: Падрабязней у дакументацыі message_html: Ваш кластар Elasticsearch мае больш за адзін вузел, але Mastodon не наладжаны на іх выкарыстанне. @@ -906,7 +937,7 @@ be: elasticsearch_reset_chewy: message_html: Ваш сістэмны індэкс Elasticsearch састарэлы з-за змены налад. Для яго абнаўлення выканайце каманду tootctl search deploy --reset-chewy. elasticsearch_running_check: - message_html: Немагчыма падключыцца да Elasticsearch. Калі ласка, праверце, што ён запушчаны, або адключыце паўнатэкставы пошук + message_html: Немагчыма падключыцца да Elasticsearch. Праверце, што ён запушчаны, або адключыце пошук па ўсім тэксце elasticsearch_version_check: message_html: 'Несумяшчальная версія Elasticsearch: %{value}' version_comparison: Elasticsearch %{running_version} выяўлены, але патрабуецца %{required_version} @@ -916,11 +947,11 @@ be: sidekiq_process_check: message_html: Не працуе працэс Sidekiq для %{value} чаргі. Калі ласка праверце вашу канфігурацыю Sidekiq software_version_check: - action: Праверыць наяўнасьць абнаўленьняў - message_html: Даступна абнаўленьне Mastodon. + action: Праверыць наяўнасць абнаўленняў + message_html: Даступна абнаўленне Mastodon. software_version_critical_check: action: Прагледзець даступныя абнаўленні - message_html: Даступна крытычнае абнаўленне Mastodon, калі ласка, зрабіце абнаўленне як мага хутчэй. + message_html: Даступна крытычнае абнаўленне Mastodon, абнавіцеся як мага хутчэй. software_version_patch_check: action: Прагледзець даступныя абнаўленні message_html: Даступна абнаўленне Mastodon з выпраўленнем памылак. @@ -932,13 +963,13 @@ be: message_html: "Ваша сховішча не наладжана. Прыватнасць карыстальнікаў пад пагрозай." tags: moderation: - not_trendable: Не ў трэндзе + not_trendable: Не можа трапіць у трэнды not_usable: Непрыгодныя pending_review: Чакае праверкі review_requested: Патрабуюць прагледжвання reviewed: Прагледжаныя title: Стан - trendable: У трэндзе + trendable: Можа трапіць у трэнды unreviewed: Непрагледжаныя usable: Прыгодныя name: Назва @@ -951,18 +982,26 @@ be: title: Хэштэгі updated_msg: Налады хэштэгаў паспяхова змененыя terms_of_service: - back: Вернуцца да ўмоваў абслугоўваньня - changelog: Што зьмянілася - create: Выкарыстоўвайце свой уласны - current: Цякучы + back: Вернуцца да ўмоў выкарыстання + changelog: Што змянілася + create: Выкарыстоўвайце свой тэкст + current: Бягучыя draft: Чарнавік generate: Выкарыстаць шаблон generates: - action: Зґенераваць + action: Згенерыраваць + history: Гісторыя + live: Дзейнічае + notify_users: Апавясціць карыстальнікаў + publish: Апублікаваць + published_on_html: Апублікавана %{date} + save_draft: Захаваць чарнавік + title: Умовы выкарыстання title: Адміністрацыя trends: allow: Дазволіць approved: Пацверджаны + confirm_allow: Вы ўпэўнены, што хочаце дазволіць выбраныя тэгі? disallow: Забараніць links: allow: Дазволіць спасылка @@ -978,15 +1017,15 @@ be: many: Абагулілі %{count} чалавек за апошні тыдзень one: Абагуліў адзін чалавек за апошні тыдзень other: Абагулілі %{count} чалавек за апошні тыдзень - title: Актуальныя спасылкі + title: Трэндавыя спасылкі usage_comparison: Выкарыстоўвалася %{today} разоў сёння, у параўнанні з %{yesterday} учора - not_allowed_to_trend: Забаронена выходзіць у актуальныя + not_allowed_to_trend: Забаронена выходзіць у трэнды only_allowed: Толькі дазволенае pending_review: Чакае праверкі preview_card_providers: allowed: Спасылкі ад гэтага выдаўца не будуць у трэндзе description_html: Спасылкі з гэтых даменаў часта абагульняюцца на вашым серверы. Спасылкі не трапяць у публічныя трэнды, калі дамен спасылкі не ўхвалены. Вашае ўхваленне (ці адхіленне) распаўсюдзіцца на субдамены. - rejected: Спасылкі ад гэтага выдаўца не будуць у трэнде + rejected: Спасылкі ад гэтага выдаўца не будуць у трэндзе title: Выдаўцы rejected: Адхілена statuses: @@ -1002,7 +1041,7 @@ be: many: Пашыраны або ўпадабаны %{friendly_count} разоў one: Пашыраны або ўпадабаны %{friendly_count} раз other: Пашыраны або ўпадабаны %{friendly_count} разоў - title: Актуальныя допісы + title: Трэндавыя допісы tags: current_score: Бягучы рэзультат %{score} dashboard: @@ -1015,11 +1054,11 @@ be: listable: Можа быць прапанавана no_tag_selected: Ніводны тэг не быў зменены, бо ніводны не быў выбраны not_listable: Не будзе прапанавана - not_trendable: Не з'явіцца ў трэндах + not_trendable: Не з’явіцца сярод трэндаў not_usable: Немагчыма выкарыстаць peaked_on_and_decaying: На піку %{date}, зараз спадае title: Актуальныя хэштэгі - trendable: Можа з'явіцца сярод трэндаў + trendable: Можа з’явіцца сярод трэндаў trending_rank: 'Папулярнае #%{rank}' usable: Магчыма выкарыстаць usage_comparison: Выкарыстоўвалася %{today} разоў сёння, у параўнанні з %{yesterday} учора @@ -1029,7 +1068,7 @@ be: one: Выкарыстаў адзін чалавек за апошні тыдзень other: Выкарысталі %{count} чалавек за апошні тыдзень title: Рэкамендацыі і трэнды - trending: Папулярныя + trending: Трэндавае warning_presets: add_new: Дадаць новы delete: Выдаліць @@ -1070,7 +1109,7 @@ be: none: папярэджанне sensitive: пазначыць уліковы запіс як далікатны silence: абмежаваць уліковы запіс - suspend: выключыць уліковы запіс + suspend: каб прыпыніць гэты ўліковы запіс body: "%{target} абскарджвае рашэнне мадэратара %{action_taken_by} ад %{date}, якая была %{type}. Яны напісалі:" next_steps: Вы можаце ўхваліць апеляцыю каб адмяніць рашэнне мадэратараў ці ігнараваць яе. subject: "%{username} абскарджвае рашэнне мадэратараў на %{instance}" @@ -1090,12 +1129,12 @@ be: new_trends: body: 'Гэтыя элементы трэба праверыць, перш чым публікаваць:' new_trending_links: - title: Папулярныя спасылкі + title: Трэндавыя спасылкі new_trending_statuses: - title: Папулярныя допісы + title: Трэндавыя допісы new_trending_tags: - title: Папулярныя хэштэгі - subject: Новае ў папулярным для разгляду %{instance} + title: Трэндавыя хэштэгі + subject: Новыя трэнды да прагляду %{instance} aliases: add_new: Стварыць псеўданім created_msg: Створаны новы псеўданім. Цяпер вы можаце пачаць пераход са старога ўліковага запісу. @@ -1147,7 +1186,7 @@ be: welcome_title: Вітаем, %{name}! wrong_email_hint: Калі гэты адрас электроннай пошты памылковы, вы можаце змяніць яго ў наладах уліковага запісу. delete_account: Выдаліць уліковы запіс - delete_account_html: Калі вы жадаеце выдаліць ваш уліковы запіс, можаце працягнуць тут. Ад вас будзе запатрабавана пацвярджэнне. + delete_account_html: Калі вы жадаеце выдаліць свой уліковы запіс, вы можаце працягнуць тут. Ад вас спатрэбіцца пацвярджэнне. description: prefix_invited_by_user: "@%{name} запрашае вас далучыцца да гэтага сервера ў Mastodon!" prefix_sign_up: Зарэгістравацца ў Mastodon сёння! @@ -1166,7 +1205,7 @@ be: or_log_in_with: Або ўвайсці з дапамогай progress: confirm: Пацвердзіць email - details: Вашы дадзеныя + details: Вашы даныя review: Наш водгук rules: Прыняць правілы providers: @@ -1207,6 +1246,10 @@ be: view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз. use_security_key: Выкарыстаеце ключ бяспекі + author_attribution: + example_title: Прыклад тэксту + s_blog: Блог %{name} + title: Пазначэнне аўтарства challenge: confirm: Працягнуць hint_html: "Парада: Мы не будзем запытваць ваш пароль зноўку на працягу наступнай гадзіны." @@ -1276,7 +1319,7 @@ be: none: Папярэджанне sensitive: Пазначэнне ўліковага запісу як далікатнага silence: Абмежаванне ўліковага запісу - suspend: Выключэнне ўліковага запісу + suspend: Прыпыненне ўліковага запісу your_appeal_approved: Ваша абскарджанне было ўхвалена your_appeal_pending: Вы адправілі апеляцыю your_appeal_rejected: Ваша абскарджанне было адхілена @@ -1284,6 +1327,10 @@ be: basic_information: Асноўная інфармацыя hint_html: "Наладзьце тое, што людзі будуць бачыць у вашым профілі і побач з вашымі паведамленнямі. Іншыя людзі з большай верагоднасцю будуць сачыць і ўзаемадзейнічаць з вамі, калі ў вас ёсць запоўнены профіль і фота профілю." other: Іншае + emoji_styles: + auto: Аўтаматычны + native: Мясцовы + twemoji: Twemoji errors: '400': Запыт, які вы адправілі, памылковы або няправільны. '403': У вас няма дазволу на прагляд гэтай старонкі. @@ -1398,10 +1445,10 @@ be: other: Выберыце %{count} элементаў, якія адпавядаюць вашаму пошуку. today: сёння validation_errors: - few: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылкі ніжэй - many: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылак ніжэй - one: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце памылку ніжэй - other: Штосьці пакуль не зусім правільна! Калі ласка, праглядзіце %{count} памылак ніжэй + few: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылкі ніжэй + many: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылак ніжэй + one: Штосьці тут пакуль не сыходзіцца! Праглядзіце памылку ніжэй + other: Штосьці тут пакуль не сыходзіцца! Праглядзіце %{count} памылак ніжэй imports: errors: empty: Пусты CSV файл @@ -1413,7 +1460,7 @@ be: imported: Імпартавана mismatched_types_warning: Здаецца, вы выбралі няправільны тып для гэтага імпарту, праверце яшчэ раз. modes: - merge: Аб'яднаць + merge: Аб’яднаць merge_long: Захаваць існуючыя запісы і дадаць новыя overwrite: Перазапісаць overwrite_long: Замяніць бягучыя запісы на новыя @@ -1489,7 +1536,7 @@ be: unsubscribe: action: Так, адпісацца complete: Адпісаны - confirmation_html: Вы ўпэўнены, што жадаеце адмовіцца ад %{type} з Mastodon на дамене %{domain} на вашу электронную пошту %{email}? Вы заўсёды можаце паўторна падпісацца у наладах апавяшчэнняў па электроннай пошце. + confirmation_html: Вы ўпэўнены, што жадаеце адмовіцца ад атрымання %{type} з Mastodon на дамене %{domain} на сваю электронную пошту %{email}? Вы заўсёды можаце паўторна падпісацца ў наладах апавяшчэнняў па электроннай пошце. emails: notification_emails: favourite: апавяшчэнні на пошту пра упадабанае @@ -1529,7 +1576,7 @@ be: set_redirect: Задаць перанакіраванне warning: backreference_required: Спачатку трэба наладзіць зваротнае спасыланне новага ўліковага запісу на бягучы - before: 'Перш чым працягнуць, калі ласка, уважліва прачытайце гэтыя заўвагі:' + before: 'Перш чым працягнуць, уважліва прачытайце гэтыя заўвагі:' cooldown: Пасля «пераезду» будзе перыяд чакання, на працягу якога вы не зможаце зноў «пераехаць» disabled_account: Пасля гэтага ваш бягучы ўліковы запіс не будзе цалкам даступны. Аднак у вас будзе доступ да экспарту даных, а таксама да паўторнай актывацыі. followers: Гэтае дзеянне будзе «пераносіць» усіх падпісчыкаў з бягучага ўліковага запісу на новы @@ -1642,9 +1689,9 @@ be: title: Вы пакідаеце %{instance}. relationships: activity: Актыўнасць ул. запісу - confirm_follow_selected_followers: Вы ўпэўнены, што жадаеце падпісацца на выбраных падпісчыкаў? - confirm_remove_selected_followers: Вы ўпэўнены, што жадаеце выдаліць выбраных падпісчыкаў? - confirm_remove_selected_follows: Вы ўпэўнены, што жадаеце выдаліць выбраныя падпіскі? + confirm_follow_selected_followers: Вы ўпэўнены, што хочаце падпісацца на выбраных падпісчыкаў? + confirm_remove_selected_followers: Вы ўпэўнены, што хочаце выдаліць выбраных падпісчыкаў? + confirm_remove_selected_follows: Вы ўпэўнены, што хочаце выдаліць выбраныя падпіскі? dormant: Занядбаны follow_failure: Вы не можаце падпісацца на некаторыя акаўнты. follow_selected_followers: Падпісацца на выбраных падпісчыкаў @@ -1674,6 +1721,7 @@ be: scheduled_statuses: over_daily_limit: Вы перавысілі ліміт ў %{limit} запланаваных на сёння допісаў over_total_limit: Вы перавысілі ліміт ў %{limit} запланаваных допісаў + too_soon: дата публікацыі мусіць быць у будучыні self_destruct: lead_html: На жаль, дамен %{domain} зачыняецца назаўсёды. Калі ў вас быў уліковы запіс, вы не зможаце працягваць выкарыстоўваць яго, але вы ўсё яшчэ можаце запытаць рэзервовае капіраванне вашых даных. title: Гэты сервер зачыняецца @@ -1789,10 +1837,14 @@ be: in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе. over_character_limit: перавышаная колькасць сімвалаў у %{max} pin_errors: - direct: Допісы, бачныя толькі згаданым карыстальнікам, не могуць быць замацаваныя + direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць limit: Вы ўжо замацавалі максімальную колькасць допісаў ownership: Немагчыма замацаваць чужы допіс reblog: Немагчыма замацаваць пашырэнне + quote_policies: + followers: Падпісчыкі і згаданыя карыстальнікі + nobody: Толькі згаданыя карыстальнікі + public: Усе title: '%{name}: "%{quote}"' visibilities: direct: Асабіста @@ -1845,7 +1897,7 @@ be: tags: does_not_match_previous_name: не супадае з папярэднім імям terms_of_service: - title: Умовы абслугоўваньня + title: Умовы выкарыстання themes: contrast: Mastodon (высокі кантраст) default: Mastodon (цёмная) @@ -1874,7 +1926,7 @@ be: otp: Праграма аўтэнтыфікацыі recovery_codes: Абнавіць коды аднаўлення recovery_codes_regenerated: Новыя коды аднаўлення паспяхова створаныя - recovery_instructions_html: Калі раптам вы страціце доступ да вашага тэлефона, вы можаце скарыстацца адным з кодаў аднаўлення ніжэй каб аднавіць доступ да вашага ўліковага запісу. Захоўвайце іх у бяспечным месцы. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі. + recovery_instructions_html: Калі раптам вы страціце доступ да свайго тэлефона, вы можаце скарыстаць адзін з кодаў аднаўлення ніжэй каб аднавіць доступ да свайго ўліковага запісу. Захоўвайце іх у бяспечным месцы. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі. webauthn: Ключы бяспекі user_mailer: appeal_approved: @@ -1895,10 +1947,10 @@ be: title: Ваш архіў можна спампаваць failed_2fa: details: 'Вось падрабязнасці ўваходу:' - explanation: Хтосьці спрабаваў увайсці ў ваш уліковы запіс, але ўвёў няправільны другі фактар аўтэнтыфікацыі. + explanation: Хтосьці спрабаваў увайсці ў ваш уліковы запіс, але ўвёў няправільны часовы пароль. further_actions_html: Калі гэта не вы, мы рэкамендуем неадкладна %{action}, бо ён можа быць скампраметаваны. - subject: Збой аўтэнтыфікацыі па другім фактары - title: Няўдалая аўтэнтыфікацыя па другім фактары + subject: Збой двухфактарнай аўтэнтыфікацыі + title: Двухфактарная аўтэнтыфікацыя не ўдалася suspicious_sign_in: change_password: змяніць свой пароль details: 'Вось падрабязнасці ўваходу:' @@ -1906,6 +1958,9 @@ be: further_actions_html: Калі гэта былі не вы, раім вам неадкладна %{action}, а таксама ўключыць двухфактарную аўтэнтыфікацыю, каб захаваць бяспеку вашага ўліковага запісу. subject: У вас уліковы запіс зайшлі з новага IP-адрасу title: Новы ўваход + terms_of_service_changed: + sign_off: Каманда %{domain} + title: Важнае абнаўленне warning: appeal: Падаць апеляцыю appeal_description: "Калі вы лічыце гэта памылкай, вы можаце падаць апеляцыю \nсупрацоўнікам %{instance}." @@ -1936,7 +1991,7 @@ be: none: Папярэджанне sensitive: Уліковыя запісы, пазначаныя як далікатныя silence: Уліковы запіс абмежаваны - suspend: Уліковы запіс выключаны + suspend: Уліковы запіс прыпынены welcome: apps_android_action: Спампаваць з Google Play apps_ios_action: Спампваваць з App Store @@ -1968,9 +2023,9 @@ be: many: "%{people} чалавек за апошнія 2 дні" one: "%{people} чалавек за апошнія 2 дні" other: "%{people} чалавека за апошнія 2 дні" - hashtags_subtitle: Даведайцеся што было папулярна ў апошнія 2 дні + hashtags_subtitle: Даведайцеся што было ў трэндзе за апошнія 2 дні hashtags_title: Папулярныя хэштэгі - hashtags_view_more: Прагледзець іншыя папулярныя хэштэгі + hashtags_view_more: Прагледзець іншыя трэндавыя хэштэгі post_action: Стварыць post_step: Скажыце ўсім прывітанне з дапамогай тэксту, фатаграфій, відэа і апытанняў. post_title: Стварыце свой першы допіс @@ -1983,7 +2038,7 @@ be: follow_limit_reached: Вы не можаце падпісацца на большую колькасць людзей чым %{limit} go_to_sso_account_settings: Перайдзіце ў налады ідэнтыфікацыі вашага ўліковага запісу invalid_otp_token: Няправільны код двухфактарнай аўтэнтыфікацыі - otp_lost_help_html: Калі вы страцілі доступ да абодвух, вы можаце скарыстацца %{email} + otp_lost_help_html: Калі вы страцілі доступ да абодвух, вы можаце звязацца з намі праз %{email} rate_limited: Занадта шмат спробаў аўтэнтыфікацыі, паспрабуйце пазней. seamless_external_login: Вы ўвайшлі праз знешні сэрвіс, таму налады пароля і эл. пошты недаступныя. signed_in_as: 'Увайшлі як:' diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 1f64cb82fe2..1c858fe4cde 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -581,6 +581,7 @@ ca: moderation_notes: create: Afegeix una nota de moderació created_msg: S'ha creat la nota de moderació d'instància. + description_html: Mireu i deixeu notes per als altres moderadors i per a un mateix destroyed_msg: S'ha esborrat la nota de moderació d'instància. title: Notes de moderació private_comment: Comentari privat @@ -808,6 +809,8 @@ ca: preamble: Proporciona informació detallada sobre com funciona, com es modera i com es financia el servidor. rules_hint: Hi ha un àrea dedicada a les normes a les que s'espera que els teus usuaris s'hi adhereixin. title: Quant a + allow_referrer_origin: + title: Permeteu que llocs externs vegin el vostre servidor de Mastodon com a font de trànsit appearance: preamble: Personalitza l'interfície web de Mastodon. title: Aparença @@ -1483,6 +1486,28 @@ ca: lists_html: one: Esteu a punt de reemplaçar les vostres llistes amb contactes de %{filename}. S'afegirà %{count} compte a les noves llistes. other: Esteu a punt de reemplaçar les vostres llistes amb contactes de %{filename}. S'afegiran fins a %{count} comptes a les noves llistes. + muting_html: + one: Esteu a punt de reemplaçar la vostra llista de comptes silenciats amb fins a %{count} compte de %{filename}. + other: Esteu a punt de reemplaçar la vostra llista de comptes silenciats amb fins a %{count} comptes de %{filename}. + preambles: + blocking_html: + one: Esteu a punt de blocar fins a %{count} compte de %{filename}. + other: Esteu a punt de blocar fins a %{count} comptes de %{filename}. + bookmarks_html: + one: Esteu a punt d'afegir fins a %{count} publicació de %{filename} als vostres marcadors. + other: Esteu a punt d'afegir fins a %{count} publicacions de %{filename} als vostres marcadors. + domain_blocking_html: + one: Esteu a punt de blocar fins a %{count} domini de %{filename}. + other: Esteu a punt de blocar fins a %{count} dominis de %{filename}. + following_html: + one: Esteu a punt de seguir fins a %{count} compte de %{filename}. + other: Esteu a punt de seguir fins a %{count} comptes de %{filename}. + lists_html: + one: Esteu a punt d'afegir %{count} compte de %{filename} a les vostres llistes. Es crearan noves llistes si no n'hi ha cap on afegir-lo. + other: Esteu a punt d'afegir %{count} comptes de %{filename} a les vostres llistes. Es crearan noves llistes si no n'hi ha cap on afegir-los. + muting_html: + one: Esteu a punt de silenciar fins a %{count} compte de %{filename}. + other: Esteu a punt de silenciar fins a %{count} comptes de %{filename}. preface: Pots importar algunes les dades que has exportat des d'un altre servidor, com ara el llistat de les persones que estàs seguint o bloquejant. recent_imports: Importacions recents states: diff --git a/config/locales/devise.be.yml b/config/locales/devise.be.yml index 5f03bf06b88..b4498f7ee85 100644 --- a/config/locales/devise.be.yml +++ b/config/locales/devise.be.yml @@ -4,7 +4,7 @@ be: confirmations: confirmed: Адрас вашай электроннай пошты паспяхова пацверджаны. send_instructions: Цягам некалькіх хвілін вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашую электронную пошту. Калі ласка, зазірніце ў папку са спамам, калі не знойдзеце ліст. - send_paranoid_instructions: Калі адрас вашай электроннай пошты існуе ў нашай базе дадзеных, цягам некалькіх хвілін вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашую электронную пошту. Калі ласка, зазірніце ў папку са спамам, калі не знойдзеце ліст. + send_paranoid_instructions: Калі адрас вашай электроннай пошты існуе ў нашай базе даных, на працягу некалькіх хвілін вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашу электронную пошту. Калі вы не знойдзеце ліст, праверце папку са спамам. failure: already_authenticated: Вы ўжо ўвайшлі. inactive: Ваш уліковы запіс яшчэ не актываваны. @@ -15,25 +15,25 @@ be: omniauth_user_creation_failure: Памылка пры стварэнні ўліковага запісу для гэтай асобы. pending: Ваш уліковы запіс яшчэ разглядаецца. timeout: Ваш сеанс скончыўся. Каб працягнуць, увайдзіце яшчэ раз. - unauthenticated: Вам патрэбна зайсьці альбо зарэгістравацца, каб працягнуць + unauthenticated: Каб працягнуць, вам трэба ўвайсці або зарэгістравацца. unconfirmed: Вы павінны пацвердзіць свой адрас электроннай пошты, перш чым працягнуць mailer: confirmation_instructions: action: Пацвердзіць адрас электроннай пошты action_with_app: Пацвердзіць і вярнуцца да %{app} explanation: Вы стварылі ўліковы запіс на %{host} з гэтым адрасам электроннай пошты. Вам спатрэбіцца ўсяго адзін клік, каб пацвердзіць яго. Калі гэта былі не вы, то проста праігнаруйце гэты ліст. - explanation_when_pending: Вы падалі заяўку на запрашэнне на %{host} з гэтым адрасам электроннай пошты. Як толькі вы пацвердзіце свой адрас электроннай пошты, мы разгледзім вашу заяўку. Вы можаце ўвайсці, каб змяніць свае дадзеныя або выдаліць свой уліковы запіс, але вы не можаце атрымаць доступ да большасці функцый, пакуль ваш уліковы запіс не будзе зацверджаны. Калі ваша заяўка будзе адхілена, вашы даныя будуць выдалены, таму ад вас не спатрэбіцца ніякіх дадатковых дзеянняў. Калі гэта былі не вы, ігнаруйце гэты ліст + explanation_when_pending: Вы падалі заяўку на запрашэнне ў %{host} з гэтага адрасу электроннай пошты. Як толькі вы пацвердзіце свой адрас электроннай пошты, мы разгледзім вашу заяўку. Вы можаце ўвайсці, каб змяніць свае даныя або выдаліць свой уліковы запіс, але вы не атрымаеце доступ да большасці функцый, пакуль ваш уліковы запіс не будзе зацверджаны. Калі ваша заяўка будзе адхілена, вашы даныя будуць выдалены, таму ад вас не спатрэбіцца ніякіх дадатковых дзеянняў. Калі гэта былі не вы, ігнаруйце гэты ліст. extra_html: Таксама азнаёмцеся з правіламі сервера і нашымі ўмовамі абслугоўвання. subject: 'Mastodon: Інструкцыі па пацвярджэнні для %{instance}' title: Праверце адрас электроннай пошты email_changed: explanation: Адрас электроннай пошты для вашага ўліковага запісу будзе зменены на - extra: Калі вы не змянялі сваю электронную пошту, хутчэй за ўсё, нехта атрымаў доступ да вашага ўліковага запісу. Калі ласка, неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакаваны з вашага ўліковага запісу + extra: Калі вы не змянялі сваю электронную пошту, хутчэй за ўсё, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакіраваны са свайго ўліковага запісу. subject: 'Mastodon: адрас электроннай пошты зменены' title: Новы адрас электроннай пошты password_change: explanation: Пароль для вашага ўліковага запісу быў зменены - extra: Калі вы не змянялі свой пароль, верагодна, нехта атрымаў доступ да вашага ўліковага запісу. Калі ласка, неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакаваны з вашага ўліковага запісу + extra: Калі вы не змянялі свой пароль, імаверна, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакіраваны са свайго ўліковага запісу. subject: 'Mastodon: пароль зменены' title: Пароль зменены reconfirmation_instructions: @@ -88,13 +88,13 @@ be: success: Паспяховая аўтэнтыфікацыя з %{kind} уліковага запісу. passwords: no_token: Вы не можаце атрымаць доступ да гэтай старонкі не з ліста аднаўлення пароля. Калі вы ўсе ж такі перайшлі па спасылцы ў лісце аднаўлення пароля, упэўніцеся, што яна поўная. - send_instructions: Калі ваш электроны адрас існуе ў нашай базе дадзеных, вы атрымаеце спасылку для аднаўлення пароля на свой электроны адрас праз пару хвілін. Калі ласка, праверце вашу тэчку са спамам, калі вы не атрымалі такі ліст. - send_paranoid_instructions: Калі ваш электроны адрас існуе ў нашай базе дадзеных, вы атрымаеце спасылку для аднаўлення пароля на свой электроны адрас праз пару хвілін. Калі ласка, праверце вашу тэчку са спамам, калі вы не атрымалі такі ліст. + send_instructions: Калі ваш электроны адрас існуе ў нашай базе даных, вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі вы не атрымалі гэты ліст, праверце папку са спамам. + send_paranoid_instructions: Калі ваш электроны адрас існуе ў нашай базе даных, вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі вы не атрымалі гэты ліст, праверце папку са спамам. updated: Ваш пароль быў паспяхова зменены. Вы ўвайшлі ў сістэму. updated_not_active: Ваш пароль быў паспяхова зменены. registrations: destroyed: Пакуль! Ваш уліковы запіс быў паспяхова выдалены. Мы спадзяваемся хутка ўбачыць вас зноў. - update_needs_confirmation: Вы паспяхова абнавілі свой уліковы запіс, аднак, нам неабходна пацвердзіць ваш новы адрас электроннай пошты. Калі ласка, праверце вашу пошту і перайдзіце па спасылцы для пацверджання вашага новага адраса электроннай пошты. Праверце тэчку са спамам, калі вы не атрымалі такі ліст. + update_needs_confirmation: Вы паспяхова абнавілі свой уліковы запіс, аднак, нам неабходна пацвердзіць ваш новы адрас электроннай пошты. Праверце вашу пошту і перайдзіце па спасылцы для пацвярджэння свайго новага адраса электроннай пошты. Калі вы не атрымалі гэты ліст, праверце папку са спамам. updated: Ваш уліковы запіс быў паспяхова абноўлены. sessions: already_signed_out: Выхад паспяховы. @@ -103,7 +103,7 @@ be: unlocks: send_instructions: Вы атрымаеце ліст з інструкцыямі па разблакаванні вашага ўліковага запісу цягам некалькіх хвілін. Праверце тэчку са спамам, калі вы не атрымалі такі ліст. send_paranoid_instructions: Калі ваш уліковы запіс існуе, вы атрымаеце ліст з інструкцыямі па яго разблакаванні цягам некалькіх хвілін. Праверце тэчку са спамам, калі вы не атрымалі такі ліст. - unlocked: Ваш уліковы запіс быў паспяхова разблакаваны. Калі ласка, увайдзіце, каб працягнуць + unlocked: Ваш уліковы запіс быў разблакіраваны. Увайдзіце, каб працягнуць. errors: messages: already_confirmed: ужо пацверджана, паспрабуйце ўвайсці diff --git a/config/locales/devise.el.yml b/config/locales/devise.el.yml index 3d3d4f87d94..106a48a49e6 100644 --- a/config/locales/devise.el.yml +++ b/config/locales/devise.el.yml @@ -29,7 +29,7 @@ el: email_changed: explanation: 'Αλλάζεις τη διεύθυνση email για τον λογαριασμό σου στην:' extra: Αν δεν άλλαξες εσύ το email σου, ίσως κάποιος να έχει αποκτήσει πρόσβαση στο λογαριασμό σου. Παρακαλούμε άλλαξε το συνθηματικό σου άμεσα ή επικοινώνησε με τον διαχειριστή του κόμβου σου αν έχεις κλειδωθεί απ' έξω. - subject: 'Mastodon: Αλλαγή διεύθυνσης email' + subject: 'Mastodon: Το email άλλαξε' title: Νέα διεύθυνση email password_change: explanation: Το συνθηματικό του λογαριασμού σου άλλαξε. diff --git a/config/locales/devise.vi.yml b/config/locales/devise.vi.yml index 5b4bbf38396..2b4dc44db98 100644 --- a/config/locales/devise.vi.yml +++ b/config/locales/devise.vi.yml @@ -74,18 +74,18 @@ vi: subject: 'Mastodon: Xóa khóa bảo mật' title: Một trong những khóa bảo mật của bạn vừa bị xóa webauthn_disabled: - explanation: Xác minh bằng khóa bảo mật đã bị vô hiệu hóa đối với tài khoản của bạn. + explanation: Xác thực bằng khóa bảo mật đã bị vô hiệu hóa đối với tài khoản của bạn. extra: Hiện tại, bạn chỉ có thể đăng nhập bằng cách sử dụng mã token được tạo bởi ứng dụng TOTP được ghép nối. subject: 'Mastodon: Vô hiệu hóa xác thực bằng khóa bảo mật' title: Đã vô hiệu hóa khóa bảo mật webauthn_enabled: explanation: Khóa bảo mật đã được bật cho tài khoản của bạn. extra: Hiện bạn đã có thể dùng khóa bảo mật để đăng nhập. - subject: 'Mastodon: Kích hoạt xác minh bằng khóa bảo mật' + subject: 'Mastodon: Kích hoạt xác thực bằng khóa bảo mật' title: Đã kích hoạt khóa bảo mật omniauth_callbacks: - failure: Không thể xác minh bạn từ %{kind} bởi vì "%{reason}". - success: Xác minh tài khoản %{kind} thành công. + failure: Không thể xác thực bạn từ %{kind} bởi vì "%{reason}". + success: Xác thực tài khoản %{kind} thành công. passwords: no_token: Bạn chỉ có thể truy cập trang này khi nhận được email khôi phục mật khẩu. Nếu vẫn không được, vui lòng chắc chắn rằng bạn đã dùng chính xác URL được cung cấp. send_instructions: Nếu địa chỉ email của bạn tồn tại trong cơ sở dữ liệu của chúng tôi, bạn sẽ nhận được liên kết khôi phục mật khẩu sau vài phút. Xin kiểm tra thư rác nếu như bạn không thấy email này. diff --git a/config/locales/doorkeeper.be.yml b/config/locales/doorkeeper.be.yml index defe0ddf0db..b01f476a203 100644 --- a/config/locales/doorkeeper.be.yml +++ b/config/locales/doorkeeper.be.yml @@ -89,7 +89,7 @@ be: missing_param: 'Адсутнічае абавязковы параметр: %{value}.' request_not_authorized: Запыт павінен быць аўтарызаваны. Абавязковы параметр для запыту аўтарызацыі адсутнічае або несапраўдны. unknown: У запыце адсутнічае абавязковы параметр, уключае значэнне параметра, якое не падтрымліваецца, альбо ён сфарміраваны іншым няправільным чынам. - invalid_resource_owner: Прадстаўленыя ўліковыя дадзеныя ўладальніка рэсурсу несапраўдныя або немагчыма знайсці ўладальніка рэсурсу + invalid_resource_owner: Пададзеныя ўліковыя даныя ўладальніка рэсурсу несапраўдныя або ўладальніка рэсурсу немагчыма знайсці invalid_scope: Запытаная вобласць абмежаванняў несапраўдная, невядомая альбо няправільная. invalid_token: expired: Тэрмін дзеяння токена доступу скончыўся @@ -148,7 +148,7 @@ be: application: title: Патрабуецца аўтарызацыя OAuth scopes: - admin:read: чытаць усе дадзеныя на серверы + admin:read: чытаць усе даныя на серверы admin:read:accounts: чытаць канфідэнцыйную інфармацыю ўсіх акаўнтаў admin:read:canonical_email_blocks: чытаць канфідэнцыйную інфармацыю ўсіх кананічных блокаў электроннай пошты admin:read:domain_allows: чытаць канфідэнцыйную інфармацыю ўсіх дазволеных даменаў diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml index 7b25004385c..0dd4fe4194e 100644 --- a/config/locales/doorkeeper.eu.yml +++ b/config/locales/doorkeeper.eu.yml @@ -83,6 +83,7 @@ eu: access_denied: Baliabidearen jabeak edo baimenaren zerbitzariak eskaera ukatu du. credential_flow_not_configured: Baliabidearen jabearen pasahitza kredentzialen fluxuak huts egin du Doorkeeper.configure.resource_owner_from_credentials konfiguratu gabe dagoelako. invalid_client: Bezeroaren autentifikazioak huts egin du bezero ezezaguna delako, ez delako bezero autentifikazioa txertatu, edo autentifikazio metodoa ez delako onartzen. + invalid_code_challenge_method: Kodearen erronka metodoa S256 izan behar da, arrunta ez da onartzen. invalid_grant: Emandako baimena baliogabea da, iraungi edo indargabetu da, ez dator bat baimen-eskaeran erabilitako birbideratze URI-arekin edo beste bezero batek sortu du. invalid_redirect_uri: Sartutako birbideratze URI-a baliogabea da. invalid_request: diff --git a/config/locales/el.yml b/config/locales/el.yml index 0b1ee7b772b..f7c3df0f54e 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -218,7 +218,7 @@ el: reject_appeal: Απόρριψη Έφεσης reject_user: Απόρριψη Χρήστη remove_avatar_user: Αφαίρεση Άβαταρ - reopen_report: Ξανάνοιγμα Ανααφοράς + reopen_report: Ξανάνοιγμα Αναφοράς resend_user: Επαναποστολή του email επιβεβαίωσης reset_password_user: Επαναφορά Συνθηματικού resolve_report: Επίλυση Αναφοράς @@ -553,8 +553,8 @@ el: dashboard: instance_accounts_dimension: Λογαριασμοί με τους περισσότερους ακόλουθους instance_accounts_measure: αποθηκευμένοι λογαριασμοί - instance_followers_measure: οι ακόλουθοί μας εκεί - instance_follows_measure: οι ακόλουθοί τους εδώ + instance_followers_measure: οι ακόλουθοι μας εκεί + instance_follows_measure: οι ακόλουθοι τους εδώ instance_languages_dimension: Κορυφαίες γλώσσες instance_media_attachments_measure: αποθηκευμένα συνημμένα πολυμέσων instance_reports_measure: αναφορές γι' αυτές @@ -578,6 +578,13 @@ el: all: Όλα limited: Περιορισμένα title: Συντονισμός + moderation_notes: + create: Προσθήκη Σημείωσης Συντονισμού + created_msg: Επιτυχής δημιουργία σημείωσης συντονισμού οντότητας! + description_html: Δες και άφησε σημειώσεις για άλλους συντονιστές και τον μελλοντικό εαυτό σου + destroyed_msg: Η σημείωση συντονισμού οντότητας διαγράφτηκε επιτυχώς! + placeholder: Πληροφορίες σχετικά μ' αυτή την οντότητα, ενέργειες που έχουν ληφθεί ή οτιδήποτε άλλο θα σε βοηθήσει να συντονίσεις αυτή την οντότητα στο μέλλον. + title: Σημειώσεις Συντονισμού private_comment: Ιδιωτικό σχόλιο public_comment: Δημόσιο σχόλιο purge: Εκκαθάριση @@ -885,7 +892,7 @@ el: title: Πολυμέσα metadata: Μεταδεδομένα no_history: Αυτή η ανάρτηση δεν έχει επεξεργαστεί - no_status_selected: Καμία δημοσίευση δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη + no_status_selected: Καμία ανάρτηση δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη open: Άνοιγμα ανάρτησης original_status: Αρχική ανάρτηση reblogs: Αναδημοσιεύσεις @@ -1045,7 +1052,7 @@ el: confirm_disallow: Σίγουρα θες να απορρίψεις τις επιλεγμένες καταστάσεις; confirm_disallow_account: Σίγουρα θες να απορρίψεις τους επιλεγμένους λογαριασμούς; description_html: Αυτές είναι αναρτήσεις για τις οποίες ο διακομιστής σας γνωρίζει ότι κοινοποιούνται και αρέσουν πολύ αυτή τη περίοδο. Μπορεί να βοηθήσει νέους και χρήστες που επιστρέφουν, να βρουν περισσότερα άτομα να ακολουθήσουν. Καμία ανάρτηση δεν εμφανίζεται δημόσια μέχρι να εγκρίνεις τον συντάκτη και ο συντάκτης να επιτρέπει ο λογαριασμός του να προτείνεται και σε άλλους. Μπορείς επίσης να επιτρέψεις ή να απορρίψεις μεμονωμένες δημοσιεύσεις. - disallow: Να μην επιτρέπεται η δημοσίευση + disallow: Να μην επιτρέπεται η ανάρτηση disallow_account: Να μην επιτρέπεται ο συντάκτης no_status_selected: Καμία δημοφιλής ανάρτηση δεν άλλαξε αφού καμία δεν επιλέχθηκε not_discoverable: Ο συντάκτης δεν έχει επιλέξει να είναι ανακαλύψιμος @@ -1087,7 +1094,7 @@ el: webhooks: add_new: Προσθήκη σημείου τερματισμού delete: Διαγραφή - description_html: Ένα webhook επιτρέπει στο Mastodon να στείλει ειδοποιήσεις πραγματικού χρόνου σχετικά με επιλεγμένα γεγονότα στη δική σου εφαρμογή, ώστε η εφαρμογή σας να μπορεί να προκαλέσει αντιδράσεις αυτόματα. + description_html: Ένα webhook επιτρέπει στο Mastodon να στείλει ειδοποιήσεις πραγματικού χρόνου σχετικά με επιλεγμένα γεγονότα στη δική σου εφαρμογή, ώστε η εφαρμογή σου να μπορεί να προκαλέσει αντιδράσεις αυτόματα. disable: Απενεργοποίηση disabled: Απενεργοποιημένα edit: Επεξεργασία σημείου τερματισμού @@ -1151,7 +1158,7 @@ el: remove: Αποσύνδεση ψευδώνυμου appearance: advanced_web_interface: Προηγμένη διεπαφή ιστού - advanced_web_interface_hint: 'Αν θέλεις να χρησιμοποιήσεις ολόκληρο το πλάτος της οθόνης σου, η προηγμένη λειτουργία χρήσης σου επιτρέπει να ορίσεις πολλαπλές στύλες ώστε να βλέπεις ταυτόχρονα όση πληροφορία θέλεις: Την αρχική ροή, τις ειδοποιήσεις, την ροή συναλλαγών και όσες λίστες και ετικέτες θέλεις.' + advanced_web_interface_hint: 'Αν θέλεις να χρησιμοποιήσεις ολόκληρο το πλάτος της οθόνης σου, η προηγμένη λειτουργία χρήσης σου επιτρέπει να ορίσεις πολλαπλές στήλες ώστε να βλέπεις ταυτόχρονα όση πληροφορία θέλεις: Την αρχική ροή, τις ειδοποιήσεις, την ροή συναλλαγών και όσες λίστες και ετικέτες θέλεις.' animations_and_accessibility: Εφέ κινήσεων και προσβασιμότητα confirmation_dialogs: Ερωτήσεις επιβεβαίωσης discovery: Ανακάλυψη @@ -1342,6 +1349,10 @@ el: basic_information: Βασικές πληροφορίες hint_html: "Τροποποίησε τί βλέπουν άτομα στο δημόσιο προφίλ σου και δίπλα στις αναρτήσεις σου. Είναι πιο πιθανό κάποιος να σε ακολουθήσει πίσω και να αλληλεπιδράσουν μαζί σου αν έχεις ολοκληρωμένο προφίλ και εικόνα προφίλ." other: Άλλο + emoji_styles: + auto: Αυτόματο + native: Εγγενές + twemoji: Twemoji errors: '400': Το αίτημα ήταν άκυρο ή με λάθος μορφή. '403': Δεν έχεις δικαίωμα πρόσβασης σε αυτή τη σελίδα. @@ -1675,7 +1686,7 @@ el: trillion: Τρις otp_authentication: code_hint: Για να συνεχίσεις, γράψε τον κωδικό που δημιούργησε η εφαρμογή πιστοποίησης - description_html: Αν ενεργοποιήσεις την ταυτοποίηση δύο παραγόντων χρησιμοποιώντας εφαρμογή ταυτοποίησης, για να συνδεθείς θα πρέπει να έχεις το τηλέφωνό σου, που θα σού δημιουργήσει κλειδιά εισόδου. + description_html: Αν ενεργοποιήσεις την ταυτοποίηση δύο παραγόντων χρησιμοποιώντας εφαρμογή ταυτοποίησης, για να συνδεθείς θα πρέπει να έχεις το τηλέφωνό σου, που θα σου δημιουργήσει κλειδιά εισόδου για να τα εισάγεις. enable: Ενεργοποίηση instructions_html: "Σάρωσε αυτόν τον κωδικό QR με την εφαρμογή Google Authenticator ή κάποια άλλη αντίστοιχη στο τηλέφωνό σου. Από εδώ και στο εξής, η εφαρμογή θα δημιουργεί κλειδιά που θα πρέπει να εισάγεις όταν συνδέεσαι." manual_instructions: 'Αν δεν μπορείς να σαρώσεις τον κωδικό QR και χρειάζεσαι να τον εισάγεις χειροκίνητα, ορίστε η μυστική φράση σε μορφή κειμένου:' @@ -2012,7 +2023,7 @@ el: delete_statuses: Μερικές από τις αναρτήσεις σου έχουν βρεθεί να παραβιάζουν μία ή περισσότερες οδηγίες κοινότητας και έχουν συνεπώς αφαιρεθεί από τους συντονιστές του %{instance}. disable: Δεν μπορείς πλέον να χρησιμοποιήσεις τον λογαριασμό σου, αλλά το προφίλ σου και άλλα δεδομένα παραμένουν άθικτα. Μπορείς να ζητήσεις ένα αντίγραφο ασφαλείας των δεδομένων σου, να αλλάξεις τις ρυθμίσεις του λογαριασμού σου ή να διαγράψεις τον λογαριασμό σου. mark_statuses_as_sensitive: Μερικές από τις αναρτήσεις σου έχουν επισημανθεί ως ευαίσθητες από τους συντονιστές του %{instance}. Αυτό σημαίνει ότι οι άνθρωποι θα πρέπει να πατήσουν τα πολυμέσα στις αναρτήσεις πριν εμφανιστεί μια προεπισκόπηση. Μπορείς να επισημάνεις τα πολυμέσα ως ευαίσθητα όταν δημοσιεύεις στο μέλλον. - sensitive: Από δω και στο εξής, όλα τα μεταφορτωμένα αρχεία πολυμέσων σου θα επισημανθούν ως ευαίσθητα και κρυμμένα πίσω από μια προειδοποίηση -πατήστε για εμφάνιση. + sensitive: Από δω και στο εξής, όλα τα μεταφορτωμένα αρχεία πολυμέσων σου θα επισημανθούν ως ευαίσθητα και κρυμμένα πίσω από μια προειδοποίηση που πρέπει να πατηθεί. silence: Μπορείς ακόμα να χρησιμοποιείς τον λογαριασμό σου, αλλά μόνο άτομα που σε ακολουθούν ήδη θα δουν τις αναρτήσεις σου σε αυτόν τον διακομιστή και μπορεί να αποκλειστείς από διάφορες δυνατότητες ανακάλυψης. Ωστόσο, οι άλλοι μπορούν ακόμα να σε ακολουθήσουν με μη αυτόματο τρόπο. suspend: Δε μπορείς πλέον να χρησιμοποιήσεις τον λογαριασμό σου και το προφίλ σου και άλλα δεδομένα δεν είναι πλέον προσβάσιμα. Μπορείς ακόμα να συνδεθείς για να αιτηθείς αντίγραφο των δεδομένων σου μέχρι να αφαιρεθούν πλήρως σε περίπου 30 μέρες αλλά, θα διατηρήσουμε κάποια βασικά δεδομένα για να σε αποτρέψουμε να παρακάμψεις την αναστολή. reason: 'Αιτιολογία:' @@ -2045,7 +2056,7 @@ el: edit_profile_title: Εξατομίκευση του προφίλ σου explanation: Μερικές συμβουλές για να ξεκινήσεις feature_action: Μάθε περισσότερα - feature_audience: Το Mastodon σού παρέχει μια μοναδική δυνατότητα διαχείρισης του κοινού σου χωρίς μεσάζοντες. Το Mastodon όταν αναπτύσσεται στη δική σου υποδομή σού επιτρέπει να ακολουθείς και να ακολουθείσαι από οποιονδήποτε άλλο συνδεδεμένο διακομιστή Mastodon και κανείς δεν τον ελέγχει, εκτός από σένα. + feature_audience: Το Mastodon σου παρέχει μια μοναδική δυνατότητα διαχείρισης του κοινού σου χωρίς μεσάζοντες. Το Mastodon όταν αναπτύσσεται στη δική σου υποδομή σου επιτρέπει να ακολουθείς και να ακολουθείσαι από οποιονδήποτε άλλο συνδεδεμένο διακομιστή Mastodon και κανείς δεν τον ελέγχει, εκτός από σένα. feature_audience_title: Χτίσε το κοινό σου με σιγουριά feature_control: Εσύ ξέρεις καλύτερα τι θες να βλέπεις στην αρχική σου ροή. Δεν υπάρχουν αλγόριθμοι ή διαφημίσεις για να σπαταλάς το χρόνο σου. Ακολούθησε οποιονδήποτε σε οποιονδήποτε διακομιστή Mastodon από έναν λογαριασμό και δες τις αναρτήσεις τους με χρονολογική σειρά και κάνε τη δική σου γωνιά του ίντερνετ, λίγο πιο προσωπική. feature_control_title: Διατήρησε τον έλεγχο της ροής σου diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 42fb04a65a0..d321490bd55 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -289,6 +289,7 @@ eu: update_custom_emoji_html: "%{name} erabiltzaileak %{target} emoji-a eguneratu du" update_domain_block_html: "%{name} erabiltzaileak %{target} domeinu-blokeoa eguneratu du" update_ip_block_html: "%{name} erabiltzaileak %{target} IParen araua aldatu du" + update_report_html: "%{name}-k %{target} txostena eguneratu du" update_status_html: "%{name} erabiltzaileak %{target} erabiltzailearen bidalketa eguneratu du" update_user_role_html: "%{name} erabiltzaileak %{target} rola aldatu du" deleted_account: ezabatu kontua @@ -307,6 +308,8 @@ eu: new: create: Sortu iragarpena title: Iragarpen berria + preview: + explanation_html: 'Mezu elektronikoa %{display_count} erabiltzaileei bidaliko zaie. Testu hau gehituko zaio mezu elektronikoari:' publish: Argitaratu published_msg: Iragarpena ongi argitaratu da! scheduled_for: "%{time}-rako programatuta" @@ -692,6 +695,7 @@ eu: delete_data_html: "@%{acct} kontuaren profila eta edukia, gaurtik hasita, 30 egunez ezabatu, ez bada bitartean kontua berraktibatzen" preview_preamble_html: "@%{acct} kontuak ondorengo edukia duen abisu bat jasoko du:" record_strike_html: "@%{acct} kontuak eginiko eraso bat erregistratu, kontu honek etorkizunean egin ditzakeen erasoen aurrean erabakiak hartzen laguntzeko" + send_email_html: Bidali abisu-mezu elektroniko bat @%{acct}-ri warning_placeholder: Moderazio-ekintzarako aukerazkoak diren arrazoiketa gehigarriak. target_origin: Salatutako kontuaren jatorria title: Salaketak @@ -876,6 +880,8 @@ eu: system_checks: database_schema_check: message_html: Aplikatu gabeko datu-basearen migrazioak daude. Exekutatu aplikazioak esperotako portaera izan dezan + elasticsearch_analysis_index_mismatch: + message_html: Elasticsearch indize-analizatzailearen ezarpenak zaharkituta daude. Mesedez, exekutatu tootctl search deploy --only-mapping --only=%{value} elasticsearch_health_red: message_html: Elasticsearch-en klusterra ez dago osasuntsu (egoera gorria), bilaketa-eginbideak ez daude erabilgarri elasticsearch_health_yellow: @@ -916,6 +922,7 @@ eu: message_html: "Zure objektuen biltegiratzea ez dago ongi konfiguratua. Zure erabiltzaileen pribatutasuna arriskuan dago." tags: moderation: + not_trendable: Ez dago modan not_usable: Ez erabilgarri pending_review: Berrikusketaren zain reviewed: Berrikusita @@ -932,12 +939,17 @@ eu: title: Traolak updated_msg: Traola-ezarpenak ongi eguneratu dira terms_of_service: + back: Zerbitzuaren baldintzetara itzuli changelog: Zer aldatu da + create: Erabili zeurea current: Oraingoa draft: Zirriborroa generate: Txantiloila erabili generates: action: Sortu + chance_to_review_html: "Sortutako zerbitzu-baldintzak ez dira automatikoki argitaratuko. Emaitzak berrikusteko aukera izango duzu. Mesedez, bete beharrezko xehetasunak aurrera egiteko." + explanation_html: Zerbitzu-baldintzen txantiloia informazio-helburuetarako soilik da, eta ez da gai bati buruzko aholku juridikotzat hartu behar. Mesedez, kontsultatu zure aholkulari juridikoa zure egoerari eta galdera juridiko zehatzei buruz. + going_live_on_html: Argitaratua, indarrean %{date} history: Historia live: Zuzenean publish: Argitaratu @@ -1091,6 +1103,7 @@ eu: sensitive_content: Eduki hunkigarria application_mailer: salutation: "%{name}," + settings: 'Posta elektronikoaren lehentasunak aldatu: %{link}' unsubscribe: Kendu harpidetza view: 'Ikusi:' view_profile: Ikusi profila @@ -1160,6 +1173,7 @@ eu: setup: email_below_hint_html: Begiratu zure spameko karpetan, edo eskatu beste bat. Zure helbide elektronikoa zuzen dezakezu oker badago. link_not_received: Ez duzu estekarik jaso? + new_confirmation_instructions_sent: Minutu gutxiren buruan baieztapen-esteka duen mezu elektroniko bat jasoko duzu! title: Begiratu zure sarrera-ontzia sign_in: preamble_html: Zure %{domain}-(e)ko egiaztagiriekin saioa hasi. Zure kontua beste zerbitzari batean badago, ezin izango duzu hemen saioa hasi. @@ -1170,6 +1184,7 @@ eu: status: account_status: Kontuaren egoera functional: Zure kontua guztiz erabilgarri dago. + pending: Gure taldea zure eskaera berrikusten ari da. Honek denbora pixka bat beharko du. Mezu elektroniko bat jasoko duzu zure eskaera onartzen bada. redirecting_to: Zure kontua ez dago aktibo orain %{acct} kontura birbideratzen duelako. self_destruct: "%{domain} domeinua itxiko denez, konturako sarbide mugatua soilik izango duzu." view_strikes: Ikusi zure kontuaren aurkako neurriak @@ -1437,6 +1452,9 @@ eu: unsubscribe: action: Bai, kendu harpidetza complete: Harpidetza kenduta + emails: + notification_emails: + follow: jarraitu jakinarazpen-mezu elektronikoak title: Kendu harpidetza media_attachments: validations: @@ -1778,6 +1796,8 @@ eu: does_not_match_previous_name: ez dator aurreko izenarekin bat terms_of_service: title: Erabilera baldintzak + terms_of_service_interstitial: + future_preamble_html: Gure zerbitzu-baldintzetan aldaketa batzuk egiten ari gara, eta %{date}-tik aurrera jarriko dira indarrean. Eguneratutako baldintzak berrikustea gomendatzen dizugu. themes: contrast: Mastodon (Kontraste altua) default: Mastodon (Iluna) @@ -1809,6 +1829,8 @@ eu: recovery_instructions_html: Zure telefonora sarbidea galtzen baduzu, beheko berreskuratze kode bat erabili dezakezu kontura berriro sartu ahal izateko. Gore barreskuratze kodeak toki seguruan. Adibidez inprimatu eta dokumentu garrantzitsuekin batera gorde. webauthn: Segurtasun gakoak user_mailer: + announcement_published: + title: "%{domain} zerbitzuaren iragarpena" appeal_approved: action: Kontuaren ezarpenak explanation: "%{strike_date}(e)an zure kontuari ezarritako neurriaren aurka %{appeal_date}(e)an jarri zenuen apelazioa onartu da. Zure kontua egoera onean dago berriro." diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 317900e56ba..28c98a58d67 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -405,9 +405,69 @@ nan: remove_all_data: Tse ē tī lí ê服侍器內底,kā tuì tsit ê域名ê口座來ê所有內容、媒體kap個人資料lóng thâi掉。 stop_communication: Lí ê服侍器ē停止kap hia ê服侍器聯絡。 title: 確認封鎖域名 %{domain} + undo_relationships: Tse ē取消任何ê佇in ê服侍器ê口座kap lí ê之間ê跟tuè關係。 + created_msg: 當leh封鎖網域 + destroyed_msg: 已經取消封鎖域名 + domain: 域名 edit: 編輯域名封鎖 + existing_domain_block: Lí已經kā %{name} 下koh khah嚴ê限制。 + existing_domain_block_html: Lí已經kā %{name} 下koh khah嚴ê限制,lí著先解除封鎖。 export: 輸出 import: 輸入 + new: + create: 加添封鎖 + hint: 封鎖域名bē當擋口座記錄受加添佇資料庫,m̄-kú ē 自動自尾kàu頭,kā hia ê口座使用指定ê管理方式。 + severity: + desc_html: "限制ē kā hit ê域名ê口座所送ê PO文,設做kan-ta跟tuè伊ê tsiah通看見。中止權限ē thâi掉tī lí ê 服侍器內底,所有tuì hit ê域名ê口座來ê內容、媒體kap個人資料。Nā kan-ta beh拒絕媒體檔案,請用。" + noop: 無 + silence: 限制 + suspend: 中止權限 + title: 新ê域名封鎖 + no_domain_block_selected: 因為無揀任何域名封鎖,所以lóng無改變 + not_permitted: Lí無允准行tsit ê動作 + obfuscate: Kā域名舞bē清 + obfuscate_hint: Nā beh啟用廣告域名列單ê限制,tiō tī列單kā域名ê部份舞buē清。 + private_comment: 私人評論 + private_comment_hint: 請評論關係tsit ê域名ê制限,hōo管理員做內部ê路用。 + public_comment: 公開ê評論 + public_comment_hint: 請為一般大眾評論關係tsit ê域名ê制限,若beh啟用廣告域名列單ê限制。 + reject_media: 拒絕媒體檔案 + reject_media_hint: Thâi掉本地tiông ê媒體檔案,mā bē koh kā任何tuì hia來ê載落去。Hām中止權限無tī-tāi + reject_reports: 拒絕檢舉 + reject_reports_hint: 忽略ta̍k ê tuì tsit ê域名來ê檢舉,hām中止權限無tī-tāi。 + undo: 取消域名封鎖 + view: 檢視域名封鎖 + email_domain_blocks: + add_new: 加新ê + allow_registrations_with_approval: 許可了後允准註冊 + attempts_over_week: + other: 頂禮拜lóng總有 %{count} pái試註冊 + created_msg: 成功封鎖電子phue域名 + delete: Thâi掉 + dns: + types: + mx: MX記錄 + domain: 域名 + new: + create: 加添域名 + resolve: 解析域名 + title: 封鎖新ê電子phue網域 + no_email_domain_block_selected: 因為無揀任何電子phue域名封鎖,所以lóng無改變 + not_permitted: 無允准 + resolved_through_html: 通過 %{domain} 解析 + title: 封鎖ê電子phue網域 + export_domain_allows: + new: + title: 輸入允准ê域名 + no_file: Iáu bē揀檔案 + export_domain_blocks: + import: + description_html: Lí teh-beh輸入封鎖域名ê列單。請koh kā tsit ê列單斟酌檢查,特別是lí無家tī編tsit ê列單ê時。 + existing_relationships_warning: 有ê跟tuè關係 + private_comment_description_html: 為著幫tsān lí追蹤輸入ê封鎖tuì toh來,輸入ê封鎖ē kap下kha ê私人評論sann-kap加添:%{comment} + private_comment_template: 佇 %{date} tuì %{source} 輸入 + title: 輸入域名封鎖 + invalid_domain_block: 因為下kha ê錯誤,làng過tsi̍t ê以上ê域名封鎖:%{error} instances: dashboard: instance_languages_dimension: Tsia̍p用ê語言 diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index b68e03b0575..8a5c912e20c 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -7,7 +7,7 @@ be: display_name: Ваша поўнае імя або ваш псеўданім. fields: Ваша хатняя старонка, займеннікі, узрост, усё, што заўгодна. indexable: Вашыя публічныя допісы могуць з'яўляцца ў рэзультатах пошуку Mastodon. Людзі, якія ўзаемадзейнічалі з вашымі допісамі, усё роўна маюць магчымасць іх знаходзіць. - note: 'Вы можаце @згадаць іншых людзей або выкарыстоўваць #хэштэгі.' + note: 'Вы можаце @згадваць іншых людзей або выкарыстоўваць #хэштэгі.' show_collections: Людзі змогуць праглядаць спіс вашых падпісак і падпісчыкаў. Людзі, на якіх вы падпісаны ў любым выпадку будуць бачыць, што вы іх чытаеце. account_alias: acct: Прызначце карыстальнік@дамен уліковага запісу з якога вы хочаце пераехаць @@ -30,10 +30,10 @@ be: warning_preset_id: Неабавязкова. Вы можаце дадаць уласны тэкст напрыканцы шаблону announcement: all_day: Калі пазначана, будуць паказаны толькі даты з пазначанага прамежку часу - ends_at: Неабавязкова. Аб'ява будзе аўтаматычна знята ў пэўны час - scheduled_at: Пакіньце пустым, каб апублікаваць аб'яву імаверна - starts_at: Неабавязкова. На выпадак, калі ваша аб'ява прывязана да пэўнага перыяду часу - text: Вы можаце карыстацца сінтаксісам допісаў. Калі ласка, улічвайце месца, якое аб'ява зойме на экране карыстальніка + ends_at: Неабавязкова. Аб’ява будзе аўтаматычна знята ў пэўны час + scheduled_at: Пакіньце пустым, каб апублікаваць аб’яву адразу + starts_at: Неабавязкова. На выпадак, калі ваша аб’ява прывязана да пэўнага перыяду часу + text: Вы можаце карыстацца сінтаксісам допісаў. Улічвайце месца, якое аб’ява зойме на экране карыстальніка appeal: text: Вы можаце абскардзіць рашэнне толькі адзін раз defaults: @@ -41,7 +41,7 @@ be: avatar: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions}} пікселяў bot: Паведаміць іншым, што гэты ўліковы запіс у асноўным выконвае аўтаматычныя дзеянні і можа не кантралявацца context: Адзін ці некалькі кантэкстаў, да якіх трэба прымяніць фільтр - current_password: У мэтах бяспекі, калі ласка, увядзіце пароль бягучага ўліковага запісу + current_password: У мэтах бяспекі, увядзіце пароль бягучага ўліковага запісу current_username: Каб пацвердзіць, увядзіце, калі ласка імя карыстальніка бягучага ўліковага запісу digest: Будзе даслана толькі пасля доўгага перыяду неактыўнасці і толькі калі вы атрымалі асабістыя паведамленні падчас вашай адсутнасці email: Пацвярджэнне будзе выслана па электроннай пошце @@ -51,13 +51,14 @@ be: locale: Мова карыстальніцкага інтэрфейсу, электронных паведамленняў і апавяшчэнняў password: Не менш за 8 сімвалаў phrase: Параўнанне адбудзецца нягледзячы на рэгістр тэксту і папярэджанні аб змесціве допісу - scopes: Якімі API праграм будзе дазволена карыстацца. Калі вы абярэце найвышэйшы ўзровень, не трэба абіраць асобныя. + scopes: Абярыце, якімі API праграма зможа карыстацца. Выбар дазволу найвышэйшага ўзроўню ўключае ў сябе дазволу астатніх узроўняў. setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія пашырылі нядаўна (закранае толькі нядаўнія пашырэнні) setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам setting_display_media_default: Хаваць медыя пазначаныя як далікатныя setting_display_media_hide_all: Заўсёды хаваць медыя setting_display_media_show_all: Заўсёды паказваць медыя + setting_emoji_style: Як паказваць эмодзі. "Аўтаматычны" будзе намагацца выкарыстоўваць мясцовыя эмодзі, але для састарэлых браўзераў — Twemoji. setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі username: Вы можаце выкарыстоўваць літары, лічбы і падкрэсліванне @@ -80,19 +81,19 @@ be: backups_retention_period: Карыстальнікі могуць ствараць архівы сваіх допісаў для наступнай запампоўкі. Пры станоўчай колькасці дзён гэтыя архівы будуць аўтаматычна выдаляцца са сховішча пасля заканчэння названай колькасці дзён. bootstrap_timeline_accounts: Гэтыя ўліковыя запісы будуць замацаваны ў топе рэкамендацый для новых карыстальнікаў. closed_registrations_message: Паказваецца, калі рэгістрацыя закрытая - content_cache_retention_period: Усе допісы з іншых сервераў (уключаючы пашырэнні і адказы) будуць выдаленыя праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадкі паміж карыстальнікамі з розных інстанс таксама будуць страчаныя і не змогуць быць адноўлены. Выкарыстанне гэтай налады прызначана для асобнікаў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах. + content_cache_retention_period: Усе допісы з іншых сервераў (разам з пашырэннямі і адказамі) будуць выдалены праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадванні паміж карыстальнікамі з розных экзэмпляраў сервераў таксама будуць страчаны і іх нельга будзе аднавіць. Выкарыстанне гэтай налады прызначана для экзэмпляраў сервераў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах. custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon. favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок. mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе. - media_cache_retention_period: Медыяфайлы з допісаў, зробленых выдаленымі карыстальнікамі, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыядадзеныя будуць запытаны пасля выдалення, яны будуць загружаны паўторна, калі зыходны кантэнт усё яшчэ даступны. У сувязі з абмежаваннямі на частату абнаўлення відарысаў іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш відарысы не будуць загружацца па запыце раней за гэты тэрмін. - peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у федэсвеце. Дадзеныя аб тым, ці знаходзіцеся вы з пэўным серверам у федэрацыі, не ўключаныя, ёсць толькі тое, што ваш сервер ведае пра гэта. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. + media_cache_retention_period: Медыяфайлы з допісаў, зробленых выдаленымі карыстальнікамі, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаны пасля выдалення, яны будуць спампаваны зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін. + peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у fediverse. Даныя пра тое, ці знаходзіцеся вы з дадзеным серверам у федэрацыі, не ўключаны. Уключаны толькі даныя пра тое, што ваш сервер ведае пра іншыя серверы. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. profile_directory: Дырэкторыя профіляў змяшчае спіс усіх карыстальнікаў, якія вырашылі быць бачнымі. require_invite_text: Калі рэгістрацыя патрабуе ручнога пацвержання, зрабіце поле "Чаму вы хочаце далучыцца?" абавязковым site_contact_email: Як людзі могуць звязацца з вамі па юрыдычных запытах або пытаннях падтрымкі. site_contact_username: Як людзі могуць звязацца з вамі на Mastodon. site_extended_description: Любая дадатковая інфармацыя, якая можа быць карыснай наведвальнікам ды вашым карыстальнікам. Можна карыстацца сінтаксісам Markdown каб структураваць тэкст. site_short_description: Кароткае апісанне, каб дапамагчы адназначна ідэнтыфікаваць ваш сервер. Хто яго падтрымлівае, для каго ён? - site_terms: Апішыце ўласную палітыку прыватнасці альбо пакіньце поле пустым, калі хочаце скарыстацца прадвызначанай. Можна карыстацца сінтаксісам Markdown каб структураваць тэкст. + site_terms: Апішыце ўласную палітыку прыватнасці альбо пакіньце поле пустым, калі хочаце выкарыстоўваць прадвызначаную. Можна карыстацца сінтаксісам Markdown для структуравання тэксту. site_title: Як людзі могуць звяртацца да вашага серверу акрамя яго даменнага імя. status_page_url: URL старонкі, дзе людзі могуць бачыць стан гэтага сервера падчас збою theme: Тэма, што бачаць новыя карыстальнікі ды наведвальнікі, якія выйшлі. @@ -127,6 +128,8 @@ be: show_application: Вы ў любым выпадку зможаце ўбачыць, якая праграма апублікавала ваш допіс. tag: name: Вы можаце змяняць толькі рэгістр літар, напрыклад для таго, каб падвысіць чытабельнасць + terms_of_service: + text: Тэкст можна структураваць з дапамогай сінтаксісу Markdown. user: chosen_languages: У публічных стужках будуць паказвацца допісы толькі на тых мовах, якія вы пазначыце user_role: @@ -207,6 +210,7 @@ be: setting_boost_modal: Паказваць акно пацвярджэння перад пашырэннем setting_default_language: Мова допісаў setting_default_privacy: Прыватнасць допісаў + setting_default_quote_policy: Хто можа цытаваць setting_default_sensitive: Заўсёды пазначаць кантэнт як далікатны setting_delete_modal: Паказваць акно пацвярджэння перад выдаленнем допісу setting_disable_hover_cards: Адключыць перадпрагляд профілю пры навядзенні @@ -215,13 +219,14 @@ be: setting_display_media_default: Перадвызначана setting_display_media_hide_all: Схаваць усё setting_display_media_show_all: Паказаць усё + setting_emoji_style: Стыль эмодзі setting_expand_spoilers: Заўжды разгортваць допісы з папярэджаннем аб змесціве setting_hide_network: Схаваць вашы сувязі setting_reduce_motion: Памяншэнне руху ў анімацыях setting_system_font_ui: Выкарыстоўваць прадвызначаны сістэмны шрыфт setting_theme: Тэма сайта setting_trends: Паказваць трэнды дня - setting_unfollow_modal: Паказваць акно пацвярджэння перад адпіскай + setting_unfollow_modal: Паказваць акно пацвярджэння перад адпісваннем setting_use_blurhash: Паказваць каляровыя градыенты замест схаваных медыя setting_use_pending_items: Павольны рэжым severity: Узровень @@ -276,7 +281,7 @@ be: invite: comment: Каментар invite_request: - text: Чаму вы жадаеце далучыцца? + text: Чаму вы хочаце далучыцца? ip_block: comment: Каментар ip: IP @@ -313,10 +318,18 @@ be: name: Хэштэг trendable: Дазволіць паказ гэтага хэштэга ў трэндах usable: Дазволіць допісам выкарыстоўваць гэты хэштэг лакальна + terms_of_service: + changelog: Што змянілася? + text: Умовы выкарыстання terms_of_service_generator: - domain: Дамэн + choice_of_law: Выбар заканадаўства + domain: Дамен jurisdiction: Юрысдыкцыя + min_age: Мінімальны ўзрост user: + date_of_birth_1i: Дзень + date_of_birth_2i: Месяц + date_of_birth_3i: Год role: Роля time_zone: Часавы пояс user_role: diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index cfc9c218ce1..bbdc1431c45 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -61,6 +61,7 @@ el: setting_display_media_default: Απόκρυψη ευαίσθητων πολυμέσων setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων setting_display_media_show_all: Πάντα εμφάνιση πολυμέσων + setting_emoji_style: Πώς να εμφανίσετε emojis. Το "Αυτόματο" θα προσπαθήσει να χρησιμοποιήσει εγγενή emoji, αλλά πέφτει πίσω στο Twemoji για προγράμματα περιήγησης παλαιού τύπου. setting_system_scrollbars_ui: Ισχύει μόνο για προγράμματα περιήγησης υπολογιστή με βάση το Safari και το Chrome setting_use_blurhash: Οι χρωματισμοί βασίζονται στα χρώματα του κρυμμένου πολυμέσου αλλά θολώνουν τις λεπτομέρειες setting_use_pending_items: Εμφάνιση ενημερώσεων ροής μετά από κλικ αντί για αυτόματη κύλισή τους @@ -74,7 +75,7 @@ el: featured_tag: name: 'Εδώ είναι μερικά από τα hashtags που χρησιμοποιήσατε περισσότερο πρόσφατα:' filters: - action: Επιλέξτε ποια ενέργεια θα εκτελεστεί όταν μια δημοσίευση ταιριάζει με το φίλτρο + action: Επιλέξτε ποια ενέργεια θα εκτελεστεί όταν μια ανάρτηση ταιριάζει με το φίλτρο actions: blur: Απόκρυψη πολυμέσων πίσω από μια προειδοποίηση, χωρίς να κρύβεται το ίδιο το κείμενο hide: Πλήρης αποκρυψη του φιλτραρισμένου περιεχομένου, συμπεριφέρεται σαν να μην υπήρχε @@ -241,9 +242,10 @@ el: setting_display_media_default: Προκαθορισμένο setting_display_media_hide_all: Απόκρυψη όλων setting_display_media_show_all: Εμφάνιση όλων + setting_emoji_style: Στυλ Emoji setting_expand_spoilers: Μόνιμη ανάπτυξη των τουτ με προειδοποίηση περιεχομένου setting_hide_network: Κρύψε τις διασυνδέσεις σου - setting_missing_alt_text_modal: Εμφάνιση διαλόγου επιβεβαίωσης πριν από την δημοσίευση πολυμέσων χωρίς alt κείμενο + setting_missing_alt_text_modal: Εμφάνιση διαλόγου επιβεβαίωσης πριν από τη δημοσίευση πολυμέσων χωρίς εναλλακτικό κείμενο setting_reduce_motion: Μείωση κίνησης κινουμένων στοιχείων setting_system_font_ui: Χρήση της προεπιλεγμένης γραμματοσειράς του συστήματος setting_system_scrollbars_ui: Χρήση προκαθορισμένης γραμμής κύλισης του συστήματος @@ -318,12 +320,12 @@ el: notification_emails: appeal: Κάποιος κάνει έφεση σε απόφαση συντονιστή digest: Αποστολή συνοπτικών email - favourite: Αποστολή email όταν κάποιος σημειώνει ως αγαπημένη τη δημοσίευσή σου - follow: Αποστολή email όταν κάποιος σε ακολουθεί - follow_request: Αποστολή email όταν κάποιος ζητάει να σε ακολουθήσει - mention: Αποστολή email όταν κάποιος σε αναφέρει - pending_account: Αποστολή email όταν υπάρχει νέος λογαριασμός για επιθεώρηση - reblog: Αποστολή email όταν κάποιος προωθεί τη δημοσίευση σου + favourite: Κάποιος αγάπησε την ανάρτηση σου + follow: Κάποιος σε ακολούθησε + follow_request: Κάποιος ζήτησε να σε ακολουθήσει + mention: Κάποιος σε επισήμανε + pending_account: Νέος λογαριασμός χρειάζεται αναθεώρηση + reblog: Κάποιος ενίσχυσε την ανάρτηση σου report: Υποβλήθηκε νέα αναφορά software_updates: all: Ειδοποίηση για όλες τις ενημερώσεις diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index 7706db6637d..fc79319ed7e 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -264,6 +264,7 @@ eu: favicon: Gune-ikurra mascot: Maskota pertsonalizatua (zaharkitua) media_cache_retention_period: Multimediaren cachea atxikitzeko epea + min_age: Gutxieneko adin-eskakizuna peers_api_enabled: Argitaratu aurkitutako zerbitzarien zerrenda APIan profile_directory: Gaitu profil-direktorioa registrations_mode: Nork eman dezake izena diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index ab2a193558e..b2620ba896a 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -61,6 +61,7 @@ sv: setting_display_media_default: Dölj media markerad som känslig setting_display_media_hide_all: Dölj alltid all media setting_display_media_show_all: Visa alltid media markerad som känslig + setting_emoji_style: Hur emojier visas. "Automatiskt" kommer att försöka använda webbläsarens emojier, men faller tillbaka till Twemoji för äldre webbläsare. setting_system_scrollbars_ui: Gäller endast för webbläsare som är baserade på Safari och Chrome setting_use_blurhash: Gradienter är baserade på färgerna av de dolda objekten men fördunklar alla detaljer setting_use_pending_items: Dölj tidslinjeuppdateringar bakom ett klick istället för att automatiskt bläddra i flödet @@ -241,6 +242,7 @@ sv: setting_display_media_default: Standard setting_display_media_hide_all: Dölj alla setting_display_media_show_all: Visa alla + setting_emoji_style: Emoji-stil setting_expand_spoilers: Utöka alltid tutningar markerade med innehållsvarningar setting_hide_network: Göm ditt nätverk setting_missing_alt_text_modal: Visa bekräftelsedialog innan du skickar media utan alt-text diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 4f011da36ba..ca23e350545 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1349,6 +1349,10 @@ sv: basic_information: Allmän information hint_html: "Anpassa vad folk ser på din offentliga profil och bredvid dina inlägg. Andra personer är mer benägna att följa dig och interagera med dig när du har en ifylld profil och en profilbild." other: Övrigt + emoji_styles: + auto: Automatiskt + native: Ursprunglig + twemoji: Twemoji errors: '400': Förfrågningen som du skickade in var ogiltig eller felaktigt utformad. '403': Du har inte behörighet att visa den här sidan. diff --git a/config/locales/vi.yml b/config/locales/vi.yml index b8e2b9ef728..d56878d1615 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1646,7 +1646,7 @@ vi: thousand: K trillion: T otp_authentication: - code_hint: Nhập mã được tạo bởi ứng dụng xác minh của bạn để xác nhận + code_hint: Nhập mã được tạo bởi ứng dụng xác thực của bạn để xác nhận description_html: Bạn sẽ đăng nhập bằng mã token được tạo ra bởi điện thoại của bạn. enable: Kích hoạt instructions_html: "Quét mã QR bằng Google Authenticator hoặc một ứng dụng TOTP tương tự trên điện thoại của bạn. Kể từ bây giờ, ứng dụng đó sẽ tạo những token để bạn đăng nhập." @@ -1921,8 +1921,8 @@ vi: enabled_success: Xác thực 2 bước được kích hoạt thành công generate_recovery_codes: Tạo mã khôi phục lost_recovery_codes: Mã khôi phục cho phép bạn lấy lại quyền truy cập vào tài khoản của mình nếu bạn mất điện thoại. Nếu bạn bị mất mã khôi phục, bạn có thể tạo lại chúng ở đây. Mã khôi phục cũ của bạn sẽ bị vô hiệu. - methods: Phương pháp xác minh - otp: Ứng dụng xác minh + methods: Phương thức xác thực + otp: Ứng dụng xác thực recovery_codes: Mã khôi phục dự phòng recovery_codes_regenerated: Mã khôi phục được phục hồi thành công recovery_instructions_html: Nếu bạn bị mất điện thoại, hãy dùng một trong các mã khôi phục bên dưới để lấy lại quyền truy cập vào tài khoản của mình. Giữ mã khôi phục an toàn. Ví dụ, bạn có thể in chúng ra giấy. From a2c5eace88fe668ceacc91925c99db3499f260a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:07:21 +0200 Subject: [PATCH 050/660] chore(deps): update dependency annotaterb to v4.17.0 (#35368) 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 9695abadcdf..6837c110c6b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,7 +90,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) - annotaterb (4.16.0) + annotaterb (4.17.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) ast (2.4.3) From a3ffd2edf8241983979f8a57e3a21e002ed8c4c0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 14 Jul 2025 05:20:50 -0400 Subject: [PATCH 051/660] Use sequence for unique names on webauthn cred fabricator (#35356) --- spec/fabricators/webauthn_credential_fabricator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/fabricators/webauthn_credential_fabricator.rb b/spec/fabricators/webauthn_credential_fabricator.rb index b578d55f00d..3e6f3b11d5e 100644 --- a/spec/fabricators/webauthn_credential_fabricator.rb +++ b/spec/fabricators/webauthn_credential_fabricator.rb @@ -4,6 +4,6 @@ Fabricator(:webauthn_credential) do user_id { Fabricate(:user).id } external_id { Base64.urlsafe_encode64(SecureRandom.random_bytes(16)) } public_key { OpenSSL::PKey::EC.generate('prime256v1').public_key } - nickname 'USB key' + nickname { sequence(:nickname) { |i| "USB Key number #{i}" } } sign_count 0 end From 7273f6c03cfc7fb74cc29ca9df610d1efb16417d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 14 Jul 2025 05:23:18 -0400 Subject: [PATCH 052/660] Move shared params to common method in admin/reports/actions (#35353) --- .../admin/reports/actions_controller.rb | 42 ++++++++++--------- .../admin/reports/actions_controller_spec.rb | 6 +++ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/app/controllers/admin/reports/actions_controller.rb b/app/controllers/admin/reports/actions_controller.rb index 554f7906f83..fb7b6878bae 100644 --- a/app/controllers/admin/reports/actions_controller.rb +++ b/app/controllers/admin/reports/actions_controller.rb @@ -13,27 +13,9 @@ class Admin::Reports::ActionsController < Admin::BaseController case action_from_button when 'delete', 'mark_as_sensitive' - status_batch_action = Admin::StatusBatchAction.new( - type: action_from_button, - status_ids: @report.status_ids, - current_account: current_account, - report_id: @report.id, - send_email_notification: !@report.spam?, - text: params[:text] - ) - - status_batch_action.save! + Admin::StatusBatchAction.new(status_batch_action_params).save! when 'silence', 'suspend' - account_action = Admin::AccountAction.new( - type: action_from_button, - report_id: @report.id, - target_account: @report.target_account, - current_account: current_account, - send_email_notification: !@report.spam?, - text: params[:text] - ) - - account_action.save! + Admin::AccountAction.new(account_action_params).save! else return redirect_to admin_report_path(@report), alert: I18n.t('admin.reports.unknown_action_msg', action: action_from_button) end @@ -43,6 +25,26 @@ class Admin::Reports::ActionsController < Admin::BaseController private + def status_batch_action_params + shared_params + .merge(status_ids: @report.status_ids) + end + + def account_action_params + shared_params + .merge(target_account: @report.target_account) + end + + def shared_params + { + current_account: current_account, + report_id: @report.id, + send_email_notification: !@report.spam?, + text: params[:text], + type: action_from_button, + } + end + def set_report @report = Report.find(params[:report_id]) end diff --git a/spec/controllers/admin/reports/actions_controller_spec.rb b/spec/controllers/admin/reports/actions_controller_spec.rb index 87a48a7a3a0..4f82e69a760 100644 --- a/spec/controllers/admin/reports/actions_controller_spec.rb +++ b/spec/controllers/admin/reports/actions_controller_spec.rb @@ -144,6 +144,12 @@ RSpec.describe Admin::Reports::ActionsController do expect(media_attached_status.reload.sensitive).to be true end end + + context 'when the action is "invalid_action"' do + let(:action) { 'invalid_action' } + + it { is_expected.to redirect_to(admin_report_path(report)) } + end end context 'with action as submit button' do From dec1fb71f4958771e8bb80d5415956fd3428e429 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 15 Jul 2025 02:27:36 -0400 Subject: [PATCH 053/660] Add coverage for `FollowRecommendationMute` model (#35376) --- app/models/follow_recommendation_mute.rb | 2 +- .../models/follow_recommendation_mute_spec.rb | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 spec/models/follow_recommendation_mute_spec.rb diff --git a/app/models/follow_recommendation_mute.rb b/app/models/follow_recommendation_mute.rb index d166d0a6206..ef931988e71 100644 --- a/app/models/follow_recommendation_mute.rb +++ b/app/models/follow_recommendation_mute.rb @@ -14,7 +14,7 @@ class FollowRecommendationMute < ApplicationRecord belongs_to :account belongs_to :target_account, class_name: 'Account' - validates :target_account, uniqueness: { scope: :account_id } + validates :target_account_id, uniqueness: { scope: :account_id } after_commit :invalidate_follow_recommendations_cache diff --git a/spec/models/follow_recommendation_mute_spec.rb b/spec/models/follow_recommendation_mute_spec.rb new file mode 100644 index 00000000000..0141d9042a1 --- /dev/null +++ b/spec/models/follow_recommendation_mute_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe FollowRecommendationMute do + describe 'Associations' do + it { is_expected.to belong_to(:account) } + it { is_expected.to belong_to(:target_account).class_name('Account') } + end + + describe 'Validations' do + subject { Fabricate.build :follow_recommendation_mute } + + it { is_expected.to validate_uniqueness_of(:target_account_id).scoped_to(:account_id) } + end + + describe 'Callbacks' do + describe 'Maintaining the recommendation cache' do + let(:account) { Fabricate :account } + let(:cache_key) { "follow_recommendations/#{account.id}" } + + before { Rails.cache.write(cache_key, 123) } + + it 'purges on save' do + expect { Fabricate :follow_recommendation_mute, account: account } + .to(change { Rails.cache.exist?(cache_key) }.from(true).to(false)) + end + end + end +end From 16372970859ba2edc2655aa2b222cf1d19ff9e47 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 15 Jul 2025 02:28:40 -0400 Subject: [PATCH 054/660] Add coverage for `CustomFilterStatus` model (#35374) --- app/models/custom_filter_status.rb | 8 +++--- spec/models/custom_filter_status_spec.rb | 33 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 spec/models/custom_filter_status_spec.rb diff --git a/app/models/custom_filter_status.rb b/app/models/custom_filter_status.rb index 58b61cd79d4..c85b811280e 100644 --- a/app/models/custom_filter_status.rb +++ b/app/models/custom_filter_status.rb @@ -17,12 +17,14 @@ class CustomFilterStatus < ApplicationRecord belongs_to :custom_filter belongs_to :status - validates :status, uniqueness: { scope: :custom_filter } - validate :validate_status_access + validates :status_id, uniqueness: { scope: :custom_filter_id } + validate :validate_status_access, if: [:custom_filter_account, :status] + + delegate :account, to: :custom_filter, prefix: true, allow_nil: true private def validate_status_access - errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter.account, status).show? + errors.add(:status_id, :invalid) unless StatusPolicy.new(custom_filter_account, status).show? end end diff --git a/spec/models/custom_filter_status_spec.rb b/spec/models/custom_filter_status_spec.rb new file mode 100644 index 00000000000..3f161c00bd8 --- /dev/null +++ b/spec/models/custom_filter_status_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CustomFilterStatus do + describe 'Associations' do + it { is_expected.to belong_to(:custom_filter) } + it { is_expected.to belong_to(:status) } + end + + describe 'Validations' do + subject { Fabricate.build :custom_filter_status } + + it { is_expected.to validate_uniqueness_of(:status_id).scoped_to(:custom_filter_id) } + + describe 'Status access' do + subject { Fabricate.build :custom_filter_status, custom_filter:, status: } + + let(:custom_filter) { Fabricate :custom_filter } + let(:status) { Fabricate :status } + + context 'when policy allows filter account to access status' do + it { is_expected.to allow_value(status.id).for(:status_id) } + end + + context 'when policy does not allow filter account to access status' do + before { status.account.touch(:suspended_at) } + + it { is_expected.to_not allow_value(status.id).for(:status_id) } + end + end + end +end From 30344d6abf4e81d191e563358ec19b7a9edeb4b4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 15 Jul 2025 02:31:00 -0400 Subject: [PATCH 055/660] Confirm `User#login_activities` in auth/sessions spec (#35372) --- .../auth/omniauth_callbacks_controller.rb | 3 +- app/controllers/auth/sessions_controller.rb | 31 +++++++++++-------- .../settings/login_activities_controller.rb | 2 +- app/models/user.rb | 1 + .../auth/sessions_controller_spec.rb | 19 +++++++++--- spec/models/user_spec.rb | 1 + 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/app/controllers/auth/omniauth_callbacks_controller.rb b/app/controllers/auth/omniauth_callbacks_controller.rb index 9d496220a3d..16de03fd72d 100644 --- a/app/controllers/auth/omniauth_callbacks_controller.rb +++ b/app/controllers/auth/omniauth_callbacks_controller.rb @@ -38,8 +38,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController private def record_login_activity - LoginActivity.create( - user: @user, + @user.login_activities.create( success: true, authentication_method: :omniauth, provider: @provider, diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 2808066aaf9..c52bda67b0a 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -151,12 +151,11 @@ class Auth::SessionsController < Devise::SessionsController sign_in(user) flash.delete(:notice) - LoginActivity.create( - user: user, - success: true, - authentication_method: security_measure, - ip: request.remote_ip, - user_agent: request.user_agent + user.login_activities.create( + request_details.merge( + authentication_method: security_measure, + success: true + ) ) UserMailer.suspicious_sign_in(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! if @login_is_suspicious @@ -167,13 +166,12 @@ class Auth::SessionsController < Devise::SessionsController end def on_authentication_failure(user, security_measure, failure_reason) - LoginActivity.create( - user: user, - success: false, - authentication_method: security_measure, - failure_reason: failure_reason, - ip: request.remote_ip, - user_agent: request.user_agent + user.login_activities.create( + request_details.merge( + authentication_method: security_measure, + failure_reason: failure_reason, + success: false + ) ) # Only send a notification email every hour at most @@ -182,6 +180,13 @@ class Auth::SessionsController < Devise::SessionsController UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later! end + def request_details + { + ip: request.remote_ip, + user_agent: request.user_agent, + } + end + def second_factor_attempts_key(user) "2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}" end diff --git a/app/controllers/settings/login_activities_controller.rb b/app/controllers/settings/login_activities_controller.rb index 50e2d70cb9a..ae32dbf5570 100644 --- a/app/controllers/settings/login_activities_controller.rb +++ b/app/controllers/settings/login_activities_controller.rb @@ -5,6 +5,6 @@ class Settings::LoginActivitiesController < Settings::BaseController skip_before_action :require_functional! def index - @login_activities = LoginActivity.where(user: current_user).order(id: :desc).page(params[:page]) + @login_activities = current_user.login_activities.order(id: :desc).page(params[:page]) end end diff --git a/app/models/user.rb b/app/models/user.rb index 966ffbe9c37..762522f2822 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -90,6 +90,7 @@ class User < ApplicationRecord has_many :applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: nil has_many :backups, inverse_of: :user, dependent: nil has_many :invites, inverse_of: :user, dependent: nil + has_many :login_activities, inverse_of: :user, dependent: :destroy has_many :markers, inverse_of: :user, dependent: :destroy has_many :webauthn_credentials, dependent: :destroy has_many :ips, class_name: 'UserIp', inverse_of: :user, dependent: nil diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index c852b16a69c..949af2a4259 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -100,11 +100,14 @@ RSpec.describe Auth::SessionsController do let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') } context 'when using a valid password' do - before do + subject do post :create, params: { user: { email: user.email, password: user.password } } end it 'redirects to home and logs the user in' do + expect { subject } + .to change(user.login_activities.where(success: true), :count).by(1) + expect(response).to redirect_to(root_path) expect(controller.current_user).to eq user @@ -265,10 +268,9 @@ RSpec.describe Auth::SessionsController do it 'does not log the user in, sets a flash message, and sends a suspicious sign in email', :inline_jobs do emails = capture_emails do - Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR.times do - post :create, params: { user: { otp_attempt: '1234' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s } - expect(controller.current_user).to be_nil - end + expect { process_maximum_two_factor_attempts } + .to change(user.login_activities.where(success: false), :count).by(1) + post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s } end @@ -286,6 +288,13 @@ RSpec.describe Auth::SessionsController do subject: eq(I18n.t('user_mailer.failed_2fa.subject')) ) end + + def process_maximum_two_factor_attempts + Auth::SessionsController::MAX_2FA_ATTEMPTS_PER_HOUR.times do + post :create, params: { user: { otp_attempt: '1234' } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s } + expect(controller.current_user).to be_nil + end + end end context 'when using a valid OTP' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b732b2d84d0..c71b7a600ba 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -24,6 +24,7 @@ RSpec.describe User do describe 'Associations' do it { is_expected.to belong_to(:account).required } + it { is_expected.to have_many(:login_activities) } end describe 'Validations' do From 6c2db9b1cf36d146556ade8cec7f6cba93ee6774 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 06:32:00 +0000 Subject: [PATCH 056/660] fix(deps): update dependency vite-plugin-static-copy to v3.1.1 (#35367) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index ea96821fbed..f06168c04a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5764,7 +5764,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.3": +"chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -13904,17 +13904,17 @@ __metadata: linkType: hard "vite-plugin-static-copy@npm:^3.1.0": - version: 3.1.0 - resolution: "vite-plugin-static-copy@npm:3.1.0" + version: 3.1.1 + resolution: "vite-plugin-static-copy@npm:3.1.1" dependencies: - chokidar: "npm:^3.5.3" + chokidar: "npm:^3.6.0" fs-extra: "npm:^11.3.0" p-map: "npm:^7.0.3" picocolors: "npm:^1.1.1" tinyglobby: "npm:^0.2.14" peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/dce43f12ecc71417f1afd530d15b316774fe0441c2502e48e2bfafcd07fd4ae90a5782621f932d8d12a8c8213bed6746e80d5452e2fb216ece2bcf7e80309f82 + checksum: 10c0/a4dd5d31212b037d4902d55c2ee83866e496857bf948f258599dc24ec61b4628cf0dd23e4a7d7dc189d33ad1489427e976fa95e4db61b394d0be4f63077ef92c languageName: node linkType: hard From 4b8e60682de0bf009069c074072e2ec145deee0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 06:32:38 +0000 Subject: [PATCH 057/660] fix(deps): update dependency react-select to v5.10.2 (#35352) 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 f06168c04a5..04b93f4ade8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11298,8 +11298,8 @@ __metadata: linkType: hard "react-select@npm:^5.7.3": - version: 5.10.1 - resolution: "react-select@npm:5.10.1" + version: 5.10.2 + resolution: "react-select@npm:5.10.2" dependencies: "@babel/runtime": "npm:^7.12.0" "@emotion/cache": "npm:^11.4.0" @@ -11313,7 +11313,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/0d10a249b96150bd648f2575d59c848b8fac7f4d368a97ae84e4aaba5bbc1035deba4cdc82e49a43904b79ec50494505809618b0e98022b2d51e7629551912ed + checksum: 10c0/2027ef57b0a375d1accdad60550329dc0911b31d429ec6eb9ae391ae9baf9f26991b0ff8615eb99de319a55ce6e9382107783e615cb2116522ed0275b214b7f7 languageName: node linkType: hard From 82a6ff091f79c4933a10dc901db1411625629013 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 15 Jul 2025 09:52:34 +0200 Subject: [PATCH 058/660] fix: Improve `Dropdown` component accessibility (#35373) --- .../mastodon/components/dropdown_menu.tsx | 80 ++++++------------- .../mastodon/components/icon_button.tsx | 13 --- .../navigation_panel/components/more_link.tsx | 26 +++--- .../styles/mastodon/components.scss | 8 +- 4 files changed, 47 insertions(+), 80 deletions(-) diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx index 23d77f0dda2..d9c87e93a72 100644 --- a/app/javascript/mastodon/components/dropdown_menu.tsx +++ b/app/javascript/mastodon/components/dropdown_menu.tsx @@ -5,6 +5,7 @@ import { useCallback, cloneElement, Children, + useId, } from 'react'; import classNames from 'classnames'; @@ -16,6 +17,7 @@ import Overlay from 'react-overlays/Overlay'; import type { OffsetValue, UsePopperOptions, + Placement, } from 'react-overlays/esm/usePopper'; import { fetchRelationships } from 'mastodon/actions/accounts'; @@ -295,6 +297,11 @@ interface DropdownProps { title?: string; disabled?: boolean; scrollable?: boolean; + placement?: Placement; + /** + * Prevent the `ScrollableList` with this scrollKey + * from being scrolled while the dropdown is open + */ scrollKey?: string; status?: ImmutableMap; forceDropdown?: boolean; @@ -316,6 +323,7 @@ export const Dropdown = ({ title = 'Menu', disabled, scrollable, + placement = 'bottom', status, forceDropdown = false, renderItem, @@ -331,16 +339,15 @@ export const Dropdown = ({ ); const [currentId] = useState(id++); const open = currentId === openDropdownId; - const activeElement = useRef(null); - const targetRef = useRef(null); + const buttonRef = useRef(null); + const menuId = useId(); const prefetchAccountId = status ? status.getIn(['account', 'id']) : undefined; const handleClose = useCallback(() => { - if (activeElement.current) { - activeElement.current.focus({ preventScroll: true }); - activeElement.current = null; + if (buttonRef.current) { + buttonRef.current.focus({ preventScroll: true }); } dispatch( @@ -375,7 +382,7 @@ export const Dropdown = ({ [handleClose, onItemClick, items], ); - const handleClick = useCallback( + const toggleDropdown = useCallback( (e: React.MouseEvent | React.KeyboardEvent) => { const { type } = e; @@ -423,38 +430,6 @@ export const Dropdown = ({ ], ); - const handleMouseDown = useCallback(() => { - if (!open && document.activeElement instanceof HTMLElement) { - activeElement.current = document.activeElement; - } - }, [open]); - - const handleButtonKeyDown = useCallback( - (e: React.KeyboardEvent) => { - switch (e.key) { - case ' ': - case 'Enter': - handleMouseDown(); - break; - } - }, - [handleMouseDown], - ); - - const handleKeyPress = useCallback( - (e: React.KeyboardEvent) => { - switch (e.key) { - case ' ': - case 'Enter': - handleClick(e); - e.stopPropagation(); - e.preventDefault(); - break; - } - }, - [handleClick], - ); - useEffect(() => { return () => { if (currentId === openDropdownId) { @@ -465,14 +440,16 @@ export const Dropdown = ({ let button: React.ReactElement; + const buttonProps = { + disabled, + onClick: toggleDropdown, + 'aria-expanded': open, + 'aria-controls': menuId, + ref: buttonRef, + }; + if (children) { - button = cloneElement(Children.only(children), { - onClick: handleClick, - onMouseDown: handleMouseDown, - onKeyDown: handleButtonKeyDown, - onKeyPress: handleKeyPress, - ref: targetRef, - }); + button = cloneElement(Children.only(children), buttonProps); } else if (icon && iconComponent) { button = ( ({ iconComponent={iconComponent} title={title} active={open} - disabled={disabled} - onClick={handleClick} - onMouseDown={handleMouseDown} - onKeyDown={handleButtonKeyDown} - onKeyPress={handleKeyPress} - ref={targetRef} + {...buttonProps} /> ); } else { @@ -499,13 +471,13 @@ export const Dropdown = ({ {({ props, arrowProps, placement }) => ( -
+
; onMouseDown?: React.MouseEventHandler; onKeyDown?: React.KeyboardEventHandler; - onKeyPress?: React.KeyboardEventHandler; active?: boolean; expanded?: boolean; style?: React.CSSProperties; @@ -45,7 +44,6 @@ export const IconButton = forwardRef( activeStyle, onClick, onKeyDown, - onKeyPress, onMouseDown, active = false, disabled = false, @@ -85,16 +83,6 @@ export const IconButton = forwardRef( [disabled, onClick], ); - const handleKeyPress: React.KeyboardEventHandler = - useCallback( - (e) => { - if (!disabled) { - onKeyPress?.(e); - } - }, - [disabled, onKeyPress], - ); - const handleMouseDown: React.MouseEventHandler = useCallback( (e) => { @@ -161,7 +149,6 @@ export const IconButton = forwardRef( onClick={handleClick} onMouseDown={handleMouseDown} onKeyDown={handleKeyDown} - onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated style={buttonStyle} tabIndex={tabIndex} disabled={disabled} diff --git a/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx b/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx index a26935eacf8..a3477ec4e55 100644 --- a/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx +++ b/app/javascript/mastodon/features/navigation_panel/components/more_link.tsx @@ -50,16 +50,22 @@ export const MoreLink: React.FC = () => { const menu = useMemo(() => { const arr: MenuItem[] = [ - { text: intl.formatMessage(messages.filters), href: '/filters' }, - { text: intl.formatMessage(messages.mutes), to: '/mutes' }, - { text: intl.formatMessage(messages.blocks), to: '/blocks' }, { - text: intl.formatMessage(messages.domainBlocks), - to: '/domain_blocks', + href: '/filters', + text: intl.formatMessage(messages.filters), + }, + { + to: '/mutes', + text: intl.formatMessage(messages.mutes), + }, + { + to: '/blocks', + text: intl.formatMessage(messages.blocks), + }, + { + to: '/domain_blocks', + text: intl.formatMessage(messages.domainBlocks), }, - ]; - - arr.push( null, { href: '/settings/privacy', @@ -77,7 +83,7 @@ export const MoreLink: React.FC = () => { href: '/settings/export', text: intl.formatMessage(messages.importExport), }, - ); + ]; if (canManageReports(permissions)) { arr.push(null, { @@ -106,7 +112,7 @@ export const MoreLink: React.FC = () => { }, [intl, dispatch, permissions]); return ( - +
); diff --git a/app/javascript/mastodon/features/notifications/components/select_with_label.tsx b/app/javascript/mastodon/features/notifications/components/select_with_label.tsx index 413267c0f8c..b25f8e66be2 100644 --- a/app/javascript/mastodon/features/notifications/components/select_with_label.tsx +++ b/app/javascript/mastodon/features/notifications/components/select_with_label.tsx @@ -1,5 +1,5 @@ import type { PropsWithChildren } from 'react'; -import { useCallback, useState, useRef } from 'react'; +import { useCallback, useState, useRef, useId } from 'react'; import classNames from 'classnames'; @@ -16,6 +16,8 @@ interface DropdownProps { options: SelectItem[]; disabled?: boolean; onChange: (value: string) => void; + 'aria-labelledby': string; + 'aria-describedby'?: string; placement?: Placement; } @@ -24,51 +26,33 @@ const Dropdown: React.FC = ({ options, disabled, onChange, + 'aria-labelledby': ariaLabelledBy, + 'aria-describedby': ariaDescribedBy, placement: initialPlacement = 'bottom-end', }) => { - const activeElementRef = useRef(null); - const containerRef = useRef(null); + const containerRef = useRef(null); + const buttonRef = useRef(null); const [isOpen, setOpen] = useState(false); const [placement, setPlacement] = useState(initialPlacement); - - const handleToggle = useCallback(() => { - if ( - isOpen && - activeElementRef.current && - activeElementRef.current instanceof HTMLElement - ) { - activeElementRef.current.focus({ preventScroll: true }); - } - - setOpen(!isOpen); - }, [isOpen, setOpen]); - - const handleMouseDown = useCallback(() => { - if (!isOpen) activeElementRef.current = document.activeElement; - }, [isOpen]); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - switch (e.key) { - case ' ': - case 'Enter': - if (!isOpen) activeElementRef.current = document.activeElement; - break; - } - }, - [isOpen], - ); + const uniqueId = useId(); + const menuId = `${uniqueId}-menu`; + const buttonLabelId = `${uniqueId}-button`; const handleClose = useCallback(() => { - if ( - isOpen && - activeElementRef.current && - activeElementRef.current instanceof HTMLElement - ) - activeElementRef.current.focus({ preventScroll: true }); + if (isOpen && buttonRef.current) { + buttonRef.current.focus({ preventScroll: true }); + } setOpen(false); }, [isOpen]); + const handleToggle = useCallback(() => { + if (isOpen) { + handleClose(); + } else { + setOpen(true); + } + }, [isOpen, handleClose]); + const handleOverlayEnter = useCallback( (state: Partial) => { if (state.placement) setPlacement(state.placement); @@ -82,13 +66,18 @@ const Dropdown: React.FC = ({
@@ -101,7 +90,7 @@ const Dropdown: React.FC = ({ popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }} > {({ props, placement }) => ( -
+
@@ -123,6 +112,8 @@ const Dropdown: React.FC = ({ interface Props { value: string; options: SelectItem[]; + label: string | React.ReactElement; + hint: string | React.ReactElement; disabled?: boolean; onChange: (value: string) => void; } @@ -130,13 +121,26 @@ interface Props { export const SelectWithLabel: React.FC> = ({ value, options, + label, + hint, disabled, - children, onChange, }) => { + const uniqueId = useId(); + const labelId = `${uniqueId}-label`; + const descId = `${uniqueId}-desc`; + return ( + // This label is only used for its click-forwarding behaviour, + // accessible names are assigned manually + // eslint-disable-next-line jsx-a11y/label-has-associated-control
- + ); }; diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index 86431f62fd5..b38e5da1594 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -8,7 +8,6 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { HotKeys } from 'react-hotkeys'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react'; @@ -20,6 +19,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { Account } from 'mastodon/components/account'; import { Icon } from 'mastodon/components/icon'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import { StatusQuoteManager } from 'mastodon/components/status_quoted'; import { me } from 'mastodon/initial_state'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -137,7 +137,7 @@ class Notification extends ImmutablePureComponent { const { intl, unread } = this.props; return ( - +
@@ -149,7 +149,7 @@ class Notification extends ImmutablePureComponent {
- + ); } @@ -157,7 +157,7 @@ class Notification extends ImmutablePureComponent { const { intl, unread } = this.props; return ( - +
@@ -169,7 +169,7 @@ class Notification extends ImmutablePureComponent {
- + ); } @@ -195,7 +195,7 @@ class Notification extends ImmutablePureComponent { const { intl, unread } = this.props; return ( - +
@@ -217,7 +217,7 @@ class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} />
- + ); } @@ -225,7 +225,7 @@ class Notification extends ImmutablePureComponent { const { intl, unread } = this.props; return ( - +
@@ -247,7 +247,7 @@ class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} />
- + ); } @@ -259,7 +259,7 @@ class Notification extends ImmutablePureComponent { } return ( - +
@@ -282,7 +282,7 @@ class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} />
- + ); } @@ -294,7 +294,7 @@ class Notification extends ImmutablePureComponent { } return ( - +
@@ -317,7 +317,7 @@ class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} />
- + ); } @@ -331,7 +331,7 @@ class Notification extends ImmutablePureComponent { } return ( - +
@@ -358,7 +358,7 @@ class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} />
- + ); } @@ -371,7 +371,7 @@ class Notification extends ImmutablePureComponent { } return ( - +
-
+
); } @@ -394,7 +394,7 @@ class Notification extends ImmutablePureComponent { } return ( - +
-
+
); } @@ -410,7 +410,7 @@ class Notification extends ImmutablePureComponent { const { intl, unread } = this.props; return ( - +
@@ -422,7 +422,7 @@ class Notification extends ImmutablePureComponent {
- + ); } @@ -438,7 +438,7 @@ class Notification extends ImmutablePureComponent { const targetLink = ; return ( - +
@@ -450,7 +450,7 @@ class Notification extends ImmutablePureComponent {
- + ); } diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx index d5eb851985c..f0f2139ad21 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx @@ -1,9 +1,8 @@ import { useMemo } from 'react'; -import { HotKeys } from 'react-hotkeys'; - import { navigateToProfile } from 'mastodon/actions/accounts'; import { mentionComposeById } from 'mastodon/actions/compose'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import type { NotificationGroup as NotificationGroupModel } from 'mastodon/models/notification_group'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -156,5 +155,5 @@ export const NotificationGroup: React.FC<{ return null; } - return {content}; + return {content}; }; diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx index e7ed8792f67..4be1eefcddc 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx @@ -3,12 +3,11 @@ import type { JSX } from 'react'; import classNames from 'classnames'; -import { HotKeys } from 'react-hotkeys'; - import { replyComposeById } from 'mastodon/actions/compose'; import { navigateToStatus } from 'mastodon/actions/statuses'; import { Avatar } from 'mastodon/components/avatar'; import { AvatarGroup } from 'mastodon/components/avatar_group'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import type { IconProp } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; @@ -91,7 +90,7 @@ export const NotificationGroupWithStatus: React.FC<{ ); return ( - +
-
+
); }; diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_with_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_with_status.tsx index de484322fb0..96a4a4d65df 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_with_status.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_with_status.tsx @@ -2,14 +2,13 @@ import { useMemo } from 'react'; import classNames from 'classnames'; -import { HotKeys } from 'react-hotkeys'; - import { replyComposeById } from 'mastodon/actions/compose'; import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions'; import { navigateToStatus, toggleStatusSpoilers, } from 'mastodon/actions/statuses'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import type { IconProp } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon'; import { StatusQuoteManager } from 'mastodon/components/status_quoted'; @@ -83,7 +82,7 @@ export const NotificationWithStatus: React.FC<{ if (!statusId || isFiltered) return null; return ( - +
-
+
); }; diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 0f02e7b50ff..64cd0c4f825 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -10,10 +10,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; -import { HotKeys } from 'react-hotkeys'; - import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { TimelineHint } from 'mastodon/components/timeline_hint'; @@ -616,7 +615,7 @@ class Status extends ImmutablePureComponent {
{ancestors} - +
-
+
{descendants} {remoteHint} diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index c9834eb0a48..e8eef704efa 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -9,13 +9,13 @@ import { Redirect, Route, withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import { debounce } from 'lodash'; -import { HotKeys } from 'react-hotkeys'; import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; import { fetchNotifications } from 'mastodon/actions/notification_groups'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import { AlertsController } from 'mastodon/components/alerts_controller'; +import { Hotkeys } from 'mastodon/components/hotkeys'; import { HoverCardController } from 'mastodon/components/hover_card_controller'; import { PictureInPicture } from 'mastodon/features/picture_in_picture'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; @@ -98,40 +98,6 @@ const mapStateToProps = state => ({ username: state.getIn(['accounts', me, 'username']), }); -const keyMap = { - help: '?', - new: 'n', - search: ['s', '/'], - forceNew: 'option+n', - toggleComposeSpoilers: 'option+x', - focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'], - reply: 'r', - favourite: 'f', - boost: 'b', - mention: 'm', - open: ['enter', 'o'], - openProfile: 'p', - moveDown: ['down', 'j'], - moveUp: ['up', 'k'], - back: 'backspace', - goToHome: 'g h', - goToNotifications: 'g n', - goToLocal: 'g l', - goToFederated: 'g t', - goToDirect: 'g d', - goToStart: 'g s', - goToFavourites: 'g f', - goToPinned: 'g p', - goToProfile: 'g u', - goToBlocked: 'g b', - goToMuted: 'g m', - goToRequests: 'g r', - toggleHidden: 'x', - toggleSensitive: 'h', - openMedia: 'e', - onTranslate: 't', -}; - class SwitchingColumnsArea extends PureComponent { static propTypes = { identity: identityContextPropShape, @@ -400,6 +366,10 @@ class UI extends PureComponent { } }; + handleDonate = () => { + location.href = 'https://joinmastodon.org/sponsors#donate' + } + componentDidMount () { const { signedIn } = this.props.identity; @@ -426,10 +396,6 @@ class UI extends PureComponent { setTimeout(() => this.props.dispatch(fetchServer()), 3000); } - - this.hotkeys.__mousetrap__.stopCallback = (e, element) => { - return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName); - }; } componentWillUnmount () { @@ -509,10 +475,6 @@ class UI extends PureComponent { } }; - setHotkeysRef = c => { - this.hotkeys = c; - }; - handleHotkeyToggleHelp = () => { if (this.props.location.pathname === '/keyboard-shortcuts') { this.props.history.goBack(); @@ -593,10 +555,11 @@ class UI extends PureComponent { goToBlocked: this.handleHotkeyGoToBlocked, goToMuted: this.handleHotkeyGoToMuted, goToRequests: this.handleHotkeyGoToRequests, + cheat: this.handleDonate, }; return ( - +
{children} @@ -611,7 +574,7 @@ class UI extends PureComponent {
-
+
); } diff --git a/package.json b/package.json index 2ddb85b7631..f19a5564f86 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", - "react-hotkeys": "^1.1.4", "react-immutable-proptypes": "^2.2.0", "react-immutable-pure-component": "^2.2.2", "react-intl": "^7.1.10", diff --git a/yarn.lock b/yarn.lock index 3575fbedde0..3ede60941aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2706,7 +2706,6 @@ __metadata: react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-helmet: "npm:^6.1.0" - react-hotkeys: "npm:^1.1.4" react-immutable-proptypes: "npm:^2.2.0" react-immutable-pure-component: "npm:^2.2.2" react-intl: "npm:^7.1.10" @@ -9172,27 +9171,6 @@ __metadata: languageName: node linkType: hard -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 - languageName: node - linkType: hard - -"lodash.isequal@npm:^4.5.0": - version: 4.5.0 - resolution: "lodash.isequal@npm:4.5.0" - checksum: 10c0/dfdb2356db19631a4b445d5f37868a095e2402292d59539a987f134a8778c62a2810c2452d11ae9e6dcac71fc9de40a6fedcb20e2952a15b431ad8b29e50e28f - languageName: node - linkType: hard - -"lodash.isobject@npm:^3.0.2": - version: 3.0.2 - resolution: "lodash.isobject@npm:3.0.2" - checksum: 10c0/da4c8480d98b16835b59380b2fbd43c54081acd9466febb788ba77c434384349e0bec162d1c4e89f613f21687b2b6d8384d8a112b80da00c78d28d9915a5cdde - languageName: node - linkType: hard - "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -9603,13 +9581,6 @@ __metadata: languageName: node linkType: hard -"mousetrap@npm:^1.5.2": - version: 1.6.5 - resolution: "mousetrap@npm:1.6.5" - checksum: 10c0/5c361bdbbff3966fd58d70f39b9fe1f8e32c78f3ce65989d83af7aad32a3a95313ce835a8dd8a55cb5de9eeb7c1f0c2b9048631a3073b5606241589e8fc0ba53 - languageName: node - linkType: hard - "mrmime@npm:^2.0.0": version: 2.0.1 resolution: "mrmime@npm:2.0.1" @@ -11115,22 +11086,6 @@ __metadata: languageName: node linkType: hard -"react-hotkeys@npm:^1.1.4": - version: 1.1.4 - resolution: "react-hotkeys@npm:1.1.4" - dependencies: - lodash.isboolean: "npm:^3.0.3" - lodash.isequal: "npm:^4.5.0" - lodash.isobject: "npm:^3.0.2" - mousetrap: "npm:^1.5.2" - prop-types: "npm:^15.6.0" - peerDependencies: - react: ">= 0.14.0" - react-dom: ">= 0.14.0" - checksum: 10c0/6bd566ea97e00058749d43d768ee843e5132f988571536e090b564d5dbaa71093695255514fc5b9fcf9fbd03fcb0603f6e135dcab6dcaaffe43dedbfe742a163 - languageName: node - linkType: hard - "react-immutable-proptypes@npm:^2.2.0": version: 2.2.0 resolution: "react-immutable-proptypes@npm:2.2.0" From ee21f7221143d94aac00a6cbadea352ebfe44905 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Mon, 21 Jul 2025 17:57:20 +0200 Subject: [PATCH 087/660] fix: Don't submit post when pressing Enter in CW field (#35445) --- .../compose/components/compose_form.jsx | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index b563a6b45d4..5bc77c4bcd8 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -92,13 +92,29 @@ class ComposeForm extends ImmutablePureComponent { this.props.onChange(e.target.value); }; - handleKeyDown = (e) => { - if (e.key.toLowerCase() === 'enter' && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } + blurOnEscape = (e) => { if (['esc', 'escape'].includes(e.key.toLowerCase())) { - this.textareaRef.current?.blur(); + e.target.blur(); } + } + + handleKeyDownPost = (e) => { + if (e.key.toLowerCase() === 'enter' && (e.ctrlKey || e.metaKey)) { + this.handleSubmit(); + } + this.blurOnEscape(e); + }; + + handleKeyDownSpoiler = (e) => { + if (e.key.toLowerCase() === 'enter') { + if (e.ctrlKey || e.metaKey) { + this.handleSubmit(); + } else { + e.preventDefault(); + this.textareaRef.current?.focus(); + } + } + this.blurOnEscape(e); }; getFulltextForCharacterCounting = () => { @@ -251,7 +267,7 @@ class ComposeForm extends ImmutablePureComponent { value={this.props.spoilerText} disabled={isSubmitting} onChange={this.handleChangeSpoilerText} - onKeyDown={this.handleKeyDown} + onKeyDown={this.handleKeyDownSpoiler} ref={this.setSpoilerText} suggestions={this.props.suggestions} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} @@ -276,7 +292,7 @@ class ComposeForm extends ImmutablePureComponent { onChange={this.handleChange} suggestions={this.props.suggestions} onFocus={this.handleFocus} - onKeyDown={this.handleKeyDown} + onKeyDown={this.handleKeyDownPost} onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} onSuggestionsClearRequested={this.onSuggestionsClearRequested} onSuggestionSelected={this.onSuggestionSelected} From 20b3c43ddee386e603e4c8c519b1df9464803d11 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 21 Jul 2025 12:24:55 -0400 Subject: [PATCH 088/660] Update playwright-ruby-client to version 1.54.0 (#35448) --- Gemfile.lock | 6 +++--- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1c187ac9df1..094c9b1d6ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -438,7 +438,7 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0514) + mime-types-data (3.2025.0715) mini_mime (1.1.5) mini_portile2 (2.8.9) minitest (5.25.5) @@ -610,7 +610,7 @@ GEM pg (1.5.9) pghero (3.7.0) activerecord (>= 7.1) - playwright-ruby-client (1.52.0) + playwright-ruby-client (1.54.0) concurrent-ruby (>= 1.1.6) mime-types (>= 3.0) pp (0.6.2) @@ -722,7 +722,7 @@ GEM redlock (1.3.2) redis (>= 3.0.0, < 6.0) regexp_parser (2.10.0) - reline (0.6.1) + reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) diff --git a/package.json b/package.json index f19a5564f86..70ae4cd331c 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ "lint-staged": "^16.0.0", "msw": "^2.10.2", "msw-storybook-addon": "^2.0.5", - "playwright": "^1.52.0", + "playwright": "^1.54.1", "prettier": "^3.3.3", "react-test-renderer": "^18.2.0", "storybook": "^9.0.4", diff --git a/yarn.lock b/yarn.lock index 3ede60941aa..f44bce6efc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2698,7 +2698,7 @@ __metadata: msw: "npm:^2.10.2" msw-storybook-addon: "npm:^2.0.5" path-complete-extname: "npm:^1.0.0" - playwright: "npm:^1.52.0" + playwright: "npm:^1.54.1" postcss-preset-env: "npm:^10.1.5" prettier: "npm:^3.3.3" prop-types: "npm:^15.8.1" @@ -10313,27 +10313,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.52.0": - version: 1.52.0 - resolution: "playwright-core@npm:1.52.0" +"playwright-core@npm:1.54.1": + version: 1.54.1 + resolution: "playwright-core@npm:1.54.1" bin: playwright-core: cli.js - checksum: 10c0/640945507e6ca2144e9f596b2a6ecac042c2fd3683ff99e6271e9a7b38f3602d415f282609d569456f66680aab8b3c5bb1b257d8fb63a7fc0ed648261110421f + checksum: 10c0/b821262b024d7753b1bfa71eb2bc99f2dda12a869d175b2e1bc6ac2764bd661baf36d9d42f45caf622854ad7e4a6077b9b57014c74bb5a78fe339c9edf1c9019 languageName: node linkType: hard -"playwright@npm:^1.52.0": - version: 1.52.0 - resolution: "playwright@npm:1.52.0" +"playwright@npm:^1.54.1": + version: 1.54.1 + resolution: "playwright@npm:1.54.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.52.0" + playwright-core: "npm:1.54.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/2c6edf1e15e59bbaf77f3fa0fe0ac975793c17cff835d9c8b8bc6395a3b6f1c01898b3058ab37891b2e4d424bcc8f1b4844fe70d943e0143d239d7451408c579 + checksum: 10c0/c5fedae31a03a1f4c4846569aef3ffb98da23000a4d255abfc8c2ede15b43cc7cd87b80f6fa078666c030373de8103787cf77ef7653ae9458aabbbd4320c2599 languageName: node linkType: hard From bf17895d19dcd77ca4b16f54fadc1620d79557f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:28:01 +0200 Subject: [PATCH 089/660] chore(deps): update dependency chromatic to v13 (#35064) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 70ae4cd331c..a6ee9cef004 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "@vitest/browser": "^3.2.1", "@vitest/coverage-v8": "^3.2.0", "@vitest/ui": "^3.2.1", - "chromatic": "^12.1.0", + "chromatic": "^13.0.0", "eslint": "^9.23.0", "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", diff --git a/yarn.lock b/yarn.lock index f44bce6efc3..94ed02e6749 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2659,7 +2659,7 @@ __metadata: babel-plugin-formatjs: "npm:^10.5.37" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - chromatic: "npm:^12.1.0" + chromatic: "npm:^13.0.0" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" @@ -5815,9 +5815,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^12.1.0": - version: 12.1.0 - resolution: "chromatic@npm:12.1.0" +"chromatic@npm:^13.0.0": + version: 13.0.0 + resolution: "chromatic@npm:13.0.0" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -5830,7 +5830,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/4acb70a4a84605f1963a823beed4f3062ec91e373104500f4295af2298b8d0b49f864d06ca81bc9389e44cae3a284332aac07c6cbfc123aa6457f3b52a4c4b78 + checksum: 10c0/30c697eb84d5b3b8cdab989df0e4fed0bf51f4bfefb616873f68fc00337978b9b38b84e52af22861769176181bd98525d467baeb22daa712a0f7a58bd61bf336 languageName: node linkType: hard From 1ed58aaaf2f5296102197e1b5adda4f769312bf4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:28:49 +0200 Subject: [PATCH 090/660] fix(deps): update dependency axios to v1.10.0 (#35050) 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 94ed02e6749..c4d3179b76a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5379,13 +5379,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.9.0 - resolution: "axios@npm:1.9.0" + version: 1.10.0 + resolution: "axios@npm:1.10.0" dependencies: follow-redirects: "npm:^1.15.6" form-data: "npm:^4.0.0" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/9371a56886c2e43e4ff5647b5c2c3c046ed0a3d13482ef1d0135b994a628c41fbad459796f101c655e62f0c161d03883454474d2e435b2e021b1924d9f24994c + checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8 languageName: node linkType: hard From ae13063460a4b7ccc744a31858a11a8aac1a571d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 16:31:10 +0000 Subject: [PATCH 091/660] chore(deps): update dependency eslint-plugin-jsdoc to v51 (#35026) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 131 +++++++++++++++++++++++++-------------------------- 2 files changed, 65 insertions(+), 68 deletions(-) diff --git a/package.json b/package.json index a6ee9cef004..e1085909b76 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-import": "~2.31.0", - "eslint-plugin-jsdoc": "^50.6.9", + "eslint-plugin-jsdoc": "^51.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 c4d3179b76a..598b15acb53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,14 +1932,16 @@ __metadata: languageName: node linkType: hard -"@es-joy/jsdoccomment@npm:~0.49.0": - version: 0.49.0 - resolution: "@es-joy/jsdoccomment@npm:0.49.0" +"@es-joy/jsdoccomment@npm:~0.52.0": + version: 0.52.0 + resolution: "@es-joy/jsdoccomment@npm:0.52.0" dependencies: + "@types/estree": "npm:^1.0.8" + "@typescript-eslint/types": "npm:^8.34.1" comment-parser: "npm:1.4.1" esquery: "npm:^1.6.0" jsdoc-type-pratt-parser: "npm:~4.1.0" - checksum: 10c0/16717507d557d37e7b59456fedeefbe0a3bc93aa2d9c043d5db91e24e076509b6fcb10ee6fd1dafcb0c5bbe50ae329b45de5b83541cb5994a98c9e862a45641e + checksum: 10c0/4def78060ef58859f31757b9d30c4939fc33e7d9ee85637a7f568c1d209c33aa0abd2cf5a3a4f3662ec5b12b85ecff2f2035d809dc93b9382a31a6dfb200d83c languageName: node linkType: hard @@ -2674,7 +2676,7 @@ __metadata: eslint-import-resolver-typescript: "npm:^4.2.5" eslint-plugin-formatjs: "npm:^5.3.1" eslint-plugin-import: "npm:~2.31.0" - eslint-plugin-jsdoc: "npm:^50.6.9" + eslint-plugin-jsdoc: "npm:^51.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" @@ -3088,13 +3090,6 @@ __metadata: languageName: node linkType: hard -"@pkgr/core@npm:^0.1.0": - version: 0.1.1 - resolution: "@pkgr/core@npm:0.1.1" - checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8 - languageName: node - linkType: hard - "@polka/url@npm:^1.0.0-next.24": version: 1.0.0-next.29 resolution: "@polka/url@npm:1.0.0-next.29" @@ -3985,10 +3980,10 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.7, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": - version: 1.0.7 - resolution: "@types/estree@npm:1.0.7" - checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c +"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 languageName: node linkType: hard @@ -3999,6 +3994,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:1.0.7": + version: 1.0.7 + resolution: "@types/estree@npm:1.0.7" + checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^4.17.33": version: 4.17.41 resolution: "@types/express-serve-static-core@npm:4.17.41" @@ -4501,13 +4503,20 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.33.0, @typescript-eslint/types@npm:^8.33.0": +"@typescript-eslint/types@npm:8.33.0": version: 8.33.0 resolution: "@typescript-eslint/types@npm:8.33.0" checksum: 10c0/348b64eb408719d7711a433fc9716e0c2aab8b3f3676f5a1cc2e00269044132282cf655deb6d0dd9817544116909513de3b709005352d186949d1014fad1a3cb languageName: node linkType: hard +"@typescript-eslint/types@npm:^8.33.0, @typescript-eslint/types@npm:^8.34.1": + version: 8.36.0 + resolution: "@typescript-eslint/types@npm:8.36.0" + checksum: 10c0/cacb941a0caad6ab556c416051b97ec33b364b7c8e0703e2729ae43f12daf02b42eef12011705329107752e3f1685ca82cfffe181d637f85907293cb634bee31 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.29.1": version: 8.29.1 resolution: "@typescript-eslint/typescript-estree@npm:8.29.1" @@ -4996,12 +5005,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.14.0, acorn@npm:^8.8.2": - version: 8.14.1 - resolution: "acorn@npm:8.14.1" +"acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.8.2": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" bin: acorn: bin/acorn - checksum: 10c0/dbd36c1ed1d2fa3550140000371fcf721578095b18777b85a79df231ca093b08edc6858d75d6e48c73e431c174dcf9214edbd7e6fa5911b93bd8abfa54e47123 + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec languageName: node linkType: hard @@ -6780,7 +6789,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.5.3, es-module-lexer@npm:^1.7.0": +"es-module-lexer@npm:^1.7.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b @@ -7040,24 +7049,23 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^50.6.9": - version: 50.6.9 - resolution: "eslint-plugin-jsdoc@npm:50.6.9" +"eslint-plugin-jsdoc@npm:^51.0.0": + version: 51.3.4 + resolution: "eslint-plugin-jsdoc@npm:51.3.4" dependencies: - "@es-joy/jsdoccomment": "npm:~0.49.0" + "@es-joy/jsdoccomment": "npm:~0.52.0" are-docs-informative: "npm:^0.0.2" comment-parser: "npm:1.4.1" - debug: "npm:^4.3.6" + debug: "npm:^4.4.1" escape-string-regexp: "npm:^4.0.0" - espree: "npm:^10.1.0" + espree: "npm:^10.4.0" esquery: "npm:^1.6.0" - parse-imports: "npm:^2.1.1" - semver: "npm:^7.6.3" + parse-imports-exports: "npm:^0.2.4" + semver: "npm:^7.7.2" spdx-expression-parse: "npm:^4.0.0" - synckit: "npm:^0.9.1" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/cad199d262c2e889a3af4e402f6adc624e4273b3d5ca1940e7227b37d87af8090ca3444f7fff57f58dab9a827faed8722fc2f5d4daf31ec085eb00e9f5a338a7 + checksum: 10c0/59e5aa972bdd1bd4e2ca2796ed4455dff1069044abc028621e107aa4b0cbb62ce09554c8e7c2ff3a44a1cbd551e54b6970adc420ba3a89adc6236b94310a81ff languageName: node linkType: hard @@ -7163,10 +7171,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0": - version: 4.2.0 - resolution: "eslint-visitor-keys@npm:4.2.0" - checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269 +"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 languageName: node linkType: hard @@ -7220,14 +7228,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.1.0, espree@npm:^10.3.0": - version: 10.3.0 - resolution: "espree@npm:10.3.0" +"espree@npm:^10.0.1, espree@npm:^10.3.0, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" dependencies: - acorn: "npm:^8.14.0" + acorn: "npm:^8.15.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462 + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b languageName: node linkType: hard @@ -10003,13 +10011,12 @@ __metadata: languageName: node linkType: hard -"parse-imports@npm:^2.1.1": - version: 2.1.1 - resolution: "parse-imports@npm:2.1.1" +"parse-imports-exports@npm:^0.2.4": + version: 0.2.4 + resolution: "parse-imports-exports@npm:0.2.4" dependencies: - es-module-lexer: "npm:^1.5.3" - slashes: "npm:^3.0.12" - checksum: 10c0/c9bb0b4e1823f84f034d2d7bd2b37415b1715a5c963fda14968c706186b48b02c10e97d04bce042b9dcd679b42f29c391ea120799ddf581c7f54786edd99e3a9 + parse-statements: "npm:1.0.11" + checksum: 10c0/51b729037208abdf65c4a1f8e9ed06f4e7ccd907c17c668a64db54b37d95bb9e92081f8b16e4133e14102af3cb4e89870975b6ad661b4d654e9ec8f4fb5c77d6 languageName: node linkType: hard @@ -10025,6 +10032,13 @@ __metadata: languageName: node linkType: hard +"parse-statements@npm:1.0.11": + version: 1.0.11 + resolution: "parse-statements@npm:1.0.11" + checksum: 10c0/48960e085019068a5f5242e875fd9d21ec87df2e291acf5ad4e4887b40eab6929a8c8d59542acb85a6497e870c5c6a24f5ab7f980ef5f907c14cc5f7984a93f3 + languageName: node + linkType: hard + "parse5@npm:^7.2.1": version: 7.2.1 resolution: "parse5@npm:7.2.1" @@ -12031,7 +12045,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.1": +"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.2": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -12236,13 +12250,6 @@ __metadata: languageName: node linkType: hard -"slashes@npm:^3.0.12": - version: 3.0.12 - resolution: "slashes@npm:3.0.12" - checksum: 10c0/71ca2a1fcd1ab6814b0fdb8cf9c33a3d54321deec2aa8d173510f0086880201446021a9b9e6a18561f7c472b69a2145977c6a8fb9c53a8ff7be31778f203d175 - languageName: node - linkType: hard - "slice-ansi@npm:^4.0.0": version: 4.0.0 resolution: "slice-ansi@npm:4.0.0" @@ -12930,16 +12937,6 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.9.1": - version: 0.9.1 - resolution: "synckit@npm:0.9.1" - dependencies: - "@pkgr/core": "npm:^0.1.0" - tslib: "npm:^2.6.2" - checksum: 10c0/d8b89e1bf30ba3ffb469d8418c836ad9c0c062bf47028406b4d06548bc66af97155ea2303b96c93bf5c7c0f0d66153a6fbd6924c76521b434e6a9898982abc2e - languageName: node - linkType: hard - "systemjs@npm:^6.15.1": version: 6.15.1 resolution: "systemjs@npm:6.15.1" @@ -13280,7 +13277,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 From be3dc5b50857021fe164733ac4c998d35d72b4fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:10:37 +0200 Subject: [PATCH 092/660] New Crowdin Translations (automated) (#35453) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/pa.json | 50 +++++++++++++++- app/javascript/mastodon/locales/zh-CN.json | 2 + config/locales/et.yml | 4 ++ config/locales/ga.yml | 4 +- config/locales/nan.yml | 70 ++++++++++++++++++++++ config/locales/pa.yml | 9 +++ config/locales/simple_form.es-MX.yml | 6 +- 7 files changed, 138 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index 8ab4eb22527..5236d246c0f 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -1,8 +1,12 @@ { "about.contact": "ਸੰਪਰਕ:", + "about.default_locale": "ਮੂਲ", + "about.disclaimer": "ਮਸਟੋਡੋਨ ਇੱਕ ਆਜ਼ਾਦ, ਖੁੱਲ੍ਹੇ ਸਰੋਤ ਵਾਲਾ ਸਾਫਟਵੇਅਰ ਹੈ ਅਤੇ Mastodon gGmbH ਦਾ ਮਾਰਕਾ ਹੈ।", "about.domain_blocks.no_reason_available": "ਕਾਰਨ ਮੌਜੂਦ ਨਹੀਂ ਹੈ", "about.domain_blocks.silenced.title": "ਸੀਮਿਤ", - "about.domain_blocks.suspended.title": "ਮੁਅੱਤਲ ਕੀਤੀ", + "about.domain_blocks.suspended.title": "ਸਸਪੈਂਡ ਕੀਤਾ", + "about.language_label": "ਭਾਸ਼ਾ", + "about.not_available": "ਇਹ ਜਾਣਕਾਰੀ ਨੂੰ ਇਸ ਸਰਵਰ ਉੱਤੇ ਉਪਲੱਬਧ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।", "about.rules": "ਸਰਵਰ ਨਿਯਮ", "account.account_note_header": "ਨਿੱਜੀ ਨੋਟ", "account.add_or_remove_from_list": "ਸੂਚੀ ਵਿੱਚ ਜੋੜੋ ਜਾਂ ਹਟਾਓ", @@ -12,21 +16,33 @@ "account.block_domain": "{domain} ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਓ", "account.block_short": "ਪਾਬੰਦੀ", "account.blocked": "ਪਾਬੰਦੀਸ਼ੁਦਾ", + "account.blocking": "ਪਾਬੰਦੀ ਲਾਉਣੀ", "account.cancel_follow_request": "ਫ਼ਾਲੋ ਕਰਨ ਨੂੰ ਰੱਦ ਕਰੋ", "account.copy": "ਪਰੋਫਾਇਲ ਲਈ ਲਿੰਕ ਕਾਪੀ ਕਰੋ", "account.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ @{name}", + "account.disable_notifications": "ਜਦੋਂ {name} ਕੋਈ ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਨਾ ਦਿਓ", + "account.domain_blocking": "ਡੋਮੇਨ ਉੱਤੇ ਪਾਬੰਦੀ", "account.edit_profile": "ਪਰੋਫਾਈਲ ਨੂੰ ਸੋਧੋ", "account.enable_notifications": "ਜਦੋਂ {name} ਪੋਸਟ ਕਰੇ ਤਾਂ ਮੈਨੂੰ ਸੂਚਨਾ ਦਿਓ", "account.endorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ", + "account.familiar_followers_one": "{name1} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ", + "account.familiar_followers_two": "{name1} ਅਤੇ {name2} ਵਲੋਂ ਫ਼ਾਲੋ ਕੀਤਾ", + "account.featured": "ਫ਼ੀਚਰ", + "account.featured.accounts": "ਪਰੋਫਾਈਲ", + "account.featured.hashtags": "ਹੈਸ਼ਟੈਗ", "account.featured_tags.last_status_at": "{date} ਨੂੰ ਆਖਰੀ ਪੋਸਟ", "account.featured_tags.last_status_never": "ਕੋਈ ਪੋਸਟ ਨਹੀਂ", "account.follow": "ਫ਼ਾਲੋ", "account.follow_back": "ਵਾਪਸ ਫਾਲ਼ੋ ਕਰੋ", "account.followers": "ਫ਼ਾਲੋਅਰ", "account.followers.empty": "ਇਸ ਵਰਤੋਂਕਾਰ ਨੂੰ ਹਾਲੇ ਕੋਈ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", + "account.followers_counter": "{count, plural, one {{counter} ਫ਼ਾਲੋਅਰ} other {{counter} ਫ਼ਾਲੋਅਰ}}", + "account.followers_you_know_counter": "{counter} ਤੁਸੀਂ ਜਾਣਦੇ ਹੋ", "account.following": "ਫ਼ਾਲੋ ਕੀਤਾ", "account.follows.empty": "ਇਹ ਵਰਤੋਂਕਾਰ ਹਾਲੇ ਕਿਸੇ ਨੂੰ ਫ਼ਾਲੋ ਨਹੀਂ ਕਰਦਾ ਹੈ।", + "account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹਨ", "account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ", + "account.hide_reblogs": "{name} ਵਲੋਂ ਬੂਸਟ ਨੂੰ ਲੁਕਾਓ", "account.joined_short": "ਜੁਆਇਨ ਕੀਤਾ", "account.media": "ਮੀਡੀਆ", "account.mention": "@{name} ਦਾ ਜ਼ਿਕਰ", @@ -34,6 +50,7 @@ "account.mute_notifications_short": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਮੌਨ ਕਰੋ", "account.mute_short": "ਮੌਨ ਕਰੋ", "account.muted": "ਮੌਨ ਕੀਤੀਆਂ", + "account.mutual": "ਤੁਸੀਂ ਇੱਕ ਦੂਜੇ ਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹੋ", "account.no_bio": "ਕੋਈ ਵਰਣਨ ਨਹੀਂ ਦਿੱਤਾ।", "account.open_original_page": "ਅਸਲ ਸਫ਼ੇ ਨੂੰ ਖੋਲ੍ਹੋ", "account.posts": "ਪੋਸਟਾਂ", @@ -42,8 +59,10 @@ "account.requested": "ਮਨਜ਼ੂਰੀ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਫ਼ਾਲੋ ਬੇਨਤੀਆਂ ਨੂੰ ਰੱਦ ਕਰਨ ਲਈ ਕਲਿੱਕ ਕਰੋ", "account.requested_follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", "account.share": "{name} ਦਾ ਪਰੋਫ਼ਾਇਲ ਸਾਂਝਾ ਕਰੋ", + "account.statuses_counter": "{count, plural, one {{counter} ਪੋਸਟ} other {{counter} ਪੋਸਟਾਂ}}", "account.unblock": "@{name} ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", "account.unblock_domain": "{domain} ਡੋਮੇਨ ਤੋਂ ਪਾਬੰਦੀ ਹਟਾਓ", + "account.unblock_domain_short": "ਪਾਬੰਦੀ ਹਟਾਓ", "account.unblock_short": "ਪਾਬੰਦੀ ਹਟਾਓ", "account.unendorse": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਫ਼ੀਚਰ ਨਾ ਕਰੋ", "account.unfollow": "ਅਣ-ਫ਼ਾਲੋ", @@ -148,6 +167,8 @@ "confirmations.missing_alt_text.secondary": "ਕਿਵੇਂ ਵੀ ਪੋਸਟ ਕਰੋ", "confirmations.mute.confirm": "ਮੌਨ ਕਰੋ", "confirmations.redraft.confirm": "ਹਟਾਓ ਤੇ ਮੁੜ-ਡਰਾਫਟ", + "confirmations.remove_from_followers.confirm": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਓ", + "confirmations.remove_from_followers.title": "ਫ਼ਾਲੋਅਰ ਨੂੰ ਹਟਾਉਣਾ ਹੈ?", "confirmations.unfollow.confirm": "ਅਣ-ਫ਼ਾਲੋ", "confirmations.unfollow.message": "ਕੀ ਤੁਸੀਂ {name} ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?", "confirmations.unfollow.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਅਣ-ਫ਼ਾਲੋ ਕਰਨਾ ਹੈ?", @@ -182,7 +203,9 @@ "emoji_button.custom": "ਕਸਟਮ", "emoji_button.flags": "ਝੰਡੀਆਂ", "emoji_button.food": "ਖਾਣਾ-ਪੀਣਾ", + "emoji_button.label": "ਇਮੋਜੀ ਪਾਓ", "emoji_button.nature": "ਕੁਦਰਤ", + "emoji_button.not_found": "ਕੋਈ ਮਿਲਦਾ ਇਮੋਜ਼ੀ ਨਹੀਂ ਲੱਭਿਆ", "emoji_button.objects": "ਇਕਾਈ", "emoji_button.people": "ਲੋਕ", "emoji_button.recent": "ਅਕਸਰ ਵਰਤੇ", @@ -199,9 +222,15 @@ "empty_column.list": "ਇਸ ਸੂਚੀ ਵਿੱਚ ਹਾਲੇ ਕੁਝ ਵੀ ਨਹੀਂ ਹੈ। ਜਦੋਂ ਇਸ ਸੂਚੀ ਦੇ ਮੈਂਬਰ ਨਵੀਆਂ ਪੋਸਟਾਂ ਪਾਉਂਦੇ ਹਨ ਤਾਂ ਉਹ ਇੱਥੇ ਦਿਖਾਈ ਦੇਣਗੀਆਂ।", "errors.unexpected_crash.report_issue": "ਮੁੱਦੇ ਦੀ ਰਿਪੋਰਟ ਕਰੋ", "explore.suggested_follows": "ਲੋਕ", + "explore.title": "ਰੁਝਾਨ", "explore.trending_links": "ਖ਼ਬਰਾਂ", "explore.trending_statuses": "ਪੋਸਟਾਂ", "explore.trending_tags": "ਹੈਸ਼ਟੈਗ", + "featured_carousel.header": "{count, plural, one {ਟੰਗੀ ਹੋਈ ਪੋਸਟ} other {ਟੰਗੀਆਂ ਹੋਈਆਂ ਪੋਸਟਾਂ}}", + "featured_carousel.next": "ਅੱਗੇ", + "featured_carousel.post": "ਪੋਸਟ", + "featured_carousel.previous": "ਪਿੱਛੇ", + "featured_carousel.slide": "{total} ਵਿੱਚੋਂ {index}", "filter_modal.added.expired_title": "ਫਿਲਟਰ ਦੀ ਮਿਆਦ ਪੁੱਗੀ!", "filter_modal.added.review_and_configure_title": "ਫਿਲਟਰ ਸੈਟਿੰਗਾਂ", "filter_modal.added.settings_link": "ਸੈਟਿੰਗਾਂ ਸਫ਼ਾ", @@ -252,6 +281,8 @@ "home.column_settings.show_replies": "ਜਵਾਬਾਂ ਨੂੰ ਵੇਖੋ", "home.hide_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ", "home.pending_critical_update.link": "ਅੱਪਡੇਟ ਵੇਖੋ", + "home.pending_critical_update.title": "ਗੰਭੀਰ ਸੁਰੱਖਿਆ ਅੱਪਡੇਟ ਮੌਜੂਦ ਹੈ!", + "home.show_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਵੇਖਾਓ", "ignore_notifications_modal.ignore": "ਨੋਟਫਿਕੇਸ਼ਨਾਂ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰੋ", "info_button.label": "ਮਦਦ", "interaction_modal.go": "ਜਾਓ", @@ -332,9 +363,12 @@ "media_gallery.hide": "ਲੁਕਾਓ", "mute_modal.hide_from_notifications": "ਨੋਟੀਫਿਕੇਸ਼ਨਾਂ ਵਿੱਚੋਂ ਲੁਕਾਓ", "mute_modal.show_options": "ਚੋਣਾਂ ਨੂੰ ਵੇਖਾਓ", + "mute_modal.title": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਮੌਨ ਕਰਨਾ ਹੈ?", "navigation_bar.about": "ਇਸ ਬਾਰੇ", + "navigation_bar.account_settings": "ਪਾਸਵਰਡ ਅਤੇ ਸੁਰੱਖਿਆ", "navigation_bar.administration": "ਪਰਸ਼ਾਸ਼ਨ", "navigation_bar.advanced_interface": "ਤਕਨੀਕੀ ਵੈੱਬ ਇੰਟਰਫੇਸ ਵਿੱਚ ਖੋਲ੍ਹੋ", + "navigation_bar.automated_deletion": "ਆਪਣੇ-ਆਪ ਹਟਾਈ ਪੋਸਟ", "navigation_bar.blocks": "ਪਾਬੰਦੀ ਲਾਏ ਵਰਤੋਂਕਾਰ", "navigation_bar.bookmarks": "ਬੁੱਕਮਾਰਕ", "navigation_bar.direct": "ਨਿੱਜੀ ਜ਼ਿਕਰ", @@ -346,11 +380,16 @@ "navigation_bar.follows_and_followers": "ਫ਼ਾਲੋ ਅਤੇ ਫ਼ਾਲੋ ਕਰਨ ਵਾਲੇ", "navigation_bar.lists": "ਸੂਚੀਆਂ", "navigation_bar.logout": "ਲਾਗ ਆਉਟ", + "navigation_bar.more": "ਹੋਰ", "navigation_bar.mutes": "ਮੌਨ ਕੀਤੇ ਵਰਤੋਂਕਾਰ", "navigation_bar.preferences": "ਪਸੰਦਾਂ", + "navigation_bar.privacy_and_reach": "ਪਰਦੇਦਾਰੀ ਅਤੇ ਪਹੁੰਚ", "navigation_bar.search": "ਖੋਜੋ", + "navigation_bar.search_trends": "ਖੋਜ / ਰੁਝਾਨ", "not_signed_in_indicator.not_signed_in": "ਇਹ ਸਰੋਤ ਵਰਤਣ ਲਈ ਤੁਹਾਨੂੰ ਲਾਗਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।", "notification.admin.sign_up": "{name} ਨੇ ਸਾਈਨ ਅੱਪ ਕੀਤਾ", + "notification.favourite": "{name} ਨੇ ਤੁਹਾਡੀ ਪੋਸਟ ਨੂੰ ਪਸੰਦ ਕੀਤਾ", + "notification.favourite_pm": "{name} ਨੇ ਤੁਹਾਡੇ ਨਿੱਜੀ ਜ਼ਿਕਰ ਨੂੰ ਪਸੰਦ ਕੀਤਾ", "notification.follow": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ", "notification.follow.name_and_others": "{name} ਅਤੇ {count, plural, one {# ਹੋਰ} other {# ਹੋਰਾਂ}} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕੀਤਾ", "notification.follow_request": "{name} ਨੇ ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਨ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ", @@ -365,6 +404,7 @@ "notification.moderation_warning.action_silence": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਸੀਮਿਤ ਕੀਤਾ ਗਿਆ ਹੈ।", "notification.moderation_warning.action_suspend": "ਤੁਹਾਡੇ ਖਾਤੇ ਨੂੰ ਮੁਅੱਤਲ ਕੀਤਾ ਗਿਆ ਹੈ।", "notification.reblog": "{name} boosted your status", + "notification.relationships_severance_event": "{name} ਨਾਲ ਕਨੈਕਸ਼ਨ ਗੁਆਚੇ", "notification.relationships_severance_event.learn_more": "ਹੋਰ ਜਾਣੋ", "notification.status": "{name} ਨੇ ਹੁਣੇ ਪੋਸਟ ਕੀਤਾ", "notification.update": "{name} ਨੋ ਪੋਸਟ ਨੂੰ ਸੋਧਿਆ", @@ -540,7 +580,10 @@ "status.unpin": "ਪਰੋਫਾਈਲ ਤੋਂ ਲਾਹੋ", "subscribed_languages.save": "ਤਬਦੀਲੀਆਂ ਸੰਭਾਲੋ", "tabs_bar.home": "ਘਰ", + "tabs_bar.menu": "ਮੇਨੂ", "tabs_bar.notifications": "ਸੂਚਨਾਵਾਂ", + "tabs_bar.publish": "ਨਵੀਂ ਪੋਸਟ", + "tabs_bar.search": "ਖੋਜੋ", "terms_of_service.title": "ਸੇਵਾ ਦੀਆਂ ਸ਼ਰਤਾਂ", "time_remaining.days": "{number, plural, one {# ਦਿਨ} other {# ਦਿਨ}} ਬਾਕੀ", "time_remaining.hours": "{number, plural, one {# ਘੰਟਾ} other {# ਘੰਟੇ}} ਬਾਕੀ", @@ -561,6 +604,9 @@ "video.expand": "ਵੀਡੀਓ ਨੂੰ ਫੈਲਾਓ", "video.fullscreen": "ਪੂਰੀ ਸਕਰੀਨ", "video.hide": "ਵੀਡੀਓ ਨੂੰ ਲੁਕਾਓ", + "video.mute": "ਮੌਨ", "video.pause": "ਠਹਿਰੋ", - "video.play": "ਚਲਾਓ" + "video.play": "ਚਲਾਓ", + "video.volume_down": "ਅਵਾਜ਼ ਘਟਾਓ", + "video.volume_up": "ਅਵਾਜ਼ ਵਧਾਓ" } diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 57958076a7b..8928f253b16 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -537,8 +537,10 @@ "mute_modal.you_wont_see_mentions": "你看不到提及对方的嘟文。", "mute_modal.you_wont_see_posts": "对方可以看到你的嘟文,但是你看不到对方的。", "navigation_bar.about": "关于", + "navigation_bar.account_settings": "密码与安全", "navigation_bar.administration": "管理", "navigation_bar.advanced_interface": "在高级网页界面中打开", + "navigation_bar.automated_deletion": "自动删除嘟文", "navigation_bar.blocks": "已屏蔽的用户", "navigation_bar.bookmarks": "书签", "navigation_bar.direct": "私下提及", diff --git a/config/locales/et.yml b/config/locales/et.yml index dba31f4ffd7..962f6be9db8 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -318,6 +318,8 @@ et: new: create: Loo teadaanne title: Uus teadaanne + preview: + title: Info teavituse üle vaatamine publish: Postita published_msg: Teadaande avaldamine õnnestus! scheduled_for: Kavandatud ajaks %{time} @@ -485,6 +487,7 @@ et: request_body: Päringu sisu providers: active: Aktiivne + base_url: Baas-URL callback: Pöördliiklus delete: Kustuta edit: Muuda teenusepakkujat @@ -499,6 +502,7 @@ et: reject: Keeldu title: Kinnita FASP-i registreerimine save: Salvesta + select_capabilities: Vali oskused sign_in: Logi sisse status: Olek title: Täiendavad teenusepakkujad Födiversumis (FASP - Fediverse Auxiliary Service Providers) diff --git a/config/locales/ga.yml b/config/locales/ga.yml index c9e943c91c1..111ae9c56f0 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1220,9 +1220,9 @@ ga: confirmation_dialogs: Dialóga deimhnithe discovery: Fionnachtain localization: - body: Aistríonn oibrithe deonacha Mastodon. + body: Oibrithe deonacha a dhéanann aistriúchán Mastodon. guide_link: https://crowdin.com/project/mastodon - guide_link_text: Is féidir le gach duine rannchuidiú. + guide_link_text: Is féidir le gach duine cur leis. sensitive_content: Ábhar íogair application_mailer: notification_preferences: Athraigh roghanna ríomhphoist diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 28c98a58d67..354ef8b0f85 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -454,6 +454,7 @@ nan: title: 封鎖新ê電子phue網域 no_email_domain_block_selected: 因為無揀任何電子phue域名封鎖,所以lóng無改變 not_permitted: 無允准 + resolved_dns_records_hint_html: 域名解析做下kha ê MX域名,tsiah ê域名上後負責收電子phue。封鎖MX域名ē封任何有siâng款MX域名ê電子郵件ê註冊,就算通看見ê域名無kâng,mā án-ne。Tio̍h細膩,m̄通封鎖主要ê電子phue提供者。 resolved_through_html: 通過 %{domain} 解析 title: 封鎖ê電子phue網域 export_domain_allows: @@ -468,9 +469,78 @@ nan: private_comment_template: 佇 %{date} tuì %{source} 輸入 title: 輸入域名封鎖 invalid_domain_block: 因為下kha ê錯誤,làng過tsi̍t ê以上ê域名封鎖:%{error} + new: + title: 輸入域名封鎖 + no_file: Iáu bē揀檔案 + fasp: + debug: + callbacks: + created_at: 建立佇 + delete: Thâi掉 + ip: IP地址 + request_body: 請求主文 + title: 除蟲callback + providers: + active: 有效 + base_url: 基本URL + callback: Callback + delete: Thâi掉 + edit: 編輯提供者 + finish_registration: 完成註冊 + name: 名 + providers: 提供者 + public_key_fingerprint: 公開鎖匙ê指頭仔螺(public key fingerprint) + registration_requested: 註冊請求ah + registrations: + confirm: 確認 + description: Lí收著FASP ê註冊ah。nā準lí bô啟動,請拒絕。若有啟動,請佇確認註冊以前,細膩比較名kap鎖匙ê指頭仔螺。 + reject: 拒絕 + title: 確認FASP註冊 + save: 儲存 + select_capabilities: 揀功能 + sign_in: 登入 + status: 狀態 + title: 聯邦宇宙輔助服務提供者 (FASP) + title: FASP + follow_recommendations: + description_html: "跟tuè建議幫tsān新用者緊tshuē著心適ê內容。Nā使用者無hām別lâng有夠額ê互動,來形成個人化ê跟tuè建議,就ē推薦tsiah ê口座。In是佇指定語言內底,由最近上tsia̍p參與ê,kap上tsē lâng跟tuè ê口座,用ta̍k kang做基礎,相濫koh計算出來ê。" + language: 揀語言 + status: 狀態 + suppress: Khàm掉跟tuè建議 + suppressed: Khàm掉ê + title: 跟tuè建議 + unsuppress: 恢復跟tuè建議 instances: + audit_log: + title: 最近ê審核日誌 + view_all: 看完整ê審核日誌 + availability: + description_html: + other: Nā佇 %{count} kang內,寄送kàu hit ê域名lóng失敗,除非收著hit ê域名來ê寄送,a̍h無buē koh試寄送。 + failure_threshold_reached: 佇 %{date} kàu失敗ê底限。 + failures_recorded: + other: 連suà %{count} kang lóng寄失敗。 + no_failures_recorded: 報告內底無失敗。 + title: 可用性 + warning: 頂kái試連接tsit臺服侍器是無成功 + back_to_all: 全部 + back_to_limited: 受限制 + back_to_warning: 警告 + by_domain: 域名 + confirm_purge: Lí kám確定beh永永thâi掉tsit ê域名來ê資料? + content_policies: + comment: 內部ê筆記 + description_html: Lí ē當定義用tī所有tuì tsit ê域名kap伊ê子域名來ê口座ê內容政策。 dashboard: instance_languages_dimension: Tsia̍p用ê語言 + invites: + filter: + available: 通用ê + expired: 過期ê + title: 過濾器 + title: 邀請 + ip_blocks: + add_new: 建立規則 statuses: language: 語言 trends: diff --git a/config/locales/pa.yml b/config/locales/pa.yml index 1899d71008a..f9508f9b9a4 100644 --- a/config/locales/pa.yml +++ b/config/locales/pa.yml @@ -7,7 +7,13 @@ pa: hosted_on: "%{domain} ਉੱਤੇ ਹੋਸਟ ਕੀਤਾ ਮਸਟਾਡੋਨ" title: ਇਸ ਬਾਰੇ accounts: + followers: + one: ਫ਼ਾਲੋਅਰ + other: ਫ਼ਾਲੋਅਰ following: ਫ਼ਾਲੋ ਕੀਤੇ ਜਾ ਰਹੇ + posts: + one: ਪੋਸਟ + other: ਪੋਸਟਾਂ posts_tab_heading: ਪੋਸਟਾਂ admin: account_moderation_notes: @@ -126,6 +132,9 @@ pa: thread: ਗੱਲਾਂਬਾਤਾਂ index: delete: ਹਟਾਓ + statuses: + one: "%{count} ਪੋਸਟ" + other: "%{count} ਪੋਸਟ" generic: all: ਸਭ copy: ਕਾਪੀ ਕਰੋ diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 02ecd4ebe8d..e8080b2a768 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -61,7 +61,7 @@ es-MX: setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible - setting_emoji_style: Cómo se mostrarán los emojis. "Auto" intentará usar emojis nativos, cambiando a Twemoji en navegadores antiguos. + setting_emoji_style: Cómo se muestran los emojis. «Automático» intentará usar emojis nativos, pero vuelve a Twemoji para los navegadores antiguos. setting_system_scrollbars_ui: Solo se aplica a los navegadores de escritorio basados en Safari y Chrome setting_use_blurhash: Los degradados se basan en los colores de los elementos visuales ocultos, pero ocultan cualquier detalle setting_use_pending_items: Ocultar las publicaciones de la línea de tiempo tras un clic en lugar de desplazar automáticamente el feed @@ -151,8 +151,8 @@ es-MX: user: chosen_languages: Cuando se marca, solo se mostrarán las publicaciones en los idiomas seleccionados en las líneas de tiempo públicas date_of_birth: - one: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No guardaremos esta información. - other: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No guardaremos esta información. + one: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No almacenaremos esta información. + other: Tenemos que asegurarnos de que tienes al menos %{count} para usar %{domain}. No almacenaremos esta información. role: El rol controla qué permisos tiene el usuario. user_role: color: Color que se usará para el rol en toda la interfaz de usuario, como RGB en formato hexadecimal From 0af2c4829ff86cf84ed9e7804d023f31e805542b Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 22 Jul 2025 15:58:04 +0200 Subject: [PATCH 093/660] Creates Vite plugin to generate assets file (#35454) --- config/vite/plugin-assets-manifest.ts | 84 +++++++++++++++++++++++++++ vite.config.mts | 20 +------ 2 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 config/vite/plugin-assets-manifest.ts diff --git a/config/vite/plugin-assets-manifest.ts b/config/vite/plugin-assets-manifest.ts new file mode 100644 index 00000000000..3d465549cea --- /dev/null +++ b/config/vite/plugin-assets-manifest.ts @@ -0,0 +1,84 @@ +// Heavily inspired by https://github.com/ElMassimo/vite_ruby + +import { createHash } from 'node:crypto'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import glob from 'fast-glob'; +import type { Plugin } from 'vite'; + +interface AssetManifestChunk { + file: string; + integrity: string; +} + +const ALGORITHM = 'sha384'; + +export function MastodonAssetsManifest(): Plugin { + let manifest: string | boolean = true; + let jsRoot = ''; + + return { + name: 'mastodon-assets-manifest', + applyToEnvironment(environment) { + return !!environment.config.build.manifest; + }, + configResolved(resolvedConfig) { + manifest = resolvedConfig.build.manifest; + jsRoot = resolvedConfig.root; + }, + async generateBundle() { + // Glob all assets and return an array of absolute paths. + const assetPaths = await glob('{fonts,icons,images}/**/*', { + cwd: jsRoot, + absolute: true, + }); + + const assetManifest: Record = {}; + const excludeExts = ['', '.md']; + for (const file of assetPaths) { + // Exclude files like markdown or README files with no extension. + const ext = path.extname(file); + if (excludeExts.includes(ext)) { + continue; + } + + // Read the file and emit it as an asset. + const contents = await fs.readFile(file); + const ref = this.emitFile({ + name: path.basename(file), + type: 'asset', + source: contents, + }); + const hashedFilename = this.getFileName(ref); + + // With the emitted file information, hash the contents and store in manifest. + const name = path.relative(jsRoot, file); + const hash = createHash(ALGORITHM) + .update(contents) + .digest() + .toString('base64'); + assetManifest[name] = { + file: hashedFilename, + integrity: `${ALGORITHM}-${hash}`, + }; + } + + if (Object.keys(assetManifest).length === 0) { + console.warn('Asset manifest is empty'); + return; + } + + // Get manifest location and emit the manifest. + const manifestDir = + typeof manifest === 'string' ? path.dirname(manifest) : '.vite'; + const fileName = `${manifestDir}/manifest-assets.json`; + + this.emitFile({ + fileName, + type: 'asset', + source: JSON.stringify(assetManifest, null, 2), + }); + }, + }; +} diff --git a/vite.config.mts b/vite.config.mts index b47bea382cf..7f93157b7e1 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -4,7 +4,6 @@ import { readdir } from 'node:fs/promises'; import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin'; import legacy from '@vitejs/plugin-legacy'; import react from '@vitejs/plugin-react'; -import glob from 'fast-glob'; import postcssPresetEnv from 'postcss-preset-env'; import Compress from 'rollup-plugin-gzip'; import { visualizer } from 'rollup-plugin-visualizer'; @@ -24,6 +23,7 @@ import { MastodonServiceWorkerLocales } from './config/vite/plugin-sw-locales'; import { MastodonEmojiCompressed } from './config/vite/plugin-emoji-compressed'; import { MastodonThemes } from './config/vite/plugin-mastodon-themes'; import { MastodonNameLookup } from './config/vite/plugin-name-lookup'; +import { MastodonAssetsManifest } from './config/vite/plugin-assets-manifest'; const jsRoot = path.resolve(__dirname, 'app/javascript'); @@ -120,6 +120,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { }, }), MastodonThemes(), + MastodonAssetsManifest(), viteStaticCopy({ targets: [ { @@ -144,7 +145,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { isProdBuild && (Compress() as PluginOption), command === 'build' && manifestSRI({ - manifestPaths: ['.vite/manifest.json', '.vite/manifest-assets.json'], + manifestPaths: ['.vite/manifest.json'], }), VitePWA({ srcDir: path.resolve(jsRoot, 'mastodon/service_worker'), @@ -211,21 +212,6 @@ async function findEntrypoints() { } } - // Lastly other assets - const assetEntrypoints = await glob('{fonts,icons,images}/**/*', { - cwd: jsRoot, - absolute: true, - }); - const excludeExts = ['', '.md']; - for (const file of assetEntrypoints) { - const ext = path.extname(file); - if (excludeExts.includes(ext)) { - continue; - } - const name = path.basename(file); - entrypoints[name] = path.resolve(jsRoot, file); - } - return entrypoints; } From 760d00b7f7491911a43ffc5bf6e8ce709c9da000 Mon Sep 17 00:00:00 2001 From: Echo Date: Tue, 22 Jul 2025 16:43:15 +0200 Subject: [PATCH 094/660] Emoji Rendering (#35424) --- .../mastodon/components/account_bio.tsx | 38 +- .../components/hover_card_account.tsx | 2 +- .../mastodon/components/status_content.jsx | 22 +- .../components/account_header.tsx | 3 +- .../mastodon/features/emoji/constants.ts | 10 + .../mastodon/features/emoji/database.ts | 117 +++++-- .../mastodon/features/emoji/emoji_html.tsx | 81 +++++ .../mastodon/features/emoji/emoji_text.tsx | 45 +++ .../mastodon/features/emoji/hooks.ts | 16 + .../mastodon/features/emoji/index.ts | 17 +- .../mastodon/features/emoji/loader.ts | 2 +- .../mastodon/features/emoji/locale.ts | 2 +- .../mastodon/features/emoji/mode.ts | 119 +++++++ .../mastodon/features/emoji/normalize.test.ts | 12 +- .../mastodon/features/emoji/normalize.ts | 11 +- .../mastodon/features/emoji/render.test.ts | 163 +++++++++ .../mastodon/features/emoji/render.ts | 331 ++++++++++++++++++ .../mastodon/features/emoji/types.ts | 64 ++++ .../mastodon/features/emoji/utils.test.ts | 47 +++ .../mastodon/features/emoji/utils.ts | 13 + app/javascript/mastodon/initial_state.js | 2 + app/javascript/mastodon/main.tsx | 7 +- app/serializers/initial_state_serializer.rb | 1 + package.json | 1 + yarn.lock | 8 + 25 files changed, 1064 insertions(+), 70 deletions(-) create mode 100644 app/javascript/mastodon/features/emoji/emoji_html.tsx create mode 100644 app/javascript/mastodon/features/emoji/emoji_text.tsx create mode 100644 app/javascript/mastodon/features/emoji/hooks.ts create mode 100644 app/javascript/mastodon/features/emoji/mode.ts create mode 100644 app/javascript/mastodon/features/emoji/render.test.ts create mode 100644 app/javascript/mastodon/features/emoji/render.ts create mode 100644 app/javascript/mastodon/features/emoji/types.ts create mode 100644 app/javascript/mastodon/features/emoji/utils.test.ts create mode 100644 app/javascript/mastodon/features/emoji/utils.ts diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index e0127f20923..cdac41b8a7d 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -2,27 +2,44 @@ import { useCallback } from 'react'; import { useLinks } from 'mastodon/hooks/useLinks'; +import { EmojiHTML } from '../features/emoji/emoji_html'; +import { isFeatureEnabled } from '../initial_state'; +import { useAppSelector } from '../store'; + interface AccountBioProps { - note: string; className: string; - dropdownAccountId?: string; + accountId: string; + showDropdown?: boolean; } export const AccountBio: React.FC = ({ - note, className, - dropdownAccountId, + accountId, + showDropdown = false, }) => { - const handleClick = useLinks(!!dropdownAccountId); + const handleClick = useLinks(showDropdown); const handleNodeChange = useCallback( (node: HTMLDivElement | null) => { - if (!dropdownAccountId || !node || node.childNodes.length === 0) { + if (!showDropdown || !node || node.childNodes.length === 0) { return; } - addDropdownToHashtags(node, dropdownAccountId); + addDropdownToHashtags(node, accountId); }, - [dropdownAccountId], + [showDropdown, accountId], ); + const note = useAppSelector((state) => { + const account = state.accounts.get(accountId); + if (!account) { + return ''; + } + return isFeatureEnabled('modern_emojis') + ? account.note + : account.note_emojified; + }); + const extraEmojis = useAppSelector((state) => { + const account = state.accounts.get(accountId); + return account?.emojis; + }); if (note.length === 0) { return null; @@ -31,10 +48,11 @@ export const AccountBio: React.FC = ({ return (
+ > + +
); }; diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx index a6bdda21686..a5a5e4c9575 100644 --- a/app/javascript/mastodon/components/hover_card_account.tsx +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -102,7 +102,7 @@ export const HoverCardAccount = forwardRef< <>
diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 0628e0791b5..02f06ec96ac 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -13,7 +13,8 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; +import { autoPlayGif, isFeatureEnabled, languages as preloadedLanguages } from 'mastodon/initial_state'; +import { EmojiHTML } from '../features/emoji/emoji_html'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) @@ -23,6 +24,9 @@ const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) * @returns {string} */ export function getStatusContent(status) { + if (isFeatureEnabled('modern_emojis')) { + return status.getIn(['translation', 'content']) || status.get('content'); + } return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml'); } @@ -228,7 +232,7 @@ class StatusContent extends PureComponent { const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); - const content = { __html: statusContent ?? getStatusContent(status) }; + const content = statusContent ?? getStatusContent(status); const language = status.getIn(['translation', 'language']) || status.get('language'); const classNames = classnames('status__content', { 'status__content--with-action': this.props.onClick && this.props.history, @@ -253,7 +257,12 @@ class StatusContent extends PureComponent { return ( <>
-
+ {poll} {translateButton} @@ -265,7 +274,12 @@ class StatusContent extends PureComponent { } else { return (
-
+ {poll} {translateButton} 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 b9f83bebaaa..0bae0395031 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -898,8 +898,7 @@ export const AccountHeader: React.FC<{ )} diff --git a/app/javascript/mastodon/features/emoji/constants.ts b/app/javascript/mastodon/features/emoji/constants.ts index d38f17f2160..09022371b22 100644 --- a/app/javascript/mastodon/features/emoji/constants.ts +++ b/app/javascript/mastodon/features/emoji/constants.ts @@ -15,6 +15,16 @@ export const SKIN_TONE_CODES = [ 0x1f3ff, // Dark skin tone ] as const; +// Emoji rendering modes. A mode is what we are using to render emojis, a style is what the user has selected. +export const EMOJI_MODE_NATIVE = 'native'; +export const EMOJI_MODE_NATIVE_WITH_FLAGS = 'native-flags'; +export const EMOJI_MODE_TWEMOJI = 'twemoji'; + +export const EMOJI_TYPE_UNICODE = 'unicode'; +export const EMOJI_TYPE_CUSTOM = 'custom'; + +export const EMOJI_STATE_MISSING = 'missing'; + export const EMOJIS_WITH_DARK_BORDER = [ '🎱', // 1F3B1 '🐜', // 1F41C diff --git a/app/javascript/mastodon/features/emoji/database.ts b/app/javascript/mastodon/features/emoji/database.ts index 618f0108509..0b8ddd34fbe 100644 --- a/app/javascript/mastodon/features/emoji/database.ts +++ b/app/javascript/mastodon/features/emoji/database.ts @@ -1,17 +1,19 @@ import { SUPPORTED_LOCALES } from 'emojibase'; -import type { FlatCompactEmoji, Locale } from 'emojibase'; -import type { DBSchema } from 'idb'; +import type { Locale } from 'emojibase'; +import type { DBSchema, IDBPDatabase } from 'idb'; import { openDB } from 'idb'; -import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; - -import type { LocaleOrCustom } from './locale'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; +import type { + CustomEmojiData, + UnicodeEmojiData, + LocaleOrCustom, +} from './types'; interface EmojiDB extends LocaleTables, DBSchema { custom: { key: string; - value: ApiCustomEmojiJSON; + value: CustomEmojiData; indexes: { category: string; }; @@ -24,7 +26,7 @@ interface EmojiDB extends LocaleTables, DBSchema { interface LocaleTable { key: string; - value: FlatCompactEmoji; + value: UnicodeEmojiData; indexes: { group: number; label: string; @@ -36,63 +38,114 @@ type LocaleTables = Record; const SCHEMA_VERSION = 1; -const db = await openDB('mastodon-emoji', SCHEMA_VERSION, { - upgrade(database) { - const customTable = database.createObjectStore('custom', { - keyPath: 'shortcode', - autoIncrement: false, - }); - customTable.createIndex('category', 'category'); +let db: IDBPDatabase | null = null; - database.createObjectStore('etags'); - - for (const locale of SUPPORTED_LOCALES) { - const localeTable = database.createObjectStore(locale, { - keyPath: 'hexcode', +async function loadDB() { + if (db) { + return db; + } + db = await openDB('mastodon-emoji', SCHEMA_VERSION, { + upgrade(database) { + const customTable = database.createObjectStore('custom', { + keyPath: 'shortcode', autoIncrement: false, }); - localeTable.createIndex('group', 'group'); - localeTable.createIndex('label', 'label'); - localeTable.createIndex('order', 'order'); - localeTable.createIndex('tags', 'tags', { multiEntry: true }); - } - }, -}); + customTable.createIndex('category', 'category'); -export async function putEmojiData(emojis: FlatCompactEmoji[], locale: Locale) { + database.createObjectStore('etags'); + + for (const locale of SUPPORTED_LOCALES) { + const localeTable = database.createObjectStore(locale, { + keyPath: 'hexcode', + autoIncrement: false, + }); + localeTable.createIndex('group', 'group'); + localeTable.createIndex('label', 'label'); + localeTable.createIndex('order', 'order'); + localeTable.createIndex('tags', 'tags', { multiEntry: true }); + } + }, + }); + return db; +} + +export async function putEmojiData(emojis: UnicodeEmojiData[], locale: Locale) { + const db = await loadDB(); const trx = db.transaction(locale, 'readwrite'); await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); await trx.done; } -export async function putCustomEmojiData(emojis: ApiCustomEmojiJSON[]) { +export async function putCustomEmojiData(emojis: CustomEmojiData[]) { + const db = await loadDB(); const trx = db.transaction('custom', 'readwrite'); await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); await trx.done; } -export function putLatestEtag(etag: string, localeString: string) { +export async function putLatestEtag(etag: string, localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); + const db = await loadDB(); return db.put('etags', etag, locale); } -export function searchEmojiByHexcode(hexcode: string, localeString: string) { +export async function searchEmojiByHexcode( + hexcode: string, + localeString: string, +) { const locale = toSupportedLocale(localeString); + const db = await loadDB(); return db.get(locale, hexcode); } -export function searchEmojiByTag(tag: string, localeString: string) { +export async function searchEmojisByHexcodes( + hexcodes: string[], + localeString: string, +) { + const locale = toSupportedLocale(localeString); + const db = await loadDB(); + return db.getAll( + locale, + IDBKeyRange.bound(hexcodes[0], hexcodes[hexcodes.length - 1]), + ); +} + +export async function searchEmojiByTag(tag: string, localeString: string) { const locale = toSupportedLocale(localeString); const range = IDBKeyRange.only(tag.toLowerCase()); + const db = await loadDB(); return db.getAllFromIndex(locale, 'tags', range); } -export function searchCustomEmojiByShortcode(shortcode: string) { +export async function searchCustomEmojiByShortcode(shortcode: string) { + const db = await loadDB(); return db.get('custom', shortcode); } +export async function searchCustomEmojisByShortcodes(shortcodes: string[]) { + const db = await loadDB(); + return db.getAll( + 'custom', + IDBKeyRange.bound(shortcodes[0], shortcodes[shortcodes.length - 1]), + ); +} + +export async function findMissingLocales(localeStrings: string[]) { + const locales = new Set(localeStrings.map(toSupportedLocale)); + const missingLocales: Locale[] = []; + const db = await loadDB(); + for (const locale of locales) { + const rowCount = await db.count(locale); + if (!rowCount) { + missingLocales.push(locale); + } + } + return missingLocales; +} + export async function loadLatestEtag(localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); + const db = await loadDB(); const rowCount = await db.count(locale); if (!rowCount) { return null; // No data for this locale, return null even if there is an etag. diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx new file mode 100644 index 00000000000..27af2dda279 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx @@ -0,0 +1,81 @@ +import type { HTMLAttributes } from 'react'; +import { useEffect, useMemo, useState } from 'react'; + +import type { List as ImmutableList } from 'immutable'; +import { isList } from 'immutable'; + +import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; +import { isFeatureEnabled } from '@/mastodon/initial_state'; +import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; + +import { useEmojiAppState } from './hooks'; +import { emojifyElement } from './render'; +import type { ExtraCustomEmojiMap } from './types'; + +type EmojiHTMLProps = Omit< + HTMLAttributes, + 'dangerouslySetInnerHTML' +> & { + htmlString: string; + extraEmojis?: ExtraCustomEmojiMap | ImmutableList; +}; + +export const EmojiHTML: React.FC = ({ + htmlString, + extraEmojis, + ...props +}) => { + if (isFeatureEnabled('modern_emojis')) { + return ( + + ); + } + return
; +}; + +const ModernEmojiHTML: React.FC = ({ + extraEmojis: rawEmojis, + htmlString: text, + ...props +}) => { + const appState = useEmojiAppState(); + const [innerHTML, setInnerHTML] = useState(''); + + const extraEmojis: ExtraCustomEmojiMap = useMemo(() => { + if (!rawEmojis) { + return {}; + } + if (isList(rawEmojis)) { + return ( + rawEmojis.toJS() as ApiCustomEmojiJSON[] + ).reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + return rawEmojis; + }, [rawEmojis]); + + useEffect(() => { + if (!text) { + return; + } + const cb = async () => { + const div = document.createElement('div'); + div.innerHTML = text; + const ele = await emojifyElement(div, appState, extraEmojis); + setInnerHTML(ele.innerHTML); + }; + void cb(); + }, [text, appState, extraEmojis]); + + if (!innerHTML) { + return null; + } + + return
; +}; diff --git a/app/javascript/mastodon/features/emoji/emoji_text.tsx b/app/javascript/mastodon/features/emoji/emoji_text.tsx new file mode 100644 index 00000000000..253371391a4 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/emoji_text.tsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from 'react'; + +import { useEmojiAppState } from './hooks'; +import { emojifyText } from './render'; + +interface EmojiTextProps { + text: string; +} + +export const EmojiText: React.FC = ({ text }) => { + const appState = useEmojiAppState(); + const [rendered, setRendered] = useState<(string | HTMLImageElement)[]>([]); + + useEffect(() => { + const cb = async () => { + const rendered = await emojifyText(text, appState); + setRendered(rendered ?? []); + }; + void cb(); + }, [text, appState]); + + if (rendered.length === 0) { + return null; + } + + return ( + <> + {rendered.map((fragment, index) => { + if (typeof fragment === 'string') { + return {fragment}; + } + return ( + {fragment.alt} + ); + })} + + ); +}; diff --git a/app/javascript/mastodon/features/emoji/hooks.ts b/app/javascript/mastodon/features/emoji/hooks.ts new file mode 100644 index 00000000000..fd38129a19b --- /dev/null +++ b/app/javascript/mastodon/features/emoji/hooks.ts @@ -0,0 +1,16 @@ +import { useAppSelector } from '@/mastodon/store'; + +import { toSupportedLocale } from './locale'; +import { determineEmojiMode } from './mode'; +import type { EmojiAppState } from './types'; + +export function useEmojiAppState(): EmojiAppState { + const locale = useAppSelector((state) => + toSupportedLocale(state.meta.get('locale') as string), + ); + const mode = useAppSelector((state) => + determineEmojiMode(state.meta.get('emoji_style') as string), + ); + + return { currentLocale: locale, locales: [locale], mode }; +} diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 6975465b55f..ef6cd67aeb5 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -2,7 +2,7 @@ import initialState from '@/mastodon/initial_state'; import { toSupportedLocale } from './locale'; -const serverLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); +const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); const worker = 'Worker' in window @@ -16,13 +16,22 @@ export async function initializeEmoji() { worker.addEventListener('message', (event: MessageEvent) => { const { data: message } = event; if (message === 'ready') { - worker.postMessage(serverLocale); worker.postMessage('custom'); + void loadEmojiLocale(userLocale); + // Load English locale as well, because people are still used to + // using it from before we supported other locales. + if (userLocale !== 'en') { + void loadEmojiLocale('en'); + } } }); } else { - const { importCustomEmojiData, importEmojiData } = await import('./loader'); - await Promise.all([importCustomEmojiData(), importEmojiData(serverLocale)]); + const { importCustomEmojiData } = await import('./loader'); + await importCustomEmojiData(); + await loadEmojiLocale(userLocale); + if (userLocale !== 'en') { + await loadEmojiLocale('en'); + } } } diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index f9c69713515..482d9e5c359 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -11,7 +11,7 @@ import { putLatestEtag, } from './database'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; -import type { LocaleOrCustom } from './locale'; +import type { LocaleOrCustom } from './types'; export async function importEmojiData(localeString: string) { const locale = toSupportedLocale(localeString); diff --git a/app/javascript/mastodon/features/emoji/locale.ts b/app/javascript/mastodon/features/emoji/locale.ts index 561c94afb0a..8ff23f5161a 100644 --- a/app/javascript/mastodon/features/emoji/locale.ts +++ b/app/javascript/mastodon/features/emoji/locale.ts @@ -1,7 +1,7 @@ import type { Locale } from 'emojibase'; import { SUPPORTED_LOCALES } from 'emojibase'; -export type LocaleOrCustom = Locale | 'custom'; +import type { LocaleOrCustom } from './types'; export function toSupportedLocale(localeBase: string): Locale { const locale = localeBase.toLowerCase(); diff --git a/app/javascript/mastodon/features/emoji/mode.ts b/app/javascript/mastodon/features/emoji/mode.ts new file mode 100644 index 00000000000..0f581d8b504 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/mode.ts @@ -0,0 +1,119 @@ +// Credit to Nolan Lawson for the original implementation. +// See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/utils/testColorEmojiSupported.js + +import { isDevelopment } from '@/mastodon/utils/environment'; + +import { + EMOJI_MODE_NATIVE, + EMOJI_MODE_NATIVE_WITH_FLAGS, + EMOJI_MODE_TWEMOJI, +} from './constants'; +import type { EmojiMode } from './types'; + +type Feature = Uint8ClampedArray; + +// See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/constants.js +const FONT_FAMILY = + '"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' + + '"Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif'; + +function getTextFeature(text: string, color: string) { + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + + const ctx = canvas.getContext('2d', { + // Improves the performance of `getImageData()` + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getContextAttributes#willreadfrequently + willReadFrequently: true, + }); + if (!ctx) { + throw new Error('Canvas context not available'); + } + ctx.textBaseline = 'top'; + ctx.font = `100px ${FONT_FAMILY}`; + ctx.fillStyle = color; + ctx.scale(0.01, 0.01); + ctx.fillText(text, 0, 0); + + return ctx.getImageData(0, 0, 1, 1).data satisfies Feature; +} + +function compareFeatures(feature1: Feature, feature2: Feature) { + const feature1Str = [...feature1].join(','); + const feature2Str = [...feature2].join(','); + // This is RGBA, so for 0,0,0, we are checking that the first RGB is not all zeroes. + // Most of the time when unsupported this is 0,0,0,0, but on Chrome on Mac it is + // 0,0,0,61 - there is a transparency here. + return feature1Str === feature2Str && !feature1Str.startsWith('0,0,0,'); +} + +function testEmojiSupport(text: string) { + // Render white and black and then compare them to each other and ensure they're the same + // color, and neither one is black. This shows that the emoji was rendered in color. + const feature1 = getTextFeature(text, '#000'); + const feature2 = getTextFeature(text, '#fff'); + return compareFeatures(feature1, feature2); +} + +const EMOJI_VERSION_TEST_EMOJI = '🫨'; // shaking head, from v15 +const EMOJI_FLAG_TEST_EMOJI = '🇨🇭'; + +export function determineEmojiMode(style: string): EmojiMode { + if (style === EMOJI_MODE_NATIVE) { + // If flags are not supported, we replace them with Twemoji. + if (shouldReplaceFlags()) { + return EMOJI_MODE_NATIVE_WITH_FLAGS; + } + return EMOJI_MODE_NATIVE; + } + if (style === EMOJI_MODE_TWEMOJI) { + return EMOJI_MODE_TWEMOJI; + } + + // Auto style so determine based on browser capabilities. + if (shouldUseTwemoji()) { + return EMOJI_MODE_TWEMOJI; + } else if (shouldReplaceFlags()) { + return EMOJI_MODE_NATIVE_WITH_FLAGS; + } + return EMOJI_MODE_NATIVE; +} + +export function shouldUseTwemoji(): boolean { + if (typeof window === 'undefined') { + return false; + } + try { + // Test a known color emoji to see if 15.1 is supported. + return !testEmojiSupport(EMOJI_VERSION_TEST_EMOJI); + } catch (err: unknown) { + // If an error occurs, fall back to Twemoji to be safe. + if (isDevelopment()) { + console.warn( + 'Emoji rendering test failed, defaulting to Twemoji. Error:', + err, + ); + } + return true; + } +} + +// Based on https://github.com/talkjs/country-flag-emoji-polyfill/blob/master/src/index.ts#L19 +export function shouldReplaceFlags(): boolean { + if (typeof window === 'undefined') { + return false; + } + try { + // Test a known flag emoji to see if it is rendered in color. + return !testEmojiSupport(EMOJI_FLAG_TEST_EMOJI); + } catch (err: unknown) { + // If an error occurs, assume flags should be replaced. + if (isDevelopment()) { + console.warn( + 'Flag emoji rendering test failed, defaulting to replacement. Error:', + err, + ); + } + return true; + } +} diff --git a/app/javascript/mastodon/features/emoji/normalize.test.ts b/app/javascript/mastodon/features/emoji/normalize.test.ts index ee9cd89487f..f0ea140590b 100644 --- a/app/javascript/mastodon/features/emoji/normalize.test.ts +++ b/app/javascript/mastodon/features/emoji/normalize.test.ts @@ -22,9 +22,9 @@ const emojiSVGFiles = await readdir( ); const svgFileNames = emojiSVGFiles .filter((file) => file.isFile() && file.name.endsWith('.svg')) - .map((file) => basename(file.name, '.svg').toUpperCase()); + .map((file) => basename(file.name, '.svg')); const svgFileNamesWithoutBorder = svgFileNames.filter( - (fileName) => !fileName.endsWith('_BORDER'), + (fileName) => !fileName.endsWith('_border'), ); const unicodeEmojis = flattenEmojiData(unicodeRawEmojis); @@ -60,13 +60,13 @@ describe('unicodeToTwemojiHex', () => { describe('twemojiHasBorder', () => { test.concurrent.for( svgFileNames - .filter((file) => file.endsWith('_BORDER')) + .filter((file) => file.endsWith('_border')) .map((file) => { - const hexCode = file.replace('_BORDER', ''); + const hexCode = file.replace('_border', ''); return [ hexCode, - CODES_WITH_LIGHT_BORDER.includes(hexCode), - CODES_WITH_DARK_BORDER.includes(hexCode), + CODES_WITH_LIGHT_BORDER.includes(hexCode.toUpperCase()), + CODES_WITH_DARK_BORDER.includes(hexCode.toUpperCase()), ] as const; }), )('twemojiHasBorder for %s', ([hexCode, isLight, isDark], { expect }) => { diff --git a/app/javascript/mastodon/features/emoji/normalize.ts b/app/javascript/mastodon/features/emoji/normalize.ts index 94dc33a6ea2..6a64c3b8bfa 100644 --- a/app/javascript/mastodon/features/emoji/normalize.ts +++ b/app/javascript/mastodon/features/emoji/normalize.ts @@ -7,6 +7,7 @@ import { EMOJIS_WITH_DARK_BORDER, EMOJIS_WITH_LIGHT_BORDER, } from './constants'; +import type { TwemojiBorderInfo } from './types'; // Misc codes that have special handling const SKIER_CODE = 0x26f7; @@ -51,13 +52,7 @@ export function unicodeToTwemojiHex(unicodeHex: string): string { normalizedCodes.push(code); } - return hexNumbersToString(normalizedCodes, 0); -} - -interface TwemojiBorderInfo { - hexCode: string; - hasLightBorder: boolean; - hasDarkBorder: boolean; + return hexNumbersToString(normalizedCodes, 0).toLowerCase(); } export const CODES_WITH_DARK_BORDER = @@ -77,7 +72,7 @@ export function twemojiHasBorder(twemojiHex: string): TwemojiBorderInfo { hasDarkBorder = true; } return { - hexCode: normalizedHex, + hexCode: twemojiHex, hasLightBorder, hasDarkBorder, }; diff --git a/app/javascript/mastodon/features/emoji/render.test.ts b/app/javascript/mastodon/features/emoji/render.test.ts new file mode 100644 index 00000000000..23f85c36b3e --- /dev/null +++ b/app/javascript/mastodon/features/emoji/render.test.ts @@ -0,0 +1,163 @@ +import { + EMOJI_MODE_NATIVE, + EMOJI_MODE_NATIVE_WITH_FLAGS, + EMOJI_MODE_TWEMOJI, +} from './constants'; +import { emojifyElement, tokenizeText } from './render'; +import type { CustomEmojiData, UnicodeEmojiData } from './types'; + +vitest.mock('./database', () => ({ + searchCustomEmojisByShortcodes: vitest.fn( + () => + [ + { + shortcode: 'custom', + static_url: 'emoji/static', + url: 'emoji/custom', + category: 'test', + visible_in_picker: true, + }, + ] satisfies CustomEmojiData[], + ), + searchEmojisByHexcodes: vitest.fn( + () => + [ + { + hexcode: '1F60A', + group: 0, + label: 'smiling face with smiling eyes', + order: 0, + tags: ['smile', 'happy'], + unicode: '😊', + }, + { + hexcode: '1F1EA-1F1FA', + group: 0, + label: 'flag-eu', + order: 0, + tags: ['flag', 'european union'], + unicode: '🇪🇺', + }, + ] satisfies UnicodeEmojiData[], + ), + findMissingLocales: vitest.fn(() => []), +})); + +describe('emojifyElement', () => { + const testElement = document.createElement('div'); + testElement.innerHTML = '

Hello 😊🇪🇺!

:custom:

'; + + const expectedSmileImage = + '😊'; + const expectedFlagImage = + '🇪🇺'; + const expectedCustomEmojiImage = + ':custom:'; + + function cloneTestElement() { + return testElement.cloneNode(true) as HTMLElement; + } + + test('emojifies custom emoji in native mode', async () => { + const emojifiedElement = await emojifyElement(cloneTestElement(), { + locales: ['en'], + mode: EMOJI_MODE_NATIVE, + currentLocale: 'en', + }); + expect(emojifiedElement.innerHTML).toBe( + `

Hello 😊🇪🇺!

${expectedCustomEmojiImage}

`, + ); + }); + + test('emojifies flag emoji in native-with-flags mode', async () => { + const emojifiedElement = await emojifyElement(cloneTestElement(), { + locales: ['en'], + mode: EMOJI_MODE_NATIVE_WITH_FLAGS, + currentLocale: 'en', + }); + expect(emojifiedElement.innerHTML).toBe( + `

Hello 😊${expectedFlagImage}!

${expectedCustomEmojiImage}

`, + ); + }); + + test('emojifies everything in twemoji mode', async () => { + const emojifiedElement = await emojifyElement(cloneTestElement(), { + locales: ['en'], + mode: EMOJI_MODE_TWEMOJI, + currentLocale: 'en', + }); + expect(emojifiedElement.innerHTML).toBe( + `

Hello ${expectedSmileImage}${expectedFlagImage}!

${expectedCustomEmojiImage}

`, + ); + }); +}); + +describe('tokenizeText', () => { + test('returns empty array for string with only whitespace', () => { + expect(tokenizeText(' \n')).toEqual([]); + }); + + test('returns an array of text to be a single token', () => { + expect(tokenizeText('Hello')).toEqual(['Hello']); + }); + + test('returns tokens for text with emoji', () => { + expect(tokenizeText('Hello 😊 🇿🇼!!')).toEqual([ + 'Hello ', + { + type: 'unicode', + code: '😊', + }, + ' ', + { + type: 'unicode', + code: '🇿🇼', + }, + '!!', + ]); + }); + + test('returns tokens for text with custom emoji', () => { + expect(tokenizeText('Hello :smile:!!')).toEqual([ + 'Hello ', + { + type: 'custom', + code: 'smile', + }, + '!!', + ]); + }); + + test('handles custom emoji with underscores and numbers', () => { + expect(tokenizeText('Hello :smile_123:!!')).toEqual([ + 'Hello ', + { + type: 'custom', + code: 'smile_123', + }, + '!!', + ]); + }); + + test('returns tokens for text with mixed emoji', () => { + expect(tokenizeText('Hello 😊 :smile:!!')).toEqual([ + 'Hello ', + { + type: 'unicode', + code: '😊', + }, + ' ', + { + type: 'custom', + code: 'smile', + }, + '!!', + ]); + }); + + test('does not capture custom emoji with invalid characters', () => { + expect(tokenizeText('Hello :smile-123:!!')).toEqual([ + 'Hello :smile-123:!!', + ]); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts new file mode 100644 index 00000000000..8f0c1ee15fe --- /dev/null +++ b/app/javascript/mastodon/features/emoji/render.ts @@ -0,0 +1,331 @@ +import type { Locale } from 'emojibase'; +import EMOJI_REGEX from 'emojibase-regex/emoji-loose'; + +import { autoPlayGif } from '@/mastodon/initial_state'; +import { assetHost } from '@/mastodon/utils/config'; + +import { loadEmojiLocale } from '.'; +import { + EMOJI_MODE_NATIVE, + EMOJI_MODE_NATIVE_WITH_FLAGS, + EMOJI_TYPE_UNICODE, + EMOJI_TYPE_CUSTOM, + EMOJI_STATE_MISSING, +} from './constants'; +import { + findMissingLocales, + searchCustomEmojisByShortcodes, + searchEmojisByHexcodes, +} from './database'; +import { + emojiToUnicodeHex, + twemojiHasBorder, + unicodeToTwemojiHex, +} from './normalize'; +import type { + CustomEmojiToken, + EmojiAppState, + EmojiLoadedState, + EmojiMode, + EmojiState, + EmojiStateMap, + EmojiToken, + ExtraCustomEmojiMap, + LocaleOrCustom, + UnicodeEmojiToken, +} from './types'; +import { stringHasUnicodeFlags } from './utils'; + +const localeCacheMap = new Map([ + [EMOJI_TYPE_CUSTOM, new Map()], +]); + +// Emojifies an element. This modifies the element in place, replacing text nodes with emojified versions. +export async function emojifyElement( + element: Element, + appState: EmojiAppState, + extraEmojis: ExtraCustomEmojiMap = {}, +): Promise { + const queue: (HTMLElement | Text)[] = [element]; + while (queue.length > 0) { + const current = queue.shift(); + if ( + !current || + current instanceof HTMLScriptElement || + current instanceof HTMLStyleElement + ) { + continue; + } + + if ( + current.textContent && + (current instanceof Text || !current.hasChildNodes()) + ) { + const renderedContent = await emojifyText( + current.textContent, + appState, + extraEmojis, + ); + if (renderedContent) { + if (!(current instanceof Text)) { + current.textContent = null; // Clear the text content if it's not a Text node. + } + current.replaceWith(renderedToHTMLFragment(renderedContent)); + } + continue; + } + + for (const child of current.childNodes) { + if (child instanceof HTMLElement || child instanceof Text) { + queue.push(child); + } + } + } + return element; +} + +export async function emojifyText( + text: string, + appState: EmojiAppState, + extraEmojis: ExtraCustomEmojiMap = {}, +) { + // Exit if no text to convert. + if (!text.trim()) { + return null; + } + + const tokens = tokenizeText(text); + + // If only one token and it's a string, exit early. + if (tokens.length === 1 && typeof tokens[0] === 'string') { + return null; + } + + // Get all emoji from the state map, loading any missing ones. + await ensureLocalesAreLoaded(appState.locales); + await loadMissingEmojiIntoCache(tokens, appState.locales); + + const renderedFragments: (string | HTMLImageElement)[] = []; + for (const token of tokens) { + if (typeof token !== 'string' && shouldRenderImage(token, appState.mode)) { + let state: EmojiState | undefined; + if (token.type === EMOJI_TYPE_CUSTOM) { + const extraEmojiData = extraEmojis[token.code]; + if (extraEmojiData) { + state = { type: EMOJI_TYPE_CUSTOM, data: extraEmojiData }; + } else { + state = emojiForLocale(token.code, EMOJI_TYPE_CUSTOM); + } + } else { + state = emojiForLocale( + emojiToUnicodeHex(token.code), + appState.currentLocale, + ); + } + + // If the state is valid, create an image element. Otherwise, just append as text. + if (state && typeof state !== 'string') { + const image = stateToImage(state); + renderedFragments.push(image); + continue; + } + } + const text = typeof token === 'string' ? token : token.code; + renderedFragments.push(text); + } + + return renderedFragments; +} + +// Private functions + +async function ensureLocalesAreLoaded(locales: Locale[]) { + const missingLocales = await findMissingLocales(locales); + for (const locale of missingLocales) { + await loadEmojiLocale(locale); + } +} + +const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; +const TOKENIZE_REGEX = new RegExp( + `(${EMOJI_REGEX.source}|${CUSTOM_EMOJI_REGEX.source})`, + 'g', +); + +type TokenizedText = (string | EmojiToken)[]; + +export function tokenizeText(text: string): TokenizedText { + if (!text.trim()) { + return []; + } + + const tokens = []; + let lastIndex = 0; + for (const match of text.matchAll(TOKENIZE_REGEX)) { + if (match.index > lastIndex) { + tokens.push(text.slice(lastIndex, match.index)); + } + + const code = match[0]; + + if (code.startsWith(':') && code.endsWith(':')) { + // Custom emoji + tokens.push({ + type: EMOJI_TYPE_CUSTOM, + code: code.slice(1, -1), // Remove the colons + } satisfies CustomEmojiToken); + } else { + // Unicode emoji + tokens.push({ + type: EMOJI_TYPE_UNICODE, + code: code, + } satisfies UnicodeEmojiToken); + } + lastIndex = match.index + code.length; + } + if (lastIndex < text.length) { + tokens.push(text.slice(lastIndex)); + } + return tokens; +} + +function cacheForLocale(locale: LocaleOrCustom): EmojiStateMap { + return localeCacheMap.get(locale) ?? (new Map() as EmojiStateMap); +} + +function emojiForLocale( + code: string, + locale: LocaleOrCustom, +): EmojiState | undefined { + const cache = cacheForLocale(locale); + return cache.get(code); +} + +async function loadMissingEmojiIntoCache( + tokens: TokenizedText, + locales: Locale[], +) { + const missingUnicodeEmoji = new Set(); + const missingCustomEmoji = new Set(); + + // Iterate over tokens and check if they are in the cache already. + for (const token of tokens) { + if (typeof token === 'string') { + continue; // Skip plain strings. + } + + // If this is a custom emoji, check it separately. + if (token.type === EMOJI_TYPE_CUSTOM) { + const code = token.code; + const emojiState = emojiForLocale(code, EMOJI_TYPE_CUSTOM); + if (!emojiState) { + missingCustomEmoji.add(code); + } + // Otherwise this is a unicode emoji, so check it against all locales. + } else { + const code = emojiToUnicodeHex(token.code); + if (missingUnicodeEmoji.has(code)) { + continue; // Already marked as missing. + } + for (const locale of locales) { + const emojiState = emojiForLocale(code, locale); + if (!emojiState) { + // If it's missing in one locale, we consider it missing for all. + missingUnicodeEmoji.add(code); + } + } + } + } + + if (missingUnicodeEmoji.size > 0) { + const missingEmojis = Array.from(missingUnicodeEmoji).toSorted(); + for (const locale of locales) { + const emojis = await searchEmojisByHexcodes(missingEmojis, locale); + const cache = cacheForLocale(locale); + for (const emoji of emojis) { + cache.set(emoji.hexcode, { type: EMOJI_TYPE_UNICODE, data: emoji }); + } + const notFoundEmojis = missingEmojis.filter((code) => + emojis.every((emoji) => emoji.hexcode !== code), + ); + for (const code of notFoundEmojis) { + cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. + } + localeCacheMap.set(locale, cache); + } + } + + if (missingCustomEmoji.size > 0) { + const missingEmojis = Array.from(missingCustomEmoji).toSorted(); + const emojis = await searchCustomEmojisByShortcodes(missingEmojis); + const cache = cacheForLocale(EMOJI_TYPE_CUSTOM); + for (const emoji of emojis) { + cache.set(emoji.shortcode, { type: EMOJI_TYPE_CUSTOM, data: emoji }); + } + const notFoundEmojis = missingEmojis.filter((code) => + emojis.every((emoji) => emoji.shortcode !== code), + ); + for (const code of notFoundEmojis) { + cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. + } + localeCacheMap.set(EMOJI_TYPE_CUSTOM, cache); + } +} + +function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { + if (token.type === EMOJI_TYPE_UNICODE) { + // If the mode is native or native with flags for non-flag emoji + // we can just append the text node directly. + if ( + mode === EMOJI_MODE_NATIVE || + (mode === EMOJI_MODE_NATIVE_WITH_FLAGS && + !stringHasUnicodeFlags(token.code)) + ) { + return false; + } + } + + return true; +} + +function stateToImage(state: EmojiLoadedState) { + const image = document.createElement('img'); + image.draggable = false; + image.classList.add('emojione'); + + if (state.type === EMOJI_TYPE_UNICODE) { + const emojiInfo = twemojiHasBorder(unicodeToTwemojiHex(state.data.hexcode)); + if (emojiInfo.hasLightBorder) { + image.dataset.lightCode = `${emojiInfo.hexCode}_BORDER`; + } else if (emojiInfo.hasDarkBorder) { + image.dataset.darkCode = `${emojiInfo.hexCode}_BORDER`; + } + + image.alt = state.data.unicode; + image.title = state.data.label; + image.src = `${assetHost}/emoji/${emojiInfo.hexCode}.svg`; + } else { + // Custom emoji + const shortCode = `:${state.data.shortcode}:`; + image.classList.add('custom-emoji'); + image.alt = shortCode; + image.title = shortCode; + image.src = autoPlayGif ? state.data.url : state.data.static_url; + image.dataset.original = state.data.url; + image.dataset.static = state.data.static_url; + } + + return image; +} + +function renderedToHTMLFragment(renderedArray: (string | HTMLImageElement)[]) { + const fragment = document.createDocumentFragment(); + for (const fragmentItem of renderedArray) { + if (typeof fragmentItem === 'string') { + fragment.appendChild(document.createTextNode(fragmentItem)); + } else if (fragmentItem instanceof HTMLImageElement) { + fragment.appendChild(fragmentItem); + } + } + return fragment; +} diff --git a/app/javascript/mastodon/features/emoji/types.ts b/app/javascript/mastodon/features/emoji/types.ts new file mode 100644 index 00000000000..f5932ed97fd --- /dev/null +++ b/app/javascript/mastodon/features/emoji/types.ts @@ -0,0 +1,64 @@ +import type { FlatCompactEmoji, Locale } from 'emojibase'; + +import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; + +import type { + EMOJI_MODE_NATIVE, + EMOJI_MODE_NATIVE_WITH_FLAGS, + EMOJI_MODE_TWEMOJI, + EMOJI_STATE_MISSING, + EMOJI_TYPE_CUSTOM, + EMOJI_TYPE_UNICODE, +} from './constants'; + +export type EmojiMode = + | typeof EMOJI_MODE_NATIVE + | typeof EMOJI_MODE_NATIVE_WITH_FLAGS + | typeof EMOJI_MODE_TWEMOJI; + +export type LocaleOrCustom = Locale | typeof EMOJI_TYPE_CUSTOM; + +export interface EmojiAppState { + locales: Locale[]; + currentLocale: Locale; + mode: EmojiMode; +} + +export interface UnicodeEmojiToken { + type: typeof EMOJI_TYPE_UNICODE; + code: string; +} +export interface CustomEmojiToken { + type: typeof EMOJI_TYPE_CUSTOM; + code: string; +} +export type EmojiToken = UnicodeEmojiToken | CustomEmojiToken; + +export type CustomEmojiData = ApiCustomEmojiJSON; +export type UnicodeEmojiData = FlatCompactEmoji; +export type AnyEmojiData = CustomEmojiData | UnicodeEmojiData; + +export type EmojiStateMissing = typeof EMOJI_STATE_MISSING; +export interface EmojiStateUnicode { + type: typeof EMOJI_TYPE_UNICODE; + data: UnicodeEmojiData; +} +export interface EmojiStateCustom { + type: typeof EMOJI_TYPE_CUSTOM; + data: CustomEmojiData; +} +export type EmojiState = + | EmojiStateMissing + | EmojiStateUnicode + | EmojiStateCustom; +export type EmojiLoadedState = EmojiStateUnicode | EmojiStateCustom; + +export type EmojiStateMap = Map; + +export type ExtraCustomEmojiMap = Record; + +export interface TwemojiBorderInfo { + hexCode: string; + hasLightBorder: boolean; + hasDarkBorder: boolean; +} diff --git a/app/javascript/mastodon/features/emoji/utils.test.ts b/app/javascript/mastodon/features/emoji/utils.test.ts new file mode 100644 index 00000000000..75cac8c5b4c --- /dev/null +++ b/app/javascript/mastodon/features/emoji/utils.test.ts @@ -0,0 +1,47 @@ +import { stringHasUnicodeEmoji, stringHasUnicodeFlags } from './utils'; + +describe('stringHasEmoji', () => { + test.concurrent.for([ + ['only text', false], + ['text with emoji 😀', true], + ['multiple emojis 😀😃😄', true], + ['emoji with skin tone 👍🏽', true], + ['emoji with ZWJ 👩‍❤️‍👨', true], + ['emoji with variation selector ✊️', true], + ['emoji with keycap 1️⃣', true], + ['emoji with flags 🇺🇸', true], + ['emoji with regional indicators 🇦🇺', true], + ['emoji with gender 👩‍⚕️', true], + ['emoji with family 👨‍👩‍👧‍👦', true], + ['emoji with zero width joiner 👩‍🔬', true], + ['emoji with non-BMP codepoint 🧑‍🚀', true], + ['emoji with combining marks 👨‍👩‍👧‍👦', true], + ['emoji with enclosing keycap #️⃣', true], + ['emoji with no visible glyph \u200D', false], + ] as const)( + 'stringHasEmoji has emojis in "%s": %o', + ([text, expected], { expect }) => { + expect(stringHasUnicodeEmoji(text)).toBe(expected); + }, + ); +}); + +describe('stringHasFlags', () => { + test.concurrent.for([ + ['EU 🇪🇺', true], + ['Germany 🇩🇪', true], + ['Canada 🇨🇦', true], + ['São Tomé & Príncipe 🇸🇹', true], + ['Scotland 🏴󠁧󠁢󠁳󠁣󠁴󠁿', true], + ['black flag 🏴', false], + ['arrr 🏴‍☠️', false], + ['rainbow flag 🏳️‍🌈', false], + ['non-flag 🔥', false], + ['only text', false], + ] as const)( + 'stringHasFlags has flag in "%s": %o', + ([text, expected], { expect }) => { + expect(stringHasUnicodeFlags(text)).toBe(expected); + }, + ); +}); diff --git a/app/javascript/mastodon/features/emoji/utils.ts b/app/javascript/mastodon/features/emoji/utils.ts new file mode 100644 index 00000000000..d00accea8c5 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/utils.ts @@ -0,0 +1,13 @@ +import EMOJI_REGEX from 'emojibase-regex/emoji-loose'; + +export function stringHasUnicodeEmoji(text: string): boolean { + return EMOJI_REGEX.test(text); +} + +// From https://github.com/talkjs/country-flag-emoji-polyfill/blob/master/src/index.ts#L49-L50 +const EMOJIS_FLAGS_REGEX = + /[\u{1F1E6}-\u{1F1FF}|\u{E0062}-\u{E0063}|\u{E0065}|\u{E0067}|\u{E006C}|\u{E006E}|\u{E0073}-\u{E0074}|\u{E0077}|\u{E007F}]+/u; + +export function stringHasUnicodeFlags(text: string): boolean { + return EMOJIS_FLAGS_REGEX.test(text); +} diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 590c4c8d2b4..7763d9cb798 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -45,6 +45,7 @@ * @property {string} sso_redirect * @property {string} status_page_url * @property {boolean} terms_of_service_enabled + * @property {string?} emoji_style */ /** @@ -95,6 +96,7 @@ export const disableHoverCards = getMeta('disable_hover_cards'); export const disabledAccountId = getMeta('disabled_account_id'); export const displayMedia = getMeta('display_media'); export const domain = getMeta('domain'); +export const emojiStyle = getMeta('emoji_style') || 'auto'; export const expandSpoilers = getMeta('expand_spoilers'); export const forceSingleColumn = !getMeta('advanced_layout'); export const limitedFederationMode = getMeta('limited_federation_mode'); diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index 70e6391beee..e840429c41e 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -4,7 +4,7 @@ import { Globals } from '@react-spring/web'; import { setupBrowserNotifications } from 'mastodon/actions/notifications'; import Mastodon from 'mastodon/containers/mastodon'; -import { me, reduceMotion } from 'mastodon/initial_state'; +import { isFeatureEnabled, me, reduceMotion } from 'mastodon/initial_state'; import * as perf from 'mastodon/performance'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; @@ -29,6 +29,11 @@ function main() { }); } + if (isFeatureEnabled('modern_emojis')) { + const { initializeEmoji } = await import('@/mastodon/features/emoji'); + await initializeEmoji(); + } + const root = createRoot(mountNode); root.render(); store.dispatch(setupBrowserNotifications()); diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index ddcb214a472..cc95d8e7543 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -30,6 +30,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:use_blurhash] = object_account_user.setting_use_blurhash store[:use_pending_items] = object_account_user.setting_use_pending_items store[:show_trends] = Setting.trends && object_account_user.setting_trends + store[:emoji_style] = object_account_user.settings['web.emoji_style'] if Mastodon::Feature.modern_emojis_enabled? else store[:auto_play_gif] = Setting.auto_play_gif store[:display_media] = Setting.display_media diff --git a/package.json b/package.json index e1085909b76..1d1952e262f 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", "emojibase-data": "^16.0.3", + "emojibase-regex": "^16.0.0", "escape-html": "^1.0.3", "fast-glob": "^3.3.3", "fuzzysort": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 598b15acb53..147da72ad12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2671,6 +2671,7 @@ __metadata: emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" emojibase-data: "npm:^16.0.3" + emojibase-regex: "npm:^16.0.0" escape-html: "npm:^1.0.3" eslint: "npm:^9.23.0" eslint-import-resolver-typescript: "npm:^4.2.5" @@ -6607,6 +6608,13 @@ __metadata: languageName: node linkType: hard +"emojibase-regex@npm:^16.0.0": + version: 16.0.0 + resolution: "emojibase-regex@npm:16.0.0" + checksum: 10c0/8ee5ff798e51caa581434b1cb2f9737e50195093c4efa1739df21a50a5496f80517924787d865e8cf7d6a0b4c90dbedc04bdc506dcbcc582e14cdf0bb47af0f0 + languageName: node + linkType: hard + "emojibase@npm:^16.0.0": version: 16.0.0 resolution: "emojibase@npm:16.0.0" From d36236cbcdd6f83ca9ae15c2818ce998b4d30eaa Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 23 Jul 2025 09:39:36 +0200 Subject: [PATCH 095/660] fix: Fix glitchy status keyboard navigation (#35455) --- .../mastodon/components/status_list.jsx | 77 ++++++++++++++----- .../styles/mastodon/components.scss | 6 ++ 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index cca449b0ca8..70b7968fba1 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -40,6 +40,12 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: true, }; + componentDidMount() { + this.columnHeaderHeight = parseFloat( + getComputedStyle(this.node.node).getPropertyValue('--column-header-height') + ) || 0; + } + getFeaturedStatusCount = () => { return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0; }; @@ -53,35 +59,68 @@ export default class StatusList extends ImmutablePureComponent { }; handleMoveUp = (id, featured) => { - const elementIndex = this.getCurrentStatusIndex(id, featured) - 1; - this._selectChild(elementIndex, true); + const index = this.getCurrentStatusIndex(id, featured); + this._selectChild(id, index, -1); }; handleMoveDown = (id, featured) => { - const elementIndex = this.getCurrentStatusIndex(id, featured) + 1; - this._selectChild(elementIndex, false); + const index = this.getCurrentStatusIndex(id, featured); + this._selectChild(id, index, 1); }; + _selectChild = (id, index, direction) => { + const listContainer = this.node.node; + let listItem = listContainer.querySelector( + // :nth-child uses 1-based indexing + `.item-list > :nth-child(${index + 1 + direction})` + ); + + if (!listItem) { + return; + } + + // If selected container element is empty, we skip it + if (listItem.matches(':empty')) { + this._selectChild(id, index + direction, direction); + return; + } + + // Check if the list item is a post + let targetElement = listItem.querySelector('.focusable'); + + // Otherwise, check if the item contains follow suggestions or + // is a 'load more' button. + if ( + !targetElement && ( + listItem.querySelector('.inline-follow-suggestions') || + listItem.matches('.load-more') + ) + ) { + targetElement = listItem; + } + + if (targetElement) { + const elementRect = targetElement.getBoundingClientRect(); + + const isFullyVisible = + elementRect.top >= this.columnHeaderHeight && + elementRect.bottom <= window.innerHeight; + + if (!isFullyVisible) { + targetElement.scrollIntoView({ + block: direction === 1 ? 'start' : 'center', + }); + } + + targetElement.focus(); + } + } + handleLoadOlder = debounce(() => { const { statusIds, lastId, onLoadMore } = this.props; onLoadMore(lastId || (statusIds.size > 0 ? statusIds.last() : undefined)); }, 300, { leading: true }); - _selectChild (index, align_top) { - const container = this.node.node; - // TODO: This breaks at the inline-follow-suggestions container - const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`); - - if (element) { - if (align_top && container.scrollTop > element.offsetTop) { - element.scrollIntoView(true); - } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) { - element.scrollIntoView(false); - } - element.focus(); - } - } - setRef = c => { this.node = c; }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 40b073f68b9..0fd97fb7129 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2868,6 +2868,8 @@ a.account__display-name { } &__main { + --column-header-height: 62px; + box-sizing: border-box; width: 100%; flex: 0 1 auto; @@ -8815,6 +8817,10 @@ 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 6917cd2f403da5a042cbde2a0808646dea6d254b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 07:40:24 +0000 Subject: [PATCH 096/660] chore(deps): update dependency propshaft to v1.2.1 (#35451) 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 094c9b1d6ee..344be1fe5b7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -627,7 +627,7 @@ GEM prism (1.4.0) prometheus_exporter (2.2.0) webrick - propshaft (1.2.0) + propshaft (1.2.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack From 62a23b198555a9f7f2a22fa9e9c643db39ec4cd5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:42:48 +0200 Subject: [PATCH 097/660] chore(deps): update dependency rspec-sidekiq to v5.2.0 (#35436) 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 344be1fe5b7..97c4db4a583 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -759,7 +759,7 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-sidekiq (5.1.0) + rspec-sidekiq (5.2.0) rspec-core (~> 3.0) rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) From 70058ae49df2670a904efc964a2d12d1fd4059ee Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 23 Jul 2025 03:50:35 -0400 Subject: [PATCH 098/660] Add `Form::BaseBatch` class for "batch form update" objects (#35458) --- app/models/form/account_batch.rb | 10 ++++------ app/models/form/base_batch.rb | 14 ++++++++++++++ app/models/form/custom_emoji_batch.rb | 12 +++++------- app/models/form/domain_block_batch.rb | 8 ++------ app/models/form/email_domain_block_batch.rb | 8 ++------ app/models/form/ip_block_batch.rb | 8 ++------ 6 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 app/models/form/base_batch.rb diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb index 98e3be1a0c9..f3109ad62a7 100644 --- a/app/models/form/account_batch.rb +++ b/app/models/form/account_batch.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true -class Form::AccountBatch - include ActiveModel::Model - include Authorization - include AccountableConcern +class Form::AccountBatch < Form::BaseBatch include Payloadable - attr_accessor :account_ids, :action, :current_account, - :select_all_matching, :query + attr_accessor :account_ids, + :query, + :select_all_matching def save case action diff --git a/app/models/form/base_batch.rb b/app/models/form/base_batch.rb new file mode 100644 index 00000000000..d3af923784a --- /dev/null +++ b/app/models/form/base_batch.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Form::BaseBatch + include ActiveModel::Model + include Authorization + include AccountableConcern + + attr_accessor :action, + :current_account + + def save + raise 'Override in subclass' + end +end diff --git a/app/models/form/custom_emoji_batch.rb b/app/models/form/custom_emoji_batch.rb index c63996e0695..b8cfb363999 100644 --- a/app/models/form/custom_emoji_batch.rb +++ b/app/models/form/custom_emoji_batch.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -class Form::CustomEmojiBatch - include ActiveModel::Model - include Authorization - include AccountableConcern - - attr_accessor :custom_emoji_ids, :action, :current_account, - :category_id, :category_name, :visible_in_picker +class Form::CustomEmojiBatch < Form::BaseBatch + attr_accessor :category_id, + :category_name, + :visible_in_picker, + :custom_emoji_ids def save case action diff --git a/app/models/form/domain_block_batch.rb b/app/models/form/domain_block_batch.rb index 39012df5173..af792fd41f6 100644 --- a/app/models/form/domain_block_batch.rb +++ b/app/models/form/domain_block_batch.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true -class Form::DomainBlockBatch - include ActiveModel::Model - include Authorization - include AccountableConcern - - attr_accessor :domain_blocks_attributes, :action, :current_account +class Form::DomainBlockBatch < Form::BaseBatch + attr_accessor :domain_blocks_attributes def save case action diff --git a/app/models/form/email_domain_block_batch.rb b/app/models/form/email_domain_block_batch.rb index df120182bc2..6292f2b1e12 100644 --- a/app/models/form/email_domain_block_batch.rb +++ b/app/models/form/email_domain_block_batch.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true -class Form::EmailDomainBlockBatch - include ActiveModel::Model - include Authorization - include AccountableConcern - - attr_accessor :email_domain_block_ids, :action, :current_account +class Form::EmailDomainBlockBatch < Form::BaseBatch + attr_accessor :email_domain_block_ids def save case action diff --git a/app/models/form/ip_block_batch.rb b/app/models/form/ip_block_batch.rb index bdfeb91c8a8..b6a189750e9 100644 --- a/app/models/form/ip_block_batch.rb +++ b/app/models/form/ip_block_batch.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true -class Form::IpBlockBatch - include ActiveModel::Model - include Authorization - include AccountableConcern - - attr_accessor :ip_block_ids, :action, :current_account +class Form::IpBlockBatch < Form::BaseBatch + attr_accessor :ip_block_ids def save case action From b19131202fd4f535845b84cf2bd7ba2a5184fa1c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 23 Jul 2025 03:55:54 -0400 Subject: [PATCH 099/660] Extract constants for captcha directives/sources (#35439) --- .../concerns/auth/captcha_concern.rb | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/app/controllers/concerns/auth/captcha_concern.rb b/app/controllers/concerns/auth/captcha_concern.rb index c01da212499..a6232db943b 100644 --- a/app/controllers/concerns/auth/captcha_concern.rb +++ b/app/controllers/concerns/auth/captcha_concern.rb @@ -5,6 +5,18 @@ module Auth::CaptchaConcern include Hcaptcha::Adapters::ViewMethods + CAPTCHA_DIRECTIVES = %w( + connect_src + frame_src + script_src + style_src + ).freeze + + CAPTCHA_SOURCES = %w( + https://*.hcaptcha.com + https://hcaptcha.com + ).freeze + included do helper_method :render_captcha end @@ -42,20 +54,9 @@ module Auth::CaptchaConcern end def extend_csp_for_captcha! - policy = request.content_security_policy&.clone + return unless captcha_required? && request.content_security_policy.present? - return unless captcha_required? && policy.present? - - %w(script_src frame_src style_src connect_src).each do |directive| - values = policy.send(directive) - - values << 'https://hcaptcha.com' unless values.include?('https://hcaptcha.com') || values.include?('https:') - values << 'https://*.hcaptcha.com' unless values.include?('https://*.hcaptcha.com') || values.include?('https:') - - policy.send(directive, *values) - end - - request.content_security_policy = policy + request.content_security_policy = captcha_adjusted_policy end def render_captcha @@ -63,4 +64,24 @@ module Auth::CaptchaConcern hcaptcha_tags end + + private + + def captcha_adjusted_policy + request.content_security_policy.clone.tap do |policy| + populate_captcha_policy(policy) + end + end + + def populate_captcha_policy(policy) + CAPTCHA_DIRECTIVES.each do |directive| + values = policy.send(directive) + + CAPTCHA_SOURCES.each do |source| + values << source unless values.include?(source) || values.include?('https:') + end + + policy.send(directive, *values) + end + end end From d065ec9298fa473075c4b1a0bd2ee3531370e8cf Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 23 Jul 2025 03:59:14 -0400 Subject: [PATCH 100/660] Combine assignment params in `admin/accounts#batch` action (#35463) --- app/controllers/admin/accounts_controller.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 10391aa3e21..e1406930147 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -16,11 +16,14 @@ module Admin def batch authorize :account, :index? - @form = Form::AccountBatch.new(form_account_batch_params) - @form.current_account = current_account - @form.action = action_from_button - @form.select_all_matching = params[:select_all_matching] - @form.query = filtered_accounts + @form = Form::AccountBatch.new( + form_account_batch_params.merge( + action: action_from_button, + current_account:, + query: filtered_accounts, + select_all_matching: params[:select_all_matching] + ) + ) @form.save rescue ActionController::ParameterMissing flash[:alert] = I18n.t('admin.accounts.no_account_selected') From d2ef9ac04a16c918d7e8862a9d3141eb209db432 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 23 Jul 2025 04:02:07 -0400 Subject: [PATCH 101/660] Use `report_range` method in admin/tags to generate reporting period (#35465) --- app/controllers/admin/tags_controller.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb index a7bfd647944..f2c28328f86 100644 --- a/app/controllers/admin/tags_controller.rb +++ b/app/controllers/admin/tags_controller.rb @@ -5,6 +5,7 @@ module Admin before_action :set_tag, except: [:index] PER_PAGE = 20 + PERIOD_DAYS = 6.days def index authorize :tag, :index? @@ -15,7 +16,7 @@ module Admin def show authorize @tag, :show? - @time_period = (6.days.ago.to_date...Time.now.utc.to_date) + @time_period = report_range end def update @@ -24,7 +25,7 @@ module Admin if @tag.update(tag_params.merge(reviewed_at: Time.now.utc)) redirect_to admin_tag_path(@tag.id), notice: I18n.t('admin.tags.updated_msg') else - @time_period = (6.days.ago.to_date...Time.now.utc.to_date) + @time_period = report_range render :show end @@ -36,6 +37,10 @@ module Admin @tag = Tag.find(params[:id]) end + def report_range + (PERIOD_DAYS.ago.to_date...Time.now.utc.to_date) + end + def tag_params params .expect(tag: [:name, :display_name, :trendable, :usable, :listable]) From 593cdae4043148c00d38df7bda2d2d6bc1549c1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 08:12:16 +0000 Subject: [PATCH 102/660] fix(deps): update dependency axios to v1.11.0 (#35471) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/yarn.lock b/yarn.lock index 147da72ad12..67739e74d4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3294,23 +3294,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0": - version: 5.1.4 - resolution: "@rollup/pluginutils@npm:5.1.4" - dependencies: - "@types/estree": "npm:^1.0.0" - estree-walker: "npm:^2.0.2" - picomatch: "npm:^4.0.2" - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10c0/6d58fbc6f1024eb4b087bc9bf59a1d655a8056a60c0b4021d3beaeec3f0743503f52467fd89d2cf0e7eccf2831feb40a05ad541a17637ea21ba10b21c2004deb - languageName: node - linkType: hard - -"@rollup/pluginutils@npm:^5.1.3": +"@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0, @rollup/pluginutils@npm:^5.1.3": version: 5.2.0 resolution: "@rollup/pluginutils@npm:5.2.0" dependencies: @@ -5389,13 +5373,13 @@ __metadata: linkType: hard "axios@npm:^1.4.0": - version: 1.10.0 - resolution: "axios@npm:1.10.0" + version: 1.11.0 + resolution: "axios@npm:1.11.0" dependencies: follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" + form-data: "npm:^4.0.4" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8 + checksum: 10c0/5de273d33d43058610e4d252f0963cc4f10714da0bfe872e8ef2cbc23c2c999acc300fd357b6bce0fc84a2ca9bd45740fa6bb28199ce2c1266c8b1a393f2b36e languageName: node linkType: hard @@ -7601,14 +7585,16 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.1 - resolution: "form-data@npm:4.0.1" +"form-data@npm:^4.0.4": + version: 4.0.4 + resolution: "form-data@npm:4.0.4" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" mime-types: "npm:^2.1.12" - checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 + checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695 languageName: node linkType: hard From cec26d58c884510cd60fb1ee8c1d83bf6bbaa673 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 08:13:04 +0000 Subject: [PATCH 103/660] chore(deps): update dependency json-ld-preloaded to v3.3.2 (#35470) 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 97c4db4a583..4c232743bf1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -362,7 +362,7 @@ GEM rack (>= 2.2, < 4) rdf (~> 3.3) rexml (~> 3.2) - json-ld-preloaded (3.3.1) + json-ld-preloaded (3.3.2) json-ld (~> 3.3) rdf (~> 3.3) json-schema (5.2.1) From 14a781fa24c969a6be4f2ccc3e6e5c9f83db7437 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 23 Jul 2025 15:42:07 +0200 Subject: [PATCH 104/660] Add button to load new replies in web UI (#35210) --- app/controllers/api/v1/statuses_controller.rb | 13 +- .../mastodon/actions/statuses_typed.ts | 9 +- app/javascript/mastodon/api.ts | 46 +++++++- .../mastodon/api/async_refreshes.ts | 5 + app/javascript/mastodon/api/statuses.ts | 15 ++- .../mastodon/api_types/async_refreshes.ts | 7 ++ .../status/components/refresh_controller.tsx | 111 ++++++++++++++++++ .../mastodon/features/status/index.jsx | 12 +- app/javascript/mastodon/locales/en.json | 4 +- app/javascript/mastodon/reducers/contexts.ts | 12 +- .../concerns/status/fetch_replies_concern.rb | 5 +- app/models/worker_batch.rb | 79 +++++++++++++ .../activitypub/fetch_all_replies_service.rb | 2 +- .../activitypub/fetch_replies_service.rb | 7 +- .../activitypub/fetch_all_replies_worker.rb | 2 +- app/workers/fetch_reply_worker.rb | 3 + spec/models/worker_batch_spec.rb | 104 ++++++++++++++++ .../fetch_all_replies_worker_spec.rb | 1 - 18 files changed, 411 insertions(+), 26 deletions(-) create mode 100644 app/javascript/mastodon/api/async_refreshes.ts create mode 100644 app/javascript/mastodon/api_types/async_refreshes.ts create mode 100644 app/javascript/mastodon/features/status/components/refresh_controller.tsx create mode 100644 app/models/worker_batch.rb create mode 100644 spec/models/worker_batch_spec.rb diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index d3b0e89e97b..e25b161afd8 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -2,6 +2,7 @@ class Api::V1::StatusesController < Api::BaseController include Authorization + include AsyncRefreshesConcern before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy] @@ -57,9 +58,17 @@ class Api::V1::StatusesController < Api::BaseController @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) statuses = [@status] + @context.ancestors + @context.descendants - render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) + refresh_key = "context:#{@status.id}:refresh" + async_refresh = AsyncRefresh.new(refresh_key) - ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) if !current_account.nil? && @status.should_fetch_replies? + if async_refresh.running? + add_async_refresh_header(async_refresh) + elsif !current_account.nil? && @status.should_fetch_replies? + add_async_refresh_header(AsyncRefresh.create(refresh_key)) + ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) + end + + render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) end def create diff --git a/app/javascript/mastodon/actions/statuses_typed.ts b/app/javascript/mastodon/actions/statuses_typed.ts index b98abbe122e..cc9c389cdab 100644 --- a/app/javascript/mastodon/actions/statuses_typed.ts +++ b/app/javascript/mastodon/actions/statuses_typed.ts @@ -1,3 +1,5 @@ +import { createAction } from '@reduxjs/toolkit'; + import { apiGetContext } from 'mastodon/api/statuses'; import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; @@ -6,13 +8,18 @@ import { importFetchedStatuses } from './importer'; export const fetchContext = createDataLoadingThunk( 'status/context', ({ statusId }: { statusId: string }) => apiGetContext(statusId), - (context, { dispatch }) => { + ({ context, refresh }, { dispatch }) => { const statuses = context.ancestors.concat(context.descendants); dispatch(importFetchedStatuses(statuses)); return { context, + refresh, }; }, ); + +export const completeContextRefresh = createAction<{ statusId: string }>( + 'status/context/complete', +); diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index dc9c20b5085..1820e00a537 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -20,6 +20,50 @@ export const getLinks = (response: AxiosResponse) => { return LinkHeader.parse(value); }; +export interface AsyncRefreshHeader { + id: string; + retry: number; +} + +const isAsyncRefreshHeader = (obj: object): obj is AsyncRefreshHeader => + 'id' in obj && 'retry' in obj; + +export const getAsyncRefreshHeader = ( + response: AxiosResponse, +): AsyncRefreshHeader | null => { + const value = response.headers['mastodon-async-refresh'] as + | string + | undefined; + + if (!value) { + return null; + } + + const asyncRefreshHeader: Record = {}; + + value.split(/,\s*/).forEach((pair) => { + const [key, val] = pair.split('=', 2); + + let typedValue: string | number; + + if (key && ['id', 'retry'].includes(key) && val) { + if (val.startsWith('"')) { + typedValue = val.slice(1, -1); + } else { + typedValue = parseInt(val); + } + + asyncRefreshHeader[key] = typedValue; + } + }); + + if (isAsyncRefreshHeader(asyncRefreshHeader)) { + return asyncRefreshHeader; + } + + return null; +}; + const csrfHeader: RawAxiosRequestHeaders = {}; const setCSRFHeader = () => { @@ -83,7 +127,7 @@ export default function api(withAuthorization = true) { return instance; } -type ApiUrl = `v${1 | 2}/${string}`; +type ApiUrl = `v${1 | '1_alpha' | 2}/${string}`; type RequestParamsOrData = Record; export async function apiRequest( diff --git a/app/javascript/mastodon/api/async_refreshes.ts b/app/javascript/mastodon/api/async_refreshes.ts new file mode 100644 index 00000000000..953300a4a86 --- /dev/null +++ b/app/javascript/mastodon/api/async_refreshes.ts @@ -0,0 +1,5 @@ +import { apiRequestGet } from 'mastodon/api'; +import type { ApiAsyncRefreshJSON } from 'mastodon/api_types/async_refreshes'; + +export const apiGetAsyncRefresh = (id: string) => + apiRequestGet(`v1_alpha/async_refreshes/${id}`); diff --git a/app/javascript/mastodon/api/statuses.ts b/app/javascript/mastodon/api/statuses.ts index 921a7bfe636..48eff2a692f 100644 --- a/app/javascript/mastodon/api/statuses.ts +++ b/app/javascript/mastodon/api/statuses.ts @@ -1,5 +1,14 @@ -import { apiRequestGet } from 'mastodon/api'; +import api, { getAsyncRefreshHeader } from 'mastodon/api'; import type { ApiContextJSON } from 'mastodon/api_types/statuses'; -export const apiGetContext = (statusId: string) => - apiRequestGet(`v1/statuses/${statusId}/context`); +export const apiGetContext = async (statusId: string) => { + const response = await api().request({ + method: 'GET', + url: `/api/v1/statuses/${statusId}/context`, + }); + + return { + context: response.data, + refresh: getAsyncRefreshHeader(response), + }; +}; diff --git a/app/javascript/mastodon/api_types/async_refreshes.ts b/app/javascript/mastodon/api_types/async_refreshes.ts new file mode 100644 index 00000000000..2d2fed24127 --- /dev/null +++ b/app/javascript/mastodon/api_types/async_refreshes.ts @@ -0,0 +1,7 @@ +export interface ApiAsyncRefreshJSON { + async_refresh: { + id: string; + status: 'running' | 'finished'; + result_count: number; + }; +} diff --git a/app/javascript/mastodon/features/status/components/refresh_controller.tsx b/app/javascript/mastodon/features/status/components/refresh_controller.tsx new file mode 100644 index 00000000000..04046302b62 --- /dev/null +++ b/app/javascript/mastodon/features/status/components/refresh_controller.tsx @@ -0,0 +1,111 @@ +import { useEffect, useState, useCallback } from 'react'; + +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { + fetchContext, + completeContextRefresh, +} from 'mastodon/actions/statuses'; +import type { AsyncRefreshHeader } from 'mastodon/api'; +import { apiGetAsyncRefresh } from 'mastodon/api/async_refreshes'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +const messages = defineMessages({ + loading: { + id: 'status.context.loading', + defaultMessage: 'Checking for more replies', + }, +}); + +export const RefreshController: React.FC<{ + statusId: string; + withBorder?: boolean; +}> = ({ statusId, withBorder }) => { + const refresh = useAppSelector( + (state) => state.contexts.refreshing[statusId], + ); + const dispatch = useAppDispatch(); + const intl = useIntl(); + const [ready, setReady] = useState(false); + const [loading, setLoading] = useState(false); + + useEffect(() => { + let timeoutId: ReturnType; + + const scheduleRefresh = (refresh: AsyncRefreshHeader) => { + timeoutId = setTimeout(() => { + void apiGetAsyncRefresh(refresh.id).then((result) => { + if (result.async_refresh.status === 'finished') { + dispatch(completeContextRefresh({ statusId })); + + if (result.async_refresh.result_count > 0) { + setReady(true); + } + } else { + scheduleRefresh(refresh); + } + + return ''; + }); + }, refresh.retry * 1000); + }; + + if (refresh) { + scheduleRefresh(refresh); + } + + return () => { + clearTimeout(timeoutId); + }; + }, [dispatch, setReady, statusId, refresh]); + + const handleClick = useCallback(() => { + setLoading(true); + setReady(false); + + dispatch(fetchContext({ statusId })) + .then(() => { + setLoading(false); + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [dispatch, setReady, statusId]); + + if (ready && !loading) { + return ( + + ); + } + + if (!refresh && !loading) { + return null; + } + + return ( +
+ +
+ ); +}; diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 64cd0c4f825..77d23f55f6d 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -68,7 +68,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import ActionBar from './components/action_bar'; import { DetailedStatus } from './components/detailed_status'; - +import { RefreshController } from './components/refresh_controller'; const messages = defineMessages({ revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, @@ -548,7 +548,7 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants, remoteHint; - const { isLoading, status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture } = this.props; + const { isLoading, status, ancestorsIds, descendantsIds, refresh, intl, domain, multiColumn, pictureInPicture } = this.props; const { fullscreen } = this.state; if (isLoading) { @@ -578,11 +578,9 @@ class Status extends ImmutablePureComponent { if (!isLocal) { remoteHint = ( - } - label={{status.getIn(['account', 'acct']).split('@')[1]} }} />} + ); } diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 59d39a15361..13b7aa42121 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "See more followers on {domain}", "hints.profiles.see_more_follows": "See more follows on {domain}", "hints.profiles.see_more_posts": "See more posts on {domain}", - "hints.threads.replies_may_be_missing": "Replies from other servers may be missing.", - "hints.threads.see_more": "See more replies on {domain}", "home.column_settings.show_quotes": "Show quotes", "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", @@ -847,6 +845,8 @@ "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", + "status.context.load_new_replies": "New replies available", + "status.context.loading": "Checking for more replies", "status.continued_thread": "Continued thread", "status.copy": "Copy link to post", "status.delete": "Delete", diff --git a/app/javascript/mastodon/reducers/contexts.ts b/app/javascript/mastodon/reducers/contexts.ts index 7ecc6e3b29f..cf378b4c048 100644 --- a/app/javascript/mastodon/reducers/contexts.ts +++ b/app/javascript/mastodon/reducers/contexts.ts @@ -4,6 +4,7 @@ import type { Draft, UnknownAction } from '@reduxjs/toolkit'; import type { List as ImmutableList } from 'immutable'; import { timelineDelete } from 'mastodon/actions/timelines_typed'; +import type { AsyncRefreshHeader } from 'mastodon/api'; import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; import type { ApiStatusJSON, @@ -12,7 +13,7 @@ import type { import type { Status } from 'mastodon/models/status'; import { blockAccountSuccess, muteAccountSuccess } from '../actions/accounts'; -import { fetchContext } from '../actions/statuses'; +import { fetchContext, completeContextRefresh } from '../actions/statuses'; import { TIMELINE_UPDATE } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -25,11 +26,13 @@ interface TimelineUpdateAction extends UnknownAction { interface State { inReplyTos: Record; replies: Record; + refreshing: Record; } const initialState: State = { inReplyTos: {}, replies: {}, + refreshing: {}, }; const normalizeContext = ( @@ -127,6 +130,13 @@ export const contextsReducer = createReducer(initialState, (builder) => { builder .addCase(fetchContext.fulfilled, (state, action) => { normalizeContext(state, action.meta.arg.statusId, action.payload.context); + + if (action.payload.refresh) { + state.refreshing[action.meta.arg.statusId] = action.payload.refresh; + } + }) + .addCase(completeContextRefresh, (state, action) => { + delete state.refreshing[action.payload.statusId]; }) .addCase(blockAccountSuccess, (state, action) => { filterContexts( diff --git a/app/models/concerns/status/fetch_replies_concern.rb b/app/models/concerns/status/fetch_replies_concern.rb index fd9929aba49..cc117cb5ac6 100644 --- a/app/models/concerns/status/fetch_replies_concern.rb +++ b/app/models/concerns/status/fetch_replies_concern.rb @@ -3,9 +3,6 @@ module Status::FetchRepliesConcern extend ActiveSupport::Concern - # enable/disable fetching all replies - FETCH_REPLIES_ENABLED = ENV['FETCH_REPLIES_ENABLED'] == 'true' - # debounce fetching all replies to minimize DoS FETCH_REPLIES_COOLDOWN_MINUTES = (ENV['FETCH_REPLIES_COOLDOWN_MINUTES'] || 15).to_i.minutes FETCH_REPLIES_INITIAL_WAIT_MINUTES = (ENV['FETCH_REPLIES_INITIAL_WAIT_MINUTES'] || 5).to_i.minutes @@ -36,7 +33,7 @@ module Status::FetchRepliesConcern def should_fetch_replies? # we aren't brand new, and we haven't fetched replies since the debounce window - FETCH_REPLIES_ENABLED && !local? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && ( + !local? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && ( fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago ) end diff --git a/app/models/worker_batch.rb b/app/models/worker_batch.rb new file mode 100644 index 00000000000..f741071ba95 --- /dev/null +++ b/app/models/worker_batch.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +class WorkerBatch + include Redisable + + TTL = 3600 + + def initialize(id = nil) + @id = id || SecureRandom.hex(12) + end + + attr_reader :id + + # Connect the batch with an async refresh. When the number of processed jobs + # passes the given threshold, the async refresh will be marked as finished. + # @param [String] async_refresh_key + # @param [Float] threshold + def connect(async_refresh_key, threshold: 1.0) + redis.hset(key, { 'async_refresh_key' => async_refresh_key, 'threshold' => threshold }) + end + + # Add jobs to the batch. Usually when the batch is created. + # @param [Array] jids + def add_jobs(jids) + if jids.blank? + async_refresh_key = redis.hget(key, 'async_refresh_key') + + if async_refresh_key.present? + async_refresh = AsyncRefresh.new(async_refresh_key) + async_refresh.finish! + end + + return + end + + redis.multi do |pipeline| + pipeline.sadd(key('jobs'), jids) + pipeline.expire(key('jobs'), TTL) + pipeline.hincrby(key, 'pending', jids.size) + pipeline.expire(key, TTL) + end + end + + # Remove a job from the batch, such as when it's been processed or it has failed. + # @param [String] jid + def remove_job(jid) + _, pending, processed, async_refresh_key, threshold = redis.multi do |pipeline| + pipeline.srem(key('jobs'), jid) + pipeline.hincrby(key, 'pending', -1) + pipeline.hincrby(key, 'processed', 1) + pipeline.hget(key, 'async_refresh_key') + pipeline.hget(key, 'threshold') + end + + if async_refresh_key.present? + async_refresh = AsyncRefresh.new(async_refresh_key) + async_refresh.increment_result_count(by: 1) + async_refresh.finish! if pending.zero? || processed >= threshold.to_f * (processed + pending) + end + end + + # Get pending jobs. + # @returns [Array] + def jobs + redis.smembers(key('jobs')) + end + + # Inspect the batch. + # @returns [Hash] + def info + redis.hgetall(key) + end + + private + + def key(suffix = nil) + "worker_batch:#{@id}#{":#{suffix}" if suffix}" + end +end diff --git a/app/services/activitypub/fetch_all_replies_service.rb b/app/services/activitypub/fetch_all_replies_service.rb index 765e5c8ae82..e9c1712ed66 100644 --- a/app/services/activitypub/fetch_all_replies_service.rb +++ b/app/services/activitypub/fetch_all_replies_service.rb @@ -6,7 +6,7 @@ class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService # Limit of replies to fetch per status MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i - def call(status_uri, collection_or_uri, max_pages: 1, request_id: nil) + def call(status_uri, collection_or_uri, max_pages: 1, async_refresh_key: nil, request_id: nil) @status_uri = status_uri super diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 6a6d9e391a4..25eb275ca5c 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -6,7 +6,7 @@ class ActivityPub::FetchRepliesService < BaseService # Limit of fetched replies MAX_REPLIES = 5 - def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, request_id: nil) + def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, async_refresh_key: nil, request_id: nil) @reference_uri = reference_uri @allow_synchronous_requests = allow_synchronous_requests @@ -14,7 +14,10 @@ class ActivityPub::FetchRepliesService < BaseService return if @items.nil? @items = filter_replies(@items) - FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id }] } + + batch = WorkerBatch.new + batch.connect(async_refresh_key) if async_refresh_key.present? + batch.add_jobs(FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }] }) [@items, n_pages] end diff --git a/app/workers/activitypub/fetch_all_replies_worker.rb b/app/workers/activitypub/fetch_all_replies_worker.rb index 40b251cf148..ab9eebc4ec7 100644 --- a/app/workers/activitypub/fetch_all_replies_worker.rb +++ b/app/workers/activitypub/fetch_all_replies_worker.rb @@ -55,7 +55,7 @@ class ActivityPub::FetchAllRepliesWorker replies_collection_or_uri = get_replies_uri(status) return if replies_collection_or_uri.nil? - ActivityPub::FetchAllRepliesService.new.call(value_or_id(status), replies_collection_or_uri, max_pages: max_pages, **options.deep_symbolize_keys) + ActivityPub::FetchAllRepliesService.new.call(value_or_id(status), replies_collection_or_uri, max_pages: max_pages, async_refresh_key: "context:#{@root_status.id}:refresh", **options.deep_symbolize_keys) end # Get the URI of the replies collection of a status diff --git a/app/workers/fetch_reply_worker.rb b/app/workers/fetch_reply_worker.rb index ecb232bbbb0..da3b9a8c131 100644 --- a/app/workers/fetch_reply_worker.rb +++ b/app/workers/fetch_reply_worker.rb @@ -7,6 +7,9 @@ class FetchReplyWorker sidekiq_options queue: 'pull', retry: 3 def perform(child_url, options = {}) + batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id'] FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys) + ensure + batch&.remove_job(jid) end end diff --git a/spec/models/worker_batch_spec.rb b/spec/models/worker_batch_spec.rb new file mode 100644 index 00000000000..b58dc48618a --- /dev/null +++ b/spec/models/worker_batch_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe WorkerBatch do + subject { described_class.new } + + let(:async_refresh_key) { 'test_refresh' } + let(:async_refresh) { nil } + + describe '#id' do + it 'returns a string' do + expect(subject.id).to be_a String + end + end + + describe '#connect' do + before do + subject.connect(async_refresh_key, threshold: 0.75) + end + + it 'persists the async refresh key' do + expect(subject.info['async_refresh_key']).to eq async_refresh_key + end + + it 'persists the threshold' do + expect(subject.info['threshold']).to eq '0.75' + end + end + + describe '#add_jobs' do + before do + subject.connect(async_refresh_key, threshold: 0.5) if async_refresh.present? + subject.add_jobs([]) + end + + context 'when called with empty array' do + it 'does not persist the number of pending jobs' do + expect(subject.info).to be_empty + end + + it 'does not persist the job IDs' do + expect(subject.jobs).to eq [] + end + + context 'when async refresh is connected' do + let(:async_refresh) { AsyncRefresh.new(async_refresh_key) } + + it 'immediately marks the async refresh as finished' do + expect(async_refresh.reload.finished?).to be true + end + end + end + + context 'when called with an array of job IDs' do + before do + subject.add_jobs(%w(foo bar)) + end + + it 'persists the number of pending jobs' do + expect(subject.info['pending']).to eq '2' + end + + it 'persists the job IDs' do + expect(subject.jobs).to eq %w(foo bar) + end + end + end + + describe '#remove_job' do + before do + subject.connect(async_refresh_key, threshold: 0.5) if async_refresh.present? + subject.add_jobs(%w(foo bar baz)) + subject.remove_job('foo') + end + + it 'removes the job from pending jobs' do + expect(subject.jobs).to eq %w(bar baz) + end + + it 'decrements the number of pending jobs' do + expect(subject.info['pending']).to eq '2' + end + + context 'when async refresh is connected' do + let(:async_refresh) { AsyncRefresh.new(async_refresh_key) } + + it 'increments async refresh progress' do + expect(async_refresh.reload.result_count).to eq 1 + end + + it 'marks the async refresh as finished when the threshold is reached' do + subject.remove_job('bar') + expect(async_refresh.reload.finished?).to be true + end + end + end + + describe '#info' do + it 'returns a hash' do + expect(subject.info).to be_a Hash + end + end +end diff --git a/spec/workers/activitypub/fetch_all_replies_worker_spec.rb b/spec/workers/activitypub/fetch_all_replies_worker_spec.rb index 9a8bdac0307..9795c4619a1 100644 --- a/spec/workers/activitypub/fetch_all_replies_worker_spec.rb +++ b/spec/workers/activitypub/fetch_all_replies_worker_spec.rb @@ -123,7 +123,6 @@ RSpec.describe ActivityPub::FetchAllRepliesWorker do end before do - stub_const('Status::FetchRepliesConcern::FETCH_REPLIES_ENABLED', true) all_items.each do |item| next if [top_note_uri, reply_note_uri].include? item From 7d3ef27a8dc8fb281bae959b245d3db63aa82260 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 23 Jul 2025 16:01:45 +0200 Subject: [PATCH 105/660] Fix accidentally instantiating Web Worker (#35473) --- .../mastodon/features/emoji/index.ts | 24 ++++++++++++------- .../mastodon/features/emoji/render.ts | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index ef6cd67aeb5..4f23dc5395e 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -4,19 +4,27 @@ import { toSupportedLocale } from './locale'; const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); -const worker = - 'Worker' in window - ? new Worker(new URL('./worker', import.meta.url), { - type: 'module', - }) - : null; +let worker: Worker | null = null; export async function initializeEmoji() { + if (!worker && 'Worker' in window) { + try { + worker = new Worker(new URL('./worker', import.meta.url), { + type: 'module', + credentials: 'omit', + }); + } catch (err) { + console.warn('Error creating web worker:', err); + } + } + if (worker) { - worker.addEventListener('message', (event: MessageEvent) => { + // Assign worker to const to make TS happy inside the event listener. + const thisWorker = worker; + thisWorker.addEventListener('message', (event: MessageEvent) => { const { data: message } = event; if (message === 'ready') { - worker.postMessage('custom'); + thisWorker.postMessage('custom'); void loadEmojiLocale(userLocale); // Load English locale as well, because people are still used to // using it from before we supported other locales. diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts index 8f0c1ee15fe..6ef9492147c 100644 --- a/app/javascript/mastodon/features/emoji/render.ts +++ b/app/javascript/mastodon/features/emoji/render.ts @@ -4,7 +4,6 @@ import EMOJI_REGEX from 'emojibase-regex/emoji-loose'; import { autoPlayGif } from '@/mastodon/initial_state'; import { assetHost } from '@/mastodon/utils/config'; -import { loadEmojiLocale } from '.'; import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, @@ -17,6 +16,7 @@ import { searchCustomEmojisByShortcodes, searchEmojisByHexcodes, } from './database'; +import { loadEmojiLocale } from './index'; import { emojiToUnicodeHex, twemojiHasBorder, From a6794c066d422bff0b1e132ce22fd73fcd9cc56b Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 24 Jul 2025 01:09:24 +0200 Subject: [PATCH 106/660] =?UTF-8?q?Fix=20=E2=80=9CExpand=20this=20post?= =?UTF-8?q?=E2=80=9D=20link=20including=20user=20`@undefined`=20(#35478)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/picture_in_picture/components/footer.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx index 080aaca4512..24c88f95050 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.tsx @@ -21,6 +21,7 @@ import { openModal } from 'mastodon/actions/modal'; import { IconButton } from 'mastodon/components/icon_button'; import { useIdentity } from 'mastodon/identity_context'; import { me } from 'mastodon/initial_state'; +import type { Account } from 'mastodon/models/account'; import type { Status } from 'mastodon/models/status'; import { makeGetStatus } from 'mastodon/selectors'; import type { RootState } from 'mastodon/store'; @@ -66,10 +67,7 @@ export const Footer: React.FC<{ const dispatch = useAppDispatch(); const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector; const status = useAppSelector((state) => getStatus(state, { id: statusId })); - const accountId = status?.get('account') as string | undefined; - const account = useAppSelector((state) => - accountId ? state.accounts.get(accountId) : undefined, - ); + const account = status?.get('account') as Account | undefined; const askReplyConfirmation = useAppSelector( (state) => (state.compose.get('text') as string).trim().length !== 0, ); From 7f9ad7eabf9d86731ce4af33a8d4a8d35ca3e077 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 24 Jul 2025 09:14:27 +0200 Subject: [PATCH 107/660] Enables cross-origin Web Workers (#35483) --- .../mastodon/features/emoji/index.ts | 4 +-- .../mastodon/features/emoji/loader.ts | 9 +++--- app/javascript/mastodon/utils/workers.ts | 29 +++++++++++++++++++ config/environments/development.rb | 3 ++ vite.config.mts | 5 ++++ 5 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 app/javascript/mastodon/utils/workers.ts diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 4f23dc5395e..541cea9aa99 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -1,4 +1,5 @@ import initialState from '@/mastodon/initial_state'; +import { loadWorker } from '@/mastodon/utils/workers'; import { toSupportedLocale } from './locale'; @@ -9,9 +10,8 @@ let worker: Worker | null = null; export async function initializeEmoji() { if (!worker && 'Worker' in window) { try { - worker = new Worker(new URL('./worker', import.meta.url), { + worker = loadWorker(new URL('./worker', import.meta.url), { type: 'module', - credentials: 'omit', }); } catch (err) { console.warn('Error creating web worker:', err); diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index 482d9e5c359..454b8383f07 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -36,15 +36,16 @@ async function fetchAndCheckEtag( ): Promise { const locale = toSupportedLocaleOrCustom(localeOrCustom); - let uri: string; + // Use location.origin as this script may be loaded from a CDN domain. + const url = new URL(location.origin); if (locale === 'custom') { - uri = '/api/v1/custom_emojis'; + url.pathname = '/api/v1/custom_emojis'; } else { - uri = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`; + url.pathname = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`; } const oldEtag = await loadLatestEtag(locale); - const response = await fetch(uri, { + const response = await fetch(url, { headers: { 'Content-Type': 'application/json', 'If-None-Match': oldEtag ?? '', // Send the old ETag to check for modifications diff --git a/app/javascript/mastodon/utils/workers.ts b/app/javascript/mastodon/utils/workers.ts new file mode 100644 index 00000000000..02dd66d86e0 --- /dev/null +++ b/app/javascript/mastodon/utils/workers.ts @@ -0,0 +1,29 @@ +/** + * Loads Web Worker that is compatible with cross-origin scripts for CDNs. + * + * Returns null if the environment doesn't support web workers. + */ +export function loadWorker(url: string | URL, options: WorkerOptions = {}) { + if (!('Worker' in window)) { + return null; + } + + try { + // Check if the script origin and the window origin are the same. + const scriptUrl = new URL(import.meta.url); + if (location.origin === scriptUrl.origin) { + // Not cross-origin, can just load normally. + return new Worker(url, options); + } + } catch (err) { + // In case the URL parsing fails. + console.warn('Error instantiating Worker:', err); + } + + // Import the worker script from a same-origin Blob. + const contents = `import ${JSON.stringify(url)};`; + const blob = URL.createObjectURL( + new Blob([contents], { type: 'text/javascript' }), + ); + return new Worker(blob, options); +} diff --git a/config/environments/development.rb b/config/environments/development.rb index ca9e876e26b..79b491869cc 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -19,6 +19,9 @@ Rails.application.configure do # Enable server timing. config.server_timing = true + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + config.asset_host = ENV['CDN_HOST'] if ENV['CDN_HOST'].present? + # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join('tmp', 'caching-dev.txt').exist? diff --git a/vite.config.mts b/vite.config.mts index 7f93157b7e1..f7871ece4d6 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -65,6 +65,11 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { // but it needs to be scoped to the whole domain 'Service-Worker-Allowed': '/', }, + hmr: { + // Forcing the protocol to be insecure helps if you are proxying your dev server with SSL, + // because Vite still tries to connect to localhost. + protocol: 'ws', + }, port: 3036, }, build: { From 4241ce9888a632ed10ac792fc4fa4d3fb3aea41f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 24 Jul 2025 03:33:53 -0400 Subject: [PATCH 108/660] Silence json key duplicate warning from `api/web/push_subscriptions` (#35481) --- app/controllers/api/web/push_subscriptions_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb index 2711071b4a5..ced68d39fc7 100644 --- a/app/controllers/api/web/push_subscriptions_controller.rb +++ b/app/controllers/api/web/push_subscriptions_controller.rb @@ -49,7 +49,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController { policy: 'all', alerts: Notification::TYPES.index_with { alerts_enabled }, - } + }.deep_stringify_keys end def alerts_enabled From 290e36d7e8af9d45c81ccf5b5567aa6e249eea54 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 24 Jul 2025 03:46:09 -0400 Subject: [PATCH 109/660] Finish migration of `api/web/push_subscriptions` controller->request spec (#35482) --- .../web/push_subscriptions_controller_spec.rb | 124 ------------------ .../api/web/push_subscriptions_spec.rb | 124 +++++++++++++++++- 2 files changed, 123 insertions(+), 125 deletions(-) delete mode 100644 spec/controllers/api/web/push_subscriptions_controller_spec.rb diff --git a/spec/controllers/api/web/push_subscriptions_controller_spec.rb b/spec/controllers/api/web/push_subscriptions_controller_spec.rb deleted file mode 100644 index 1e01709262b..00000000000 --- a/spec/controllers/api/web/push_subscriptions_controller_spec.rb +++ /dev/null @@ -1,124 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::Web::PushSubscriptionsController do - render_views - - let(:user) { Fabricate(:user) } - - let(:create_payload) do - { - subscription: { - endpoint: 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX', - keys: { - p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=', - auth: 'eH_C8rq2raXqlcBVDa1gLg==', - }, - standard: standard, - }, - } - end - - let(:alerts_payload) do - { - data: { - policy: 'all', - - alerts: { - follow: true, - follow_request: false, - favourite: false, - reblog: true, - mention: false, - poll: true, - status: false, - }, - }, - } - end - let(:standard) { '1' } - - before do - sign_in(user) - - stub_request(:post, create_payload[:subscription][:endpoint]).to_return(status: 200) - end - - describe 'POST #create' do - it 'saves push subscriptions' do - post :create, format: :json, params: create_payload - - expect(response).to have_http_status(200) - - user.reload - - expect(created_push_subscription) - .to have_attributes( - endpoint: eq(create_payload[:subscription][:endpoint]), - key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]), - key_auth: eq(create_payload[:subscription][:keys][:auth]) - ) - .and be_standard - expect(user.session_activations.first.web_push_subscription).to eq(created_push_subscription) - end - - context 'when standard is provided as false value' do - let(:standard) { '0' } - - it 'saves push subscription with standard as false' do - post :create, format: :json, params: create_payload - - expect(created_push_subscription) - .to_not be_standard - end - end - - context 'with a user who has a session with a prior subscription' do - let!(:prior_subscription) { Fabricate(:web_push_subscription, session_activation: user.session_activations.last) } - - it 'destroys prior subscription when creating new one' do - post :create, format: :json, params: create_payload - - expect(response).to have_http_status(200) - expect { prior_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'with initial data' do - it 'saves alert settings' do - post :create, format: :json, params: create_payload.merge(alerts_payload) - - expect(response).to have_http_status(200) - - expect(created_push_subscription.data['policy']).to eq 'all' - - %w(follow follow_request favourite reblog mention poll status).each do |type| - expect(created_push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) - end - end - end - end - - describe 'PUT #update' do - it 'changes alert settings' do - post :create, format: :json, params: create_payload - - expect(response).to have_http_status(200) - - alerts_payload[:id] = created_push_subscription.id - - put :update, format: :json, params: alerts_payload - - expect(created_push_subscription.data['policy']).to eq 'all' - - %w(follow follow_request favourite reblog mention poll status).each do |type| - expect(created_push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) - end - end - end - - def created_push_subscription - Web::PushSubscription.find_by(endpoint: create_payload[:subscription][:endpoint]) - end -end diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb index 42545b3d6e2..91d7b85bc58 100644 --- a/spec/requests/api/web/push_subscriptions_spec.rb +++ b/spec/requests/api/web/push_subscriptions_spec.rb @@ -3,6 +3,38 @@ require 'rails_helper' RSpec.describe 'API Web Push Subscriptions' do + let(:create_payload) do + { + subscription: { + endpoint: 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX', + keys: { + p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=', + auth: 'eH_C8rq2raXqlcBVDa1gLg==', + }, + standard: standard, + }, + } + end + + let(:alerts_payload) do + { + data: { + policy: 'all', + + alerts: { + follow: true, + follow_request: false, + favourite: false, + reblog: true, + mention: false, + poll: true, + status: false, + }, + }, + } + end + let(:standard) { '1' } + describe 'DELETE /api/web/push_subscriptions/:id' do subject { delete api_web_push_subscription_path(token) } @@ -54,7 +86,9 @@ RSpec.describe 'API Web Push Subscriptions' do end describe 'POST /api/web/push_subscriptions' do - before { sign_in Fabricate :user } + before { sign_in(user) } + + let(:user) { Fabricate :user } it 'gracefully handles invalid nested params' do post api_web_push_subscriptions_path, params: { subscription: 'invalid' } @@ -62,6 +96,69 @@ RSpec.describe 'API Web Push Subscriptions' do expect(response) .to have_http_status(400) end + + it 'saves push subscriptions with valid params' do + post api_web_push_subscriptions_path, params: create_payload + expect(response) + .to have_http_status(200) + + expect(created_push_subscription) + .to have_attributes( + endpoint: eq(create_payload[:subscription][:endpoint]), + key_p256dh: eq(create_payload[:subscription][:keys][:p256dh]), + key_auth: eq(create_payload[:subscription][:keys][:auth]) + ) + .and be_standard + expect(user.session_activations.first.web_push_subscription) + .to eq(created_push_subscription) + end + + context 'when standard is provided as false value' do + let(:standard) { '0' } + + it 'saves push subscription with standard as false' do + post api_web_push_subscriptions_path, params: create_payload + + expect(created_push_subscription) + .to_not be_standard + end + end + + context 'with a user who has a session with a prior subscription' do + before do + # Trigger creation of a `SessionActivation` for the user so that the + # prior_subscription setup and verification works as expected + get about_path + end + + let!(:prior_subscription) { Fabricate(:web_push_subscription, user:, session_activation: user.session_activations.last) } + + it 'destroys prior subscription when creating new one' do + post api_web_push_subscriptions_path, params: create_payload + + expect(response) + .to have_http_status(200) + expect { prior_subscription.reload } + .to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with initial data' do + it 'saves alert settings' do + post api_web_push_subscriptions_path, params: create_payload.merge(alerts_payload) + + expect(response) + .to have_http_status(200) + + expect(created_push_subscription.data['policy']) + .to eq 'all' + + alert_types.each do |type| + expect(created_push_subscription.data['alerts'][type]) + .to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) + end + end + end end describe 'PUT /api/web/push_subscriptions' do @@ -75,5 +172,30 @@ RSpec.describe 'API Web Push Subscriptions' do expect(response) .to have_http_status(400) end + + it 'changes existing alert settings' do + # Create record this way to correctly associate a `SessionActivation` + # during full POST->create cycle + post api_web_push_subscriptions_path params: create_payload + expect(response) + .to have_http_status(200) + + put api_web_push_subscription_path(created_push_subscription), params: alerts_payload + expect(created_push_subscription.data['policy']) + .to eq 'all' + alert_types.each do |type| + expect(created_push_subscription.data['alerts'][type]) + .to eq(alerts_payload[:data][:alerts][type.to_sym].to_s) + end + end + end + + def created_push_subscription + Web::PushSubscription + .find_by(endpoint: create_payload[:subscription][:endpoint]) + end + + def alert_types + Notification::LEGACY_TYPE_CLASS_MAP.values.map(&:to_s) end end From 469304359a0a034dc28182d1cb4976daa70e2aaf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:49:09 +0200 Subject: [PATCH 110/660] New Crowdin Translations (automated) (#35495) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ar.json | 2 - app/javascript/mastodon/locales/ast.json | 1 - app/javascript/mastodon/locales/be.json | 2 - app/javascript/mastodon/locales/bg.json | 2 - app/javascript/mastodon/locales/ca.json | 4 +- app/javascript/mastodon/locales/cs.json | 4 +- app/javascript/mastodon/locales/cy.json | 2 - app/javascript/mastodon/locales/da.json | 4 +- app/javascript/mastodon/locales/de.json | 6 +- app/javascript/mastodon/locales/el.json | 2 - app/javascript/mastodon/locales/en-GB.json | 2 - app/javascript/mastodon/locales/eo.json | 18 +++- app/javascript/mastodon/locales/es-AR.json | 4 +- app/javascript/mastodon/locales/es-MX.json | 4 +- app/javascript/mastodon/locales/es.json | 4 +- app/javascript/mastodon/locales/et.json | 2 - app/javascript/mastodon/locales/eu.json | 8 +- app/javascript/mastodon/locales/fa.json | 2 - app/javascript/mastodon/locales/fi.json | 4 +- app/javascript/mastodon/locales/fo.json | 4 +- app/javascript/mastodon/locales/fr-CA.json | 2 - app/javascript/mastodon/locales/fr.json | 2 - app/javascript/mastodon/locales/fy.json | 2 - app/javascript/mastodon/locales/ga.json | 2 - app/javascript/mastodon/locales/gd.json | 2 - app/javascript/mastodon/locales/gl.json | 4 +- app/javascript/mastodon/locales/he.json | 4 +- app/javascript/mastodon/locales/hu.json | 4 +- app/javascript/mastodon/locales/ia.json | 2 - app/javascript/mastodon/locales/io.json | 2 - app/javascript/mastodon/locales/is.json | 4 +- app/javascript/mastodon/locales/it.json | 4 +- app/javascript/mastodon/locales/ja.json | 2 - app/javascript/mastodon/locales/kab.json | 2 - app/javascript/mastodon/locales/ko.json | 2 - app/javascript/mastodon/locales/ku.json | 1 - app/javascript/mastodon/locales/lad.json | 2 - app/javascript/mastodon/locales/lt.json | 2 - app/javascript/mastodon/locales/lv.json | 2 - app/javascript/mastodon/locales/nan.json | 2 - app/javascript/mastodon/locales/nl.json | 4 +- app/javascript/mastodon/locales/nn.json | 2 - app/javascript/mastodon/locales/no.json | 2 - app/javascript/mastodon/locales/pa.json | 1 - app/javascript/mastodon/locales/pl.json | 2 - app/javascript/mastodon/locales/pt-BR.json | 2 - app/javascript/mastodon/locales/pt-PT.json | 2 - app/javascript/mastodon/locales/ru.json | 6 +- app/javascript/mastodon/locales/sc.json | 2 - app/javascript/mastodon/locales/si.json | 2 - app/javascript/mastodon/locales/sk.json | 2 - app/javascript/mastodon/locales/sl.json | 2 - app/javascript/mastodon/locales/sq.json | 2 - app/javascript/mastodon/locales/sv.json | 2 - app/javascript/mastodon/locales/th.json | 2 - app/javascript/mastodon/locales/tok.json | 2 - app/javascript/mastodon/locales/tr.json | 2 - app/javascript/mastodon/locales/uk.json | 2 - app/javascript/mastodon/locales/vi.json | 4 +- app/javascript/mastodon/locales/zh-CN.json | 2 - app/javascript/mastodon/locales/zh-TW.json | 4 +- config/locales/ru.yml | 112 ++++++++++----------- 62 files changed, 113 insertions(+), 180 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 813a92761d3..24acd77e316 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "عرض المزيد من المتابعين على {domain}", "hints.profiles.see_more_follows": "اطلع على المزيد من المتابعين على {domain}", "hints.profiles.see_more_posts": "عرض المزيد من المنشورات من {domain}", - "hints.threads.replies_may_be_missing": "قد تكون الردود الواردة من الخوادم الأخرى غائبة.", - "hints.threads.see_more": "اطلع على المزيد من الردود على {domain}", "home.column_settings.show_quotes": "إظهار الاقتباسات", "home.column_settings.show_reblogs": "اعرض المعاد نشرها", "home.column_settings.show_replies": "اعرض الردود", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index e039b9e6128..cc929664316 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -266,7 +266,6 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} participante} other {{counter} participantes}}", "hashtag.follow": "Siguir a la etiqueta", "hashtag.unfollow": "Dexar de siguir a la etiqueta", - "hints.threads.replies_may_be_missing": "Ye posible que falten les rempuestes d'otros sirvidores.", "home.column_settings.show_reblogs": "Amosar los artículos compartíos", "home.column_settings.show_replies": "Amosar les rempuestes", "home.pending_critical_update.body": "¡Anueva'l sirvidor de Mastodon namás que puedas!", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 213ec61aca6..0f49cc69799 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -404,8 +404,6 @@ "hints.profiles.see_more_followers": "Глядзець больш падпісаных на {domain}", "hints.profiles.see_more_follows": "Глядзець больш падпісак на {domain}", "hints.profiles.see_more_posts": "Глядзець больш допісаў на {domain}", - "hints.threads.replies_may_be_missing": "Адказы з іншых сервераў могуць адсутнічаць.", - "hints.threads.see_more": "Глядзіце больш адказаў на {domain}", "home.column_settings.show_quotes": "Паказаць цытаты", "home.column_settings.show_reblogs": "Паказваць пашырэнні", "home.column_settings.show_replies": "Паказваць адказы", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 8628b68954c..392f9470c0e 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -419,8 +419,6 @@ "hints.profiles.see_more_followers": "Преглед на още последователи на {domain}", "hints.profiles.see_more_follows": "Преглед на още последвания на {domain}", "hints.profiles.see_more_posts": "Преглед на още публикации на {domain}", - "hints.threads.replies_may_be_missing": "Отговори от други сървъри може да липсват.", - "hints.threads.see_more": "Преглед на още отговори на {domain}", "home.column_settings.show_quotes": "Показване на цитираното", "home.column_settings.show_reblogs": "Показване на подсилванията", "home.column_settings.show_replies": "Показване на отговорите", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 7fab405f8e2..645a3751243 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Vegeu més seguidors a {domain}", "hints.profiles.see_more_follows": "Vegeu més seguiments a {domain}", "hints.profiles.see_more_posts": "Vegeu més publicacions a {domain}", - "hints.threads.replies_may_be_missing": "Es poden haver perdut respostes d'altres servidors.", - "hints.threads.see_more": "Vegeu més respostes a {domain}", "home.column_settings.show_quotes": "Mostrar les cites", "home.column_settings.show_reblogs": "Mostra els impulsos", "home.column_settings.show_replies": "Mostra les respostes", @@ -846,6 +844,8 @@ "status.bookmark": "Marca", "status.cancel_reblog_private": "Desfés l'impuls", "status.cannot_reblog": "No es pot impulsar aquest tut", + "status.context.load_new_replies": "Hi ha respostes noves", + "status.context.loading": "Comprovació de més respostes", "status.continued_thread": "Continuació del fil", "status.copy": "Copia l'enllaç al tut", "status.delete": "Elimina", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index f64a6196074..24486800f30 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Zobrazit více sledujících na {domain}", "hints.profiles.see_more_follows": "Zobrazit další sledování na {domain}", "hints.profiles.see_more_posts": "Zobrazit další příspěvky na {domain}", - "hints.threads.replies_may_be_missing": "Odpovědi z jiných serverů mohou chybět.", - "hints.threads.see_more": "Zobrazit další odpovědi na {domain}", "home.column_settings.show_quotes": "Zobrazit citace", "home.column_settings.show_reblogs": "Zobrazit boosty", "home.column_settings.show_replies": "Zobrazit odpovědi", @@ -847,6 +845,8 @@ "status.bookmark": "Přidat do záložek", "status.cancel_reblog_private": "Zrušit boostnutí", "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý", + "status.context.load_new_replies": "K dispozici jsou nové odpovědi", + "status.context.loading": "Hledání dalších odpovědí", "status.continued_thread": "Pokračuje ve vlákně", "status.copy": "Zkopírovat odkaz na příspěvek", "status.delete": "Smazat", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index da40f1e010b..dfd900bece9 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Gweld mwy o ddilynwyr ar {domain}", "hints.profiles.see_more_follows": "Gweld mwy o 'yn dilyn' ar {domain}", "hints.profiles.see_more_posts": "Gweld mwy o bostiadau ar {domain}", - "hints.threads.replies_may_be_missing": "Mae'n bosibl y bydd ymatebion gan weinyddion eraill ar goll.", - "hints.threads.see_more": "Gweld mwy o ymatebion ar {domain}", "home.column_settings.show_quotes": "Dangos dyfyniadau", "home.column_settings.show_reblogs": "Dangos hybiau", "home.column_settings.show_replies": "Dangos ymatebion", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 6a3542fee35..1cd633c7476 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Se flere følgere på {domain}", "hints.profiles.see_more_follows": "Se flere fulgte på {domain}", "hints.profiles.see_more_posts": "Se flere indlæg på {domain}", - "hints.threads.replies_may_be_missing": "Der kan mangle svar fra andre servere.", - "hints.threads.see_more": "Se flere svar på {domain}", "home.column_settings.show_quotes": "Vis citater", "home.column_settings.show_reblogs": "Vis fremhævelser", "home.column_settings.show_replies": "Vis svar", @@ -847,6 +845,8 @@ "status.bookmark": "Bogmærk", "status.cancel_reblog_private": "Fjern fremhævelse", "status.cannot_reblog": "Dette indlæg kan ikke fremhæves", + "status.context.load_new_replies": "Nye svar tilgængelige", + "status.context.loading": "Tjekker for flere svar", "status.continued_thread": "Fortsat tråd", "status.copy": "Kopiér link til indlæg", "status.delete": "Slet", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index f0beb106051..0bd451d64b6 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -6,7 +6,7 @@ "about.domain_blocks.no_reason_available": "Grund unbekannt", "about.domain_blocks.preamble": "Mastodon erlaubt es dir grundsätzlich, alle Inhalte von allen Nutzer*innen auf allen Servern im Fediverse zu sehen und mit ihnen zu interagieren. Für diesen Server gibt es aber ein paar Ausnahmen.", "about.domain_blocks.silenced.explanation": "Standardmäßig werden von diesem Server keine Inhalte oder Profile angezeigt. Du kannst die Profile und Inhalte aber dennoch sehen, wenn du explizit nach diesen suchst oder diesen folgst.", - "about.domain_blocks.silenced.title": "Stummgeschaltet", + "about.domain_blocks.silenced.title": "Ausgeblendet", "about.domain_blocks.suspended.explanation": "Es werden keine Daten von diesem Server verarbeitet, gespeichert oder ausgetauscht, sodass eine Interaktion oder Kommunikation mit Nutzer*innen dieses Servers nicht möglich ist.", "about.domain_blocks.suspended.title": "Gesperrt", "about.language_label": "Sprache", @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Weitere Follower auf {domain} ansehen", "hints.profiles.see_more_follows": "Weitere gefolgte Profile auf {domain} ansehen", "hints.profiles.see_more_posts": "Weitere Beiträge auf {domain} ansehen", - "hints.threads.replies_may_be_missing": "Möglicherweise werden nicht alle Antworten von anderen Servern angezeigt.", - "hints.threads.see_more": "Weitere Antworten auf {domain} ansehen", "home.column_settings.show_quotes": "Zitierte Beiträge anzeigen", "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", "home.column_settings.show_replies": "Antworten anzeigen", @@ -847,6 +845,8 @@ "status.bookmark": "Lesezeichen setzen", "status.cancel_reblog_private": "Beitrag nicht mehr teilen", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", + "status.context.load_new_replies": "Neue Antworten verfügbar", + "status.context.loading": "Weitere Antworten werden abgerufen", "status.continued_thread": "Fortgeführter Thread", "status.copy": "Link zum Beitrag kopieren", "status.delete": "Beitrag löschen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 864632a6353..8223d5d2008 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Δες περισσότερους ακόλουθους στο {domain}", "hints.profiles.see_more_follows": "Δες περισσότερα άτομα που ακολουθούνται στο {domain}", "hints.profiles.see_more_posts": "Δες περισσότερες αναρτήσεις στο {domain}", - "hints.threads.replies_may_be_missing": "Απαντήσεις από άλλους διακομιστές μπορεί να λείπουν.", - "hints.threads.see_more": "Δες περισσότερες απαντήσεις στο {domain}", "home.column_settings.show_quotes": "Εμφάνιση παραθεμάτων", "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων", "home.column_settings.show_replies": "Εμφάνιση απαντήσεων", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 1702076b3fb..4f3a649f0bb 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "See more followers on {domain}", "hints.profiles.see_more_follows": "See more follows on {domain}", "hints.profiles.see_more_posts": "See more posts on {domain}", - "hints.threads.replies_may_be_missing": "Replies from other servers may be missing.", - "hints.threads.see_more": "See more replies on {domain}", "home.column_settings.show_quotes": "Show quotes", "home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_replies": "Show replies", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 3af96c22c45..92f529a8f41 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -1,7 +1,7 @@ { "about.blocks": "Reguligitaj serviloj", "about.contact": "Kontakto:", - "about.default_locale": "기본", + "about.default_locale": "Defaŭlta", "about.disclaimer": "Mastodon estas libera, malfermitkoda programo kaj varmarko de la firmao Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Kialo ne disponeblas", "about.domain_blocks.preamble": "Mastodon ĝenerale rajtigas vidi la enhavojn de uzantoj el aliaj serviloj en la fediverso, kaj komuniki kun ili. Jen la limigoj deciditaj de tiu ĉi servilo mem.", @@ -331,6 +331,7 @@ "featured_carousel.next": "Antaŭen", "featured_carousel.post": "Afiŝi", "featured_carousel.previous": "Malantaŭen", + "featured_carousel.slide": "{index} de {total}", "filter_modal.added.context_mismatch_explanation": "Ĉi tiu filtrilkategorio ne kongruas kun la kunteksto en kiu vi akcesis ĉi tiun afiŝon. Se vi volas ke la afiŝo estas ankaŭ filtrita en ĉi tiu kunteksto, vi devus redakti la filtrilon.", "filter_modal.added.context_mismatch_title": "Ne kongruas la kunteksto!", "filter_modal.added.expired_explanation": "Ĉi tiu filtrilkategorio eksvalidiĝis, vu bezonos ŝanĝi la eksvaliddaton por ĝi.", @@ -409,8 +410,7 @@ "hints.profiles.see_more_followers": "Vidi pli da sekvantoj sur {domain}", "hints.profiles.see_more_follows": "Vidi pli da sekvatoj sur {domain}", "hints.profiles.see_more_posts": "Vidi pli da afiŝoj sur {domain}", - "hints.threads.replies_may_be_missing": "Respondoj de aliaj serviloj eble mankas.", - "hints.threads.see_more": "Vidi pli da respondoj sur {domain}", + "home.column_settings.show_quotes": "Montri citaĵojn", "home.column_settings.show_reblogs": "Montri diskonigojn", "home.column_settings.show_replies": "Montri respondojn", "home.hide_announcements": "Kaŝi la anoncojn", @@ -533,8 +533,10 @@ "mute_modal.you_wont_see_mentions": "Vi ne vidos afiŝojn, kiuj mencias ilin.", "mute_modal.you_wont_see_posts": "Ili ankoraŭ povas vidi viajn afiŝojn, sed vi ne vidos iliajn.", "navigation_bar.about": "Pri", + "navigation_bar.account_settings": "Pasvorto kaj sekureco", "navigation_bar.administration": "Administrado", "navigation_bar.advanced_interface": "Malfermi altnivelan retpaĝan interfacon", + "navigation_bar.automated_deletion": "Aŭtomata forigo de afiŝoj", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.bookmarks": "Legosignoj", "navigation_bar.direct": "Privataj mencioj", @@ -544,6 +546,7 @@ "navigation_bar.follow_requests": "Petoj de sekvado", "navigation_bar.followed_tags": "Sekvataj kradvortoj", "navigation_bar.follows_and_followers": "Sekvatoj kaj sekvantoj", + "navigation_bar.import_export": "Importo kaj eksporto", "navigation_bar.lists": "Listoj", "navigation_bar.logout": "Elsaluti", "navigation_bar.moderation": "Modereco", @@ -551,6 +554,7 @@ "navigation_bar.mutes": "Silentigitaj uzantoj", "navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.", "navigation_bar.preferences": "Preferoj", + "navigation_bar.privacy_and_reach": "Privateco kaj atingo", "navigation_bar.search": "Serĉi", "not_signed_in_indicator.not_signed_in": "Necesas saluti por aliri tiun rimedon.", "notification.admin.report": "{name} raportis {target}", @@ -787,7 +791,7 @@ "search.quick_action.open_url": "Malfermi URL en Mastodono", "search.quick_action.status_search": "Afiŝoj kiuj konformas kun {x}", "search.search_or_paste": "Serĉu aŭ algluu URL-on", - "search_popout.full_text_search_disabled_message": "Ne havebla sur {domain}.", + "search_popout.full_text_search_disabled_message": "Ne disponebla sur {domain}.", "search_popout.full_text_search_logged_out_message": "Disponebla nur kiam ensalutinte.", "search_popout.language_code": "ISO-lingva kodo", "search_popout.options": "Serĉaj opcioj", @@ -820,6 +824,8 @@ "status.bookmark": "Aldoni al la legosignoj", "status.cancel_reblog_private": "Ne plu diskonigi", "status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi", + "status.context.load_new_replies": "Disponeblaj novaj respondoj", + "status.context.loading": "Serĉante pliajn respondojn", "status.continued_thread": "Daŭrigis fadenon", "status.copy": "Kopii la ligilon al la afiŝo", "status.delete": "Forigi", @@ -845,6 +851,9 @@ "status.mute_conversation": "Silentigi konversacion", "status.open": "Pligrandigu ĉi tiun afiŝon", "status.pin": "Alpingli al la profilo", + "status.quote_error.not_found": "Ĉi tiu afiŝo ne povas esti montrata.", + "status.quote_error.rejected": "Ĉi tiu afiŝo ne povas esti montrata ĉar la originala aŭtoro ne permesas ĝian citadon.", + "status.quote_error.removed": "Ĉi tiu afiŝo estis forigita de ĝia aŭtoro.", "status.read_more": "Legi pli", "status.reblog": "Diskonigi", "status.reblog_private": "Diskonigi kun la sama videbleco", @@ -876,6 +885,7 @@ "tabs_bar.home": "Hejmo", "tabs_bar.menu": "Menuo", "tabs_bar.notifications": "Sciigoj", + "tabs_bar.publish": "Nova afiŝo", "tabs_bar.search": "Serĉi", "terms_of_service.effective_as_of": "Ĝi ekvalidas de {date}", "terms_of_service.title": "Kondiĉoj de uzado", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 89f3873564a..3eca9eab37c 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Ver más seguidores en {domain}", "hints.profiles.see_more_follows": "Ver más seguimientos en {domain}", "hints.profiles.see_more_posts": "Ver más mensajes en {domain}", - "hints.threads.replies_may_be_missing": "Es posible que falten respuestas de otros servidores.", - "hints.threads.see_more": "Ver más respuestas en {domain}", "home.column_settings.show_quotes": "Mostrar citas", "home.column_settings.show_reblogs": "Mostrar adhesiones", "home.column_settings.show_replies": "Mostrar respuestas", @@ -847,6 +845,8 @@ "status.bookmark": "Marcar", "status.cancel_reblog_private": "Quitar adhesión", "status.cannot_reblog": "No se puede adherir a este mensaje", + "status.context.load_new_replies": "Hay nuevas respuestas", + "status.context.loading": "Buscando más respuestas", "status.continued_thread": "Continuación de hilo", "status.copy": "Copiar enlace al mensaje", "status.delete": "Eliminar", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index d764b3dac97..67a91402f87 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Ver más seguidores en {domain}", "hints.profiles.see_more_follows": "Ver más perfiles seguidos en {domain}", "hints.profiles.see_more_posts": "Ver más publicaciones en {domain}", - "hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.", - "hints.threads.see_more": "Ver más respuestas en {domain}", "home.column_settings.show_quotes": "Mostrar citas", "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respuestas", @@ -847,6 +845,8 @@ "status.bookmark": "Añadir marcador", "status.cancel_reblog_private": "Deshacer impulso", "status.cannot_reblog": "Esta publicación no puede ser impulsada", + "status.context.load_new_replies": "Nuevas respuestas disponibles", + "status.context.loading": "Comprobando si hay más respuestas", "status.continued_thread": "Hilo continuado", "status.copy": "Copiar enlace al estado", "status.delete": "Borrar", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 47d23e5b40d..cbd79ea10fd 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Ver más seguidores en {domain}", "hints.profiles.see_more_follows": "Ver más perfiles seguidos en {domain}", "hints.profiles.see_more_posts": "Ver más publicaciones en {domain}", - "hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.", - "hints.threads.see_more": "Ver más respuestas en {domain}", "home.column_settings.show_quotes": "Mostrar citas", "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respuestas", @@ -847,6 +845,8 @@ "status.bookmark": "Añadir marcador", "status.cancel_reblog_private": "Deshacer impulso", "status.cannot_reblog": "Esta publicación no se puede impulsar", + "status.context.load_new_replies": "Hay nuevas respuestas", + "status.context.loading": "Buscando más respuestas", "status.continued_thread": "Continuó el hilo", "status.copy": "Copiar enlace a la publicación", "status.delete": "Borrar", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index ffb33c7e7a6..cf6c39cad48 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Vaata rohkem jälgijaid kohas {domain}", "hints.profiles.see_more_follows": "Vaata rohkem jälgitavaid kohas {domain}", "hints.profiles.see_more_posts": "Vaata rohkem postitusi kohas {domain}", - "hints.threads.replies_may_be_missing": "Vastuseid teistest serveritest võib olla puudu.", - "hints.threads.see_more": "Vaata rohkem vastuseid kohas {domain}", "home.column_settings.show_quotes": "Näita tsiteeritut", "home.column_settings.show_reblogs": "Näita jagamisi", "home.column_settings.show_replies": "Näita vastuseid", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index b757fb0cdaa..65f3e87ac15 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -30,6 +30,7 @@ "account.edit_profile": "Editatu profila", "account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean", "account.endorse": "Nabarmendu profilean", + "account.familiar_followers_many": "Jarraitzaileak: {name1}, {name2} eta beste {othersCount, plural, one {ezagun bat} other {# ezagun}}", "account.familiar_followers_one": "{name1}-k jarraitzen du", "account.familiar_followers_two": "{name1}-k eta {name2}-k jarraitzen dute", "account.featured": "Gailenak", @@ -118,6 +119,8 @@ "annual_report.summary.most_used_hashtag.most_used_hashtag": "traola erabiliena", "annual_report.summary.most_used_hashtag.none": "Bat ere ez", "annual_report.summary.new_posts.new_posts": "bidalketa berriak", + "annual_report.summary.percentile.text": "Horrek jartzen zaitu top (e)an {domain} erabiltzaileen artean ", + "annual_report.summary.percentile.we_wont_tell_bernie": "Bernieri ez diogu ezer esango ;)..", "annual_report.summary.thanks": "Eskerrik asko Mastodonen parte izateagatik!", "attachments_list.unprocessed": "(prozesatu gabe)", "audio.hide": "Ezkutatu audioa", @@ -216,6 +219,7 @@ "confirmations.discard_draft.edit.message": "Jarraitzeak editatzen ari zaren mezuan egindako aldaketak baztertuko ditu.", "confirmations.discard_draft.edit.title": "Baztertu zure argitalpenari egindako aldaketak?", "confirmations.discard_draft.post.cancel": "Zirriborroa berrekin", + "confirmations.discard_draft.post.message": "Jarraituz gero, idazten ari zaren sarrera bertan behera geratuko da.", "confirmations.discard_draft.post.title": "Zure argitalpenaren zirriborroa baztertu nahi duzu?", "confirmations.discard_edit_media.confirm": "Baztertu", "confirmations.discard_edit_media.message": "Multimediaren deskribapen edo aurrebistan gorde gabeko aldaketak daude, baztertu nahi dituzu?", @@ -413,8 +417,6 @@ "hints.profiles.see_more_followers": "Ikusi jarraitzaile gehiago {domain}-(e)n", "hints.profiles.see_more_follows": "Ikusi jarraitzaile gehiago {domain}-(e)n", "hints.profiles.see_more_posts": "Ikusi bidalketa gehiago {domain}-(e)n", - "hints.threads.replies_may_be_missing": "Baliteke beste zerbitzari batzuen erantzun batzuk ez erakustea.", - "hints.threads.see_more": "Ikusi erantzun gehiago {domain}-(e)n", "home.column_settings.show_quotes": "Erakutsi aipamenak", "home.column_settings.show_reblogs": "Erakutsi bultzadak", "home.column_settings.show_replies": "Erakutsi erantzunak", @@ -435,6 +437,7 @@ "ignore_notifications_modal.not_following_title": "Jarraitzen ez dituzun pertsonen jakinarazpenei ez ikusiarena egin?", "ignore_notifications_modal.private_mentions_title": "Eskatu gabeko aipamen pribatuen jakinarazpenei ez ikusiarena egin?", "info_button.label": "Laguntza", + "info_button.what_is_alt_text": "

Zer da Alt testua?

Alt testuak irudiak deskribatzeko aukera ematen du, ikusmen-urritasunak, banda-zabalera txikiko konexioak edo testuinguru gehigarria nahi duten pertsonentzat.

Alt testu argi, zehatz eta objektiboen bidez, guztion irisgarritasuna eta ulermena hobetu ditzakezu.

  • Hartu elementu garrantzitsuenak
  • Laburbildu irudietako testua
  • Erabili esaldien egitura erregularra
  • Baztertu informazio erredundantea.
  • Enfokatu joeretan eta funtsezko elementuetan irudi konplexuetan (diagrametan edo mapetan, adibidez)
", "interaction_modal.action.favourite": "Jarraitzeko, zure kontutik atsegindu behar duzu.", "interaction_modal.action.follow": "Jarraitzeko zure kontutik jarraitu behar duzu.", "interaction_modal.action.reply": "Jarraitzeko zure kontutik erantzun behar duzu.", @@ -903,6 +906,7 @@ "video.hide": "Ezkutatu bideoa", "video.pause": "Pausatu", "video.play": "Jo", + "video.skip_forward": "Jauzi aurrerantz", "video.unmute": "Soinua ezarri", "video.volume_down": "Bolumena jaitsi", "video.volume_up": "Bolumena Igo" diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index fadbb247007..c35687e0317 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "دیدن پی‌گیرندگان بیش‌تر روی {domain}", "hints.profiles.see_more_follows": "دیدن پی‌گرفته‌های بیش‌تر روی {domain}", "hints.profiles.see_more_posts": "دیدن فرسته‌های بیش‌تر روی {domain}", - "hints.threads.replies_may_be_missing": "شاید پاسخ‌ها از دیگر کارسازها نباشند.", - "hints.threads.see_more": "دیدن پاسخ‌های بیش‌تر روی {domain}", "home.column_settings.show_quotes": "نمایش نقل‌قول‌ها", "home.column_settings.show_reblogs": "نمایش تقویت‌ها", "home.column_settings.show_replies": "نمایش پاسخ‌ها", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 7de3fc07ea9..36fcfd4e947 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Näytä lisää seuraajia palvelimella {domain}", "hints.profiles.see_more_follows": "Näytä lisää seurattavia palvelimella {domain}", "hints.profiles.see_more_posts": "Näytä lisää julkaisuja palvelimella {domain}", - "hints.threads.replies_may_be_missing": "Muiden palvelinten vastauksia saattaa puuttua.", - "hints.threads.see_more": "Näytä lisää vastauksia palvelimella {domain}", "home.column_settings.show_quotes": "Näytä lainaukset", "home.column_settings.show_reblogs": "Näytä tehostukset", "home.column_settings.show_replies": "Näytä vastaukset", @@ -847,6 +845,8 @@ "status.bookmark": "Lisää kirjanmerkki", "status.cancel_reblog_private": "Peru tehostus", "status.cannot_reblog": "Tätä julkaisua ei voi tehostaa", + "status.context.load_new_replies": "Uusia vastauksia saatavilla", + "status.context.loading": "Tarkistetaan lisävastauksia", "status.continued_thread": "Jatkoi ketjua", "status.copy": "Kopioi linkki julkaisuun", "status.delete": "Poista", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index afd75afc42b..1bdccaeae86 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Sí fleiri fylgjarar á {domain}", "hints.profiles.see_more_follows": "Sí fleiri, ið viðkomandi fylgir, á {domain}", "hints.profiles.see_more_posts": "Sí fleiri postar á {domain}", - "hints.threads.replies_may_be_missing": "Svar frá øðrum ambætarum mangla møguliga.", - "hints.threads.see_more": "Sí fleiri svar á {domain}", "home.column_settings.show_quotes": "Vís siteringar", "home.column_settings.show_reblogs": "Vís lyft", "home.column_settings.show_replies": "Vís svar", @@ -847,6 +845,8 @@ "status.bookmark": "Goym", "status.cancel_reblog_private": "Strika stimbran", "status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin", + "status.context.load_new_replies": "Nýggj svar tøk", + "status.context.loading": "Kanni um tað eru fleiri svar", "status.continued_thread": "Framhaldandi tráður", "status.copy": "Kopiera leinki til postin", "status.delete": "Strika", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 0fdd593859a..840b0e508d5 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -423,8 +423,6 @@ "hints.profiles.see_more_followers": "Afficher plus d'abonné·e·s sur {domain}", "hints.profiles.see_more_follows": "Afficher plus d'abonné·e·s sur {domain}", "hints.profiles.see_more_posts": "Voir plus de messages sur {domain}", - "hints.threads.replies_may_be_missing": "Les réponses provenant des autres serveurs pourraient être manquantes.", - "hints.threads.see_more": "Afficher plus de réponses sur {domain}", "home.column_settings.show_quotes": "Afficher les citations", "home.column_settings.show_reblogs": "Afficher boosts", "home.column_settings.show_replies": "Afficher réponses", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 6f1bbae61c1..c0c9580a46d 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -423,8 +423,6 @@ "hints.profiles.see_more_followers": "Afficher plus d'abonné·e·s sur {domain}", "hints.profiles.see_more_follows": "Afficher plus d'abonné·e·s sur {domain}", "hints.profiles.see_more_posts": "Voir plus de messages sur {domain}", - "hints.threads.replies_may_be_missing": "Les réponses provenant des autres serveurs pourraient être manquantes.", - "hints.threads.see_more": "Afficher plus de réponses sur {domain}", "home.column_settings.show_quotes": "Afficher les citations", "home.column_settings.show_reblogs": "Afficher les partages", "home.column_settings.show_replies": "Afficher les réponses", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 33c6d89d195..25f5c0cc01f 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Besjoch mear folgers op {domain}", "hints.profiles.see_more_follows": "Besjoch mear folge accounts op {domain}", "hints.profiles.see_more_posts": "Besjoch mear berjochten op {domain}", - "hints.threads.replies_may_be_missing": "Antwurden fan oare servers kinne ûntbrekke.", - "hints.threads.see_more": "Besjoch mear reaksjes op {domain}", "home.column_settings.show_quotes": "Sitaten toane", "home.column_settings.show_reblogs": "Boosts toane", "home.column_settings.show_replies": "Reaksjes toane", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 92d06a05cf1..bca4ed8d1e7 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Féach ar a thuilleadh leantóirí ar {domain}", "hints.profiles.see_more_follows": "Féach tuilleadh seo a leanas ar {domain}", "hints.profiles.see_more_posts": "Féach ar a thuilleadh postálacha ar {domain}", - "hints.threads.replies_may_be_missing": "Seans go bhfuil freagraí ó fhreastalaithe eile in easnamh.", - "hints.threads.see_more": "Féach ar a thuilleadh freagraí ar {domain}", "home.column_settings.show_quotes": "Taispeáin Sleachta", "home.column_settings.show_reblogs": "Taispeáin moltaí", "home.column_settings.show_replies": "Taispeán freagraí", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 208e117037b..c783182cd5f 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Faic barrachd luchd-leantainn air {domain}", "hints.profiles.see_more_follows": "Faic barrachd a tha 'gan leantainn air {domain}", "hints.profiles.see_more_posts": "Faic barrachd phostaichean air {domain}", - "hints.threads.replies_may_be_missing": "Dh’fhaoidte gu bheil freagairtean o fhrithealaichean eile a dhìth.", - "hints.threads.see_more": "Faic barrachd fhreagairtean air {domain}", "home.column_settings.show_quotes": "Seall luaidhean", "home.column_settings.show_reblogs": "Seall na brosnachaidhean", "home.column_settings.show_replies": "Seall na freagairtean", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 7cb227215e4..6a8570c1d05 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Mira máis seguidoras en {domain}", "hints.profiles.see_more_follows": "Mira máis seguimentos en {domain}", "hints.profiles.see_more_posts": "Mira máis publicacións en {domain}", - "hints.threads.replies_may_be_missing": "Poderían faltar respostas desde outros servidores.", - "hints.threads.see_more": "Mira máis respostas en {domain}", "home.column_settings.show_quotes": "Mostrar citas", "home.column_settings.show_reblogs": "Amosar compartidos", "home.column_settings.show_replies": "Amosar respostas", @@ -847,6 +845,8 @@ "status.bookmark": "Marcar", "status.cancel_reblog_private": "Desfacer compartido", "status.cannot_reblog": "Esta publicación non pode ser promovida", + "status.context.load_new_replies": "Non hai respostas dispoñibles", + "status.context.loading": "Mirando se hai máis respostas", "status.continued_thread": "Continua co fío", "status.copy": "Copiar ligazón á publicación", "status.delete": "Eliminar", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index cb5ffb52db4..715ec36f541 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "צפיה בעוד עוקבים משרת {domain}", "hints.profiles.see_more_follows": "צפיה בעוד נעקבים בשרת {domain}", "hints.profiles.see_more_posts": "צפיה בעוד פרסומים בשרת {domain}", - "hints.threads.replies_may_be_missing": "תגובות משרתים אחרים עלולות להיות חסרות.", - "hints.threads.see_more": "צפיה בעוד תגובות משרת {domain}", "home.column_settings.show_quotes": "הצגת ציטוטים", "home.column_settings.show_reblogs": "הצגת הדהודים", "home.column_settings.show_replies": "הצגת תגובות", @@ -847,6 +845,8 @@ "status.bookmark": "סימניה", "status.cancel_reblog_private": "הסרת הדהוד", "status.cannot_reblog": "לא ניתן להדהד חצרוץ זה", + "status.context.load_new_replies": "הגיעו תגובות חדשות", + "status.context.loading": "מחפש תגובות חדשות", "status.continued_thread": "שרשור מתמשך", "status.copy": "העתק/י קישור להודעה זו", "status.delete": "מחיקה", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index abd9b8ad116..06a8bf88465 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "További követők megtekintése itt: {domain}", "hints.profiles.see_more_follows": "További követések megtekintése itt: {domain}", "hints.profiles.see_more_posts": "További bejegyzések megtekintése itt: {domain}", - "hints.threads.replies_may_be_missing": "A más kiszolgálókról érkező válaszok lehet, hogy hiányoznak.", - "hints.threads.see_more": "További válaszok megtekintése itt: {domain}", "home.column_settings.show_quotes": "Idézetek megjelenítése", "home.column_settings.show_reblogs": "Megtolások megjelenítése", "home.column_settings.show_replies": "Válaszok megjelenítése", @@ -847,6 +845,8 @@ "status.bookmark": "Könyvjelzőzés", "status.cancel_reblog_private": "Megtolás visszavonása", "status.cannot_reblog": "Ezt a bejegyzést nem lehet megtolni", + "status.context.load_new_replies": "Új válaszok érhetőek el", + "status.context.loading": "További válaszok keresése", "status.continued_thread": "Folytatott szál", "status.copy": "Link másolása bejegyzésbe", "status.delete": "Törlés", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index f9deb2f859d..2edb77b7f39 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -404,8 +404,6 @@ "hints.profiles.see_more_followers": "Vider plus de sequitores sur {domain}", "hints.profiles.see_more_follows": "Vider plus de sequites sur {domain}", "hints.profiles.see_more_posts": "Vider plus de messages sur {domain}", - "hints.threads.replies_may_be_missing": "Responsas de altere servitores pote mancar.", - "hints.threads.see_more": "Vider plus de responsas sur {domain}", "home.column_settings.show_reblogs": "Monstrar impulsos", "home.column_settings.show_replies": "Monstrar responsas", "home.hide_announcements": "Celar annuncios", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 69e549ea884..deb1cab89a4 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -383,8 +383,6 @@ "hints.profiles.see_more_followers": "Vidar plu multa sequanti sur {domain}", "hints.profiles.see_more_follows": "Vidar plu multa sequati sur {domain}", "hints.profiles.see_more_posts": "Vidar plu multa posti sur {domain}", - "hints.threads.replies_may_be_missing": "Respondi de altra servili forsan ne esas hike.", - "hints.threads.see_more": "Vidar plu multa demandi sur {domain}", "home.column_settings.show_reblogs": "Montrar repeti", "home.column_settings.show_replies": "Montrar respondi", "home.hide_announcements": "Celez anunci", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 52b3f5d97dd..54aec7cbee8 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Sjá fleiri fylgjendur á {domain}", "hints.profiles.see_more_follows": "Sjá fleiri sem þú fylgist með á {domain}", "hints.profiles.see_more_posts": "Sjá fleiri færslur á {domain}", - "hints.threads.replies_may_be_missing": "Svör af öðrum netþjónum gæti vantað.", - "hints.threads.see_more": "Sjá fleiri svör á {domain}", "home.column_settings.show_quotes": "Birta tilvitnanir", "home.column_settings.show_reblogs": "Sýna endurbirtingar", "home.column_settings.show_replies": "Birta svör", @@ -847,6 +845,8 @@ "status.bookmark": "Bókamerki", "status.cancel_reblog_private": "Taka úr endurbirtingu", "status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta", + "status.context.load_new_replies": "Ný svör hafa borist", + "status.context.loading": "Athuga með fleiri svör", "status.continued_thread": "Hélt samtali áfram", "status.copy": "Afrita tengil í færslu", "status.delete": "Eyða", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index f39862e7cc5..5134b39e673 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Vedi altri seguaci su {domain}", "hints.profiles.see_more_follows": "Vedi altri profili seguiti su {domain}", "hints.profiles.see_more_posts": "Vedi altri post su {domain}", - "hints.threads.replies_may_be_missing": "Le risposte da altri server potrebbero essere mancanti.", - "hints.threads.see_more": "Vedi altre risposte su {domain}", "home.column_settings.show_quotes": "Mostra le citazioni", "home.column_settings.show_reblogs": "Mostra reblog", "home.column_settings.show_replies": "Mostra risposte", @@ -847,6 +845,8 @@ "status.bookmark": "Aggiungi segnalibro", "status.cancel_reblog_private": "Annulla reblog", "status.cannot_reblog": "Questo post non può essere condiviso", + "status.context.load_new_replies": "Nuove risposte disponibili", + "status.context.loading": "Controllo per altre risposte", "status.continued_thread": "Discussione continua", "status.copy": "Copia link al post", "status.delete": "Elimina", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index ff12bf1dd18..59ed074012a 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -423,8 +423,6 @@ "hints.profiles.see_more_followers": "{domain} で正確な情報を見る", "hints.profiles.see_more_follows": "{domain} で正確な情報を見る", "hints.profiles.see_more_posts": "{domain} でその他の投稿を見る", - "hints.threads.replies_may_be_missing": "リモートの返信は表示されない場合があります。", - "hints.threads.see_more": "{domain} でその他の返信を見る", "home.column_settings.show_quotes": "引用を表示", "home.column_settings.show_reblogs": "ブースト表示", "home.column_settings.show_replies": "返信表示", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 0c2faa120e1..0bc4665a253 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -297,8 +297,6 @@ "hashtag.follow": "Ḍfeṛ ahacṭag", "hashtag.mute": "Sgugem #{hashtag}", "hashtags.and_other": "…d {count, plural, one {}other {# nniḍen}}", - "hints.threads.replies_may_be_missing": "Tiririyin d-yusan deg iqeddacen nniḍen, yezmer ur d-ddant ara.", - "hints.threads.see_more": "Wali ugar n tririt deg {domain}", "home.column_settings.show_reblogs": "Ssken-d beṭṭu", "home.column_settings.show_replies": "Ssken-d tiririyin", "home.hide_announcements": "Ffer ulɣuyen", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 6c2cc7ea142..cfb3ba8c61d 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "{domain}에서 더 많은 팔로워 보기", "hints.profiles.see_more_follows": "{domain}에서 더 많은 팔로우 보기", "hints.profiles.see_more_posts": "{domain}에서 더 많은 게시물 보기", - "hints.threads.replies_may_be_missing": "다른 서버의 답글은 일부 누락되었을 수 있습니다.", - "hints.threads.see_more": "{domain}에서 더 많은 답글 보기", "home.column_settings.show_quotes": "인용 보기", "home.column_settings.show_reblogs": "부스트 표시", "home.column_settings.show_replies": "답글 표시", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index 63110fda875..f628721f479 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -272,7 +272,6 @@ "hashtag.column_settings.tag_toggle": "Ji bo vê stûnê hin pêvekan tevlî bike", "hashtag.follow": "Hashtagê bişopîne", "hashtag.unfollow": "Hashtagê neşopîne", - "hints.threads.replies_may_be_missing": "Beriv ji rajekarên din dibe ku wendayî bin.", "home.column_settings.show_reblogs": "Bilindkirinan nîşan bike", "home.column_settings.show_replies": "Bersivan nîşan bide", "home.hide_announcements": "Reklaman veşêre", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index 83ffe1834be..7c6ce24a8ff 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -368,8 +368,6 @@ "hints.profiles.see_more_followers": "Ve mas suivantes en {domain}", "hints.profiles.see_more_follows": "Ve mas segidos en {domain}", "hints.profiles.see_more_posts": "Ve mas puvlikasyones en {domain}", - "hints.threads.replies_may_be_missing": "Puede mankar repuestas de otros sirvidores.", - "hints.threads.see_more": "Ve mas repuestas en {domain}", "home.column_settings.show_reblogs": "Amostra repartajasyones", "home.column_settings.show_replies": "Amostra repuestas", "home.hide_announcements": "Eskonde pregones", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 944ab8d9649..ed5fa83171e 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -396,8 +396,6 @@ "hints.profiles.see_more_followers": "Žiūrėti daugiau sekėjų serveryje {domain}", "hints.profiles.see_more_follows": "Žiūrėti daugiau sekimų serveryje {domain}", "hints.profiles.see_more_posts": "Žiūrėti daugiau įrašų serveryje {domain}", - "hints.threads.replies_may_be_missing": "Atsakymai iš kitų serverių gali būti nepateikti.", - "hints.threads.see_more": "Žiūrėti daugiau atsakymų serveryje {domain}", "home.column_settings.show_reblogs": "Rodyti pakėlimus", "home.column_settings.show_replies": "Rodyti atsakymus", "home.hide_announcements": "Slėpti skelbimus", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 104528c0e13..ee3c143412f 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -386,8 +386,6 @@ "hints.profiles.see_more_followers": "Skatīt vairāk sekotāju {domain}", "hints.profiles.see_more_follows": "Skatīt vairāk sekojumu {domain}", "hints.profiles.see_more_posts": "Skatīt vairāk ierakstu {domain}", - "hints.threads.replies_may_be_missing": "Var trūkt atbilžu no citiem serveriem.", - "hints.threads.see_more": "Skatīt vairāk atbilžu {domain}", "home.column_settings.show_quotes": "Rādīt citātus", "home.column_settings.show_reblogs": "Rādīt pastiprinātos ierakstus", "home.column_settings.show_replies": "Rādīt atbildes", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 48e1b75c2b0..e7e8d2c0cd1 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "佇 {domain} 看koh khah tsē跟tuè lí ê", "hints.profiles.see_more_follows": "佇 {domain} 看koh khah tsē lí跟tuè ê", "hints.profiles.see_more_posts": "佇 {domain} 看koh khah tsē ê PO文", - "hints.threads.replies_may_be_missing": "Tuì其他ê服侍器來ê回應可能有phah m̄見。", - "hints.threads.see_more": "佇 {domain} 看koh khah tsē ê回應", "home.column_settings.show_quotes": "顯示引用", "home.column_settings.show_reblogs": "顯示轉PO", "home.column_settings.show_replies": "顯示回應", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 90593ac2058..04ceb2591ba 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Bekijk meer volgers op {domain}", "hints.profiles.see_more_follows": "Bekijk meer gevolgde accounts op {domain}", "hints.profiles.see_more_posts": "Bekijk meer berichten op {domain}", - "hints.threads.replies_may_be_missing": "Antwoorden van andere servers kunnen ontbreken.", - "hints.threads.see_more": "Bekijk meer reacties op {domain}", "home.column_settings.show_quotes": "Citaten tonen", "home.column_settings.show_reblogs": "Boosts tonen", "home.column_settings.show_replies": "Reacties tonen", @@ -847,6 +845,8 @@ "status.bookmark": "Bladwijzer toevoegen", "status.cancel_reblog_private": "Niet langer boosten", "status.cannot_reblog": "Dit bericht kan niet geboost worden", + "status.context.load_new_replies": "Nieuwe reacties beschikbaar", + "status.context.loading": "Op nieuwe reacties aan het controleren", "status.continued_thread": "Vervolg van gesprek", "status.copy": "Link naar bericht kopiëren", "status.delete": "Verwijderen", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index b739208ab37..b5b97007c8c 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Sjå fleire fylgjarar på {domain}", "hints.profiles.see_more_follows": "Sjå fleire fylgjer på {domain}", "hints.profiles.see_more_posts": "Sjå fleire innlegg på {domain}", - "hints.threads.replies_may_be_missing": "Svar frå andre tenarar manglar kanskje.", - "hints.threads.see_more": "Sjå fleire svar på {domain}", "home.column_settings.show_quotes": "Vis sitat", "home.column_settings.show_reblogs": "Vis framhevingar", "home.column_settings.show_replies": "Vis svar", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index ca7c43e1e92..8ef9b6a4818 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -417,8 +417,6 @@ "hints.profiles.see_more_followers": "Se flere følgere på {domain}", "hints.profiles.see_more_follows": "Se flere som følger på {domain}", "hints.profiles.see_more_posts": "Se flere innlegg på {domain}", - "hints.threads.replies_may_be_missing": "Svar fra andre servere mangler kanskje.", - "hints.threads.see_more": "Se flere svar på {domain}", "home.column_settings.show_quotes": "Vis sitater", "home.column_settings.show_reblogs": "Vis fremhevinger", "home.column_settings.show_replies": "Vis svar", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index 5236d246c0f..50756eebbf0 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -276,7 +276,6 @@ "hints.profiles.see_more_followers": "{domain} ਉੱਤੇ ਹੋਰ ਫ਼ਾਲੋਅਰ ਵੇਖੋ", "hints.profiles.see_more_follows": "{domain} ਉੱਤੇ ਹੋਰ ਫ਼ਾਲੋ ਨੂੰ ਵੇਖੋ", "hints.profiles.see_more_posts": "{domain} ਉੱਤੇ ਹੋਰ ਪੋਸਟਾਂ ਨੂੰ ਵੇਖੋ", - "hints.threads.see_more": "{domain} ਤੋਂ ਹੋਰ ਜਵਾਬਾਂ ਨੂੰ ਵੇਖੋ", "home.column_settings.show_reblogs": "ਬੂਸਟਾਂ ਨੂੰ ਵੇਖੋ", "home.column_settings.show_replies": "ਜਵਾਬਾਂ ਨੂੰ ਵੇਖੋ", "home.hide_announcements": "ਐਲਾਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 1967a2333c0..bbe5789d2a4 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -383,8 +383,6 @@ "hints.profiles.see_more_followers": "Zobacz więcej obserwujących na {domain}", "hints.profiles.see_more_follows": "Zobacz więcej obserwowanych na {domain}", "hints.profiles.see_more_posts": "Zobacz więcej wpisów na {domain}", - "hints.threads.replies_may_be_missing": "Komentarze z innych serwerów mogą być niewidoczne.", - "hints.threads.see_more": "Zobacz więcej komentarzy na {domain}", "home.column_settings.show_reblogs": "Pokazuj podbicia", "home.column_settings.show_replies": "Pokazuj odpowiedzi", "home.hide_announcements": "Ukryj ogłoszenia", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index b437786b9b6..f5762701939 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -414,8 +414,6 @@ "hints.profiles.see_more_followers": "Ver mais seguidores no {domain}", "hints.profiles.see_more_follows": "Ver mais seguidores no {domain}", "hints.profiles.see_more_posts": "Ver mais publicações em {domain}", - "hints.threads.replies_may_be_missing": "Respostas de outros servidores podem estar faltando.", - "hints.threads.see_more": "Ver mais respostas no {domain}", "home.column_settings.show_reblogs": "Mostrar boosts", "home.column_settings.show_replies": "Mostrar respostas", "home.hide_announcements": "Ocultar comunicados", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 424e48ef2e3..15ccc7cb32a 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Ver mais seguidores em {domain}", "hints.profiles.see_more_follows": "Ver mais perfis seguidos em {domain}", "hints.profiles.see_more_posts": "Ver mais publicações em {domain}", - "hints.threads.replies_may_be_missing": "É possível que não estejam a ser mostradas todas as respostas de outros servidores.", - "hints.threads.see_more": "Ver mais respostas em {domain}", "home.column_settings.show_quotes": "Mostrar citações", "home.column_settings.show_reblogs": "Mostrar impulsos", "home.column_settings.show_replies": "Mostrar respostas", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index e2022b52d94..40d8341713f 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -45,7 +45,7 @@ "account.followers_counter": "{count, plural, one {{counter} подписчик} few {{counter} подписчика} other {{counter} подписчиков}}", "account.followers_you_know_counter": "{count, plural, one {{counter} ваш знакомый} other {{counter} ваших знакомых}}", "account.following": "Подписки", - "account.following_counter": "{count, plural, one {# подписка} many {# подписок} other {# подписки}}", + "account.following_counter": "{count, plural, one {{counter} подписка} few {{counter} подписки} many {{counter} подписок} other {{counter} подписок}}", "account.follows.empty": "Этот пользователь пока ни на кого не подписался.", "account.follows_you": "Подписан(а) на вас", "account.go_to_profile": "Перейти к профилю", @@ -273,7 +273,7 @@ "domain_block_modal.they_cant_follow": "Пользователи с этого сервера не смогут подписаться на вас.", "domain_block_modal.they_wont_know": "Пользователи с этого сервера не будут знать, что вы их блокируете.", "domain_block_modal.title": "Заблокировать домен?", - "domain_block_modal.you_will_lose_num_followers": "Вы потеряете {followersCount, plural, one {{followersCountDisplay} подписчика} few {{followersCountDisplay} подписчика} other {{followersCountDisplay} подписчиков}} и {followingCount, plural, one {{followingCountDisplay} подписку} few {{followingCountDisplay} подписки} other {{followingCountDisplay} подписок}}.", + "domain_block_modal.you_will_lose_num_followers": "Вы потеряете {followersCount, plural, one {{followersCountDisplay} подписчика} few {{followersCountDisplay} подписчиков} other {{followersCountDisplay} подписчиков}} и {followingCount, plural, one {{followingCountDisplay} подписку} few {{followingCountDisplay} подписки} other {{followingCountDisplay} подписок}}.", "domain_block_modal.you_will_lose_relationships": "Вы потеряете все подписки и всех подписчиков с этого сервера.", "domain_block_modal.you_wont_see_posts": "Вы не будете видеть посты и уведомления от пользователей с этого сервера.", "domain_pill.activitypub_lets_connect": "Благодаря ему вы можете связываться и взаимодействовать не только с пользователями Mastodon, но и с пользователями других платформ.", @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Перейдите на {domain}, чтобы увидеть всех подписчиков", "hints.profiles.see_more_follows": "Перейдите на {domain}, чтобы увидеть все подписки", "hints.profiles.see_more_posts": "Перейдите на {domain}, чтобы увидеть все посты", - "hints.threads.replies_may_be_missing": "Некоторые ответы с других серверов могут здесь отсутствовать.", - "hints.threads.see_more": "Перейдите на {domain}, чтобы увидеть все ответы", "home.column_settings.show_quotes": "Показывать цитирования", "home.column_settings.show_reblogs": "Показывать продвижения", "home.column_settings.show_replies": "Показывать ответы", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index d893bc2a0d0..fb856ba8a1a 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -313,8 +313,6 @@ "hashtags.and_other": "… e {count, plural, one {un'àteru} other {àteros #}}", "hints.profiles.posts_may_be_missing": "Podet èssere chi ammanchent tzertas publicatziones de custu profilu.", "hints.profiles.see_more_posts": "Bide prus publicatziones a {domain}", - "hints.threads.replies_may_be_missing": "Podet èssere chi ammanchent rispostas dae àteros serbidores.", - "hints.threads.see_more": "Bide prus rispostas a {domain}", "home.column_settings.show_reblogs": "Ammustra is cumpartziduras", "home.column_settings.show_replies": "Ammustra rispostas", "home.hide_announcements": "Cua annùntzios", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 9f9bd1590fe..438ca0b735a 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -406,8 +406,6 @@ "hints.profiles.see_more_followers": "{domain}හි තවත් අනුගාමිකයින් බලන්න", "hints.profiles.see_more_follows": "{domain}හි තවත් පහත ඒවා බලන්න.", "hints.profiles.see_more_posts": "{domain}හි තවත් සටහන් බලන්න", - "hints.threads.replies_may_be_missing": "අනෙකුත් සේවාදායකයන්ගෙන් ලැබෙන පිළිතුරු අස්ථානගත වී තිබිය හැක.", - "hints.threads.see_more": "{domain}හි තවත් පිළිතුරු බලන්න.", "home.column_settings.show_reblogs": "බූස්ට් පෙන්වන්න", "home.column_settings.show_replies": "පිළිතුරු පෙන්වන්න", "home.hide_announcements": "නිවේදන සඟවන්න", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 63fd556f79b..091ddf8ca24 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -365,8 +365,6 @@ "hints.profiles.see_more_followers": "Pozri viac nasledovateľov na {domain}", "hints.profiles.see_more_follows": "Pozri viac nasledovateľov na {domain}", "hints.profiles.see_more_posts": "Pozri viac príspevkov na {domain}", - "hints.threads.replies_may_be_missing": "Odpovede z ostatných serverov môžu chýbať.", - "hints.threads.see_more": "Pozri viac odpovedí na {domain}", "home.column_settings.show_reblogs": "Zobraziť zdieľania", "home.column_settings.show_replies": "Zobraziť odpovede", "home.hide_announcements": "Skryť oznámenia", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index 7213af36662..c256318fee9 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -383,8 +383,6 @@ "hints.profiles.see_more_followers": "Pokaži več sledilcev na {domain}", "hints.profiles.see_more_follows": "Pokaži več sledenih ljudi na zbirališču {domain}", "hints.profiles.see_more_posts": "Pokaži več objav na {domain}", - "hints.threads.replies_may_be_missing": "Odgovori z drugih strežnikov morda manjkajo.", - "hints.threads.see_more": "Pokaži več odgovorov na {domain}", "home.column_settings.show_reblogs": "Pokaži izpostavitve", "home.column_settings.show_replies": "Pokaži odgovore", "home.hide_announcements": "Skrij obvestila", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index b5a7cb0e886..75c00681603 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -419,8 +419,6 @@ "hints.profiles.see_more_followers": "Shihni më tepër ndjekës në {domain}", "hints.profiles.see_more_follows": "Shihni më tepër ndjekje në {domain}", "hints.profiles.see_more_posts": "Shihni më tepër postime në {domain}", - "hints.threads.replies_may_be_missing": "Mund të mungojnë përgjigje nga shërbyes të tjerë.", - "hints.threads.see_more": "Shihni më tepër përgjigje në {domain}", "home.column_settings.show_quotes": "Shfaq thonjëza", "home.column_settings.show_reblogs": "Shfaq përforcime", "home.column_settings.show_replies": "Shfaq përgjigje", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 0808963e5ff..48f15b28b88 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Se fler följare på {domain}", "hints.profiles.see_more_follows": "Se fler följare på {domain}", "hints.profiles.see_more_posts": "Se fler inlägg på {domain}", - "hints.threads.replies_may_be_missing": "Det kan saknas svar från andra servrar.", - "hints.threads.see_more": "Se fler svar på {domain}", "home.column_settings.show_quotes": "Visa citat", "home.column_settings.show_reblogs": "Visa boostar", "home.column_settings.show_replies": "Visa svar", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 603c1ab61ff..03faba8a660 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -397,8 +397,6 @@ "hints.profiles.see_more_followers": "ดูผู้ติดตามเพิ่มเติมใน {domain}", "hints.profiles.see_more_follows": "ดูการติดตามเพิ่มเติมใน {domain}", "hints.profiles.see_more_posts": "ดูโพสต์เพิ่มเติมใน {domain}", - "hints.threads.replies_may_be_missing": "การตอบกลับจากเซิร์ฟเวอร์อื่น ๆ อาจขาดหายไป", - "hints.threads.see_more": "ดูการตอบกลับเพิ่มเติมใน {domain}", "home.column_settings.show_reblogs": "แสดงการดัน", "home.column_settings.show_replies": "แสดงการตอบกลับ", "home.hide_announcements": "ซ่อนประกาศ", diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index c48ffa5fe24..cabee093148 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -371,8 +371,6 @@ "hints.profiles.see_more_followers": "o lukin e jan ni lon ma {domain}: ona li kute e jan ni.", "hints.profiles.see_more_follows": "o lukin e jan ni lon ma {domain}: jan ni li kute e ona.", "hints.profiles.see_more_posts": "o lukin e toki ante lon ma {domain}", - "hints.threads.replies_may_be_missing": "toki pi ma ante li weka lon ken.", - "hints.threads.see_more": "o lukin e toki ante lon ma {domain}", "home.column_settings.show_reblogs": "o lukin e pana toki", "home.hide_announcements": "o lukin ala e toki lawa suli", "home.pending_critical_update.body": "o sin e ilo Mastodon lon tenpo lili a!", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index a45acff11f5..c50ac3cbaa1 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "{domain} adresinde daha fazla takipçi gör", "hints.profiles.see_more_follows": "{domain} adresinde daha fazla takip edilen gör", "hints.profiles.see_more_posts": "{domain} adresinde daha fazla gönderi gör", - "hints.threads.replies_may_be_missing": "Diğer sunuculardan yanıtlar eksik olabilir.", - "hints.threads.see_more": "{domain} adresinde daha fazla yanıt gör", "home.column_settings.show_quotes": "Alıntıları göster", "home.column_settings.show_reblogs": "Yeniden paylaşımları göster", "home.column_settings.show_replies": "Yanıtları göster", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 56ff3444f45..e956bd05e77 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -395,8 +395,6 @@ "hints.profiles.see_more_followers": "Переглянути більше підписників на {domain}", "hints.profiles.see_more_follows": "Переглянути більше підписок на {domain}", "hints.profiles.see_more_posts": "Переглянути більше дописів на {domain}", - "hints.threads.replies_may_be_missing": "Відповіді з інших серверів можуть бути не показані.", - "hints.threads.see_more": "Переглянути більше відповідей на {domain}", "home.column_settings.show_reblogs": "Показувати поширення", "home.column_settings.show_replies": "Показувати відповіді", "home.hide_announcements": "Приховати оголошення", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index ec6e7188f22..b5c8c3ec235 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "Xem thêm người theo dõi ở {domain}", "hints.profiles.see_more_follows": "Xem thêm người mà người này theo dõi ở {domain}", "hints.profiles.see_more_posts": "Xem thêm tút ở {domain}", - "hints.threads.replies_may_be_missing": "Những trả lời từ máy chủ khác có thể không đầy đủ.", - "hints.threads.see_more": "Xem thêm ở {domain}", "home.column_settings.show_quotes": "Hiện những trích dẫn", "home.column_settings.show_reblogs": "Hiện những lượt đăng lại", "home.column_settings.show_replies": "Hiện những tút dạng trả lời", @@ -847,6 +845,8 @@ "status.bookmark": "Lưu", "status.cancel_reblog_private": "Bỏ đăng lại", "status.cannot_reblog": "Không thể đăng lại tút này", + "status.context.load_new_replies": "Có những trả lời mới", + "status.context.loading": "Kiểm tra nhiều trả lời hơn", "status.continued_thread": "Tiếp tục chủ đề", "status.copy": "Sao chép URL", "status.delete": "Xóa", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 8928f253b16..c6e0aa7f276 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -412,8 +412,6 @@ "hints.profiles.see_more_followers": "在 {domain} 查看更多关注者", "hints.profiles.see_more_follows": "在 {domain} 查看更多关注", "hints.profiles.see_more_posts": "在 {domain} 查看更多嘟文", - "hints.threads.replies_may_be_missing": "来自其它实例的回复可能没有完全显示。", - "hints.threads.see_more": "在 {domain} 查看更多回复", "home.column_settings.show_quotes": "显示引用", "home.column_settings.show_reblogs": "显示转嘟", "home.column_settings.show_replies": "显示回复", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 8fe9f71a69f..40eed25a495 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -424,8 +424,6 @@ "hints.profiles.see_more_followers": "於 {domain} 檢視更多跟隨者", "hints.profiles.see_more_follows": "於 {domain} 檢視更多正在跟隨", "hints.profiles.see_more_posts": "於 {domain} 檢視更多嘟文", - "hints.threads.replies_may_be_missing": "來自其他站點之回覆或有缺失。", - "hints.threads.see_more": "於 {domain} 檢視更多回覆", "home.column_settings.show_quotes": "顯示引用嘟文", "home.column_settings.show_reblogs": "顯示轉嘟", "home.column_settings.show_replies": "顯示回覆", @@ -847,6 +845,8 @@ "status.bookmark": "書籤", "status.cancel_reblog_private": "取消轉嘟", "status.cannot_reblog": "這則嘟文無法被轉嘟", + "status.context.load_new_replies": "有新回嘟", + "status.context.loading": "正在檢查更多回嘟", "status.continued_thread": "接續討論串", "status.copy": "複製嘟文連結", "status.delete": "刪除", diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 971846789b6..f961e0fba4b 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1477,56 +1477,56 @@ ru: other: Проверьте введённые вами данные! Далее по странице вы можете увидеть %{count} сообщений об ошибке imports: errors: - empty: Пустой CSV-файл - incompatible_type: Несовместимость с выбранным типом импорта - invalid_csv_file: 'Неверный файл CSV. Ошибка: %{error}' + empty: Файл CSV пуст + incompatible_type: Несовместим с выбранным типом данных для импорта + invalid_csv_file: 'Ошибка при чтении файла CSV: %{error}' over_rows_processing_limit: содержит более %{count} строк too_large: Файл слишком большой failures: Ошибки - imported: Импортирован - mismatched_types_warning: Возможно, вы выбрали неверный тип для этого импорта, пожалуйста, перепроверьте. + imported: Импортировано + mismatched_types_warning: По-видимому, вы выбрали неверный тип данных для импорта. Проверьте всё внимательно! modes: merge: Объединить - merge_long: Сохранить имеющиеся данные и добавить новые. - overwrite: Перезаписать - overwrite_long: Перезаписать имеющиеся данные новыми. + merge_long: Добавить новые данные к уже имеющимся + overwrite: Заменить + overwrite_long: Заменить имеющиеся данные новыми overwrite_preambles: blocking_html: - few: Вы собираетесь заменить свой список блокировки, в котором сейчас %{count} аккаунта, из файла %{filename}. - many: Вы собираетесь заменить свой список блокировки, в котором сейчас %{count} аккаунов, из файла %{filename}. - one: Вы собираетесь заменить свой список блокировки, в котором сейчас %{count} аккаунт, из файла %{filename}. - other: Вы собираетесь заменить свой список блокировки, в котором сейчас %{count} аккаунтов, из файла %{filename}. + few: Вы собираетесь заменить свой список заблокированных пользователей данными из файла %{filename}, после чего вы будете блокировать %{count} пользователей. + many: Вы собираетесь заменить свой список заблокированных пользователей данными из файла %{filename}, после чего вы будете блокировать %{count} пользователей. + one: Вы собираетесь заменить свой список заблокированных пользователей данными из файла %{filename}, после чего вы будете блокировать %{count} пользователя. + other: Вы собираетесь заменить свой список заблокированных пользователей данными из файла %{filename}, после чего вы будете блокировать %{count} пользователей. bookmarks_html: - few: Вы собираетесь заменить свои закладки, в которых сейчас %{count} поста, из файла %{filename}. - many: Вы собираетесь заменить свои закладки, в которых сейчас %{count} постов, из файла %{filename}. - one: Вы собираетесь заменить свои закладки, в которых сейчас %{count} пост, из файла %{filename}. - other: Вы собираетесь заменить свои закладки, в которых сейчас %{count} постов, из файла %{filename}. + few: Вы собираетесь заменить свои закладки данными из файла %{filename}, после чего у вас в закладках будет %{count} поста. + many: Вы собираетесь заменить свои закладки данными из файла %{filename}, после чего у вас в закладках будет %{count} постов. + one: Вы собираетесь заменить свои закладки данными из файла %{filename}, после чего у вас в закладках будет %{count} пост. + other: Вы собираетесь заменить свои закладки данными из файла %{filename}, после чего у вас в закладках будет %{count} постов. domain_blocking_html: - few: Вы собираетесь заменить свой список доменных блокировок, в котором сейчас %{count} домена, из файла %{filename}. - many: Вы собираетесь заменить свой список доменных блокировок, в котором сейчас %{count} доменов, из файла %{filename}. - one: Вы собираетесь заменить свой список доменных блокировок, в котором сейчас %{count} домен, из файла %{filename}. - other: Вы собираетесь заменить свой список доменных блокировок, в котором сейчас %{count} доменов, из файла %{filename}. + few: Вы собираетесь заменить свой список заблокированных доменов данными из файла %{filename}, после чего вы будете блокировать %{count} домена. + many: Вы собираетесь заменить свой список заблокированных доменов данными из файла %{filename}, после чего вы будете блокировать %{count} доменов. + one: Вы собираетесь заменить свой список заблокированных доменов данными из файла %{filename}, после чего вы будете блокировать %{count} домен. + other: Вы собираетесь заменить свой список заблокированных доменов данными из файла %{filename}, после чего вы будете блокировать %{count} доменов. following_html: - few: Вы собираетесь подписаться на %{count} аккаунта из файла %{filename} и отписаться от всех прочих. - many: Вы собираетесь подписаться на %{count} аккаунтов из файла %{filename} и отписаться от всех прочих. - one: Вы собираетесь подписаться на %{count} аккаунт из файла %{filename} и отписаться от всех прочих. - other: Вы собираетесь подписаться на %{count} аккаунтов из файла %{filename} и отписаться от всех прочих. + few: Вы собираетесь подписаться на %{count} пользователей из файла %{filename} и отписаться от всех прочих. + many: Вы собираетесь подписаться на %{count} пользователей из файла %{filename} и отписаться от всех прочих. + one: Вы собираетесь подписаться на %{count} пользователя из файла %{filename} и отписаться от всех прочих. + other: Вы собираетесь подписаться на %{count} пользователей из файла %{filename} и отписаться от всех прочих. lists_html: - few: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} аккаунта. - many: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} аккаунтов. - one: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будет добавлен %{count} аккаунт. - other: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} аккаунтов. + few: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} пользователя. + many: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} пользователей. + one: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будет добавлен %{count} пользователь. + other: Вы собираетесь заменить свои списки содержимым файла %{filename}. В новые списки будут добавлены %{count} пользователей. muting_html: - few: Вы собираетесь заменить свой список игнорируемых пользователей списком из %{count} аккаунтов из файла %{filename}. - many: Вы собираетесь заменить свой список игнорируемых пользователей списком из %{count} аккаунтов из файла %{filename}. - one: Вы собираетесь заменить свой список игнорируемых пользователей списком из %{count} аккаунта из файла %{filename}. - other: Вы собираетесь заменить свой список игнорируемых пользователей списком из %{count} аккаунтов из файла %{filename}. + few: Вы собираетесь заменить свой список игнорируемых пользователей данными из файла %{filename}, после чего вы будете игнорировать %{count} пользователей. + many: Вы собираетесь заменить свой список игнорируемых пользователей данными из файла %{filename}, после чего вы будете игнорировать %{count} пользователей. + one: Вы собираетесь заменить свой список игнорируемых пользователей данными из файла %{filename}, после чего вы будете игнорировать %{count} пользователя. + other: Вы собираетесь заменить свой список игнорируемых пользователей данными из файла %{filename}, после чего вы будете игнорировать %{count} пользователей. preambles: blocking_html: - few: Вы собираетесь заблокировать %{count} аккаунта из файла %{filename}. - many: Вы собираетесь заблокировать %{count} аккаунтов из файла %{filename}. - one: Вы собираетесь заблокировать %{count} аккаунт из файла %{filename}. - other: Вы собираетесь заблокировать %{count} аккаунтов из файла %{filename}. + few: Вы собираетесь заблокировать %{count} пользователей из файла %{filename}. + many: Вы собираетесь заблокировать %{count} пользователей из файла %{filename}. + one: Вы собираетесь заблокировать %{count} пользователя из файла %{filename}. + other: Вы собираетесь заблокировать %{count} пользователей из файла %{filename}. bookmarks_html: few: Вы собираетесь добавить %{count} поста из файла %{filename} в свои закладки. many: Вы собираетесь добавить %{count} постов из файла %{filename} в свои закладки. @@ -1538,20 +1538,20 @@ ru: one: Вы собираетесь заблокировать %{count} домен из файла %{filename}. other: Вы собираетесь заблокировать %{count} доменов из файла %{filename}. following_html: - few: Вы собираетесь подписаться на %{count} аккаунта из файла %{filename}. - many: Вы собираетесь подписаться на %{count} аккаунтов из файла %{filename}. - one: Вы собираетесь подписаться на %{count} аккаунт из файла %{filename}. - other: Вы собираетесь подписаться на %{count} аккаунтов из файла %{filename}. + few: Вы собираетесь подписаться на %{count} пользователей из файла %{filename}. + many: Вы собираетесь подписаться на %{count} пользователей из файла %{filename}. + one: Вы собираетесь подписаться на %{count} пользователя из файла %{filename}. + other: Вы собираетесь подписаться на %{count} пользователей из файла %{filename}. lists_html: - few: Вы собираетесь добавить %{count} аккаунта из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. - many: Вы собираетесь добавить %{count} аккаунтов из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. - one: Вы собираетесь добавить %{count} аккаунт из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. - other: Вы собираетесь добавить %{count} аккаунтов из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. + few: Вы собираетесь добавить %{count} пользователей из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. + many: Вы собираетесь добавить %{count} пользователей из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. + one: Вы собираетесь добавить %{count} пользователя из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. + other: Вы собираетесь добавить %{count} пользователей из файла %{filename} в свои списки. Если соответствующих списков нет, они будут созданы. muting_html: - few: Вы собираетесь начать игнорировать %{count} аккаунта из файла %{filename}. - many: Вы собираетесь начать игнорировать %{count} аккаунтов из файла %{filename}. - one: Вы собираетесь начать игнорировать %{count} аккаунт из файла %{filename}. - other: Вы собираетесь начать игнорировать %{count} аккаунтов из файла %{filename}. + few: Вы собираетесь игнорировать %{count} пользователей из файла %{filename}. + many: Вы собираетесь игнорировать %{count} пользователей из файла %{filename}. + one: Вы собираетесь игнорировать %{count} пользователя из файла %{filename}. + other: Вы собираетесь игнорировать %{count} пользователей из файла %{filename}. preface: Вы можете загрузить некоторые данные, например, списки людей, на которых Вы подписаны или которых блокируете, в Вашу учётную запись на этом узле из файлов, экспортированных с другого узла. recent_imports: Недавно импортированное states: @@ -1563,23 +1563,23 @@ ru: success: Ваши данные были успешно загружены и будут обработаны с должной скоростью time_started: Началось в titles: - blocking: Импорт заблокированных аккаунтов + blocking: Импорт списка заблокированных пользователей bookmarks: Импорт закладок - domain_blocking: Импорт заблокированных доменов - following: Импорт последующих аккаунтов - lists: Импортировать список - muting: Импорт отключенных аккаунтов + domain_blocking: Импорт списка заблокированных доменов + following: Импорт подписок + lists: Импорт списков + muting: Импорт списка игнорируемых пользователей type: Тип импорта type_groups: constructive: Подписки и закладки destructive: Блокировки и игнорируемые types: - blocking: Список блокировки + blocking: Заблокированные пользователи bookmarks: Закладки - domain_blocking: Список доменных блокировок + domain_blocking: Заблокированные домены following: Подписки lists: Списки - muting: Список глушения + muting: Игнорируемые пользователи upload: Загрузить invites: delete: Удалить From e54e96d61fd8b0682034d9ad50aa1c457bfbc7a5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 24 Jul 2025 03:49:20 -0400 Subject: [PATCH 111/660] Extract params hash for `api/v1/push/subscriptions#create` (#35475) --- .../api/v1/push/subscriptions_controller.rb | 23 ++++++++------ .../api/v1/push/subscriptions_spec.rb | 31 +++++++++++++------ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index f2c52f2846e..3b0cda7d931 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -16,16 +16,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController def create with_redis_lock("push_subscription:#{current_user.id}") do destroy_web_push_subscriptions! - - @push_subscription = Web::PushSubscription.create!( - endpoint: subscription_params[:endpoint], - key_p256dh: subscription_params[:keys][:p256dh], - key_auth: subscription_params[:keys][:auth], - standard: subscription_params[:standard] || false, - data: data_params, - user_id: current_user.id, - access_token_id: doorkeeper_token.id - ) + @push_subscription = Web::PushSubscription.create!(web_push_subscription_params) end render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer @@ -55,6 +46,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController not_found if @push_subscription.nil? end + def web_push_subscription_params + { + access_token_id: doorkeeper_token.id, + data: data_params, + endpoint: subscription_params[:endpoint], + key_auth: subscription_params[:keys][:auth], + key_p256dh: subscription_params[:keys][:p256dh], + standard: subscription_params[:standard] || false, + user_id: current_user.id, + } + end + def subscription_params params.expect(subscription: [:endpoint, :standard, keys: [:auth, :p256dh]]) end diff --git a/spec/requests/api/v1/push/subscriptions_spec.rb b/spec/requests/api/v1/push/subscriptions_spec.rb index 359de9d95c0..bccbda6fa23 100644 --- a/spec/requests/api/v1/push/subscriptions_spec.rb +++ b/spec/requests/api/v1/push/subscriptions_spec.rb @@ -166,17 +166,30 @@ RSpec.describe 'API V1 Push Subscriptions' do describe 'GET /api/v1/push/subscription' do subject { get '/api/v1/push/subscription', headers: headers } - before { create_subscription_with_token } + context 'with a subscription' do + before { create_subscription_with_token } - it 'shows subscription details' do - subject + it 'shows subscription details' do + subject - expect(response) - .to have_http_status(200) - expect(response.content_type) - .to start_with('application/json') - expect(response.parsed_body) - .to include(endpoint: endpoint) + expect(response) + .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) + .to include(endpoint: endpoint) + end + end + + context 'without a subscription' do + it 'returns not found' do + subject + + expect(response) + .to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') + end end end From 7d136feccf961121432e7771eca20bd8c7a70fb4 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 24 Jul 2025 09:51:56 +0200 Subject: [PATCH 112/660] Bump version to v4.4.2 (#35498) --- CHANGELOG.md | 20 ++++++++++++++++++++ docker-compose.yml | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19be8ea68e5..97a7234382c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ All notable changes to this project will be documented in this file. +## [4.4.2] - 2025-07-23 + +### Security + +- Update dependencies + +### Fixed + +- Fix menu not clickable in Firefox (#35390 and #35414 by @diondiondion) +- Add `lang` attribute to current composer language in alt text modal (#35412 by @diondiondion) +- Fix quote posts styling on notifications page (#35411 by @diondiondion) +- Improve a11y of custom select menus in notifications settings (#35403 by @diondiondion) +- Fix selected item in poll select menus is unreadable in Firefox (#35402 by @diondiondion) +- Update age limit wording (#35387 by @diondiondion) +- Fix support for quote verification in implicit status updates (#35384 by @ClearlyClaire) +- Improve `Dropdown` component accessibility (#35373 by @diondiondion) +- Fix processing some incoming quotes failing because of missing JSON-LD context (#35354 and #35380 by @ClearlyClaire) +- Make bio hashtags open the local page instead of the remote instance (#35349 by @ChaosExAnima) +- Fix styling of external log-in button (#35320 by @ClearlyClaire) + ## [4.4.1] - 2025-07-09 ### Fixed diff --git a/docker-compose.yml b/docker-compose.yml index e5d94b390f4..8fe42fa0c9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.1 + image: ghcr.io/mastodon/mastodon:v4.4.2 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/mastodon/mastodon-streaming:v4.4.1 + image: ghcr.io/mastodon/mastodon-streaming:v4.4.2 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.1 + image: ghcr.io/mastodon/mastodon:v4.4.2 restart: always env_file: .env.production command: bundle exec sidekiq From 67be8208db4c73e282686c9d459a4c66f0d6b80a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:52:34 +0200 Subject: [PATCH 113/660] chore(deps): update dependency haml_lint to v0.65.1 (#35497) 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 4c232743bf1..f5a919a52f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -287,7 +287,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.65.0) + haml_lint (0.65.1) haml (>= 5.0) parallel (~> 1.10) rainbow From 5c01ccc31fca955d16f11a2f611187cb823a0ec5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 24 Jul 2025 04:03:28 -0400 Subject: [PATCH 114/660] Set flash options via redirect where possible (#35370) --- app/controllers/admin/confirmations_controller.rb | 6 ++---- app/controllers/admin/settings_controller.rb | 3 +-- app/controllers/auth/passwords_controller.rb | 3 +-- app/controllers/settings/sessions_controller.rb | 3 +-- .../webauthn_credentials_controller.rb | 6 ++---- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index 702550eecc1..5d1555796f4 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -19,15 +19,13 @@ module Admin log_action :resend, @user - flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success') - redirect_to admin_accounts_path + redirect_to admin_accounts_path, notice: t('admin.accounts.resend_confirmation.success') end private def redirect_confirmed_user - flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed') - redirect_to admin_accounts_path + redirect_to admin_accounts_path, flash: { error: t('admin.accounts.resend_confirmation.already_confirmed') } end def user_confirmed? diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 2ae5ec82556..a08375e0a41 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -14,8 +14,7 @@ module Admin @admin_settings = Form::AdminSettings.new(settings_params) if @admin_settings.save - flash[:notice] = I18n.t('generic.changes_saved_msg') - redirect_to after_update_redirect_path + redirect_to after_update_redirect_path, notice: t('generic.changes_saved_msg') else render :show end diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index 7c1ff59671d..2680a1c5fdc 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -19,8 +19,7 @@ class Auth::PasswordsController < Devise::PasswordsController private def redirect_invalid_reset_token - flash[:error] = I18n.t('auth.invalid_reset_password_token') - redirect_to new_password_path(resource_name) + redirect_to new_password_path(resource_name), flash: { error: t('auth.invalid_reset_password_token') } end def reset_password_token_is_valid? diff --git a/app/controllers/settings/sessions_controller.rb b/app/controllers/settings/sessions_controller.rb index ee2fc5dc80f..fe59bdc4917 100644 --- a/app/controllers/settings/sessions_controller.rb +++ b/app/controllers/settings/sessions_controller.rb @@ -8,8 +8,7 @@ class Settings::SessionsController < Settings::BaseController def destroy @session.destroy! - flash[:notice] = I18n.t('sessions.revoke_success') - redirect_to edit_user_registration_path + redirect_to edit_user_registration_path, notice: t('sessions.revoke_success') end private diff --git a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb index 9714d54f954..b01f08ed8fd 100644 --- a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb +++ b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb @@ -86,13 +86,11 @@ module Settings private def redirect_invalid_otp - flash[:error] = t('webauthn_credentials.otp_required') - redirect_to settings_two_factor_authentication_methods_path + redirect_to settings_two_factor_authentication_methods_path, flash: { error: t('webauthn_credentials.otp_required') } end def redirect_invalid_webauthn - flash[:error] = t('webauthn_credentials.not_enabled') - redirect_to settings_two_factor_authentication_methods_path + redirect_to settings_two_factor_authentication_methods_path, flash: { error: t('webauthn_credentials.not_enabled') } end end end From 8a1c43bf3b51c63dfb74d1cb5ec3a1d03e305e34 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 24 Jul 2025 10:23:41 +0200 Subject: [PATCH 115/660] Use default for preselected default privacy post setting (#35422) --- app/views/settings/preferences/other/show.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml index dd79695db7f..e02bd2b1773 100644 --- a/app/views/settings/preferences/other/show.html.haml +++ b/app/views/settings/preferences/other/show.html.haml @@ -21,6 +21,7 @@ .fields-group.fields-row__column.fields-row__column-6 = ff.input :default_privacy, collection: Status.selectable_visibilities, + selected: current_user.setting_default_privacy, hint: false, include_blank: false, label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, From 8baed8b90e3e5d4e1df93a19b46c420148cdb42c Mon Sep 17 00:00:00 2001 From: Mayank <9084735+mayank99@users.noreply.github.com> Date: Thu, 24 Jul 2025 04:58:22 -0400 Subject: [PATCH 116/660] remove redundant `title` text from media modal images in web UI (#35468) --- app/javascript/mastodon/components/gifv.tsx | 2 -- .../mastodon/features/ui/components/zoomable_image.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/javascript/mastodon/components/gifv.tsx b/app/javascript/mastodon/components/gifv.tsx index 8e3a434c14b..d7d0b5f2ce0 100644 --- a/app/javascript/mastodon/components/gifv.tsx +++ b/app/javascript/mastodon/components/gifv.tsx @@ -37,7 +37,6 @@ export const GIFV = forwardRef( role='button' tabIndex={0} aria-label={alt} - title={alt} lang={lang} onClick={handleClick} /> @@ -49,7 +48,6 @@ export const GIFV = forwardRef( role='button' tabIndex={0} aria-label={alt} - title={alt} lang={lang} width={width} height={height} diff --git a/app/javascript/mastodon/features/ui/components/zoomable_image.tsx b/app/javascript/mastodon/features/ui/components/zoomable_image.tsx index 09b39d3efab..1297d243d0d 100644 --- a/app/javascript/mastodon/features/ui/components/zoomable_image.tsx +++ b/app/javascript/mastodon/features/ui/components/zoomable_image.tsx @@ -306,10 +306,8 @@ export const ZoomableImage: React.FC = ({ Date: Thu, 24 Jul 2025 09:00:32 +0000 Subject: [PATCH 117/660] fix(deps): update dependency @vitejs/plugin-react to v4.7.0 (#35421) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 133 +++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/yarn.lock b/yarn.lock index 67739e74d4c..2c1faaa91a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -73,39 +73,39 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/core@npm:7.27.4" +"@babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/core@npm:7.28.0" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.27.3" + "@babel/generator": "npm:^7.28.0" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-module-transforms": "npm:^7.27.3" - "@babel/helpers": "npm:^7.27.4" - "@babel/parser": "npm:^7.27.4" + "@babel/helpers": "npm:^7.27.6" + "@babel/parser": "npm:^7.28.0" "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.27.4" - "@babel/types": "npm:^7.27.3" + "@babel/traverse": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10c0/d2d17b106a8d91d3eda754bb3f26b53a12eb7646df73c2b2d2e9b08d90529186bc69e3823f70a96ec6e5719dc2372fb54e14ad499da47ceeb172d2f7008787b5 + checksum: 10c0/423302e7c721e73b1c096217880272e02020dfb697a55ccca60ad01bba90037015f84d0c20c6ce297cf33a19bb704bc5c2b3d3095f5284dfa592bd1de0b9e8c3 languageName: node linkType: hard -"@babel/generator@npm:^7.27.3": - version: 7.27.3 - resolution: "@babel/generator@npm:7.27.3" +"@babel/generator@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/generator@npm:7.28.0" dependencies: - "@babel/parser": "npm:^7.27.3" - "@babel/types": "npm:^7.27.3" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" + "@babel/parser": "npm:^7.28.0" + "@babel/types": "npm:^7.28.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" jsesc: "npm:^3.0.2" - checksum: 10c0/341622e17c61d008fc746b655ab95ef7febb543df8efb4148f57cf06e60ade1abe091ed7d6811df17b064d04d64f69bb7f35ab0654137116d55c54a73145a61a + checksum: 10c0/1b3d122268ea3df50fde707ad864d9a55c72621357d5cebb972db3dd76859c45810c56e16ad23123f18f80cc2692f5a015d2858361300f0f224a05dc43d36a92 languageName: node linkType: hard @@ -176,6 +176,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-member-expression-to-functions@npm:7.27.1" @@ -293,24 +300,24 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/helpers@npm:7.27.4" +"@babel/helpers@npm:^7.27.6": + version: 7.27.6 + resolution: "@babel/helpers@npm:7.27.6" dependencies: "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.3" - checksum: 10c0/3463551420926b3f403c1a30d66ac67bba5c4f73539a8ccb71544da129c4709ac37c57fac740ed8a261b3e6bbbf353b05e03b36ea1a6bf1081604b2a94ca43c1 + "@babel/types": "npm:^7.27.6" + checksum: 10c0/448bac96ef8b0f21f2294a826df9de6bf4026fd023f8a6bb6c782fe3e61946801ca24381490b8e58d861fee75cd695a1882921afbf1f53b0275ee68c938bd6d3 languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.3, @babel/parser@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/parser@npm:7.27.4" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" dependencies: - "@babel/types": "npm:^7.27.3" + "@babel/types": "npm:^7.28.0" bin: parser: ./bin/babel-parser.js - checksum: 10c0/d1bf17e7508585235e2a76594ba81828e48851877112bb8abbecd7161a31fb66654e993e458ddaedb18a3d5fa31970e5f3feca5ae2900f51e6d8d3d35da70dbf + checksum: 10c0/c2ef81d598990fa949d1d388429df327420357cb5200271d0d0a2784f1e6d54afc8301eb8bdf96d8f6c77781e402da93c7dc07980fcc136ac5b9d5f1fce701b5 languageName: node linkType: hard @@ -1157,28 +1164,28 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/traverse@npm:7.27.4" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/traverse@npm:7.28.0" dependencies: "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.27.3" - "@babel/parser": "npm:^7.27.4" + "@babel/generator": "npm:^7.28.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.0" "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.3" + "@babel/types": "npm:^7.28.0" debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10c0/6de8aa2a0637a6ee6d205bf48b9e923928a02415771fdec60085ed754dcdf605e450bb3315c2552fa51c31a4662275b45d5ae4ad527ce55a7db9acebdbbbb8ed + checksum: 10c0/32794402457827ac558173bcebdcc0e3a18fa339b7c41ca35621f9f645f044534d91bb923ff385f5f960f2e495f56ce18d6c7b0d064d2f0ccb55b285fa6bc7b9 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.4.4": - version: 7.27.3 - resolution: "@babel/types@npm:7.27.3" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.6, @babel/types@npm:^7.28.0, @babel/types@npm:^7.4.4": + version: 7.28.1 + resolution: "@babel/types@npm:7.28.1" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/bafdfc98e722a6b91a783b6f24388f478fd775f0c0652e92220e08be2cc33e02d42088542f1953ac5e5ece2ac052172b3dadedf12bec9aae57899e92fb9a9757 + checksum: 10c0/5e99b346c11ee42ffb0cadc28159fe0b184d865a2cc1593df79b199772a534f6453969b4942aa5e4a55a3081863096e1cc3fc1c724d826926dc787cf229b845d languageName: node linkType: hard @@ -2542,14 +2549,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.5 - resolution: "@jridgewell/gen-mapping@npm:0.3.5" +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.12 + resolution: "@jridgewell/gen-mapping@npm:0.3.12" dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/sourcemap-codec": "npm:^1.5.0" "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/1be4fd4a6b0f41337c4f5fdf4afc3bd19e39c3691924817108b82ffcb9c9e609c273f936932b9fba4b3a298ce2eb06d9bff4eb1cc3bd81c4f4ee1b4917e25feb + checksum: 10c0/32f771ae2467e4d440be609581f7338d786d3d621bac3469e943b9d6d116c23c4becb36f84898a92bbf2f3c0511365c54a945a3b86a83141547a2a360a5ec0c7 languageName: node linkType: hard @@ -2560,13 +2566,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 - languageName: node - linkType: hard - "@jridgewell/source-map@npm:^0.3.3": version: 0.3.6 resolution: "@jridgewell/source-map@npm:0.3.6" @@ -2577,20 +2576,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": version: 1.5.0 resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" dependencies: "@jridgewell/resolve-uri": "npm:^3.1.0" "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + checksum: 10c0/fb547ba31658c4d74eb17e7389f4908bf7c44cef47acb4c5baa57289daf68e6fe53c639f41f751b3923aca67010501264f70e7b49978ad1f040294b22c37b333 languageName: node linkType: hard @@ -3211,10 +3210,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.19": - version: 1.0.0-beta.19 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.19" - checksum: 10c0/e4205df56e6231a347ac601d044af365639741d51b5bea4e91ecc37e19e9777cb79d1daa924b8709ddf1f743ed6922e4e68e2445126434c4d420d9f4416f4feb +"@rolldown/pluginutils@npm:1.0.0-beta.27": + version: 1.0.0-beta.27 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" + checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c languageName: node linkType: hard @@ -4735,18 +4734,18 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^4.2.1": - version: 4.6.0 - resolution: "@vitejs/plugin-react@npm:4.6.0" + version: 4.7.0 + resolution: "@vitejs/plugin-react@npm:4.7.0" dependencies: - "@babel/core": "npm:^7.27.4" + "@babel/core": "npm:^7.28.0" "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.19" + "@rolldown/pluginutils": "npm:1.0.0-beta.27" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.17.0" peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 - checksum: 10c0/73b8f271978a0337debb255afd1667f49c2018c118962a8613120383375c4038255a5315cee2ef210dc7fd07cd30d5b12271077ad47db29980f8156b8a49be2c + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 languageName: node linkType: hard From 6fc77a545bef8c15cdc262f100efc2915c5900ed Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 24 Jul 2025 16:30:56 +0200 Subject: [PATCH 118/660] fix: Fix TypeError on pages with empty feeds (#35504) --- app/javascript/mastodon/components/status_list.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index 70b7968fba1..c3055aeeab5 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -41,9 +41,11 @@ export default class StatusList extends ImmutablePureComponent { }; componentDidMount() { - this.columnHeaderHeight = parseFloat( - getComputedStyle(this.node.node).getPropertyValue('--column-header-height') - ) || 0; + this.columnHeaderHeight = this.node?.node + ? parseFloat( + getComputedStyle(this.node.node).getPropertyValue('--column-header-height') + ) || 0 + : 0; } getFeaturedStatusCount = () => { @@ -69,8 +71,8 @@ export default class StatusList extends ImmutablePureComponent { }; _selectChild = (id, index, direction) => { - const listContainer = this.node.node; - let listItem = listContainer.querySelector( + const listContainer = this.node?.node; + let listItem = listContainer?.querySelector( // :nth-child uses 1-based indexing `.item-list > :nth-child(${index + 1 + direction})` ); From dfaca794bf98e40954f1ef869f9c7e9274167d74 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 24 Jul 2025 16:55:00 +0200 Subject: [PATCH 119/660] Force modern emoji experimental to be dev mode only (#35505) --- app/javascript/mastodon/components/account_bio.tsx | 6 ++---- .../mastodon/components/status_content.jsx | 5 +++-- .../mastodon/features/emoji/emoji_html.tsx | 4 ++-- app/javascript/mastodon/initial_state.js | 8 -------- app/javascript/mastodon/main.tsx | 10 +++++++--- app/javascript/mastodon/utils/environment.ts | 12 ++++++++++++ 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx index cdac41b8a7d..b720b4746d0 100644 --- a/app/javascript/mastodon/components/account_bio.tsx +++ b/app/javascript/mastodon/components/account_bio.tsx @@ -3,8 +3,8 @@ import { useCallback } from 'react'; import { useLinks } from 'mastodon/hooks/useLinks'; import { EmojiHTML } from '../features/emoji/emoji_html'; -import { isFeatureEnabled } from '../initial_state'; import { useAppSelector } from '../store'; +import { isModernEmojiEnabled } from '../utils/environment'; interface AccountBioProps { className: string; @@ -32,9 +32,7 @@ export const AccountBio: React.FC = ({ if (!account) { return ''; } - return isFeatureEnabled('modern_emojis') - ? account.note - : account.note_emojified; + return isModernEmojiEnabled() ? account.note : account.note_emojified; }); const extraEmojis = useAppSelector((state) => { const account = state.accounts.get(accountId); diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 02f06ec96ac..e1fd7734e9e 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -13,8 +13,9 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react' import { Icon } from 'mastodon/components/icon'; import { Poll } from 'mastodon/components/poll'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; -import { autoPlayGif, isFeatureEnabled, languages as preloadedLanguages } from 'mastodon/initial_state'; +import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; import { EmojiHTML } from '../features/emoji/emoji_html'; +import { isModernEmojiEnabled } from '../utils/environment'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) @@ -24,7 +25,7 @@ const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) * @returns {string} */ export function getStatusContent(status) { - if (isFeatureEnabled('modern_emojis')) { + if (isModernEmojiEnabled()) { return status.getIn(['translation', 'content']) || status.get('content'); } return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml'); diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx index 27af2dda279..85628e6723d 100644 --- a/app/javascript/mastodon/features/emoji/emoji_html.tsx +++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx @@ -5,8 +5,8 @@ import type { List as ImmutableList } from 'immutable'; import { isList } from 'immutable'; import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; -import { isFeatureEnabled } from '@/mastodon/initial_state'; import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; +import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; import { useEmojiAppState } from './hooks'; import { emojifyElement } from './render'; @@ -25,7 +25,7 @@ export const EmojiHTML: React.FC = ({ extraEmojis, ...props }) => { - if (isFeatureEnabled('modern_emojis')) { + if (isModernEmojiEnabled()) { return ( Date: Thu, 24 Jul 2025 17:08:50 +0200 Subject: [PATCH 120/660] chore(deps): update dependency httplog to v1.7.2 (#35506) 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 f5a919a52f5..ca95bbfdcf7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -315,7 +315,7 @@ GEM http_accept_language (2.1.1) httpclient (2.9.0) mutex_m - httplog (1.7.1) + httplog (1.7.2) rack (>= 2.0) rainbow (>= 2.0.0) i18n (1.14.7) From a863e68d174b176dc350d8e3472c010cd37b7807 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 24 Jul 2025 17:45:12 +0200 Subject: [PATCH 121/660] Add restrictions on which quote posts can trend (#35507) --- app/models/trends/statuses.rb | 28 +++++++++++++++++++++------- spec/models/trends/statuses_spec.rb | 8 ++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/app/models/trends/statuses.rb b/app/models/trends/statuses.rb index c85b0170fd7..e64369b08be 100644 --- a/app/models/trends/statuses.rb +++ b/app/models/trends/statuses.rb @@ -90,14 +90,28 @@ class Trends::Statuses < Trends::Base def eligible?(status) status.created_at.past? && - status.public_visibility? && - status.account.discoverable? && - !status.account.silenced? && - !status.account.sensitized? && - status.spoiler_text.blank? && - !status.sensitive? && + opted_into_trends?(status) && + !sensitive_content?(status) && !status.reply? && - valid_locale?(status.language) + valid_locale?(status.language) && + (status.quote.nil? || trendable_quote?(status.quote)) + end + + def opted_into_trends?(status) + status.public_visibility? && + status.account.discoverable? && + !status.account.silenced? + end + + def sensitive_content?(status) + status.account.sensitized? || status.spoiler_text.present? || status.sensitive? + end + + def trendable_quote?(quote) + quote.acceptable? && + quote.quoted_status.present? && + opted_into_trends?(quote.quoted_status) && + !sensitive_content?(quote.quoted_status) end def calculate_scores(statuses, at_time) diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb index 39839010427..54b227dad0c 100644 --- a/spec/models/trends/statuses_spec.rb +++ b/spec/models/trends/statuses_spec.rb @@ -111,12 +111,16 @@ RSpec.describe Trends::Statuses do let!(:yesterday) { today - 1.day } let!(:status_foo) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: yesterday) } - let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) } + let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today, quote: Quote.new(state: :accepted, quoted_status: status_foo)) } let!(:status_baz) { Fabricate(:status, text: 'Baz', language: 'en', trendable: true, created_at: today) } + let!(:untrendable) { Fabricate(:status, text: 'Untrendable', language: 'en', trendable: true, visibility: :unlisted) } + let!(:untrendable_quote) { Fabricate(:status, text: 'Untrendable quote!', language: 'en', trendable: true, created_at: today, quote: Quote.new(state: :accepted, quoted_status: untrendable)) } before do default_threshold_value.times { reblog(status_foo, today) } default_threshold_value.times { reblog(status_bar, today) } + default_threshold_value.times { reblog(untrendable, today) } + default_threshold_value.times { reblog(untrendable_quote, today) } (default_threshold_value - 1).times { reblog(status_baz, today) } end @@ -129,7 +133,7 @@ RSpec.describe Trends::Statuses do results = subject.query.limit(10).to_a expect(results).to eq [status_bar, status_foo] - expect(results).to_not include(status_baz) + expect(results).to_not include(status_baz, untrendable, untrendable_quote) end end From e5e977c24fc31d7c609fc523f76ddfcc64006307 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 25 Jul 2025 03:18:46 -0400 Subject: [PATCH 122/660] Fix `Style/GuardClause` in worker rescues (#35508) --- app/workers/mention_resolve_worker.rb | 6 +----- app/workers/redownload_avatar_worker.rb | 6 +----- app/workers/redownload_header_worker.rb | 6 +----- app/workers/redownload_media_worker.rb | 6 +----- app/workers/remote_account_refresh_worker.rb | 6 +----- 5 files changed, 5 insertions(+), 25 deletions(-) diff --git a/app/workers/mention_resolve_worker.rb b/app/workers/mention_resolve_worker.rb index 8c5938aeaf1..d14adb3cf31 100644 --- a/app/workers/mention_resolve_worker.rb +++ b/app/workers/mention_resolve_worker.rb @@ -22,11 +22,7 @@ class MentionResolveWorker rescue Mastodon::UnexpectedResponseError => e response = e.response - if response_error_unsalvageable?(response) - # Give up - else - raise e - end + raise(e) unless response_error_unsalvageable?(response) end private diff --git a/app/workers/redownload_avatar_worker.rb b/app/workers/redownload_avatar_worker.rb index df17b7718dc..c4c659f73e8 100644 --- a/app/workers/redownload_avatar_worker.rb +++ b/app/workers/redownload_avatar_worker.rb @@ -20,10 +20,6 @@ class RedownloadAvatarWorker rescue Mastodon::UnexpectedResponseError => e response = e.response - if response_error_unsalvageable?(response) - # Give up - else - raise e - end + raise(e) unless response_error_unsalvageable?(response) end end diff --git a/app/workers/redownload_header_worker.rb b/app/workers/redownload_header_worker.rb index 3b142ec5f98..2d600e29641 100644 --- a/app/workers/redownload_header_worker.rb +++ b/app/workers/redownload_header_worker.rb @@ -20,10 +20,6 @@ class RedownloadHeaderWorker rescue Mastodon::UnexpectedResponseError => e response = e.response - if response_error_unsalvageable?(response) - # Give up - else - raise e - end + raise(e) unless response_error_unsalvageable?(response) end end diff --git a/app/workers/redownload_media_worker.rb b/app/workers/redownload_media_worker.rb index 343caa32c23..5342ec0b2d4 100644 --- a/app/workers/redownload_media_worker.rb +++ b/app/workers/redownload_media_worker.rb @@ -20,10 +20,6 @@ class RedownloadMediaWorker rescue Mastodon::UnexpectedResponseError => e response = e.response - if response_error_unsalvageable?(response) - # Give up - else - raise e - end + raise(e) unless response_error_unsalvageable?(response) end end diff --git a/app/workers/remote_account_refresh_worker.rb b/app/workers/remote_account_refresh_worker.rb index 9632936b547..5a4cbdf7260 100644 --- a/app/workers/remote_account_refresh_worker.rb +++ b/app/workers/remote_account_refresh_worker.rb @@ -15,10 +15,6 @@ class RemoteAccountRefreshWorker rescue Mastodon::UnexpectedResponseError => e response = e.response - if response_error_unsalvageable?(response) - # Give up - else - raise e - end + raise(e) unless response_error_unsalvageable?(response) end end From 960f6932194ed2d0dc19909b302e94d8eb1c7d88 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 25 Jul 2025 03:21:08 -0400 Subject: [PATCH 123/660] Use `field` partial in admin account show view (#35503) --- app/helpers/home_helper.rb | 4 ++-- app/views/admin/accounts/_field.html.haml | 9 +++++++++ app/views/admin/accounts/show.html.haml | 20 ++++++-------------- spec/helpers/home_helper_spec.rb | 20 ++++++++------------ 4 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 app/views/admin/accounts/_field.html.haml diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index c5b83326db3..7b9d3f4fc1a 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -49,8 +49,8 @@ module HomeHelper end end - def custom_field_classes(field) - if field.verified? + def field_verified_class(verified) + if verified 'verified' else 'emojify' diff --git a/app/views/admin/accounts/_field.html.haml b/app/views/admin/accounts/_field.html.haml new file mode 100644 index 00000000000..ce8d80785e6 --- /dev/null +++ b/app/views/admin/accounts/_field.html.haml @@ -0,0 +1,9 @@ +-# locals: (field:, account:) +%dl + %dt.emojify{ title: field.name } + = prerender_custom_emojis(h(field.name), account.emojis) + %dd{ title: field.value, class: field_verified_class(field.verified?) } + - if field.verified? + %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } + = material_symbol 'check' + = prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis) diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index f148b9a0822..977967c58fb 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -7,25 +7,17 @@ = render 'application/card', account: @account -- account = @account -- fields = account.fields -- unless fields.empty? && account.note.blank? +- if @account.fields? || @account.note? .admin-account-bio - - unless fields.empty? + - if @account.fields? %div .account__header__fields - - fields.each do |field| - %dl - %dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis) - %dd{ title: field.value, class: custom_field_classes(field) } - - if field.verified? - %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } - = material_symbol 'check' - = prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis) + = render partial: 'field', collection: @account.fields, locals: { account: @account } - - if account.note.present? + - if @account.note? %div - .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis) + .account__header__content.emojify + = prerender_custom_emojis(account_bio_format(@account), @account.emojis) = render 'admin/accounts/counters', account: @account diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index c3fbff4e8bb..a8f6d99f032 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -75,23 +75,19 @@ RSpec.describe HomeHelper do end end - describe 'custom_field_classes' do - context 'with a verified field' do - let(:field) { instance_double(Account::Field, verified?: true) } + describe 'field_verified_class' do + subject { helper.field_verified_class(verified) } - it 'returns verified string' do - result = helper.custom_field_classes(field) - expect(result).to eq 'verified' - end + context 'with a verified field' do + let(:verified) { true } + + it { is_expected.to eq('verified') } end context 'with a non-verified field' do - let(:field) { instance_double(Account::Field, verified?: false) } + let(:verified) { false } - it 'returns verified string' do - result = helper.custom_field_classes(field) - expect(result).to eq 'emojify' - end + it { is_expected.to eq('emojify') } end end From 2e35defeec7d402902eecea316dddd078d3e123c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 25 Jul 2025 03:22:05 -0400 Subject: [PATCH 124/660] Update rubocop to version 1.79.0 (#35502) --- .rubocop_todo.yml | 2 +- Gemfile.lock | 6 ++++-- app/controllers/api/v1/admin/tags_controller.rb | 1 + app/lib/emoji_formatter.rb | 4 +++- spec/controllers/concerns/accountable_concern_spec.rb | 1 + spec/routing/accounts_routing_spec.rb | 2 ++ spec/validators/existing_username_validator_spec.rb | 1 + spec/validators/language_validator_spec.rb | 1 + spec/validators/unreserved_username_validator_spec.rb | 1 + spec/validators/url_validator_spec.rb | 1 + 10 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4ec92f34121..9e69426fcf7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.77.0. +# using RuboCop version 1.79.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new diff --git a/Gemfile.lock b/Gemfile.lock index ca95bbfdcf7..d0472d538c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -765,7 +765,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.4) - rubocop (1.78.0) + rubocop (1.79.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -773,8 +773,9 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.45.1, < 2.0) + rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) + tsort (>= 0.2.0) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.46.0) parser (>= 3.3.7.2) @@ -880,6 +881,7 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + tsort (0.2.0) tty-color (0.6.0) tty-cursor (0.7.1) tty-prompt (0.23.1) diff --git a/app/controllers/api/v1/admin/tags_controller.rb b/app/controllers/api/v1/admin/tags_controller.rb index 283383acb4a..dd272120e21 100644 --- a/app/controllers/api/v1/admin/tags_controller.rb +++ b/app/controllers/api/v1/admin/tags_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Admin::TagsController < Api::BaseController include Authorization + before_action -> { authorize_if_got_token! :'admin:read' }, only: [:index, :show] before_action -> { authorize_if_got_token! :'admin:write' }, only: :update diff --git a/app/lib/emoji_formatter.rb b/app/lib/emoji_formatter.rb index c193df9bb65..a5f5103fffd 100644 --- a/app/lib/emoji_formatter.rb +++ b/app/lib/emoji_formatter.rb @@ -45,7 +45,9 @@ class EmojiFormatter i += 1 if inside_shortname && text[i] == ':' - inside_shortname = false + # https://github.com/rubocop/rubocop/issues/14383 + # False positive in line below, remove disable when resolved + inside_shortname = false # rubocop:disable Lint/UselessAssignment shortcode = text[(shortname_start_index + 1)..(i - 1)] char_after = text[i + 1] diff --git a/spec/controllers/concerns/accountable_concern_spec.rb b/spec/controllers/concerns/accountable_concern_spec.rb index cd06d872bb5..e68090fdc2f 100644 --- a/spec/controllers/concerns/accountable_concern_spec.rb +++ b/spec/controllers/concerns/accountable_concern_spec.rb @@ -6,6 +6,7 @@ RSpec.describe AccountableConcern do let(:hoge_class) do Class.new do include AccountableConcern + attr_reader :current_account def initialize(current_account) diff --git a/spec/routing/accounts_routing_spec.rb b/spec/routing/accounts_routing_spec.rb index 8ff711a681e..bb0bf082bde 100644 --- a/spec/routing/accounts_routing_spec.rb +++ b/spec/routing/accounts_routing_spec.rb @@ -49,6 +49,7 @@ RSpec.describe 'Routes under accounts/' do context 'with local username encoded at' do include RSpec::Rails::RequestExampleGroup + let(:username) { 'alice' } it 'routes /%40:username' do @@ -140,6 +141,7 @@ RSpec.describe 'Routes under accounts/' do context 'with remote username encoded at' do include RSpec::Rails::RequestExampleGroup + let(:username) { 'alice%40example.com' } let(:username_decoded) { 'alice@example.com' } diff --git a/spec/validators/existing_username_validator_spec.rb b/spec/validators/existing_username_validator_spec.rb index 25ecb1fbcde..ab5be524534 100644 --- a/spec/validators/existing_username_validator_spec.rb +++ b/spec/validators/existing_username_validator_spec.rb @@ -6,6 +6,7 @@ RSpec.describe ExistingUsernameValidator do let(:record_class) do Class.new do include ActiveModel::Validations + attr_accessor :contact, :friends def self.name diff --git a/spec/validators/language_validator_spec.rb b/spec/validators/language_validator_spec.rb index 19e55f34672..d19b33f27f8 100644 --- a/spec/validators/language_validator_spec.rb +++ b/spec/validators/language_validator_spec.rb @@ -6,6 +6,7 @@ RSpec.describe LanguageValidator do let(:record_class) do Class.new do include ActiveModel::Validations + attr_accessor :locale validates :locale, language: true diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index ad1092109db..67a2921885e 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -6,6 +6,7 @@ RSpec.describe UnreservedUsernameValidator do let(:record_class) do Class.new do include ActiveModel::Validations + attr_accessor :username validates_with UnreservedUsernameValidator diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb index 2297dddaa01..55c0347d18e 100644 --- a/spec/validators/url_validator_spec.rb +++ b/spec/validators/url_validator_spec.rb @@ -6,6 +6,7 @@ RSpec.describe URLValidator do let(:record_class) do Class.new do include ActiveModel::Validations + attr_accessor :profile validates :profile, url: true From d950298d2915a98701b0d8026a551c69bcf99f1d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 07:29:48 +0000 Subject: [PATCH 125/660] New Crowdin Translations (automated) (#35514) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/br.json | 2 + app/javascript/mastodon/locales/de.json | 6 +- app/javascript/mastodon/locales/el.json | 4 +- app/javascript/mastodon/locales/en-GB.json | 2 +- app/javascript/mastodon/locales/ga.json | 2 + app/javascript/mastodon/locales/kk.json | 1 + app/javascript/mastodon/locales/pt-PT.json | 2 + app/javascript/mastodon/locales/sc.json | 81 ++++++++++++++++++++++ app/javascript/mastodon/locales/uk.json | 2 + config/locales/fr-CA.yml | 1 + config/locales/fr.yml | 1 + config/locales/ru.yml | 53 +++++++------- config/locales/simple_form.el.yml | 4 +- 13 files changed, 127 insertions(+), 34 deletions(-) diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index b0e21ab3be0..8882ff916e3 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -558,6 +558,8 @@ "status.bookmark": "Ouzhpennañ d'ar sinedoù", "status.cancel_reblog_private": "Nac'hañ ar skignadenn", "status.cannot_reblog": "Ar c'hannad-se na c'hall ket bezañ skignet", + "status.context.load_new_replies": "Respontoù nevez zo", + "status.context.loading": "O kerc'hat muioc'h a respontoù", "status.copy": "Eilañ liamm ar c'hannad", "status.delete": "Dilemel", "status.detailed_status": "Gwel kaozeadenn munudek", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0bd451d64b6..75f99fef4c8 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -43,7 +43,7 @@ "account.followers": "Follower", "account.followers.empty": "Diesem Profil folgt noch niemand.", "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", - "account.followers_you_know_counter": "{counter} bekannt", + "account.followers_you_know_counter": "{counter} Follower kennst Du", "account.following": "Folge ich", "account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}", "account.follows.empty": "Dieses Profil folgt noch niemandem.", @@ -63,7 +63,7 @@ "account.mute_short": "Stummschalten", "account.muted": "Stummgeschaltet", "account.muting": "Stummgeschaltet", - "account.mutual": "Ihr folgt einander", + "account.mutual": "Ihr folgt euch", "account.no_bio": "Keine Beschreibung verfügbar.", "account.open_original_page": "Ursprüngliche Seite öffnen", "account.posts": "Beiträge", @@ -225,7 +225,7 @@ "confirmations.discard_draft.edit.title": "Änderungen an diesem Beitrag verwerfen?", "confirmations.discard_draft.post.cancel": "Entwurf fortsetzen", "confirmations.discard_draft.post.message": "Beim Fortfahren wird der gerade verfasste Beitrag verworfen.", - "confirmations.discard_draft.post.title": "Beitragsentwurf verwerfen?", + "confirmations.discard_draft.post.title": "Entwurf verwerfen?", "confirmations.discard_edit_media.confirm": "Verwerfen", "confirmations.discard_edit_media.message": "Du hast Änderungen an der Medienbeschreibung oder -vorschau vorgenommen, die noch nicht gespeichert sind. Trotzdem verwerfen?", "confirmations.follow_to_list.confirm": "Folgen und zur Liste hinzufügen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 8223d5d2008..b18897576c5 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -612,7 +612,7 @@ "notification.moderation_warning.action_suspend": "Ο λογαριασμός σου έχει ανασταλεί.", "notification.own_poll": "Η δημοσκόπησή σου έληξε", "notification.poll": "Μία ψηφοφορία στην οποία συμμετείχες έχει τελειώσει", - "notification.reblog": "Ο/Η {name} ενίσχυσε τη δημοσίευσή σου", + "notification.reblog": "Ο/Η {name} ενίσχυσε την ανάρτηση σου", "notification.reblog.name_and_others_with_link": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} ενίσχυσαν την ανάρτησή σου", "notification.relationships_severance_event": "Χάθηκε η σύνδεση με το {name}", "notification.relationships_severance_event.account_suspension": "Ένας διαχειριστής από το {from} ανέστειλε το {target}, πράγμα που σημαίνει ότι δεν μπορείς πλέον να λαμβάνεις ενημερώσεις από αυτούς ή να αλληλεπιδράς μαζί τους.", @@ -845,6 +845,8 @@ "status.bookmark": "Σελιδοδείκτης", "status.cancel_reblog_private": "Ακύρωση ενίσχυσης", "status.cannot_reblog": "Αυτή η ανάρτηση δεν μπορεί να ενισχυθεί", + "status.context.load_new_replies": "Νέες απαντήσεις διαθέσιμες", + "status.context.loading": "Γίνεται έλεγχος για περισσότερες απαντήσεις", "status.continued_thread": "Συνεχιζόμενο νήματος", "status.copy": "Αντιγραφή συνδέσμου ανάρτησης", "status.delete": "Διαγραφή", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 4f3a649f0bb..441cfee6d10 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -612,7 +612,7 @@ "notification.moderation_warning.action_suspend": "Your account has been suspended.", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you voted in has ended", - "notification.reblog": "{name} boosted your status", + "notification.reblog": "{name} boosted your post", "notification.reblog.name_and_others_with_link": "{name} and {count, plural, one {# other} other {# others}} boosted your post", "notification.relationships_severance_event": "Lost connections with {name}", "notification.relationships_severance_event.account_suspension": "An admin from {from} has suspended {target}, which means you can no longer receive updates from them or interact with them.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index bca4ed8d1e7..2c0b709bdc3 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -845,6 +845,8 @@ "status.bookmark": "Leabharmharcanna", "status.cancel_reblog_private": "Dímhol", "status.cannot_reblog": "Ní féidir an phostáil seo a mholadh", + "status.context.load_new_replies": "Freagraí nua ar fáil", + "status.context.loading": "Ag seiceáil le haghaidh tuilleadh freagraí", "status.continued_thread": "Snáithe ar lean", "status.copy": "Cóipeáil an nasc chuig an bpostáil", "status.delete": "Scrios", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index f0e981adb35..be0025fdb2f 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -1,6 +1,7 @@ { "about.blocks": "Модерацияланған серверлер", "about.contact": "Байланыс:", + "about.default_locale": "Әдепкі", "about.disclaimer": "Mastodon деген тегін, бастапқы коды ашық бағдарламалық жасақтама және Mastodon gGmbH-тің сауда маркасы.", "about.domain_blocks.no_reason_available": "Себеп қолжетімсіз", "about.domain_blocks.preamble": "Mastodon әдетте сізге Fediverse'тің кез келген серверінің қолданушыларының контентін көріп, олармен байланысуға мүмкіндік береді. Осы белгілі серверде жасалған ережеден тыс жағдайлар міне.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 15ccc7cb32a..6cc740f755f 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -845,6 +845,8 @@ "status.bookmark": "Guardar nos marcadores", "status.cancel_reblog_private": "Retirar impulso", "status.cannot_reblog": "Esta publicação não pode ser impulsionada", + "status.context.load_new_replies": "Novas respostas disponíveis", + "status.context.loading": "A verificar por mais respostas", "status.continued_thread": "Continuação da conversa", "status.copy": "Copiar hiperligação da publicação", "status.delete": "Eliminar", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index fb856ba8a1a..8dc3e597c11 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -1,6 +1,7 @@ { "about.blocks": "Serbidores moderados", "about.contact": "Cuntatu:", + "about.default_locale": "Predefinidu", "about.disclaimer": "Mastodon est software de còdighe lìberu e unu màrchiu de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Peruna resone a disponimentu", "about.domain_blocks.preamble": "Mastodon ti permitit de bìdere su cuntenutu de utentes de cale si siat àteru serbidore de su fediversu. Custas sunt etzetziones fatas in custu serbidore ispetzìficu.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Limitadu", "about.domain_blocks.suspended.explanation": "Perunu datu de custu serbidore at a èssere protzessadu, immagasinadu o cuncambiadu; est impossìbile duncas cale si siat interatzione o comunicatzione cun is utentes de custu serbidore.", "about.domain_blocks.suspended.title": "Suspèndidu", + "about.language_label": "Idioma", "about.not_available": "Custa informatzione no est istada posta a disponimentu in custu serbidore.", "about.powered_by": "Rete sotziale detzentralizada impulsada dae {mastodon}", "about.rules": "Règulas de su serbidore", @@ -19,13 +21,21 @@ "account.block_domain": "Bloca su domìniu {domain}", "account.block_short": "Bloca", "account.blocked": "Blocadu", + "account.blocking": "Blocadu", "account.cancel_follow_request": "Annulla sa sighidura", "account.copy": "Còpia su ligòngiu a su profilu", "account.direct": "Mèntova a @{name} in privadu", "account.disable_notifications": "Non mi notìfiches prus cando @{name} pùblichet messàgios", + "account.domain_blocking": "Blocamus su domìniu", "account.edit_profile": "Modìfica profilu", "account.enable_notifications": "Notìfica·mi cando @{name} pùblicat messàgios", "account.endorse": "Cussìgia in su profilu tuo", + "account.familiar_followers_many": "Sighidu dae {name1}, {name2} e {othersCount, plural,one {un'àtera persone chi connosches} other {àteras # persones chi connosches}}", + "account.familiar_followers_one": "Sighidu dae {name1}", + "account.familiar_followers_two": "Sighidu dae {name1} e {name2}", + "account.featured": "In evidèntzia", + "account.featured.accounts": "Profilos", + "account.featured.hashtags": "Etichetas", "account.featured_tags.last_status_at": "Ùrtima publicatzione in su {date}", "account.featured_tags.last_status_never": "Peruna publicatzione", "account.follow": "Sighi", @@ -33,9 +43,11 @@ "account.followers": "Sighiduras", "account.followers.empty": "Nemos sighit ancora custa persone.", "account.followers_counter": "{count, plural, one {{counter} sighidura} other {{counter} sighiduras}}", + "account.followers_you_know_counter": "{counter} chi connosches", "account.following": "Sighende", "account.following_counter": "{count, plural, one {sighende a {counter}} other {sighende a {counter}}}", "account.follows.empty": "Custa persone non sighit ancora a nemos.", + "account.follows_you": "Ti sighit", "account.go_to_profile": "Bae a su profilu", "account.hide_reblogs": "Cua is cumpartziduras de @{name}", "account.in_memoriam": "In memoriam.", @@ -50,18 +62,22 @@ "account.mute_notifications_short": "Pone is notìficas a sa muda", "account.mute_short": "A sa muda", "account.muted": "A sa muda", + "account.muting": "A sa muda", "account.no_bio": "Peruna descritzione frunida.", "account.open_original_page": "Aberi sa pàgina originale", "account.posts": "Publicatziones", "account.posts_with_replies": "Publicatziones e rispostas", + "account.remove_from_followers": "Cantzella a {name} dae is sighiduras", "account.report": "Signala @{name}", "account.requested": "Abetende s'aprovatzione. Incarca pro annullare sa rechesta de sighidura", "account.requested_follow": "{name} at dimandadu de ti sighire", + "account.requests_to_follow_you": "Rechestas de sighidura", "account.share": "Cumpartzi su profilu de @{name}", "account.show_reblogs": "Ammustra is cumpartziduras de @{name}", "account.statuses_counter": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}", "account.unblock": "Isbloca a @{name}", "account.unblock_domain": "Isbloca su domìniu {domain}", + "account.unblock_domain_short": "Isbloca", "account.unblock_short": "Isbloca", "account.unendorse": "Non cussiges in su profilu", "account.unfollow": "Non sigas prus", @@ -83,7 +99,22 @@ "alert.unexpected.message": "Ddoe est istada una faddina.", "alert.unexpected.title": "Oh!", "alt_text_badge.title": "Testu alternativu", + "alt_text_modal.add_alt_text": "Agiunghe testu alternativu", + "alt_text_modal.add_text_from_image": "Agiunghe testu dae un'immàgine", + "alt_text_modal.cancel": "Annulla", + "alt_text_modal.change_thumbnail": "Càmbia sa miniadura", + "alt_text_modal.done": "Fatu", "announcement.announcement": "Annùntziu", + "annual_report.summary.archetype.booster": "Semper a s'ùrtima", + "annual_report.summary.followers.followers": "sighiduras", + "annual_report.summary.followers.total": "{count} totale", + "annual_report.summary.highlighted_post.possessive": "de {name}", + "annual_report.summary.most_used_app.most_used_app": "aplicatzione prus impreada", + "annual_report.summary.most_used_hashtag.most_used_hashtag": "eticheta prus impreada", + "annual_report.summary.most_used_hashtag.none": "Peruna", + "annual_report.summary.new_posts.new_posts": "publicatziones noas", + "annual_report.summary.percentile.we_wont_tell_bernie": "No dd'amus a nàrrere a Bernie.", + "annual_report.summary.thanks": "Gràtzias de èssere parte de Mastodon!", "attachments_list.unprocessed": "(non protzessadu)", "audio.hide": "Cua s'àudio", "block_modal.remote_users_caveat": "Amus a pedire a su serbidore {domain} de rispetare sa detzisione tua. Nointames custu, su rispetu no est garantidu ca unos cantos serbidores diant pòdere gestire is blocos de manera diferente. Is publicatzione pùblicas diant pòdere ancora èssere visìbiles a is utentes chi no ant fatu s'atzessu.", @@ -107,6 +138,7 @@ "bundle_column_error.routing.body": "Impossìbile agatare sa pàgina rechesta. Seguru chi s'URL in sa barra de indiritzos est curretu?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "Serra", + "bundle_modal_error.message": "Faddina in su carrigamentu de custu ischermu.", "bundle_modal_error.retry": "Torra·bi a proare", "closed_registrations.other_server_instructions": "Dae chi Mastodon est detzentralizadu, podes creare unu contu in un'àteru serbidore e interagire cun custu.", "closed_registrations_modal.description": "Sa creatzione de contos in {domain} no est possìbile in custu momentu, però tene in cunsideru chi non tenes bisòngiu de unu contu ispetzìficu in {domain} pro impreare Mastodon.", @@ -116,13 +148,16 @@ "column.blocks": "Persones blocadas", "column.bookmarks": "Sinnalibros", "column.community": "Lìnia de tempus locale", + "column.create_list": "Crea una lista", "column.direct": "Mentziones privadas", "column.directory": "Nàviga in is profilos", "column.domain_blocks": "Domìnios blocados", + "column.edit_list": "Modifica sa lista", "column.favourites": "Preferidos", "column.firehose": "Publicatziones in direta", "column.follow_requests": "Rechestas de sighidura", "column.home": "Printzipale", + "column.list_members": "Gesti is persones de sa lista", "column.lists": "Listas", "column.mutes": "Persones a sa muda", "column.notifications": "Notìficas", @@ -135,6 +170,7 @@ "column_header.pin": "Apica", "column_header.show_settings": "Ammustra is cunfiguratziones", "column_header.unpin": "Boga dae pitzu", + "column_search.cancel": "Annulla", "community.column_settings.local_only": "Isceti locale", "community.column_settings.media_only": "Isceti multimediale", "community.column_settings.remote_only": "Isceti remotu", @@ -152,6 +188,7 @@ "compose_form.poll.duration": "Longària de su sondàgiu", "compose_form.poll.multiple": "Sèberu mùltiplu", "compose_form.poll.option_placeholder": "Optzione {number}", + "compose_form.poll.single": "Sèberu ùnicu", "compose_form.poll.switch_to_multiple": "Muda su sondàgiu pro permìtere multi-optziones", "compose_form.poll.switch_to_single": "Muda su sondàgiu pro permìtere un'optzione isceti", "compose_form.poll.type": "Istile", @@ -169,6 +206,8 @@ "confirmations.delete_list.confirm": "Cantzella", "confirmations.delete_list.message": "Seguru chi boles cantzellare custa lista in manera permanente?", "confirmations.delete_list.title": "Cantzellare sa lista?", + "confirmations.discard_draft.confirm": "Iscarta e sighi", + "confirmations.discard_draft.edit.cancel": "Sighi cun s'editzione", "confirmations.discard_edit_media.confirm": "Iscarta", "confirmations.discard_edit_media.message": "Tenes modìficas non sarvadas a is descritziones o a is anteprimas de is cuntenutos, ddas boles iscartare su matessi?", "confirmations.logout.confirm": "Essi·nche", @@ -256,6 +295,7 @@ "explore.trending_links": "Noas", "explore.trending_statuses": "Publicatziones", "explore.trending_tags": "Etichetas", + "featured_carousel.slide": "{index} de {total}", "filter_modal.added.context_mismatch_title": "Su cuntestu non currispondet.", "filter_modal.added.expired_title": "Filtru iscadidu.", "filter_modal.added.review_and_configure_title": "Cunfiguratziones de filtru", @@ -294,8 +334,12 @@ "footer.privacy_policy": "Polìtica de riservadesa", "footer.source_code": "Ammustra su còdighe de orìgine", "footer.status": "Istadu", + "footer.terms_of_service": "Cunditziones de su servìtziu", "generic.saved": "Sarvadu", "getting_started.heading": "Comente cumintzare", + "hashtag.admin_moderation": "Aberi s'interfache de moderatzione pro #{name}", + "hashtag.browse": "Nàviga in is publicatziones de #{hashtag}", + "hashtag.browse_from_account": "Nàviga in is publicatziones de @{name} in #{hashtag}", "hashtag.column_header.tag_mode.all": "e {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "sena {additional}", @@ -308,7 +352,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} partetzipante} other {{counter} partetzipantes}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}} oe", + "hashtag.feature": "In evidèntzia in su profilu", "hashtag.follow": "Sighi su hashtag", + "hashtag.mute": "Pone #{hashtag} a sa muda", + "hashtag.unfeature": "No ddu pòngias in evidèntzia in su profilu", "hashtag.unfollow": "Non sigas prus s'eticheta", "hashtags.and_other": "… e {count, plural, one {un'àteru} other {àteros #}}", "hints.profiles.posts_may_be_missing": "Podet èssere chi ammanchent tzertas publicatziones de custu profilu.", @@ -324,9 +371,14 @@ "ignore_notifications_modal.filter_instead": "Opuru filtra", "ignore_notifications_modal.filter_to_act_users": "As a pòdere ancora atzetare, refudare o sinnalare a utentes", "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrare agiudat a evitare possìbiles confusiones", + "info_button.label": "Agiudu", + "interaction_modal.go": "Sighi", + "interaction_modal.no_account_yet": "Non tenes galu perunu contu?", + "interaction_modal.on_another_server": "In un'àteru serbidore", "interaction_modal.on_this_server": "In custu serbidore", "interaction_modal.title.follow": "Sighi a {name}", "interaction_modal.title.reply": "Risponde a sa publicatzione de {name}", + "interaction_modal.username_prompt": "Pro es., {example}", "intervals.full.days": "{number, plural, one {# die} other {# dies}}", "intervals.full.hours": "{number, plural, one {# ora} other {# oras}}", "intervals.full.minutes": "{number, plural, one {# minutu} other {# minutos}}", @@ -339,6 +391,7 @@ "keyboard_shortcuts.direct": "pro abèrrere sa colunna de mèntovos privados", "keyboard_shortcuts.down": "Move in bàsciu in sa lista", "keyboard_shortcuts.enter": "Aberi una publicatzione", + "keyboard_shortcuts.favourite": "Publicatzione preferida", "keyboard_shortcuts.favourites": "Aberi sa lista de preferidos", "keyboard_shortcuts.federated": "Aberi sa lìnia de tempus federada", "keyboard_shortcuts.heading": "Incurtzaduras de tecladu", @@ -361,18 +414,25 @@ "keyboard_shortcuts.toggle_hidden": "Ammustra o cua su testu de is AC", "keyboard_shortcuts.toggle_sensitivity": "Ammustra/cua elementos multimediales", "keyboard_shortcuts.toot": "Cumintza a iscrìere una publicatzione noa", + "keyboard_shortcuts.translate": "pro tradùere una publicatzione", "keyboard_shortcuts.unfocus": "Essi de s'àrea de cumpositzione de testu o de chirca", "keyboard_shortcuts.up": "Move in susu in sa lista", "lightbox.close": "Serra", "lightbox.next": "Imbeniente", "lightbox.previous": "Pretzedente", + "lightbox.zoom_in": "Ismànnia finas a sa mannària atuale", "limited_account_hint.title": "Custu profilu est istadu cuadu dae sa moderatzione de {domain}.", + "link_preview.author": "Dae {name}", "link_preview.shares": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}", "lists.delete": "Cantzella sa lista", "lists.edit": "Modìfica sa lista", + "lists.remove_member": "Cantzella", "lists.replies_policy.followed": "Cale si siat persone chi sighis", "lists.replies_policy.list": "Persones de sa lista", "lists.replies_policy.none": "Nemos", + "lists.save": "Sarva", + "lists.search": "Chirca", + "lists.show_replies_to": "Include rispostas dae gente de sa lista a", "load_pending": "{count, plural, one {# elementu nou} other {# elementos noos}}", "loading_indicator.label": "Carrighende…", "media_gallery.hide": "Cua", @@ -387,8 +447,10 @@ "mute_modal.you_wont_see_mentions": "No as a bìdere is publicatziones chi mèntovent a custa persone.", "mute_modal.you_wont_see_posts": "At a pòdere bìdere is publicatziones tuas, però tue no as a bìdere cussas suas.", "navigation_bar.about": "Informatziones", + "navigation_bar.account_settings": "Crae e seguresa", "navigation_bar.administration": "Amministratzione", "navigation_bar.advanced_interface": "Aberi s'interfache web avantzada", + "navigation_bar.automated_deletion": "Cantzelladura automàtica de publicatziones", "navigation_bar.blocks": "Persones blocadas", "navigation_bar.bookmarks": "Sinnalibros", "navigation_bar.direct": "Mentziones privadas", @@ -398,13 +460,18 @@ "navigation_bar.follow_requests": "Rechestas de sighidura", "navigation_bar.followed_tags": "Etichetas sighidas", "navigation_bar.follows_and_followers": "Gente chi sighis e sighiduras", + "navigation_bar.import_export": "Importatzione e esportatzione", "navigation_bar.lists": "Listas", + "navigation_bar.live_feed_local": "Canale in direta (locale)", + "navigation_bar.live_feed_public": "Canale in direta (pùblicu)", "navigation_bar.logout": "Essi", "navigation_bar.moderation": "Moderatzione", + "navigation_bar.more": "Àteru", "navigation_bar.mutes": "Persones a sa muda", "navigation_bar.opened_in_classic_interface": "Publicatziones, contos e àteras pàginas ispetzìficas sunt abertas in manera predefinida in s'interfache web clàssica.", "navigation_bar.preferences": "Preferèntzias", "navigation_bar.search": "Chirca", + "navigation_bar.search_trends": "Chirca / in tendèntzia", "not_signed_in_indicator.not_signed_in": "Ti depes identificare pro atzèdere a custa resursa.", "notification.admin.report": "{name} at sinnaladu a {target}", "notification.admin.report_account": "{name} at sinnaladu {count, plural, one {una publicatzione} other {# publicatziones}} dae {target} pro {category}", @@ -461,15 +528,19 @@ "notification_requests.minimize_banner": "Mìnima su bànner de notìficas filtradas", "notification_requests.notifications_from": "Notìficas dae {name}", "notification_requests.title": "Notìficas filtradas", + "notification_requests.view": "Mustra notìficas", "notifications.clear": "Lìmpia notìficas", "notifications.clear_confirmation": "Seguru chi boles isboidare in manera permanente totu is notìficas tuas?", + "notifications.clear_title": "Boles cantzellare is notìficas?", "notifications.column_settings.admin.report": "Informes noos:", + "notifications.column_settings.admin.sign_up": "Registros noos:", "notifications.column_settings.alert": "Notìficas de iscrivania", "notifications.column_settings.favourite": "Preferidos:", "notifications.column_settings.filter_bar.advanced": "Ammustra totu is categorias", "notifications.column_settings.filter_bar.category": "Barra de filtru lestru", "notifications.column_settings.follow": "Sighiduras noas:", "notifications.column_settings.follow_request": "Rechestas noas de sighidura:", + "notifications.column_settings.group": "Grupu", "notifications.column_settings.mention": "Mèntovos:", "notifications.column_settings.poll": "Resurtados de su sondàgiu:", "notifications.column_settings.push": "Notìficas push", @@ -493,6 +564,8 @@ "notifications.permission_denied": "Is notìficas de iscrivania non sunt a disponimentu pro neghe de rechestas de permissu chi sunt istadas dennegadas in antis", "notifications.permission_denied_alert": "Is notìficas de iscrivania non podent èssere abilitadas, ca su permissu de su navigadore est istadu dennegadu in antis", "notifications.permission_required": "Is notìficas de iscrivania no sunt a disponimentu ca ammancat su permissu rechèdidu.", + "notifications.policy.accept": "Atzeta", + "notifications.policy.accept_hint": "Mustra in is notìficas", "notifications.policy.filter_new_accounts.hint": "Creadu {days, plural, one {erisero} other {in is ùrtimas # dies}}", "notifications.policy.filter_new_accounts_title": "Contos noos", "notifications.policy.filter_not_followers_title": "Gente chi non ti sighit", @@ -501,8 +574,15 @@ "notifications_permission_banner.enable": "Abilita is notìficas de iscrivania", "notifications_permission_banner.how_to_control": "Pro retzire notìficas cando Mastodon no est abertu, abilita is notìficas de iscrivania. Podes controllare cun pretzisione is castas de interatziones chi ingendrant notìficas de iscrivania pro mèdiu de su butone {icon} in subra, cando sunt abilitadas.", "notifications_permission_banner.title": "Non ti perdas mai nudda", + "onboarding.follows.back": "A coa", + "onboarding.follows.done": "Fatu", + "onboarding.follows.search": "Chirca", + "onboarding.follows.title": "Sighi a gente pro cumintzare", "onboarding.profile.display_name": "Nòmine visìbile", "onboarding.profile.note": "Biografia", + "onboarding.profile.save_and_continue": "Sarva e sighi", + "onboarding.profile.title": "Cunfiguratzione de profilu", + "onboarding.profile.upload_avatar": "Càrriga una fotografia de profilu", "picture_in_picture.restore": "Torra·ddu a ue fiat", "poll.closed": "Serradu", "poll.refresh": "Atualiza", @@ -597,6 +677,7 @@ "search_results.hashtags": "Etichetas", "search_results.see_all": "Bide totu", "search_results.statuses": "Publicatziones", + "search_results.title": "Chirca \"{q}\"", "server_banner.about_active_users": "Gente chi at impreadu custu serbidore is ùrtimas 30 dies (Utentes cun Atividade a su Mese)", "server_banner.active_users": "utentes ativos", "server_banner.administered_by": "Amministradu dae:", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index e956bd05e77..2e118f3589e 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -815,6 +815,8 @@ "status.bookmark": "Додати до закладок", "status.cancel_reblog_private": "Скасувати поширення", "status.cannot_reblog": "Цей допис не може бути поширений", + "status.context.load_new_replies": "Доступні нові відповіді", + "status.context.loading": "Перевірка додаткових відповідей", "status.continued_thread": "Продовження у потоці", "status.copy": "Копіювати посилання на допис", "status.delete": "Видалити", diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index bb68494118f..a866debe453 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -319,6 +319,7 @@ fr-CA: create: Créer une annonce title: Nouvelle annonce preview: + disclaimer: Étant donné que les utilisateurs ne peuvent pas s'en retirer, les notifications par courriel devraient être limitées à des annonces importantes telles que des violations de données personnelles ou des notifications de fermeture de serveur. explanation_html: 'L''e-mail sera envoyé à %{display_count} utilisateurs. Le texte suivant sera inclus :' title: Aperçu de la notification d'annonce publish: Publier diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6fcdb5b9722..c171a9ed731 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -319,6 +319,7 @@ fr: create: Créer une annonce title: Nouvelle annonce preview: + disclaimer: Étant donné que les utilisateurs ne peuvent pas s'en retirer, les notifications par courriel devraient être limitées à des annonces importantes telles que des violations de données personnelles ou des notifications de fermeture de serveur. explanation_html: 'L''e-mail sera envoyé à %{display_count} utilisateurs. Le texte suivant sera inclus :' title: Aperçu de la notification d'annonce publish: Publier diff --git a/config/locales/ru.yml b/config/locales/ru.yml index f961e0fba4b..35f79c67c01 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1393,7 +1393,7 @@ ru: featured_tags: add_new: Добавить errors: - limit: Вы уже добавили максимальное число хештегов + limit: Вы достигли максимального количества хештегов, которые можно рекомендовать в профиле hint_html: "Рекомендуйте самые важные для вас хештеги в своём профиле. Это отличный инструмент для того, чтобы держать подписчиков в курсе ваших долгосрочных проектов и творческих работ. Рекомендации хештегов заметны в вашем профиле и предоставляют быстрый доступ к вашим постам." filters: contexts: @@ -1552,16 +1552,16 @@ ru: many: Вы собираетесь игнорировать %{count} пользователей из файла %{filename}. one: Вы собираетесь игнорировать %{count} пользователя из файла %{filename}. other: Вы собираетесь игнорировать %{count} пользователей из файла %{filename}. - preface: Вы можете загрузить некоторые данные, например, списки людей, на которых Вы подписаны или которых блокируете, в Вашу учётную запись на этом узле из файлов, экспортированных с другого узла. - recent_imports: Недавно импортированное + preface: Вы можете перенести прежде экспортированные с другого сервера данные, такие как блокируемые вами пользователи и ваши подписки. + recent_imports: История импорта states: - finished: Готово - in_progress: В процессе - scheduled: Запланировано - unconfirmed: Неподтвержденный - status: Статус - success: Ваши данные были успешно загружены и будут обработаны с должной скоростью - time_started: Началось в + finished: Завершён + in_progress: Выполняется + scheduled: Запланирован + unconfirmed: Не подтверждён + status: Состояние + success: Ваши данные были загружены и в скором времени будут обработаны + time_started: Начат titles: blocking: Импорт списка заблокированных пользователей bookmarks: Импорт закладок @@ -1572,7 +1572,7 @@ ru: type: Тип импорта type_groups: constructive: Подписки и закладки - destructive: Блокировки и игнорируемые + destructive: Чёрный список types: blocking: Заблокированные пользователи bookmarks: Закладки @@ -1583,7 +1583,7 @@ ru: upload: Загрузить invites: delete: Удалить - expired: Истекло + expired: Срок действия истёк expires_in: '1800': 30 минут '21600': 6 часов @@ -1592,33 +1592,32 @@ ru: '604800': 1 неделя '86400': 1 день expires_in_prompt: Бессрочно - generate: Сгенерировать + generate: Создать ссылку invalid: Это приглашение недействительно - invited_by: 'Вас пригласил(а):' + invited_by: 'Вы были приглашены этим пользователем:' max_uses: few: "%{count} раза" many: "%{count} раз" one: "%{count} раз" - other: "%{count} раза" + other: "%{count} раз" max_uses_prompt: Без ограничения - prompt: Создавайте и делитесь ссылками с другими, чтобы предоставить им доступом к этому узлу. + prompt: Создавайте приглашения и делитесь ими с другими людьми, чтобы они могли зарегистрироваться на этом сервере table: expires_at: Истекает - uses: Исп. - title: Пригласить людей + uses: Регистрации + title: Приглашения lists: errors: - limit: Вы достигли максимального количества пользователей + limit: Вы достигли максимального количества списков login_activities: authentication_methods: - otp: приложение двухфакторной аутентификации - password: пароль - sign_in_token: код безопасности электронной почты - webauthn: ключи безопасности - description_html: Если вы видите неопознанное действие, смените пароль и/или включите двухфакторную авторизацию. - empty: Нет доступной истории входов - failed_sign_in_html: Неудачная попытка входа используя %{method} через %{browser} (%{ip}) - successful_sign_in_html: Успешный вход используя %{method} через %{browser} (%{ip}) + otp: приложения двухфакторной аутентификации + password: пароля + webauthn: электронного ключа + description_html: Если вы заметили действия, которых не совершали, вам следует сменить пароль и включить двухфакторную аутентификацию. + empty: История входов отсутствует + failed_sign_in_html: Неудачная попытка входа при помощи %{method} с IP-адреса %{ip} (%{browser}) + successful_sign_in_html: Вход при помощи %{method} с IP-адреса %{ip} (%{browser}) title: История входов mail_subscriptions: unsubscribe: diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index cc222cc85ee..fd32c087440 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -231,8 +231,8 @@ el: setting_always_send_emails: Πάντα να αποστέλλονται ειδοποίησεις μέσω email setting_auto_play_gif: Αυτόματη αναπαραγωγή των GIF setting_boost_modal: Επιβεβαίωση πριν την προώθηση - setting_default_language: Γλώσσα δημοσιεύσεων - setting_default_privacy: Ιδιωτικότητα δημοσιεύσεων + setting_default_language: Γλώσσα κατά την ανάρτηση + setting_default_privacy: Ιδιωτικότητα αναρτήσεων setting_default_quote_policy: Ποιος μπορεί να παραθέσει setting_default_sensitive: Σημείωση όλων των πολυμέσων ως ευαίσθητου περιεχομένου setting_delete_modal: Επιβεβαίωση πριν τη διαγραφή ενός τουτ From 81da377d8e30bb5b00fefaeb65251bf007134937 Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Fri, 25 Jul 2025 10:59:49 +0300 Subject: [PATCH 126/660] Fix Vite build failure on Node.js v20 due to undefined file.parentPath (#35509) --- vite.config.mts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vite.config.mts b/vite.config.mts index f7871ece4d6..c8e399e365a 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -193,10 +193,11 @@ async function findEntrypoints() { withFileTypes: true, }); const jsExtTest = /\.[jt]sx?$/; + const jsEntrypointsDir = path.resolve(jsRoot, 'entrypoints'); for (const file of jsEntrypoints) { if (file.isFile() && jsExtTest.test(file.name)) { entrypoints[file.name.replace(jsExtTest, '')] = path.resolve( - file.parentPath, + jsEntrypointsDir, file.name, ); } @@ -208,10 +209,11 @@ async function findEntrypoints() { { withFileTypes: true }, ); const scssExtTest = /\.s?css$/; + const scssEntrypointsDir = path.resolve(jsRoot, 'styles/entrypoints'); for (const file of scssEntrypoints) { if (file.isFile() && scssExtTest.test(file.name)) { entrypoints[file.name.replace(scssExtTest, '')] = path.resolve( - file.parentPath, + scssEntrypointsDir, file.name, ); } From 5a88b7f6835d12935be27bb6d4ee21f7201ca2f7 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 25 Jul 2025 14:35:24 +0200 Subject: [PATCH 127/660] Add experimental basic quote post authoring (#35355) --- app/controllers/api/v1/statuses_controller.rb | 13 ++ app/lib/activitypub/activity.rb | 18 +++ app/lib/activitypub/activity/accept.rb | 26 ++++ app/lib/activitypub/activity/reject.rb | 17 +++ app/lib/activitypub/case_transform.rb | 4 +- app/lib/status_cache_hydrator.rb | 2 + .../status/interaction_policy_concern.rb | 63 +++++++++ app/models/status.rb | 8 +- app/policies/status_policy.rb | 5 + .../activitypub/note_serializer.rb | 25 +++- app/serializers/rest/status_serializer.rb | 9 ++ app/services/post_status_service.rb | 13 +- .../activitypub/quote_request_worker.rb | 22 +++ .../status_update_distribution_worker.rb | 4 +- config/locales/en.yml | 1 + spec/lib/activitypub/activity/accept_spec.rb | 125 +++++++++++++++++- spec/lib/activitypub/activity/reject_spec.rb | 22 +++ spec/lib/status_cache_hydrator_spec.rb | 12 ++ .../status/interaction_policy_concern_spec.rb | 42 ++++++ spec/policies/status_policy_spec.rb | 86 ++++++++++++ spec/requests/api/v1/statuses_spec.rb | 21 +++ .../activitypub/note_serializer_spec.rb | 16 +++ spec/services/post_status_service_spec.rb | 8 ++ .../activitypub/quote_request_worker_spec.rb | 30 +++++ .../status_update_distribution_worker_spec.rb | 68 +++++++--- 25 files changed, 619 insertions(+), 41 deletions(-) create mode 100644 app/models/concerns/status/interaction_policy_concern.rb create mode 100644 app/workers/activitypub/quote_request_worker.rb create mode 100644 spec/models/concerns/status/interaction_policy_concern_spec.rb create mode 100644 spec/workers/activitypub/quote_request_worker_spec.rb diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index e25b161afd8..f047ba60466 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -10,6 +10,7 @@ class Api::V1::StatusesController < Api::BaseController before_action :set_statuses, only: [:index] before_action :set_status, only: [:show, :context] before_action :set_thread, only: [:create] + before_action :set_quoted_status, only: [:create] before_action :check_statuses_limit, only: [:index] override_rate_limit_headers :create, family: :statuses @@ -76,6 +77,7 @@ class Api::V1::StatusesController < Api::BaseController current_user.account, text: status_params[:status], thread: @thread, + quoted_status: @quoted_status, media_ids: status_params[:media_ids], sensitive: status_params[:sensitive], spoiler_text: status_params[:spoiler_text], @@ -147,6 +149,16 @@ class Api::V1::StatusesController < Api::BaseController render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404 end + def set_quoted_status + return unless Mastodon::Feature.outgoing_quotes_enabled? + + @quoted_status = Status.find(status_params[:quoted_status_id]) if status_params[:quoted_status_id].present? + authorize(@quoted_status, :quote?) if @quoted_status.present? + rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError + # TODO: distinguish between non-existing and non-quotable posts + render json: { error: I18n.t('statuses.errors.quoted_status_not_found') }, status: 404 + end + def check_statuses_limit raise(Mastodon::ValidationError) if status_ids.size > DEFAULT_STATUSES_LIMIT end @@ -163,6 +175,7 @@ class Api::V1::StatusesController < Api::BaseController params.permit( :status, :in_reply_to_id, + :quoted_status_id, :sensitive, :spoiler_text, :visibility, diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 93b45e80188..64ee9acd052 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -116,6 +116,20 @@ class ActivityPub::Activity fetch_remote_original_status end + def quote_from_request_json(json) + quoted_status_uri = value_or_id(json['object']) + quoting_status_uri = value_or_id(json['instrument']) + return if quoting_status_uri.nil? || quoted_status_uri.nil? + + quoting_status = status_from_uri(quoting_status_uri) + return unless quoting_status.present? && quoting_status.quote.present? + + quoted_status = status_from_uri(quoted_status_uri) + return unless quoted_status.present? && quoted_status.account == @account && quoting_status.quote.quoted_status == quoted_status + + quoting_status.quote + end + def dereference_object! return unless @object.is_a?(String) @@ -143,6 +157,10 @@ class ActivityPub::Activity @follow_request_from_object ||= FollowRequest.find_by(target_account: @account, uri: object_uri) unless object_uri.nil? end + def quote_request_from_object + @quote_request_from_object ||= Quote.find_by(quoted_account: @account, activity_uri: object_uri) unless object_uri.nil? + end + def follow_from_object @follow_from_object ||= ::Follow.find_by(target_account: @account, uri: object_uri) unless object_uri.nil? end diff --git a/app/lib/activitypub/activity/accept.rb b/app/lib/activitypub/activity/accept.rb index 5126e23c6a9..144ba9645c5 100644 --- a/app/lib/activitypub/activity/accept.rb +++ b/app/lib/activitypub/activity/accept.rb @@ -4,10 +4,13 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity def perform return accept_follow_for_relay if relay_follow? return accept_follow!(follow_request_from_object) unless follow_request_from_object.nil? + return accept_quote!(quote_request_from_object) unless quote_request_from_object.nil? case @object['type'] when 'Follow' accept_embedded_follow + when 'QuoteRequest' + accept_embedded_quote_request end end @@ -31,6 +34,29 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity RemoteAccountRefreshWorker.perform_async(request.target_account_id) if is_first_follow end + def accept_embedded_quote_request + approval_uri = value_or_id(first_of_value(@json['result'])) + return if approval_uri.nil? + + quote = quote_from_request_json(@object) + return unless quote.present? && quote.status.local? + + accept_quote!(quote) + end + + def accept_quote!(quote) + approval_uri = value_or_id(first_of_value(@json['result'])) + return if unsupported_uri_scheme?(approval_uri) || quote.quoted_account != @account || !quote.status.local? + + # NOTE: we are not going through `ActivityPub::VerifyQuoteService` as the `Accept` is as authoritative + # as the stamp, but this means we are not checking the stamp, which may lead to inconsistencies + # in case of an implementation bug + quote.update!(state: :accepted, approval_uri: approval_uri) + + DistributionWorker.perform_async(quote.status_id, { 'update' => true }) + ActivityPub::StatusUpdateDistributionWorker.perform_async(quote.status_id, { 'updated_at' => Time.now.utc.iso8601 }) + end + def accept_follow_for_relay relay.update!(state: :accepted) end diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb index 886dddb2355..3dafaba1882 100644 --- a/app/lib/activitypub/activity/reject.rb +++ b/app/lib/activitypub/activity/reject.rb @@ -5,10 +5,13 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity return reject_follow_for_relay if relay_follow? return follow_request_from_object.reject! unless follow_request_from_object.nil? return UnfollowService.new.call(follow_from_object.account, @account) unless follow_from_object.nil? + return reject_quote!(quote_request_from_object) unless quote_request_from_object.nil? case @object['type'] when 'Follow' reject_embedded_follow + when 'QuoteRequest' + reject_embedded_quote_request end end @@ -29,6 +32,20 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity relay.update!(state: :rejected) end + def reject_embedded_quote_request + quote = quote_from_request_json(@object) + return unless quote.present? && quote.status.local? + + reject_quote!(quoting_status.quote) + end + + def reject_quote!(quote) + return unless quote.quoted_account == @account && quote.status.local? + + # TODO: broadcast an update? + quote.reject! + end + def relay @relay ||= Relay.find_by(follow_activity_id: object_uri) unless object_uri.nil? end diff --git a/app/lib/activitypub/case_transform.rb b/app/lib/activitypub/case_transform.rb index bf5de722103..b9e1d3a62b2 100644 --- a/app/lib/activitypub/case_transform.rb +++ b/app/lib/activitypub/case_transform.rb @@ -12,9 +12,7 @@ module ActivityPub::CaseTransform when Hash then value.deep_transform_keys! { |key| camel_lower(key) } when Symbol then camel_lower(value.to_s).to_sym when String - camel_lower_cache[value] ||= if value.start_with?('_:') - "_:#{value.delete_prefix('_:').underscore.camelize(:lower)}" - elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) + camel_lower_cache[value] ||= if value.start_with?('_misskey') || LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) value else value.underscore.camelize(:lower) diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index 674945c4039..5260a723b31 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -71,6 +71,8 @@ class StatusCacheHydrator payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id) payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id payload[:filtered] = mapped_applied_custom_filter(account_id, status) + # TODO: performance optimization by not loading `Account` twice + payload[:quote_approval][:current_user] = status.quote_policy_for_account(Account.find_by(id: account_id)) if payload[:quote_approval] payload[:quote] = hydrate_quote_payload(payload[:quote], status.quote, account_id, nested:) if payload[:quote] end diff --git a/app/models/concerns/status/interaction_policy_concern.rb b/app/models/concerns/status/interaction_policy_concern.rb new file mode 100644 index 00000000000..7e7642209db --- /dev/null +++ b/app/models/concerns/status/interaction_policy_concern.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Status::InteractionPolicyConcern + extend ActiveSupport::Concern + + QUOTE_APPROVAL_POLICY_FLAGS = { + unknown: (1 << 0), + public: (1 << 1), + followers: (1 << 2), + followed: (1 << 3), + }.freeze + + def quote_policy_as_keys(kind) + case kind + when :automatic + policy = quote_approval_policy >> 16 + when :manual + policy = quote_approval_policy & 0xFFFF + end + + QUOTE_APPROVAL_POLICY_FLAGS.keys.select { |key| policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[key]) }.map(&:to_s) + end + + # Returns `:automatic`, `:manual`, `:unknown` or `:denied` + def quote_policy_for_account(other_account, preloaded_relations: {}) + return :denied if other_account.nil? + + following_author = nil + + # Post author is always allowed to quote themselves + return :automatic if account_id == other_account.id + + automatic_policy = quote_approval_policy >> 16 + manual_policy = quote_approval_policy & 0xFFFF + + # Checking for public policy first because it's less expensive than looking at mentions + return :automatic if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:public]) + + # Mentioned users are always allowed to quote + if active_mentions.loaded? + return :automatic if active_mentions.any? { |mention| mention.account_id == other_account.id } + elsif active_mentions.exists?(account: other_account) + return :automatic + end + + if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:followers]) + following_author = preloaded_relations[:following] ? preloaded_relations[:following][account_id] : other_account.following?(account) if following_author.nil? + return :automatic if following_author + end + + # We don't know we are allowed by the automatic policy, considering the manual one + return :manual if manual_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:public]) + + if manual_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:followers]) + following_author = preloaded_relations[:following] ? preloaded_relations[:following][account_id] : other_account.following?(account) if following_author.nil? + return :manual if following_author + end + + return :unknown if (automatic_policy | manual_policy).anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:unknown]) + + :denied + end +end diff --git a/app/models/status.rb b/app/models/status.rb index e6e9450264b..51150bec49e 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -43,16 +43,10 @@ class Status < ApplicationRecord include Status::SnapshotConcern include Status::ThreadingConcern include Status::Visibility + include Status::InteractionPolicyConcern MEDIA_ATTACHMENTS_LIMIT = 4 - QUOTE_APPROVAL_POLICY_FLAGS = { - unknown: (1 << 0), - public: (1 << 1), - followers: (1 << 2), - followed: (1 << 3), - }.freeze - rate_limit by: :account, family: :statuses self.discard_column = :deleted_at diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 540e266427f..d9bb7201c00 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -19,6 +19,11 @@ class StatusPolicy < ApplicationPolicy end end + # This is about requesting a quote post, not validating it + def quote? + record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied + end + def reblog? !requires_mention? && (!private? || owned?) && show? && !blocking_author? end diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 7b29e6d69be..95a869658c3 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,7 +3,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer include FormattingHelper - context_extensions :atom_uri, :conversation, :sensitive, :voters_count + context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :quotes attributes :id, :type, :summary, :in_reply_to, :published, :url, @@ -30,6 +30,11 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer attribute :voters_count, if: :poll_and_voters_count? + attribute :quote, if: :quote? + attribute :quote, key: :_misskey_quote, if: :quote? + attribute :quote, key: :quote_uri, if: :quote? + attribute :quote_authorization, if: :quote_authorization? + def id ActivityPub::TagManager.instance.uri_for(object) end @@ -194,6 +199,24 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer object.preloadable_poll&.voters_count end + def quote? + object.quote&.present? + end + + def quote_authorization? + object.quote&.approval_uri.present? + end + + def quote + # TODO: handle inlining self-quotes + ActivityPub::TagManager.instance.uri_for(object.quote.quoted_status) + end + + def quote_authorization + # TODO: approval of local quotes may work differently, perhaps? + object.quote.approval_uri + end + class MediaAttachmentSerializer < ActivityPub::Serializer context_extensions :blurhash, :focal_point diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 29e77e7d5b1..4ade8132111 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -32,6 +32,7 @@ class REST::StatusSerializer < ActiveModel::Serializer has_one :quote, key: :quote, serializer: REST::QuoteSerializer has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer + has_one :quote_approval, if: -> { Mastodon::Feature.outgoing_quotes_enabled? } def quote object.quote if object.quote&.acceptable? @@ -159,6 +160,14 @@ class REST::StatusSerializer < ActiveModel::Serializer object.active_mentions.to_a.sort_by(&:id) end + def quote_approval + { + automatic: object.quote_policy_as_keys(:automatic), + manual: object.quote_policy_as_keys(:manual), + current_user: object.quote_policy_for_account(current_user&.account), + } + end + private def relationships diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index ac4b535ea9d..73e78f00478 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -96,13 +96,11 @@ class PostStatusService < BaseService # NOTE: for now this is only for convenience in testing, as we don't support the request flow nor serialize quotes in ActivityPub # we only support incoming quotes so far - status.quote = Quote.new(quoted_status: @quoted_status) - status.quote.accept! if @status.account == @quoted_status.account || @quoted_status.active_mentions.exists?(mentions: { account_id: status.account_id }) - - # TODO: the following has yet to be implemented: - # - handle approval of local users (requires the interactionPolicy PR) - # - produce a QuoteAuthorization for quotes of local users - # - send a QuoteRequest for quotes of remote users + status.quote = Quote.create(quoted_status: @quoted_status, status: status) + if @quoted_status.local? && StatusPolicy.new(@status.account, @quoted_status).quote? + # TODO: produce a QuoteAuthorization + status.quote.accept! + end end def safeguard_mentions!(status) @@ -146,6 +144,7 @@ class PostStatusService < BaseService DistributionWorker.perform_async(@status.id) ActivityPub::DistributionWorker.perform_async(@status.id) PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll + ActivityPub::QuoteRequestWorker.perform_async(@status.quote.id) if @status.quote&.quoted_status.present? && !@status.quote&.quoted_status&.local? end def validate_media! diff --git a/app/workers/activitypub/quote_request_worker.rb b/app/workers/activitypub/quote_request_worker.rb new file mode 100644 index 00000000000..de5e054f69f --- /dev/null +++ b/app/workers/activitypub/quote_request_worker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ActivityPub::QuoteRequestWorker < ActivityPub::RawDistributionWorker + def perform(quote_id) + @quote = Quote.find(quote_id) + @account = @quote.account + + distribute! + rescue ActiveRecord::RecordNotFound + true + end + + protected + + def inboxes + @inboxes ||= [@quote.quoted_account&.inbox_url].compact + end + + def payload + @payload ||= Oj.dump(serialize_payload(@quote, ActivityPub::QuoteRequestSerializer, signer: @account)) + end +end diff --git a/app/workers/activitypub/status_update_distribution_worker.rb b/app/workers/activitypub/status_update_distribution_worker.rb index a79ede2bf61..7f70fcaecc6 100644 --- a/app/workers/activitypub/status_update_distribution_worker.rb +++ b/app/workers/activitypub/status_update_distribution_worker.rb @@ -17,10 +17,10 @@ class ActivityPub::StatusUpdateDistributionWorker < ActivityPub::DistributionWor def activity ActivityPub::ActivityPresenter.new( - id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @status.edited_at.to_i].join, + id: [ActivityPub::TagManager.instance.uri_for(@status), '#updates/', @options[:updated_at]&.to_datetime&.to_i || @status.edited_at.to_i].join, type: 'Update', actor: ActivityPub::TagManager.instance.uri_for(@status.account), - published: @status.edited_at, + published: @options[:updated_at]&.to_datetime || @status.edited_at, to: ActivityPub::TagManager.instance.to(@status), cc: ActivityPub::TagManager.instance.cc(@status), virtual_object: @status diff --git a/config/locales/en.yml b/config/locales/en.yml index 4df63f4c738..204340f504c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1873,6 +1873,7 @@ en: edited_at_html: Edited %{date} errors: in_reply_not_found: The post you are trying to reply to does not appear to exist. + quoted_status_not_found: The post you are trying to quote does not appear to exist. over_character_limit: character limit of %{max} exceeded pin_errors: direct: Posts that are only visible to mentioned users cannot be pinned diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb index 6d7c05e6165..615287389c3 100644 --- a/spec/lib/activitypub/activity/accept_spec.rb +++ b/spec/lib/activitypub/activity/accept_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe ActivityPub::Activity::Accept do - let(:sender) { Fabricate(:account) } + let(:sender) { Fabricate(:account, domain: 'example.com') } let(:recipient) { Fabricate(:account) } describe '#perform' do @@ -48,5 +48,128 @@ RSpec.describe ActivityPub::Activity::Accept do end end end + + context 'with a QuoteRequest' do + let(:status) { Fabricate(:status, account: recipient) } + let(:quoted_status) { Fabricate(:status, account: sender) } + let(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status) } + let(:approval_uri) { "https://#{sender.domain}/approvals/1" } + + let(:json) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteRequest: 'https://w3id.org/fep/044f#QuoteRequest', + }, + ], + id: 'foo', + type: 'Accept', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: { + id: quote.activity_uri, + type: 'QuoteRequest', + actor: ActivityPub::TagManager.instance.uri_for(recipient), + object: ActivityPub::TagManager.instance.uri_for(quoted_status), + instrument: ActivityPub::TagManager.instance.uri_for(status), + }, + result: approval_uri, + }.with_indifferent_access + end + + it 'marks the quote as approved and distribute an update' do + expect { subject.perform } + .to change { quote.reload.accepted? }.from(false).to(true) + .and change { quote.reload.approval_uri }.to(approval_uri) + expect(DistributionWorker) + .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to have_enqueued_sidekiq_job(status.id, { 'updated_at' => be_a(String) }) + end + + context 'when the quoted status is not from the sender of the Accept' do + let(:quoted_status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com')) } + + it 'does not mark the quote as approved and does not distribute an update' do + expect { subject.perform } + .to not_change { quote.reload.accepted? }.from(false) + .and not_change { quote.reload.approval_uri }.from(nil) + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, anything) + end + end + + context 'when the quoting status is from an unrelated user' do + let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'foobar.com')) } + + it 'does not mark the quote as approved and does not distribute an update' do + expect { subject.perform } + .to not_change { quote.reload.accepted? }.from(false) + .and not_change { quote.reload.approval_uri }.from(nil) + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, anything) + end + end + + context 'when approval_uri is missing' do + let(:approval_uri) { nil } + + it 'does not mark the quote as approved and does not distribute an update' do + expect { subject.perform } + .to not_change { quote.reload.accepted? }.from(false) + .and not_change { quote.reload.approval_uri }.from(nil) + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, anything) + end + end + + context 'when the QuoteRequest is referenced by its identifier' do + let(:json) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + QuoteRequest: 'https://w3id.org/fep/044f#QuoteRequest', + }, + ], + id: 'foo', + type: 'Accept', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: quote.activity_uri, + result: approval_uri, + }.with_indifferent_access + end + + it 'marks the quote as approved and distribute an update' do + expect { subject.perform } + .to change { quote.reload.accepted? }.from(false).to(true) + .and change { quote.reload.approval_uri }.to(approval_uri) + expect(DistributionWorker) + .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to have_enqueued_sidekiq_job(status.id, { 'updated_at' => be_a(String) }) + end + + context 'when approval_uri is missing' do + let(:approval_uri) { nil } + + it 'does not mark the quote as approved and does not distribute an update' do + expect { subject.perform } + .to not_change { quote.reload.accepted? }.from(false) + .and not_change { quote.reload.approval_uri }.from(nil) + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job(status.id, anything) + end + end + end + end end end diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb index 1afb0cd4033..ee8557f1239 100644 --- a/spec/lib/activitypub/activity/reject_spec.rb +++ b/spec/lib/activitypub/activity/reject_spec.rb @@ -125,5 +125,27 @@ RSpec.describe ActivityPub::Activity::Reject do expect(relay.reload.rejected?).to be true end end + + context 'with a QuoteRequest' do + let(:status) { Fabricate(:status, account: recipient) } + let(:quoted_status) { Fabricate(:status, account: sender) } + let(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, activity_uri: 'https://abc-123/456') } + let(:approval_uri) { "https://#{sender.domain}/approvals/1" } + + let(:object_json) do + { + id: 'https://abc-123/456', + type: 'QuoteRequest', + actor: ActivityPub::TagManager.instance.uri_for(recipient), + object: ActivityPub::TagManager.instance.uri_for(quoted_status), + instrument: ActivityPub::TagManager.instance.uri_for(status), + }.with_indifferent_access + end + + it 'marks the quote as rejected' do + expect { subject.perform } + .to change { quote.reload.rejected? }.from(false).to(true) + end + end end end diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index e56393da1dd..f450997976a 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -28,6 +28,18 @@ RSpec.describe StatusCacheHydrator do end end + context 'when handling a status with a quote policy', feature: :outgoing_quotes do + let(:status) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } + + before do + account.follow!(status.account) + end + + it 'renders the same attributes as a full render' do + expect(subject).to eql(compare_to_hash) + end + end + context 'when handling a filtered status' do let(:status) { Fabricate(:status, text: 'this toot is about that banned word') } diff --git a/spec/models/concerns/status/interaction_policy_concern_spec.rb b/spec/models/concerns/status/interaction_policy_concern_spec.rb new file mode 100644 index 00000000000..af42f2bba3d --- /dev/null +++ b/spec/models/concerns/status/interaction_policy_concern_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Status::InteractionPolicyConcern do + let(:status) { Fabricate(:status, quote_approval_policy: (0b0101 << 16) | 0b0010) } + + describe '#quote_policy_as_keys' do + it 'returns the expected values' do + expect(status.quote_policy_as_keys(:automatic)).to eq ['unknown', 'followers'] + expect(status.quote_policy_as_keys(:manual)).to eq ['public'] + end + end + + describe '#quote_policy_for_account' do + let(:account) { Fabricate(:account) } + + context 'when the account is not following the user' do + it 'returns :manual because of the public entry in the manual policy' do + expect(status.quote_policy_for_account(account)).to eq :manual + end + end + + context 'when the account is following the user' do + before do + account.follow!(status.account) + end + + it 'returns :automatic because of the followers entry in the automatic policy' do + expect(status.quote_policy_for_account(account)).to eq :automatic + end + end + + context 'when the account falls into the unknown bucket' do + let(:status) { Fabricate(:status, quote_approval_policy: (0b0001 << 16) | 0b0100) } + + it 'returns :automatic because of the followers entry in the automatic policy' do + expect(status.quote_policy_for_account(account)).to eq :unknown + end + end + end +end diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 69c0bad026b..63622970455 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -86,6 +86,92 @@ RSpec.describe StatusPolicy, type: :model do end end + context 'with the permission of quote?' do + permissions :quote? do + it 'grants access when direct and account is viewer' do + status.visibility = :direct + + expect(subject).to permit(status.account, status) + end + + it 'grants access when direct and viewer is mentioned' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: alice)] + + expect(subject).to permit(alice, status) + end + + it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: bob)] + status.active_mentions.load + + expect(subject).to permit(bob, status) + end + + it 'denies access when direct and viewer is not mentioned' do + viewer = Fabricate(:account) + status.visibility = :direct + + expect(subject).to_not permit(viewer, status) + end + + it 'denies access when private and viewer is not mentioned' do + viewer = Fabricate(:account) + status.visibility = :private + + expect(subject).to_not permit(viewer, status) + end + + it 'grants access when private and viewer is mentioned' do + status.visibility = :private + status.mentions = [Fabricate(:mention, account: bob)] + + expect(subject).to permit(bob, status) + end + + it 'denies access when private and non-viewer is mentioned' do + viewer = Fabricate(:account) + status.visibility = :private + status.mentions = [Fabricate(:mention, account: bob)] + + expect(subject).to_not permit(viewer, status) + end + + it 'denies access when private and account is following viewer' do + follow = Fabricate(:follow) + status.visibility = :private + status.account = follow.target_account + + expect(subject).to_not permit(follow.account, status) + end + + it 'denies access when public but policy does not allow anyone' do + viewer = Fabricate(:account) + expect(subject).to_not permit(viewer, status) + end + + it 'grants access when public and policy allows everyone' do + status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] + viewer = Fabricate(:account) + expect(subject).to permit(viewer, status) + end + + it 'denies access when public and policy allows followers but viewer is not one' do + status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] + viewer = Fabricate(:account) + expect(subject).to_not permit(viewer, status) + end + + it 'grants access when public and policy allows followers and viewer is one' do + status.quote_approval_policy = Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] + viewer = Fabricate(:account) + viewer.follow!(status.account) + expect(subject).to permit(viewer, status) + end + end + end + context 'with the permission of reblog?' do permissions :reblog? do it 'denies access when private' do diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index 285fa936552..ac15ae24623 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -158,6 +158,27 @@ RSpec.describe '/api/v1/statuses' do end end + context 'with a self-quote post', feature: :outgoing_quotes do + let(:quoted_status) { Fabricate(:status, account: user.account) } + let(:params) do + { + status: 'Hello world, this is a self-quote', + quoted_status_id: quoted_status.id, + } + end + + it 'returns a quote post, as well as rate limit headers', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:quote]).to be_present + expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s + expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s + end + end + context 'with a safeguard' do let!(:alice) { Fabricate(:account, username: 'alice') } let!(:bob) { Fabricate(:account, username: 'bob') } diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index a6976193b20..d1af3f068f5 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -41,4 +41,20 @@ RSpec.describe ActivityPub::NoteSerializer do .and(not_include(reply_by_other_first.uri)) # Replies from others .and(not_include(reply_by_account_visibility_direct.uri)) # Replies with direct visibility end + + context 'with a quote' do + let(:quoted_status) { Fabricate(:status) } + let(:approval_uri) { 'https://example.com/foo/bar' } + let!(:quote) { Fabricate(:quote, status: parent, quoted_status: quoted_status, approval_uri: approval_uri) } + + it 'has the expected shape' do + expect(subject).to include({ + 'type' => 'Note', + 'quote' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), + 'quoteUri' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), + '_misskey_quote' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), + 'quoteAuthorization' => approval_uri, + }) + end + end end diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 8836b9e0a63..7e47506a9f7 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -291,6 +291,14 @@ RSpec.describe PostStatusService do ) end + it 'correctly requests a quote for remote posts' do + account = Fabricate(:account) + quoted_status = Fabricate(:status, account: Fabricate(:account, domain: 'example.com')) + + expect { subject.call(account, text: 'test', quoted_status: quoted_status) } + .to enqueue_sidekiq_job(ActivityPub::QuoteRequestWorker) + end + it 'returns existing status when used twice with idempotency key' do account = Fabricate(:account) status1 = subject.call(account, text: 'test', idempotency: 'meepmeep') diff --git a/spec/workers/activitypub/quote_request_worker_spec.rb b/spec/workers/activitypub/quote_request_worker_spec.rb new file mode 100644 index 00000000000..3d0131baaad --- /dev/null +++ b/spec/workers/activitypub/quote_request_worker_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::QuoteRequestWorker do + subject { described_class.new } + + let(:quoted_account) { Fabricate(:account, inbox_url: 'http://example.com', domain: 'example.com') } + let(:quoted_status) { Fabricate(:status, account: quoted_account) } + let(:status) { Fabricate(:status, text: 'foo') } + let(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, activity_uri: 'TODO') } # TODO: activity URI + + describe '#perform' do + it 'sends the expected QuoteRequest activity' do + subject.perform(quote.id) + + expect(ActivityPub::DeliveryWorker) + .to have_enqueued_sidekiq_job(match_object_shape, quote.account_id, 'http://example.com', {}) + end + + def match_object_shape + match_json_values( + type: 'QuoteRequest', + actor: ActivityPub::TagManager.instance.uri_for(quote.account), + object: ActivityPub::TagManager.instance.uri_for(quoted_status), + instrument: anything # TODO: inline post in request? + ) + end + end +end diff --git a/spec/workers/activitypub/status_update_distribution_worker_spec.rb b/spec/workers/activitypub/status_update_distribution_worker_spec.rb index e9a70d11d19..58d11db41cb 100644 --- a/spec/workers/activitypub/status_update_distribution_worker_spec.rb +++ b/spec/workers/activitypub/status_update_distribution_worker_spec.rb @@ -9,36 +9,64 @@ RSpec.describe ActivityPub::StatusUpdateDistributionWorker do let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } describe '#perform' do - before do - follower.follow!(status.account) - - status.snapshot! - status.text = 'bar' - status.edited_at = Time.now.utc - status.snapshot! - status.save! - end - - context 'with public status' do + context 'with an explicitly edited status' do before do - status.update(visibility: :public) + follower.follow!(status.account) + + status.snapshot! + status.text = 'bar' + status.edited_at = Time.now.utc + status.snapshot! + status.save! end - it 'delivers to followers' do - expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(type: 'Update'), status.account.id, 'http://example.com', anything]]) do - subject.perform(status.id) + context 'with public status' do + before do + status.update(visibility: :public) + end + + it 'delivers to followers' do + expect { subject.perform(status.id) } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Update'), status.account_id, 'http://example.com', anything) + end + end + + context 'with private status' do + before do + status.update(visibility: :private) + end + + it 'delivers to followers' do + expect { subject.perform(status.id) } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Update'), status.account_id, 'http://example.com', anything) end end end - context 'with private status' do + context 'with an implicitly edited status' do before do - status.update(visibility: :private) + follower.follow!(status.account) end - it 'delivers to followers' do - expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [[match_json_values(type: 'Update'), status.account.id, 'http://example.com', anything]]) do - subject.perform(status.id) + context 'with public status' do + before do + status.update(visibility: :public) + end + + it 'delivers to followers' do + expect { subject.perform(status.id, { 'updated_at' => Time.now.utc.iso8601 }) } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Update'), status.account_id, 'http://example.com', anything) + end + end + + context 'with private status' do + before do + status.update(visibility: :private) + end + + it 'delivers to followers' do + expect { subject.perform(status.id, { 'updated_at' => Time.now.utc.iso8601 }) } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(match_json_values(type: 'Update'), status.account_id, 'http://example.com', anything) end end end From e93efe0e131481635e88d7ad114ef66148626f90 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 25 Jul 2025 18:38:21 +0200 Subject: [PATCH 128/660] Fix unnecessary duplication in vite code for finding entrypoints (#35515) --- vite.config.mts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vite.config.mts b/vite.config.mts index c8e399e365a..30c0741aaa6 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -189,11 +189,11 @@ async function findEntrypoints() { const entrypoints: Record = {}; // First, JS entrypoints - const jsEntrypoints = await readdir(path.resolve(jsRoot, 'entrypoints'), { + const jsEntrypointsDir = path.resolve(jsRoot, 'entrypoints'); + const jsEntrypoints = await readdir(jsEntrypointsDir, { withFileTypes: true, }); const jsExtTest = /\.[jt]sx?$/; - const jsEntrypointsDir = path.resolve(jsRoot, 'entrypoints'); for (const file of jsEntrypoints) { if (file.isFile() && jsExtTest.test(file.name)) { entrypoints[file.name.replace(jsExtTest, '')] = path.resolve( @@ -204,12 +204,11 @@ async function findEntrypoints() { } // Next, SCSS entrypoints - const scssEntrypoints = await readdir( - path.resolve(jsRoot, 'styles/entrypoints'), - { withFileTypes: true }, - ); - const scssExtTest = /\.s?css$/; const scssEntrypointsDir = path.resolve(jsRoot, 'styles/entrypoints'); + const scssEntrypoints = await readdir(scssEntrypointsDir, { + withFileTypes: true, + }); + const scssExtTest = /\.s?css$/; for (const file of scssEntrypoints) { if (file.isFile() && scssExtTest.test(file.name)) { entrypoints[file.name.replace(scssExtTest, '')] = path.resolve( From f3786e08165e304efab23579832e5b5159ce33f8 Mon Sep 17 00:00:00 2001 From: william light Date: Sat, 26 Jul 2025 07:51:50 +0200 Subject: [PATCH 129/660] hotkeys: only match `just()` when no modifiers are held (#35535) --- app/javascript/mastodon/components/hotkeys/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/hotkeys/index.tsx b/app/javascript/mastodon/components/hotkeys/index.tsx index b5e0de4c594..7e840ab9558 100644 --- a/app/javascript/mastodon/components/hotkeys/index.tsx +++ b/app/javascript/mastodon/components/hotkeys/index.tsx @@ -40,7 +40,11 @@ type KeyMatcher = ( */ function just(keyName: string): KeyMatcher { return (event) => ({ - isMatch: normalizeKey(event.key) === keyName, + isMatch: + normalizeKey(event.key) === keyName && + !event.altKey && + !event.ctrlKey && + !event.metaKey, priority: hotkeyPriority.singleKey, }); } From 9d0d6f011c9760a9d166aa0f412825124e0a7045 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 03:59:32 -0400 Subject: [PATCH 130/660] Add coverage for `AnnouncementReaction` model (#35543) --- app/models/announcement_reaction.rb | 4 +- spec/models/announcement_reaction_spec.rb | 63 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 spec/models/announcement_reaction_spec.rb diff --git a/app/models/announcement_reaction.rb b/app/models/announcement_reaction.rb index 46d9fc290ff..855dfc9e3a6 100644 --- a/app/models/announcement_reaction.rb +++ b/app/models/announcement_reaction.rb @@ -14,7 +14,7 @@ # class AnnouncementReaction < ApplicationRecord - before_validation :set_custom_emoji + before_validation :set_custom_emoji, if: :name? after_commit :queue_publish belongs_to :account @@ -27,7 +27,7 @@ class AnnouncementReaction < ApplicationRecord private def set_custom_emoji - self.custom_emoji = CustomEmoji.local.enabled.find_by(shortcode: name) if name.present? + self.custom_emoji = CustomEmoji.local.enabled.find_by(shortcode: name) end def queue_publish diff --git a/spec/models/announcement_reaction_spec.rb b/spec/models/announcement_reaction_spec.rb new file mode 100644 index 00000000000..e02a8dcd069 --- /dev/null +++ b/spec/models/announcement_reaction_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AnnouncementReaction do + describe 'Associations' do + it { is_expected.to belong_to(:account) } + it { is_expected.to belong_to(:announcement).inverse_of(:announcement_reactions) } + it { is_expected.to belong_to(:custom_emoji).optional } + end + + describe 'Validations' do + subject { Fabricate.build :announcement_reaction } + + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to allow_values('😀').for(:name) } + it { is_expected.to_not allow_values('INVALID').for(:name) } + + context 'when reaction limit is reached' do + subject { Fabricate.build :announcement_reaction, announcement: announcement_reaction.announcement } + + let(:announcement_reaction) { Fabricate :announcement_reaction, name: '😊' } + + before { stub_const 'ReactionValidator::LIMIT', 1 } + + it { is_expected.to_not allow_values('😀').for(:name).against(:base) } + end + end + + describe 'Callbacks' do + describe 'Setting custom emoji association' do + subject { Fabricate.build :announcement_reaction, name: } + + context 'when name is missing' do + let(:name) { '' } + + it 'does not set association' do + expect { subject.valid? } + .to not_change(subject, :custom_emoji).from(be_blank) + end + end + + context 'when name matches a custom emoji shortcode' do + let(:name) { 'custom' } + let!(:custom_emoji) { Fabricate :custom_emoji, shortcode: 'custom' } + + it 'sets association' do + expect { subject.valid? } + .to change(subject, :custom_emoji).from(be_blank).to(custom_emoji) + end + end + + context 'when name does not match a custom emoji' do + let(:name) { 'custom' } + + it 'does not set association' do + expect { subject.valid? } + .to not_change(subject, :custom_emoji).from(be_blank) + end + end + end + end +end From b464b87c2be774ce2972b217ae791366ed35bfbc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:02:21 -0400 Subject: [PATCH 131/660] Use `moved?` query method where relevant (#35542) --- app/controllers/settings/migration/redirects_controller.rb | 2 +- app/models/account_migration.rb | 2 +- app/models/form/redirect.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb index d850e05e94f..08b01d6b108 100644 --- a/app/controllers/settings/migration/redirects_controller.rb +++ b/app/controllers/settings/migration/redirects_controller.rb @@ -22,7 +22,7 @@ class Settings::Migration::RedirectsController < Settings::BaseController end def destroy - if current_account.moved_to_account_id.present? + if current_account.moved? current_account.update!(moved_to_account: nil) ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) end diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb index 7bda388f2a8..6cdc7128ef4 100644 --- a/app/models/account_migration.rb +++ b/app/models/account_migration.rb @@ -74,7 +74,7 @@ class AccountMigration < ApplicationRecord errors.add(:acct, I18n.t('migrations.errors.not_found')) else errors.add(:acct, I18n.t('migrations.errors.missing_also_known_as')) unless target_account.also_known_as.include?(ActivityPub::TagManager.instance.uri_for(account)) - errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id + errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved? && account.moved_to_account_id == target_account.id errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id end end diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb index c5b3c1f8f39..6ab95f21f1f 100644 --- a/app/models/form/redirect.rb +++ b/app/models/form/redirect.rb @@ -40,7 +40,7 @@ class Form::Redirect if target_account.nil? errors.add(:acct, I18n.t('migrations.errors.not_found')) else - errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id + errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved? && account.moved_to_account_id == target_account.id errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id end end From 5d69157e629de797ab7bff53bfc91b4c762205eb Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:03:23 -0400 Subject: [PATCH 132/660] Prefer delegated nil-wrapping methods to safe navigation (#35541) --- app/views/admin/accounts/_counters.html.haml | 4 ++-- app/views/admin/accounts/_local_account.html.haml | 2 +- app/views/admin/reports/index.html.haml | 2 +- app/workers/move_worker.rb | 4 ++-- app/workers/publish_scheduled_status_worker.rb | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/admin/accounts/_counters.html.haml b/app/views/admin/accounts/_counters.html.haml index 00ab98d094a..3c99da9f2c8 100644 --- a/app/views/admin/accounts/_counters.html.haml +++ b/app/views/admin/accounts/_counters.html.haml @@ -30,9 +30,9 @@ = t('admin.accounts.suspended') - elsif account.silenced? = t('admin.accounts.silenced') - - elsif account.local? && account.user&.disabled? + - elsif account.local? && account.user_disabled? = t('admin.accounts.disabled') - - elsif account.local? && !account.user&.confirmed? + - elsif account.local? && !account.user_confirmed? = t('admin.accounts.confirming') - elsif account.local? && !account.user_approved? = t('admin.accounts.pending') diff --git a/app/views/admin/accounts/_local_account.html.haml b/app/views/admin/accounts/_local_account.html.haml index 892afcc5428..bff752332c6 100644 --- a/app/views/admin/accounts/_local_account.html.haml +++ b/app/views/admin/accounts/_local_account.html.haml @@ -34,7 +34,7 @@ %tr %th= t('admin.accounts.email_status') %td - - if account.user&.confirmed? + - if account.user_confirmed? = t('admin.accounts.confirmed') - else = t('admin.accounts.confirming') diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index 6f762d94ebf..44664b85fd9 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -42,7 +42,7 @@ %span.red= t('admin.accounts.suspended') - elsif target_account.silenced? %span.red= t('admin.accounts.silenced') - - elsif target_account.user&.disabled? + - elsif target_account.user_disabled? %span.red= t('admin.accounts.disabled') - else %span.neutral= t('admin.accounts.no_limits_imposed') diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index a18f38556bb..9b9c6879e5c 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -81,7 +81,7 @@ class MoveWorker def copy_account_notes! AccountNote.where(target_account: @source_account).find_each do |note| - text = I18n.with_locale(note.account.user&.locale.presence || I18n.default_locale) do + text = I18n.with_locale(note.account.user_locale.presence || I18n.default_locale) do I18n.t('move_handler.copy_account_note_text', acct: @source_account.acct) end @@ -124,7 +124,7 @@ class MoveWorker def add_account_note_if_needed!(account, id) unless AccountNote.exists?(account: account, target_account: @target_account) - text = I18n.with_locale(account.user&.locale.presence || I18n.default_locale) do + text = I18n.with_locale(account.user_locale.presence || I18n.default_locale) do I18n.t(id, acct: @source_account.acct) end AccountNote.create!(account: account, target_account: @target_account, comment: text) diff --git a/app/workers/publish_scheduled_status_worker.rb b/app/workers/publish_scheduled_status_worker.rb index 0ec081de917..bcf20b49431 100644 --- a/app/workers/publish_scheduled_status_worker.rb +++ b/app/workers/publish_scheduled_status_worker.rb @@ -9,7 +9,7 @@ class PublishScheduledStatusWorker scheduled_status = ScheduledStatus.find(scheduled_status_id) scheduled_status.destroy! - return true if scheduled_status.account.user.disabled? + return true if scheduled_status.account.user_disabled? PostStatusService.new.call( scheduled_status.account, From 73f72ec8fe1ae19b22f5a1d9cfcb3b44a6c124a0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:08:31 -0400 Subject: [PATCH 133/660] Use `attribute` for defining `rate_limit` boolean (#35555) --- app/models/concerns/rate_limitable.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/concerns/rate_limitable.rb b/app/models/concerns/rate_limitable.rb index ad1b5e44e36..c6b5d3e0844 100644 --- a/app/models/concerns/rate_limitable.rb +++ b/app/models/concerns/rate_limitable.rb @@ -3,12 +3,8 @@ module RateLimitable extend ActiveSupport::Concern - def rate_limit=(value) - @rate_limit = value - end - - def rate_limit? - @rate_limit + included do + attribute :rate_limit, :boolean, default: false end def rate_limiter(by, options = {}) From 720ee969692302b8020c5b7da393aff0c687c093 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:14:06 -0400 Subject: [PATCH 134/660] Move repeated snowflake ID gather to base classes (#35554) --- app/lib/admin/metrics/dimension/base_dimension.rb | 12 ++++++++++++ .../dimension/instance_languages_dimension.rb | 10 +--------- app/lib/admin/metrics/dimension/servers_dimension.rb | 10 +--------- .../metrics/dimension/tag_languages_dimension.rb | 10 +--------- .../admin/metrics/dimension/tag_servers_dimension.rb | 10 +--------- app/lib/admin/metrics/measure/base_measure.rb | 12 ++++++++++++ .../metrics/measure/instance_statuses_measure.rb | 10 +--------- app/lib/admin/metrics/measure/tag_servers_measure.rb | 10 +--------- 8 files changed, 30 insertions(+), 54 deletions(-) diff --git a/app/lib/admin/metrics/dimension/base_dimension.rb b/app/lib/admin/metrics/dimension/base_dimension.rb index bd2e4ececbe..0e055e0e75e 100644 --- a/app/lib/admin/metrics/dimension/base_dimension.rb +++ b/app/lib/admin/metrics/dimension/base_dimension.rb @@ -65,4 +65,16 @@ class Admin::Metrics::Dimension::BaseDimension def canonicalized_params params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';') end + + def earliest_status_id + snowflake_id(@start_at.beginning_of_day) + end + + def latest_status_id + snowflake_id(@end_at.end_of_day) + end + + def snowflake_id(datetime) + Mastodon::Snowflake.id_at(datetime, with_random: false) + end end diff --git a/app/lib/admin/metrics/dimension/instance_languages_dimension.rb b/app/lib/admin/metrics/dimension/instance_languages_dimension.rb index 661e6d93b75..5f4bb95cb8f 100644 --- a/app/lib/admin/metrics/dimension/instance_languages_dimension.rb +++ b/app/lib/admin/metrics/dimension/instance_languages_dimension.rb @@ -19,7 +19,7 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di end def sql_array - [sql_query_string, { domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + [sql_query_string, { domain: params[:domain], earliest_status_id:, latest_status_id:, limit: @limit }] end def sql_query_string @@ -36,14 +36,6 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di SQL end - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end - def params @params.permit(:domain) end diff --git a/app/lib/admin/metrics/dimension/servers_dimension.rb b/app/lib/admin/metrics/dimension/servers_dimension.rb index 2c8406d52fd..7e3ab603d03 100644 --- a/app/lib/admin/metrics/dimension/servers_dimension.rb +++ b/app/lib/admin/metrics/dimension/servers_dimension.rb @@ -14,7 +14,7 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B end def sql_array - [sql_query_string, { earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + [sql_query_string, { earliest_status_id:, latest_status_id:, limit: @limit }] end def sql_query_string @@ -28,12 +28,4 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B LIMIT :limit SQL end - - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end end diff --git a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb index 6e283d2c655..b7b9abc8b6f 100644 --- a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb +++ b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb @@ -19,7 +19,7 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi end def sql_array - [sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + [sql_query_string, { tag_id: tag_id, earliest_status_id:, latest_status_id:, limit: @limit }] end def sql_query_string @@ -39,14 +39,6 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi params[:id] end - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end - def params @params.permit(:id) end diff --git a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb index db820e965c5..29145e14871 100644 --- a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb +++ b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb @@ -18,7 +18,7 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension end def sql_array - [sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + [sql_query_string, { tag_id: tag_id, earliest_status_id:, latest_status_id:, limit: @limit }] end def sql_query_string @@ -39,14 +39,6 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension params[:id] end - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end - def params @params.permit(:id) end diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb index 8b7fe39b55a..eabbe0890b6 100644 --- a/app/lib/admin/metrics/measure/base_measure.rb +++ b/app/lib/admin/metrics/measure/base_measure.rb @@ -104,4 +104,16 @@ class Admin::Metrics::Measure::BaseMeasure def canonicalized_params params.to_h.to_a.sort_by { |k, _v| k.to_s }.map { |k, v| "#{k}=#{v}" }.join(';') end + + def earliest_status_id + snowflake_id(@start_at.beginning_of_day) + end + + def latest_status_id + snowflake_id(@end_at.end_of_day) + end + + def snowflake_id(datetime) + Mastodon::Snowflake.id_at(datetime, with_random: false) + end end diff --git a/app/lib/admin/metrics/measure/instance_statuses_measure.rb b/app/lib/admin/metrics/measure/instance_statuses_measure.rb index 324d427b18b..f0f797876e6 100644 --- a/app/lib/admin/metrics/measure/instance_statuses_measure.rb +++ b/app/lib/admin/metrics/measure/instance_statuses_measure.rb @@ -28,7 +28,7 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure end def sql_array - [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }] + [sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id:, latest_status_id: }] end def sql_query_string @@ -50,14 +50,6 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure SQL end - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb index 5db1076062b..e8d9cc43b8d 100644 --- a/app/lib/admin/metrics/measure/tag_servers_measure.rb +++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb @@ -22,7 +22,7 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base end def sql_array - [sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }] + [sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id:, latest_status_id: }] end def sql_query_string @@ -45,14 +45,6 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base SQL end - def earliest_status_id - Mastodon::Snowflake.id_at(@start_at.beginning_of_day, with_random: false) - end - - def latest_status_id - Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) - end - def tag @tag ||= Tag.find(params[:id]) end From a57a9505d40e3a8884b82f3d693a72e39a370bb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 08:14:11 +0000 Subject: [PATCH 135/660] New Crowdin Translations (automated) (#35537) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ar.json | 3 +- app/javascript/mastodon/locales/da.json | 8 +- app/javascript/mastodon/locales/de.json | 2 +- app/javascript/mastodon/locales/en-GB.json | 8 +- app/javascript/mastodon/locales/lt.json | 3 + app/javascript/mastodon/locales/nan.json | 2 + app/javascript/mastodon/locales/sk.json | 2 + app/javascript/mastodon/locales/tr.json | 2 + app/javascript/mastodon/locales/zh-CN.json | 11 +++ config/locales/br.yml | 2 + config/locales/ca.yml | 1 + config/locales/cs.yml | 1 + config/locales/cy.yml | 1 + config/locales/da.yml | 3 +- config/locales/de.yml | 1 + config/locales/el.yml | 1 + config/locales/es-AR.yml | 1 + config/locales/es-MX.yml | 1 + config/locales/es.yml | 1 + config/locales/fo.yml | 1 + config/locales/gl.yml | 1 + config/locales/he.yml | 3 +- config/locales/hu.yml | 1 + config/locales/is.yml | 1 + config/locales/lt.yml | 6 ++ config/locales/nan.yml | 101 ++++++++++++++++++++- config/locales/nl.yml | 1 + config/locales/pt-PT.yml | 1 + config/locales/ru.yml | 61 +++++++------ config/locales/simple_form.da.yml | 4 +- config/locales/simple_form.lt.yml | 7 ++ config/locales/tr.yml | 1 + config/locales/uk.yml | 1 + config/locales/vi.yml | 1 + config/locales/zh-TW.yml | 3 +- 35 files changed, 201 insertions(+), 47 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 24acd77e316..0443710638c 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -110,7 +110,7 @@ "announcement.announcement": "إعلان", "annual_report.summary.archetype.booster": "The cool-hunter", "annual_report.summary.archetype.lurker": "المتصفح الصامت", - "annual_report.summary.archetype.oracle": "حكيم", + "annual_report.summary.archetype.oracle": "الحكيم", "annual_report.summary.archetype.pollster": "مستطلع للرأي", "annual_report.summary.archetype.replier": "الفراشة الاجتماعية", "annual_report.summary.followers.followers": "المُتابِعُون", @@ -845,6 +845,7 @@ "status.bookmark": "أضفه إلى الفواصل المرجعية", "status.cancel_reblog_private": "إلغاء إعادة النشر", "status.cannot_reblog": "لا يمكن إعادة نشر هذا المنشور", + "status.context.load_new_replies": "الردود الجديدة المتاحة", "status.continued_thread": "تكملة للخيط", "status.copy": "انسخ رابط الرسالة", "status.delete": "احذف", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 1cd633c7476..15597e9a279 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -324,7 +324,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 hjemmetidslinje er tom! Følg nogle personer, for at fylde den op.", + "empty_column.home": "Din hjem-tidslinje er tom! Følg nogle 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.", @@ -476,7 +476,7 @@ "keyboard_shortcuts.favourites": "Åbn favoritlisten", "keyboard_shortcuts.federated": "Åbn fødereret tidslinje", "keyboard_shortcuts.heading": "Tastaturgenveje", - "keyboard_shortcuts.home": "Åbn hjemmetidslinje", + "keyboard_shortcuts.home": "Åbn hjem-tidslinje", "keyboard_shortcuts.hotkey": "Hurtigtast", "keyboard_shortcuts.legend": "Vis dette symbol", "keyboard_shortcuts.local": "Åbn lokal tidslinje", @@ -518,7 +518,7 @@ "lists.done": "Færdig", "lists.edit": "Redigér liste", "lists.exclusive": "Skjul medlemmer i Hjem", - "lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.", + "lists.exclusive_hint": "Hvis nogen er på denne liste, så skjul dem i hjem-feed for at undgå at se deres indlæg to gange.", "lists.find_users_to_add": "Find brugere at tilføje", "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}", "lists.list_name": "Listetitel", @@ -792,7 +792,7 @@ "report.thanks.title": "Ønsker ikke at se dette?", "report.thanks.title_actionable": "Tak for anmeldelsen, der vil blive set nærmere på dette.", "report.unfollow": "Følg ikke længere @{name}", - "report.unfollow_explanation": "Du følger denne konto. For ikke længere at se vedkommendes indlæg i din hjemmestrøm, kan du stoppe med at følge dem.", + "report.unfollow_explanation": "Du følger denne konto. Hvis du ikke længere vil se vedkommendes indlæg i dit hjem-feed, så stop med at følge dem.", "report_notification.attached_statuses": "{count, plural, one {{count} indlæg} other {{count} indlæg}} vedhæftet", "report_notification.categories.legal": "Juridisk", "report_notification.categories.legal_sentence": "ikke-tilladt indhold", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 75f99fef4c8..fc34c0fe191 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -43,7 +43,7 @@ "account.followers": "Follower", "account.followers.empty": "Diesem Profil folgt noch niemand.", "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", - "account.followers_you_know_counter": "{counter} Follower kennst Du", + "account.followers_you_know_counter": "{counter} bekannt", "account.following": "Folge ich", "account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}", "account.follows.empty": "Dieses Profil folgt noch niemandem.", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 441cfee6d10..31d1c71b0ef 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -1,7 +1,7 @@ { "about.blocks": "Moderated servers", "about.contact": "Contact:", - "about.default_locale": "Default", + "about.default_locale": "Varsayılan", "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Reason not available", "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.", @@ -9,11 +9,11 @@ "about.domain_blocks.silenced.title": "Limited", "about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.", "about.domain_blocks.suspended.title": "Suspended", - "about.language_label": "Language", + "about.language_label": "Dil", "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.account_note_header": "Personal note", + "account.account_note_header": "Kişisel not", "account.add_or_remove_from_list": "Add or Remove from lists", "account.badges.bot": "Automated", "account.badges.group": "Group", @@ -845,6 +845,8 @@ "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", + "status.context.load_new_replies": "Yeni yanıtlar geldi.", + "status.context.loading": "Daha fazla yanıt kontrol ediliyor", "status.continued_thread": "Continued thread", "status.copy": "Copy link to status", "status.delete": "Delete", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index ed5fa83171e..6060cfeda3e 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -292,6 +292,7 @@ "emoji_button.search_results": "Paieškos rezultatai", "emoji_button.symbols": "Simboliai", "emoji_button.travel": "Kelionės ir vietos", + "empty_column.account_featured_other.unknown": "Ši paskyra dar nieko neparodė.", "empty_column.account_hides_collections": "Šis (-i) naudotojas (-a) pasirinko nepadaryti šią informaciją prieinamą.", "empty_column.account_suspended": "Paskyra pristabdyta.", "empty_column.account_timeline": "Nėra čia įrašų.", @@ -794,6 +795,8 @@ "status.bookmark": "Pridėti į žymės", "status.cancel_reblog_private": "Nebepasidalinti", "status.cannot_reblog": "Šis įrašas negali būti pakeltas.", + "status.context.load_new_replies": "Yra naujų atsakymų", + "status.context.loading": "Tikrinama dėl daugiau atsakymų", "status.continued_thread": "Tęsiama gijoje", "status.copy": "Kopijuoti nuorodą į įrašą", "status.delete": "Ištrinti", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index e7e8d2c0cd1..8120312d80f 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -845,6 +845,8 @@ "status.bookmark": "冊籤", "status.cancel_reblog_private": "取消轉送", "status.cannot_reblog": "Tsit篇PO文bē當轉送", + "status.context.load_new_replies": "有新ê回應", + "status.context.loading": "Leh檢查其他ê回應", "status.continued_thread": "接續ê討論線", "status.copy": "Khóo-pih PO文ê連結", "status.delete": "Thâi掉", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 091ddf8ca24..723207a4be4 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -27,6 +27,8 @@ "account.edit_profile": "Upraviť profil", "account.enable_notifications": "Zapnúť upozornenia na príspevky od @{name}", "account.endorse": "Zobraziť na vlastnom profile", + "account.familiar_followers_one": "Nasledovanie od {name1}", + "account.familiar_followers_two": "Nasledovanie od {name1} a {name2}", "account.featured": "Zviditeľnené", "account.featured.accounts": "Profily", "account.featured.hashtags": "Hashtagy", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index c50ac3cbaa1..9a8354d052e 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -845,6 +845,8 @@ "status.bookmark": "Yer işareti ekle", "status.cancel_reblog_private": "Yeniden paylaşımı geri al", "status.cannot_reblog": "Bu gönderi yeniden paylaşılamaz", + "status.context.load_new_replies": "Yeni yanıtlar mevcut", + "status.context.loading": "Daha fazla yanıt için kontrol ediliyor", "status.continued_thread": "Devam eden akış", "status.copy": "Gönderi bağlantısını kopyala", "status.delete": "Sil", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index c6e0aa7f276..127ae87f637 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -301,6 +301,9 @@ "emoji_button.search_results": "搜索结果", "emoji_button.symbols": "符号", "emoji_button.travel": "旅行与地点", + "empty_column.account_featured.me": "你尚未设置任何精选。你知道吗?你也可以将自己最常使用的话题标签,甚至是好友的帐号,在你的个人主页上设为精选。", + "empty_column.account_featured.other": "{acct} 尚未设置任何精选。你知道吗?你也可以将自己最常使用的话题标签,甚至是好友的帐号,在你的个人主页上设为精选。", + "empty_column.account_featured_other.unknown": "该用户尚未设置任何精选。", "empty_column.account_hides_collections": "该用户选择不公开此信息", "empty_column.account_suspended": "账号已被停用", "empty_column.account_timeline": "这里没有嘟文!", @@ -402,8 +405,10 @@ "hashtag.counter_by_accounts": "{count, plural,other {{counter} 人讨论}}", "hashtag.counter_by_uses": "{count, plural, other {{counter} 条嘟文}}", "hashtag.counter_by_uses_today": "今日 {count, plural, other {{counter} 条嘟文}}", + "hashtag.feature": "设为精选", "hashtag.follow": "关注话题", "hashtag.mute": "停止提醒 #{hashtag}", + "hashtag.unfeature": "取消精选", "hashtag.unfollow": "取消关注话题", "hashtags.and_other": "… 和另外 {count, plural, other {# 个话题}}", "hints.profiles.followers_may_be_missing": "该账号的关注者列表可能没有完全显示。", @@ -558,6 +563,7 @@ "navigation_bar.preferences": "偏好设置", "navigation_bar.privacy_and_reach": "隐私与可达性", "navigation_bar.search": "搜索", + "navigation_bar.search_trends": "搜索/热门趋势", "navigation_panel.collapse_lists": "收起菜单列表", "navigation_panel.expand_lists": "展开菜单列表", "not_signed_in_indicator.not_signed_in": "你需要登录才能访问此资源。", @@ -786,6 +792,7 @@ "report_notification.categories.violation": "违反规则", "report_notification.categories.violation_sentence": "违反规则", "report_notification.open": "打开举报", + "search.clear": "清空搜索内容", "search.no_recent_searches": "无最近搜索", "search.placeholder": "搜索", "search.quick_action.account_search": "包含 {x} 的账号", @@ -827,6 +834,8 @@ "status.bookmark": "添加到书签", "status.cancel_reblog_private": "取消转嘟", "status.cannot_reblog": "不能转嘟这条嘟文", + "status.context.load_new_replies": "有新回复", + "status.context.loading": "正在检查更多回复", "status.continued_thread": "上接嘟文串", "status.copy": "复制嘟文链接", "status.delete": "删除", @@ -854,8 +863,10 @@ "status.pin": "在个人资料页面置顶", "status.quote_error.filtered": "已根据你的筛选器过滤", "status.quote_error.not_found": "无法显示这篇贴文。", + "status.quote_error.pending_approval": "此嘟文正在等待原作者批准。", "status.quote_error.rejected": "由于原作者不允许引用转发,无法显示这篇贴文。", "status.quote_error.removed": "该帖子已被作者删除。", + "status.quote_error.unauthorized": "你无权查看此嘟文,因此无法显示。", "status.quote_post_author": "{name} 的嘟文", "status.read_more": "查看更多", "status.reblog": "转嘟", diff --git a/config/locales/br.yml b/config/locales/br.yml index e5b9ff2559c..4bfd268d15a 100644 --- a/config/locales/br.yml +++ b/config/locales/br.yml @@ -560,6 +560,8 @@ br: one: "%{count} skeudenn" other: "%{count} skeudenn" two: "%{count} skeudenn" + errors: + quoted_status_not_found: War a seblant, n'eus ket eus an embannadenn emaoc'h o klask menegiñ. pin_errors: ownership: N'hallit ket spilhennañ embannadurioù ar re all quote_policies: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 1c858fe4cde..a3e62d7d721 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1870,6 +1870,7 @@ ca: edited_at_html: Editat %{date} errors: in_reply_not_found: El tut al qual intentes respondre sembla que no existeix. + quoted_status_not_found: Sembla que la publicació que vols citar no existeix. over_character_limit: Límit de caràcters de %{max} superat pin_errors: direct: Els tuts que només són visibles per als usuaris mencionats no poden ser fixats diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 1ad8df6e71f..7df98b90bbf 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1958,6 +1958,7 @@ cs: edited_at_html: Upraven %{date} errors: in_reply_not_found: Příspěvek, na který se pokoušíte odpovědět, neexistuje. + quoted_status_not_found: Zdá se, že příspěvek, který se pokoušíte citovat neexistuje. over_character_limit: byl překročen limit %{max} znaků pin_errors: direct: Příspěvky viditelné pouze zmíněným uživatelům nelze připnout diff --git a/config/locales/cy.yml b/config/locales/cy.yml index d7242fbf2e1..ab45a600128 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -2043,6 +2043,7 @@ cy: edited_at_html: Wedi'i olygu %{date} errors: in_reply_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio ei ateb yn bodoli. + quoted_status_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio'i ddyfynnu yn bodoli. over_character_limit: wedi mynd y tu hwnt i'r terfyn nodau o %{max} pin_errors: direct: Nid oes modd pinio postiadau sy'n weladwy i ddefnyddwyr a grybwyllwyd yn unig diff --git a/config/locales/da.yml b/config/locales/da.yml index b822ae9cb17..d2f6753181b 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1394,7 +1394,7 @@ da: filters: contexts: account: Profiler - home: Hjemmetidslinje + home: Hjem og lister notifications: Notifikationer public: Offentlig tidslinje thread: Samtaler @@ -1872,6 +1872,7 @@ da: edited_at_html: Redigeret %{date} errors: in_reply_not_found: Indlægget, der forsøges besvaret, ser ikke ud til at eksistere. + quoted_status_not_found: Indlægget, du forsøger at citere, ser ikke ud til at eksistere. over_character_limit: grænsen på %{max} tegn overskredet pin_errors: direct: Indlæg, som kun kan ses af omtalte brugere, kan ikke fastgøres diff --git a/config/locales/de.yml b/config/locales/de.yml index 1eb5ef1c4f9..4b29dbac5de 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1872,6 +1872,7 @@ de: edited_at_html: 'Bearbeitet: %{date}' errors: in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren. + quoted_status_not_found: Der Beitrag, den du zitieren möchtest, scheint nicht zu existieren. over_character_limit: Begrenzung von %{max} Zeichen überschritten pin_errors: direct: Beiträge, die nur für erwähnte Profile sichtbar sind, können nicht angeheftet werden diff --git a/config/locales/el.yml b/config/locales/el.yml index f7c3df0f54e..b1df6962ecf 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1872,6 +1872,7 @@ el: edited_at_html: Επεξεργάστηκε στις %{date} errors: in_reply_not_found: Η ανάρτηση στην οποία προσπαθείς να απαντήσεις δεν φαίνεται να υπάρχει. + quoted_status_not_found: Η ανάρτηση την οποία προσπαθείς να παραθέσεις δεν φαίνεται να υπάρχει. over_character_limit: υπέρβαση μέγιστου ορίου %{max} χαρακτήρων pin_errors: direct: Αναρτήσεις που είναι ορατές μόνο στους αναφερόμενους χρήστες δεν μπορούν να καρφιτσωθούν diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 3b26eebd294..93d4c3c31ad 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1872,6 +1872,7 @@ es-AR: edited_at_html: Editado el %{date} errors: in_reply_not_found: El mensaje al que intentás responder no existe. + quoted_status_not_found: El mensaje al que intentás citar parece que no existe. over_character_limit: se excedió el límite de %{max} caracteres pin_errors: direct: Los mensajes que sólo son visibles para los usuarios mencionados no pueden ser fijados diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index cfc573fab8d..e839a605e15 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1872,6 +1872,7 @@ es-MX: edited_at_html: Editado %{date} errors: in_reply_not_found: La publicación a la que estás intentando responder no existe. + quoted_status_not_found: La publicación que intentas citar no parece existir. over_character_limit: Límite de caracteres de %{max} superado pin_errors: direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse diff --git a/config/locales/es.yml b/config/locales/es.yml index 4b10f23b132..0381647115a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1872,6 +1872,7 @@ es: edited_at_html: Editado %{date} errors: in_reply_not_found: La publicación a la que intentas responder no existe. + quoted_status_not_found: La publicación que estás intentando citar no existe. over_character_limit: Límite de caracteres de %{max} superado pin_errors: direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 23a6b589765..9af87098a42 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1872,6 +1872,7 @@ fo: edited_at_html: Rættað %{date} errors: in_reply_not_found: Posturin, sum tú roynir at svara, sýnist ikki at finnast. + quoted_status_not_found: Posturin, sum tú roynir at sitera, sýnist ikki at finnast. over_character_limit: mesta tal av teknum, %{max}, rokkið pin_errors: direct: Postar, sum einans eru sjónligir hjá nevndum brúkarum, kunnu ikki festast diff --git a/config/locales/gl.yml b/config/locales/gl.yml index d19d47ab26b..0b3a4133f55 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1872,6 +1872,7 @@ gl: edited_at_html: Editado %{date} errors: in_reply_not_found: A publicación á que tentas responder semella que non existe. + quoted_status_not_found: Parece que a publicación que intentas citar non existe. over_character_limit: Excedeu o límite de caracteres %{max} pin_errors: direct: As publicacións que só son visibles para as usuarias mencionadas non se poden fixar diff --git a/config/locales/he.yml b/config/locales/he.yml index 50711387649..ed80dfd6c99 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1957,7 +1957,8 @@ he: two: 'מכיל את התגיות האסורות: %{tags}' edited_at_html: נערך ב-%{date} errors: - in_reply_not_found: נראה שההודעה שאת/ה מנסה להגיב לה לא קיימת. + in_reply_not_found: נראה שההודעה שנסית להגיב לה לא קיימת. + quoted_status_not_found: נראה שההודעה שנסית לצטט לא קיימת. over_character_limit: חריגה מגבול התווים של %{max} pin_errors: direct: לא ניתן לקבע הודעות שנראותן מוגבלת למכותבים בלבד diff --git a/config/locales/hu.yml b/config/locales/hu.yml index b46062b2c7b..ddb1841f1eb 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1872,6 +1872,7 @@ hu: edited_at_html: 'Szerkesztve: %{date}' errors: in_reply_not_found: Már nem létezik az a bejegyzés, melyre válaszolni szeretnél. + quoted_status_not_found: Már nem létezik az a bejegyzés, amelyből idézni szeretnél. over_character_limit: túllépted a maximális %{max} karakteres keretet pin_errors: direct: A csak a megemlített felhasználók számára látható bejegyzések nem tűzhetők ki diff --git a/config/locales/is.yml b/config/locales/is.yml index 5e33189cfb2..2e47f3245a9 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1876,6 +1876,7 @@ is: edited_at_html: Breytt %{date} errors: in_reply_not_found: Færslan sem þú ert að reyna að svara að er líklega ekki til. + quoted_status_not_found: Færslan sem þú ert að reyna að vitna í virðist ekki vera til. over_character_limit: hámarksfjölda stafa (%{max}) náð pin_errors: direct: Ekki er hægt að festa færslur sem einungis eru sýnilegar þeim notendum sem minnst er á diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 41cf7cae96f..3b31b138b8f 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -912,6 +912,10 @@ lt: your_appeal_rejected: Tavo apeliacija buvo atmesta edit_profile: hint_html: "Tinkink tai, ką žmonės mato tavo viešame profilyje ir šalia įrašų. Kiti žmonės labiau linkę sekti atgal ir bendrauti su tavimi, jei tavo profilis yra užpildytas ir turi profilio nuotrauką." + emoji_styles: + auto: Automatinis + native: Vietiniai + twemoji: Tvejaustukai errors: '403': Jūs neturie prieigos matyti šiam puslapiui. '404': Puslapis nerastas. @@ -1183,6 +1187,8 @@ lt: other: "%{count} vaizdų" boosted_from_html: Pakelta iš %{acct_link} content_warning: 'Turinio įspėjimas: %{warning}' + errors: + quoted_status_not_found: Įrašas, kurį bandote cituoti, atrodo, neegzistuoja. over_character_limit: pasiektas %{max} simbolių limitas pin_errors: limit: Jūs jau prisegėte maksimalų toot'ų skaičų diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 354ef8b0f85..007ca847a81 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -97,7 +97,7 @@ nan: silenced: 受限制 suspended: 權限中止ah title: 管理 - moderation_notes: 管理ê註釋 + moderation_notes: 管理ê筆記 most_recent_activity: 最近ê活動時間 most_recent_ip: 最近ê IP no_account_selected: 因為無揀任何口座,所以lóng無改變 @@ -245,7 +245,7 @@ nan: create_announcement_html: "%{name} kā公告 %{target} 建立ah" create_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue封鎖ah" create_custom_emoji_html: "%{name} kā 新ê emoji %{target} 傳上去ah" - create_domain_allow_html: "%{name} 允准 %{target} 域名加入聯邦宇宙" + create_domain_allow_html: "%{name} 允准 %{target} 域名加入聯邦" create_domain_block_html: "%{name} 封鎖域名 %{target}" create_email_domain_block_html: "%{name} kā 電子phue域名 %{target} 封鎖ah" create_ip_block_html: "%{name} 建立 IP %{target} ê規則" @@ -256,7 +256,7 @@ nan: destroy_announcement_html: "%{name} kā公告 %{target} thâi掉ah" destroy_canonical_email_block_html: "%{name} kā hash是 %{target} ê電子phue取消封鎖ah" destroy_custom_emoji_html: "%{name} kā 新ê emoji %{target} thâi掉ah" - destroy_domain_allow_html: "%{name} 無允准 %{target} 域名加入聯邦宇宙" + destroy_domain_allow_html: "%{name} 無允准 %{target} 域名加入聯邦" destroy_domain_block_html: "%{name} 取消封鎖域名 %{target}" destroy_email_domain_block_html: "%{name} kā 電子phue域名 %{target} 取消封鎖ah" destroy_instance_html: "%{name} 清除域名 %{target}" @@ -531,16 +531,111 @@ nan: content_policies: comment: 內部ê筆記 description_html: Lí ē當定義用tī所有tuì tsit ê域名kap伊ê子域名來ê口座ê內容政策。 + limited_federation_mode_description_html: Lí通選擇kám beh允准tsit ê域名加入聯邦。 + policies: + reject_media: 拒絕媒體 + reject_reports: 拒絕檢舉 + silence: 限制 + suspend: 中止權限 + policy: 政策 + reason: 公開ê理由 + title: 內容政策 dashboard: + instance_accounts_dimension: 上tsē lâng跟tuè ê口座 + instance_accounts_measure: 儲存ê口座 + instance_followers_measure: lán tī hia ê跟tuè者 + instance_follows_measure: in tī tsia ê跟tuè者 instance_languages_dimension: Tsia̍p用ê語言 + instance_media_attachments_measure: 儲存ê媒體附件 + instance_reports_measure: 關係in ê檢舉 + instance_statuses_measure: 儲存ê PO文 + delivery: + all: 全部 + clear: 清寄送ê錯誤 + failing: 失敗 + restart: 重頭啟動寄送 + stop: 停止寄送 + unavailable: Bē當用 + delivery_available: 通寄送 + delivery_error_days: 寄送錯誤ê日數 + delivery_error_hint: Nā連續 %{count} kang bē當寄送,就ē自動標做bē當寄送。 + destroyed_msg: Tuì %{domain} 來ê資料,teh排隊beh thâi掉。 + empty: Tshuē無域名。 + known_accounts: + other: "%{count} ê知影ê口座" + moderation: + all: 全部 + limited: 受限制 + title: 管理 + moderation_notes: + create: 加添管理筆記 + created_msg: 站臺ê管理記錄成功建立! + description_html: 檢視á是替別ê管理者kap未來ê家己留筆記 + destroyed_msg: 站臺ê管理記錄成功thâi掉! + placeholder: 關係本站、行ê行動,á是其他通幫tsān lí未來管本站ê資訊。 + title: 管理ê筆記 + private_comment: 私人評論 + public_comment: 公開ê評論 + purge: 清除 + purge_description_html: Nā lí想講tsit ê域名ē永永斷線,ē當tuì儲存內底thâi掉uì tsit ê域名來ê所有口座記錄kap相關資料。Huân-sè ē開點á時間。 + title: 聯邦 + total_blocked_by_us: Hōo lán封鎖 + total_followed_by_them: Hōo in跟tuè + total_followed_by_us: Hōo lán跟tuè + total_reported: 關係in ê檢舉 + total_storage: 媒體ê附件 + totals_time_period_hint_html: 下kha顯示ê總計包含ta̍k時ê資料。 + unknown_instance: 佇本服務器,現tsú時iáu無tsit ê域名ê記錄。 invites: + deactivate_all: Lóng停用 filter: + all: 全部 available: 通用ê expired: 過期ê title: 過濾器 title: 邀請 ip_blocks: add_new: 建立規則 + created_msg: 成功加添新ê IP規則 + delete: Thâi掉 + expires_in: + '1209600': 2 禮拜 + '15778476': 6個月 + '2629746': 1 個月 + '31556952': 1 年 + '86400': 1 kang + '94670856': 3 年 + new: + title: 建立新ê IP規則 + no_ip_block_selected: 因為無揀任何IP規則,所以lóng無改變 + title: IP規則 + relationships: + title: "%{acct} ê關係" + relays: + add_new: 加添新ê中繼 + delete: Thâi掉 + description_html: "聯邦ê中繼站 是中lâng ê服侍器,ē tī訂koh公開kàu hit ê中繼站ê服侍器之間,交換tsē-tsē ê 公開PO文。中繼站通幫tsān小型kap中型服侍器tuì聯邦宇宙發現內容,本地ê用者免手動跟tuè遠距離ê服侍器ê別lâng。" + disable: 停止使用 + disabled: 停止使用ê + enable: 啟用 + enable_hint: Lí ê服侍器tsi̍t-ē啟動,ē訂tuì tsit ê中繼逐ê公開PO文,mā ē開始送tsit ê服侍器ê公開PO文kàu hia。 + enabled: 啟用ê + inbox_url: 中繼 URL + pending: Teh等中繼站允准 + save_and_enable: 儲存koh啟用 + setup: 設定中繼ê連結 + signatures_not_enabled: Nā啟用安全模式á是受限ê聯邦模式,中繼可能buē-tàng正常運作 + status: 狀態 + title: 中繼 + report_notes: + created_msg: 檢舉記錄成功建立! + destroyed_msg: 檢舉記錄成功thâi掉! + reports: + account: + notes: + other: "%{count} 篇筆記" + action_log: 審查日誌 + action_taken_by: 操作由 statuses: language: 語言 trends: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index b53aa68a652..6d26903a20b 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1872,6 +1872,7 @@ nl: edited_at_html: Bewerkt op %{date} errors: in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan. + quoted_status_not_found: Het bericht die je probeert te citeren lijkt niet te bestaan. over_character_limit: Limiet van %{max} tekens overschreden pin_errors: direct: Berichten die alleen zichtbaar zijn voor vermelde gebruikers, kunnen niet worden vastgezet diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index f2171b80786..1e988b5baed 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1872,6 +1872,7 @@ pt-PT: edited_at_html: Editado em %{date} errors: in_reply_not_found: A publicação a que estás a tentar responder parece não existir. + quoted_status_not_found: A publicação que está a tentar citar parece não existir. over_character_limit: limite de caracteres %{max} excedido pin_errors: direct: As publicações que só são visíveis para os utilizadores mencionados não podem ser fixadas diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 35f79c67c01..4cc2e15b5a7 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1352,7 +1352,7 @@ ru: edit_profile: basic_information: Основные данные hint_html: "Здесь вы можете изменить всё то, что будет отображаться в вашем публичном профиле и рядом с вашими постами. На вас будут чаще подписываться и с вами будут чаще взаимодействовать, если у вас будет заполнен профиль и добавлено фото профиля." - other: Прочее + other: Разное emoji_styles: auto: Автоматически native: Как в системе @@ -1630,19 +1630,19 @@ ru: media_attachments: validations: images_and_video: Нельзя добавить видео к посту с изображениями - not_found: Медиа %{ids} не найдено или уже прикреплено к другому сообщению - not_ready: Не удаётся прикрепить файлы, обработка которых не завершена. Повторите попытку чуть позже! - too_many: Нельзя добавить более 4 файлов + not_found: Медиа %{ids} не найдены или уже прикреплены к другому посту + not_ready: Обработка некоторых прикреплённых файлов ещё не окончена. Подождите немного и попробуйте снова! + too_many: Можно прикрепить не более 4 файлов migrations: - acct: имя@домен новой учётной записи + acct: Куда cancel: Отменить переезд - cancel_explanation: Отмена перенаправления повторно активирует текущую учётную запись, но не вернёт обратно подписчиков, которые были перемещены на другую. - cancelled_msg: Переезд был успешно отменён. + cancel_explanation: После отмены перенаправления ваша текущая учётная запись снова станет активна, но ранее перенесённые подписчики не будут возвращены. + cancelled_msg: Переезд отменён. errors: - already_moved: это та же учётная запись, на которую вы мигрировали - missing_also_known_as: не ссылается на эту учетную запись + already_moved: не может быть той учётной записью, куда уже настроен переезд + missing_also_known_as: должна быть связанной учётной записью move_to_self: не может быть текущей учётной записью - not_found: не удалось найти + not_found: не найдена on_cooldown: Вы пока не можете переезжать followers_count: Подписчиков на момент переезда incoming_migrations: Переезд со старой учётной записи @@ -1650,25 +1650,25 @@ ru: moved_msg: Теперь ваша учётная запись перенаправляет к %{acct}, туда же перемещаются подписчики. not_redirecting: Для вашей учётной записи пока не настроено перенаправление. on_cooldown: Вы уже недавно переносили свою учётную запись. Эта возможность будет снова доступна через %{count} дн. - past_migrations: Прошлые переезды + past_migrations: История переездов proceed_with_move: Перенести подписчиков redirected_msg: Ваша учётная запись теперь перенаправляется на %{acct}. redirecting_to: Ваша учётная запись перенаправляет к %{acct}. set_redirect: Настроить перенаправление warning: - backreference_required: Новая учётная запись должна быть сначала настроена так, чтоб ссылаться на текущую - before: 'Прежде чем продолжить, внимательно прочитайте следующую информацию:' - cooldown: После переезда наступает период, в течение которого вы не сможете ещё раз переехать + backreference_required: Текущая учётная запись сначала должна быть добавлена как связанная в настройках новой учётной записи + before: 'Внимательно ознакомьтесь со следующими замечаниями перед тем как продолжить:' + cooldown: После переезда наступит период ожидания, в течение которого переезд будет невозможен disabled_account: Вашу текущую учётная запись впоследствии нельзя будет больше использовать. При этом, у вас будет доступ к экспорту данных, а также к повторной активации учётной записи. - followers: Это действие перенесёт всех ваших подписчиков с текущей учётной записи на новую - only_redirect_html: Или же вы можете просто настроить перенаправление в ваш профиль. + followers: В результате переезда все ваши подписчики будут перенесены с текущей учётной записи на новую + only_redirect_html: Также вы можете настроить перенаправление без переноса подписчиков. other_data: Никакие другие данные не будут автоматически перенесены - redirect: Профиль этой учётной записи будет обновлён с заметкой о перенаправлении, а также исключён из поиска + redirect: Профиль текущей учётной записи будет исключён из поиска, а в нём появится объявление о переезде moderation: title: Модерация move_handler: carry_blocks_over_text: Этот пользователь переехал с учётной записи %{acct}, которую вы заблокировали. - carry_mutes_over_text: Этот пользователь перешёл с учётной записи %{acct}, которую вы игнорируете. + carry_mutes_over_text: Этот пользователь переехал с учётной записи %{acct}, которую вы игнорируете. copy_account_note_text: 'Этот пользователь переехал с %{acct}, вот ваша предыдущая заметка о нём:' navigation: toggle_menu: Переключить меню @@ -1717,8 +1717,9 @@ ru: billion: млрд million: млн quadrillion: квадрлн - thousand: тыс + thousand: тыс. trillion: трлн + unit: '' otp_authentication: code_hint: Для подтверждения введите код, сгенерированный приложением-аутентификатором description_html: Подключив двуфакторную авторизацию, для входа в свою учётную запись вам будет необходим смартфон и приложение-аутентификатор на нём, которое будет генерировать специальные временные коды. Без этих кодов войти в учётную запись не получиться, даже если все данные верны, что существенно увеличивает безопасность вашей учётной записи. @@ -1728,32 +1729,32 @@ ru: setup: Настроить wrong_code: Введенный код недействителен! Время сервера и время устройства правильно? pagination: - newer: Новее - next: След - older: Старше - prev: Пред + newer: Позже + next: Вперёд + older: Раньше + prev: Назад truncate: "…" polls: errors: - already_voted: Вы уже голосовали в этом опросе + already_voted: Вы уже проголосовали в этом опросе duplicate_options: не должны повторяться duration_too_long: слишком велика duration_too_short: слишком мала - expired: Опрос уже завершился - invalid_choice: Выбранного варианта голосования не существует + expired: Этот опрос уже завершился + invalid_choice: Выбранного вами варианта ответа не существует over_character_limit: не должны превышать %{max} символов self_vote: Вы не можете голосовать в своих опросах too_few_options: должны содержать не меньше двух опций too_many_options: должны ограничиваться максимум %{max} опциями preferences: - other: Остальное - posting_defaults: Настройки отправки по умолчанию + other: Разное + posting_defaults: Предустановки для новых постов public_timelines: Публичные ленты privacy: hint_html: "Настройте, как вы хотите, чтобы ваш профиль и ваши сообщения были найдены. Различные функции в Mastodon могут помочь вам охватить более широкую аудиторию, если они включены. Уделите время изучению этих настроек, чтобы убедиться, что они подходят для вашего случая использования." - privacy: Конфиденциальность + privacy: Приватность privacy_hint_html: Определите, какую информацию вы хотите раскрыть в интересах других. Люди находят интересные профили и приложения, просматривая список подписчиков других людей и узнавая, из каких приложений они публикуют свои сообщения, но вы можете предпочесть скрыть это. - reach: Охват + reach: Видимость reach_hint_html: Укажите, хотите ли вы, чтобы новые люди обнаруживали вас и могли следить за вами. Хотите ли вы, чтобы ваши сообщения появлялись на экране Обзора? Хотите ли вы, чтобы другие люди видели вас в своих рекомендациях? Хотите ли вы автоматически принимать всех новых подписчиков или иметь возможность детально контролировать каждого из них? search: Поиск search_hint_html: Определите, как вас могут найти. Хотите ли вы, чтобы люди находили вас по тому, о чём вы публично писали? Хотите ли вы, чтобы люди за пределами Mastodon находили ваш профиль при поиске в Интернете? Следует помнить, что полное исключение из всех поисковых систем не может быть гарантировано для публичной информации. diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 09041a4113d..025e2decd54 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -9,7 +9,7 @@ da: fields: Din hjemmeside, dine pronominer, din alder, eller hvad du har lyst til. indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset. note: 'Du kan @omtale andre personer eller #hashtags.' - show_collections: Folk vil ikke kunne tjekke dine Følger og Følgere. Folk, du selv følger, vil stadig kunne se dette. + show_collections: Folk vil kunne se, hvem du følger, og hvem der følger dig. Personer, som du følger, vil kunne se, at du følger dem. unlocked: Man vil kunne følges af folk uden først at godkende dem. Ønsker man at gennemgå Følg-anmodninger og individuelt acceptere/afvise nye følgere, så fjern markeringen. account_alias: acct: Angiv brugernavn@domain for den konto, hvorfra du vil flytte @@ -75,7 +75,7 @@ da: featured_tag: name: 'Her er nogle af dine hyppigst brugte hashtags:' filters: - action: Vælg handlingen til eksekvering, når et indlæg matcher filteret + action: Vælg, hvilken handling, der skal udføres, når et indlæg matcher filteret actions: blur: Skjul medier bag en advarsel, uden at skjule selve teksten hide: Skjul det filtrerede indhold fuldstændigt og gør, som om det ikke eksisterer diff --git a/config/locales/simple_form.lt.yml b/config/locales/simple_form.lt.yml index 4aa89ed9ecb..bbd22e3f768 100644 --- a/config/locales/simple_form.lt.yml +++ b/config/locales/simple_form.lt.yml @@ -60,6 +60,7 @@ lt: setting_display_media_default: Slėpti mediją, pažymėtą kaip jautrią setting_display_media_hide_all: Visada slėpti mediją setting_display_media_show_all: Visada rodyti mediją + setting_emoji_style: Kaip rodyti emodžius. „Auto“ bandys naudoti vietinius jaustukus, bet senesnėse naršyklėse grįš prie Tvejaustukų. setting_system_scrollbars_ui: Taikoma tik darbalaukio naršyklėms, karkasiniais „Safari“ ir „Chrome“. setting_use_blurhash: Gradientai pagrįsti paslėptų vizualizacijų spalvomis, bet užgožia bet kokias detales. setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio srauto slinkimo. @@ -120,6 +121,11 @@ lt: min_age: Neturėtų būti žemiau mažiausio amžiaus, reikalaujamo pagal jūsų jurisdikcijos įstatymus. user: chosen_languages: Kai pažymėta, viešose laiko skalėse bus rodomi tik įrašai pasirinktomis kalbomis. + date_of_birth: + few: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime. + many: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime. + one: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime. + other: Turime įsitikinti, kad esate bent %{count} norint naudotis %{domain}. Mes to neišsaugosime. role: Vaidmuo valdo, kokius leidimus naudotojas turi. labels: account: @@ -162,6 +168,7 @@ lt: setting_display_media: Medijos rodymas setting_display_media_hide_all: Slėpti viską setting_display_media_show_all: Rodyti viską + setting_emoji_style: Jaustuko stilius setting_expand_spoilers: Visada išplėsti įrašus, pažymėtus turinio įspėjimais setting_hide_network: Slėpti savo socialinę diagramą setting_missing_alt_text_modal: Rodyti patvirtinimo dialogo langą prieš skelbiant mediją be alternatyvaus teksto. diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 89c51ca6e08..2b0164e9bb1 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1872,6 +1872,7 @@ tr: edited_at_html: "%{date} tarihinde düzenlendi" errors: in_reply_not_found: Yanıtlamaya çalıştığınız durum yok gibi görünüyor. + quoted_status_not_found: Alıntılamaya çalıştığınız gönderi mevcut görünmüyor. over_character_limit: "%{max} karakter limiti aşıldı" pin_errors: direct: Sadece değinilen kullanıcıların görebileceği gönderiler üstte tutulamaz diff --git a/config/locales/uk.yml b/config/locales/uk.yml index ceadfc34664..88a14b0aa47 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1846,6 +1846,7 @@ uk: edited_at_html: Відредаговано %{date} errors: in_reply_not_found: Допису, на який ви намагаєтеся відповісти, не існує. + quoted_status_not_found: Повідомлення, яке ви намагаєтеся цитувати, не існує. over_character_limit: перевищено ліміт символів %{max} pin_errors: direct: Не можливо прикріпити дописи, які видимі лише згаданим користувачам diff --git a/config/locales/vi.yml b/config/locales/vi.yml index d56878d1615..adc801c449c 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1829,6 +1829,7 @@ vi: edited_at_html: Sửa %{date} errors: in_reply_not_found: Bạn đang trả lời một tút không còn tồn tại. + quoted_status_not_found: Bạn đang trích dẫn một tút không còn tồn tại. over_character_limit: vượt quá giới hạn %{max} ký tự pin_errors: direct: Không thể ghim những tút nhắn riêng diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 546ac7d989a..bc29a30b332 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1830,7 +1830,8 @@ zh-TW: other: 含有不得使用的標籤: %{tags} edited_at_html: 編輯於 %{date} errors: - in_reply_not_found: 您嘗試回覆的嘟文看起來不存在。 + in_reply_not_found: 您嘗試回覆之嘟文似乎不存在。 + quoted_status_not_found: 您嘗試引用之嘟文似乎不存在。 over_character_limit: 已超過 %{max} 字的限制 pin_errors: direct: 無法釘選只有僅提及使用者可見之嘟文 From 018e5e303fd85f05ec7684fb4f1152b1ac6d9794 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 28 Jul 2025 10:20:12 +0200 Subject: [PATCH 136/660] Fix jobs being added to batch after they might already execute (#35496) --- app/models/worker_batch.rb | 40 +++++++++++++++---- .../activitypub/fetch_replies_service.rb | 7 +++- config/initializers/sidekiq.rb | 6 +-- lib/mastodon/worker_batch_middleware.rb | 11 +++++ 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 lib/mastodon/worker_batch_middleware.rb diff --git a/app/models/worker_batch.rb b/app/models/worker_batch.rb index f741071ba95..0a22ce61419 100644 --- a/app/models/worker_batch.rb +++ b/app/models/worker_batch.rb @@ -19,17 +19,22 @@ class WorkerBatch redis.hset(key, { 'async_refresh_key' => async_refresh_key, 'threshold' => threshold }) end + def within + raise NoBlockGivenError unless block_given? + + begin + Thread.current[:batch] = self + yield + ensure + Thread.current[:batch] = nil + end + end + # Add jobs to the batch. Usually when the batch is created. # @param [Array] jids def add_jobs(jids) if jids.blank? - async_refresh_key = redis.hget(key, 'async_refresh_key') - - if async_refresh_key.present? - async_refresh = AsyncRefresh.new(async_refresh_key) - async_refresh.finish! - end - + finish! return end @@ -55,8 +60,23 @@ class WorkerBatch if async_refresh_key.present? async_refresh = AsyncRefresh.new(async_refresh_key) async_refresh.increment_result_count(by: 1) - async_refresh.finish! if pending.zero? || processed >= threshold.to_f * (processed + pending) end + + if pending.zero? || processed >= (threshold || 1.0).to_f * (processed + pending) + async_refresh&.finish! + cleanup + end + end + + def finish! + async_refresh_key = redis.hget(key, 'async_refresh_key') + + if async_refresh_key.present? + async_refresh = AsyncRefresh.new(async_refresh_key) + async_refresh.finish! + end + + cleanup end # Get pending jobs. @@ -76,4 +96,8 @@ class WorkerBatch def key(suffix = nil) "worker_batch:#{@id}#{":#{suffix}" if suffix}" end + + def cleanup + redis.del(key, key('jobs')) + end end diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 25eb275ca5c..239df0ba584 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -17,7 +17,12 @@ class ActivityPub::FetchRepliesService < BaseService batch = WorkerBatch.new batch.connect(async_refresh_key) if async_refresh_key.present? - batch.add_jobs(FetchReplyWorker.push_bulk(@items) { |reply_uri| [reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }] }) + batch.finish! if @items.empty? + batch.within do + FetchReplyWorker.push_bulk(@items) do |reply_uri| + [reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }] + end + end [@items, n_pages] end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 3c2f12780c0..7edaf38a60a 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../lib/mastodon/sidekiq_middleware' +require_relative '../../lib/mastodon/worker_batch_middleware' Sidekiq.configure_server do |config| config.redis = REDIS_CONFIGURATION.sidekiq @@ -72,14 +73,12 @@ Sidekiq.configure_server do |config| config.server_middleware do |chain| chain.add Mastodon::SidekiqMiddleware - end - - config.server_middleware do |chain| chain.add SidekiqUniqueJobs::Middleware::Server end config.client_middleware do |chain| chain.add SidekiqUniqueJobs::Middleware::Client + chain.add Mastodon::WorkerBatchMiddleware end config.on(:startup) do @@ -105,6 +104,7 @@ Sidekiq.configure_client do |config| config.client_middleware do |chain| chain.add SidekiqUniqueJobs::Middleware::Client + chain.add Mastodon::WorkerBatchMiddleware end config.logger.level = Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s) diff --git a/lib/mastodon/worker_batch_middleware.rb b/lib/mastodon/worker_batch_middleware.rb new file mode 100644 index 00000000000..c4623013327 --- /dev/null +++ b/lib/mastodon/worker_batch_middleware.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Mastodon::WorkerBatchMiddleware + def call(_worker, msg, _queue, _redis_pool = nil) + if (batch = Thread.current[:batch]) + batch.add_jobs([msg['jid']]) + end + + yield + end +end From 7cd3738c19860b5ad3997ce16e8f8056d321eb62 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:26:29 -0400 Subject: [PATCH 137/660] Add `with_list_account` scope to `List` model (#35539) --- app/models/follow_request.rb | 2 +- app/models/list.rb | 2 ++ app/services/follow_service.rb | 2 +- app/services/unfollow_service.rb | 2 +- app/services/unmute_service.rb | 2 +- spec/models/list_spec.rb | 22 ++++++++++++++++++++++ 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 964d4e279a1..0b518036b10 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -37,7 +37,7 @@ class FollowRequest < ApplicationRecord if account.local? ListAccount.where(follow_request: self).update_all(follow_request_id: nil, follow_id: follow.id) MergeWorker.perform_async(target_account.id, account.id, 'home') - MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id| + MergeWorker.push_bulk(account.owned_lists.with_list_account(target_account).pluck(:id)) do |list_id| [target_account.id, list_id, 'list'] end end diff --git a/app/models/list.rb b/app/models/list.rb index 76c116ce244..8fd1953ab31 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -32,6 +32,8 @@ class List < ApplicationRecord before_destroy :clean_feed_manager + scope :with_list_account, ->(account) { joins(:list_accounts).where(list_accounts: { account: }) } + private def validate_account_lists_limit diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 2928c85390a..5ff1b63503c 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -82,7 +82,7 @@ class FollowService < BaseService LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, 'follow') MergeWorker.perform_async(@target_account.id, @source_account.id, 'home') - MergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:id)) do |list_id| + MergeWorker.push_bulk(@source_account.owned_lists.with_list_account(@target_account).pluck(:id)) do |list_id| [@target_account.id, list_id, 'list'] end diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index b3f2cd66f67..1ea8af69926 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -34,7 +34,7 @@ class UnfollowService < BaseService unless @options[:skip_unmerge] UnmergeWorker.perform_async(@target_account.id, @source_account.id, 'home') - UnmergeWorker.push_bulk(List.where(account: @source_account).joins(:list_accounts).where(list_accounts: { account_id: @target_account.id }).pluck(:list_id)) do |list_id| + UnmergeWorker.push_bulk(@source_account.owned_lists.with_list_account(@target_account).pluck(:list_id)) do |list_id| [@target_account.id, list_id, 'list'] end end diff --git a/app/services/unmute_service.rb b/app/services/unmute_service.rb index 9262961f7d7..0a9604bae2e 100644 --- a/app/services/unmute_service.rb +++ b/app/services/unmute_service.rb @@ -9,7 +9,7 @@ class UnmuteService < BaseService if account.following?(target_account) MergeWorker.perform_async(target_account.id, account.id, 'home') - MergeWorker.push_bulk(List.where(account: account).joins(:list_accounts).where(list_accounts: { account_id: target_account.id }).pluck(:id)) do |list_id| + MergeWorker.push_bulk(account.owned_lists.with_list_account(target_account).pluck(:id)) do |list_id| [target_account.id, list_id, 'list'] end end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index 48c273d3ecb..e2d91835ec7 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -28,4 +28,26 @@ RSpec.describe List do end end end + + describe 'Scopes' do + describe '.with_list_account' do + let(:alice) { Fabricate :account } + let(:bob) { Fabricate :account } + let(:list) { Fabricate :list } + let(:other_list) { Fabricate :list } + + before do + Fabricate :follow, account: list.account, target_account: alice + Fabricate :follow, account: other_list.account, target_account: bob + Fabricate :list_account, list: list, account: alice + Fabricate :list_account, list: other_list, account: bob + end + + it 'returns lists connected to the account' do + expect(described_class.with_list_account(alice)) + .to include(list) + .and not_include(other_list) + end + end + end end From 3b01f98c11d2dc0c77a581be8f53c974b39f63d4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:32:47 +0200 Subject: [PATCH 138/660] fix(deps): update dependency vite-plugin-pwa to v1.0.2 (#35529) 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 2c1faaa91a1..746487e0659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13791,8 +13791,8 @@ __metadata: linkType: hard "vite-plugin-pwa@npm:^1.0.0": - version: 1.0.1 - resolution: "vite-plugin-pwa@npm:1.0.1" + version: 1.0.2 + resolution: "vite-plugin-pwa@npm:1.0.2" dependencies: debug: "npm:^4.3.6" pretty-bytes: "npm:^6.1.1" @@ -13807,7 +13807,7 @@ __metadata: peerDependenciesMeta: "@vite-pwa/assets-generator": optional: true - checksum: 10c0/ceca04df97877ca97eb30805207d4826bd6340796194c9015afeefeb781931bf9019a630c5a0bdaa6dffcada11ce1fdf8595ac48a08d751dff81601aa0c7db38 + checksum: 10c0/e4f2f4dfff843ee2585a0d89e74187168ba20da77cd0d127ce7ad7eebcf5a68b2bf09000afb6bb86d43a2034fea9f568cd6db2a2d4b47a72e175d999a5e07eb1 languageName: node linkType: hard From 038de44110b4142d65698f08fcf733e1cd69d4bf Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:38:55 -0400 Subject: [PATCH 139/660] Fix `Style/GuardClause` in `Webfinger` lib (#35532) --- app/lib/webfinger.rb | 16 +++++------- spec/lib/webfinger_spec.rb | 50 ++++++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/app/lib/webfinger.rb b/app/lib/webfinger.rb index 83b5415a33f..c39c25e994a 100644 --- a/app/lib/webfinger.rb +++ b/app/lib/webfinger.rb @@ -84,22 +84,18 @@ class Webfinger def body_from_host_meta host_meta_request.perform do |res| - if res.code == 200 - body_from_webfinger(url_from_template(res.body_with_limit), use_fallback: false) - else - raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}" - end + raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}" unless res.code == 200 + + body_from_webfinger(url_from_template(res.body_with_limit), use_fallback: false) end end def url_from_template(str) link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]') - if link.present? - link['template'].gsub('{uri}', @uri) - else - raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger" - end + raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger" if link.blank? + + link['template'].gsub('{uri}', @uri) rescue Nokogiri::XML::XPath::SyntaxError raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}" end diff --git a/spec/lib/webfinger_spec.rb b/spec/lib/webfinger_spec.rb index 5015deac7ff..e214a03536b 100644 --- a/spec/lib/webfinger_spec.rb +++ b/spec/lib/webfinger_spec.rb @@ -4,15 +4,15 @@ require 'rails_helper' RSpec.describe Webfinger do describe 'self link' do + subject { described_class.new('acct:alice@example.com').perform } + context 'when self link is specified with type application/activity+json' do let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } it 'correctly parses the response' do stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - response = described_class.new('acct:alice@example.com').perform - - expect(response.self_link_href).to eq 'https://example.com/alice' + expect(subject.self_link_href).to eq 'https://example.com/alice' end end @@ -22,9 +22,7 @@ RSpec.describe Webfinger do it 'correctly parses the response' do stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - response = described_class.new('acct:alice@example.com').perform - - expect(response.self_link_href).to eq 'https://example.com/alice' + expect(subject.self_link_href).to eq 'https://example.com/alice' end end @@ -34,7 +32,45 @@ RSpec.describe Webfinger do it 'raises an error' do stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - expect { described_class.new('acct:alice@example.com').perform }.to raise_error(Webfinger::Error) + expect { subject } + .to raise_error(Webfinger::Error) + end + end + + context 'when webfinger fails and host meta is used' do + before { stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(status: 404) } + + context 'when host meta succeeds' do + let(:host_meta) do + <<~XML + + + + + XML + end + let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice-from-NS', type: 'application/activity+json' }] } } + + before do + stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(body: host_meta, headers: { 'Content-Type': 'application/jrd+json' }) + stub_request(:get, 'https://example.com/.well-known/nonStandardWebfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'uses host meta details' do + expect(subject.self_link_href) + .to eq 'https://example.com/alice-from-NS' + end + end + + context 'when host meta fails' do + before do + stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 500) + end + + it 'raises error' do + expect { subject } + .to raise_error(Webfinger::Error) + end end end end From 2acc942bb4fbed87620be3cdc9e93fc3abc77a63 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:39:11 -0400 Subject: [PATCH 140/660] Fix `Style/GuardClause` in `WebfingerResource` (#35531) --- app/lib/webfinger_resource.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb index e2027e164de..95de496a6d5 100644 --- a/app/lib/webfinger_resource.rb +++ b/app/lib/webfinger_resource.rb @@ -54,11 +54,9 @@ class WebfingerResource end def username_from_acct - if domain_matches_local? - local_username - else - raise ActiveRecord::RecordNotFound - end + raise ActiveRecord::RecordNotFound unless domain_matches_local? + + local_username end def split_acct From e183d7dd9af4ef8ea449f1394bf63434fc3a7de0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:40:20 -0400 Subject: [PATCH 141/660] Fix `Style/GuardClause` in app/helpers (#35526) --- app/helpers/formatting_helper.rb | 20 ++++++++++---------- app/helpers/theme_helper.rb | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb index 1b364a000cf..c27edbb0730 100644 --- a/app/helpers/formatting_helper.rb +++ b/app/helpers/formatting_helper.rb @@ -65,12 +65,12 @@ module FormattingHelper end def rss_content_preroll(status) - if status.spoiler_text? - safe_join [ - tag.p { spoiler_with_warning(status) }, - tag.hr, - ] - end + return unless status.spoiler_text? + + safe_join [ + tag.p { spoiler_with_warning(status) }, + tag.hr, + ] end def spoiler_with_warning(status) @@ -81,10 +81,10 @@ module FormattingHelper end def rss_content_postroll(status) - if status.preloadable_poll - tag.p do - poll_option_tags(status) - end + return unless status.preloadable_poll + + tag.p do + poll_option_tags(status) end end diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index 0f24063385b..00b4a6d2b3f 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -24,24 +24,24 @@ module ThemeHelper end def custom_stylesheet - if active_custom_stylesheet.present? - stylesheet_link_tag( - custom_css_path(active_custom_stylesheet), - host: root_url, - media: :all, - skip_pipeline: true - ) - end + return if active_custom_stylesheet.blank? + + stylesheet_link_tag( + custom_css_path(active_custom_stylesheet), + host: root_url, + media: :all, + skip_pipeline: true + ) end private def active_custom_stylesheet - if cached_custom_css_digest.present? - [:custom, cached_custom_css_digest.to_s.first(8)] - .compact_blank - .join('-') - end + return if cached_custom_css_digest.blank? + + [:custom, cached_custom_css_digest.to_s.first(8)] + .compact_blank + .join('-') end def cached_custom_css_digest From 63daf6b317e7b491fe631b42fcea497baacb5dea Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:40:37 -0400 Subject: [PATCH 142/660] Fix `Style/GuardClause` in `PreviewCard` (#35525) --- app/models/preview_card.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 56fe4836355..8e0e13cdb94 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -170,10 +170,9 @@ class PreviewCard < ApplicationRecord private def serialized_authors - if author_name? || author_url? || author_account_id? - PreviewCard::Author - .new(self) - end + return unless author_name? || author_url? || author_account_id? + + PreviewCard::Author.new(self) end def extract_dimensions From 456c3bda0bdcd9d3250b5ccc4c3abe8cdfcdc182 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 08:41:31 +0000 Subject: [PATCH 143/660] chore(deps): update dependency omniauth-cas to v3.0.2 (#35558) 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 d0472d538c0..dab65477bf8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -468,7 +468,7 @@ GEM hashie (>= 3.4.6) rack (>= 2.2.3) rack-protection - omniauth-cas (3.0.1) + omniauth-cas (3.0.2) addressable (~> 2.8) nokogiri (~> 1.12) omniauth (~> 2.1) From 86ef4d48840e439af9a0ae3ebf0e7b9b8868f75d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:50:19 -0400 Subject: [PATCH 144/660] Add `skip_*` methods to check move worker process (#35538) --- app/workers/move_worker.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 9b9c6879e5c..667efd69161 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -104,7 +104,7 @@ class MoveWorker def carry_blocks_over! @source_account.blocked_by_relationships.where(account: Account.local).find_each do |block| - unless block.account.blocking?(@target_account) || block.account.following?(@target_account) + unless skip_block_move?(block) BlockService.new.call(block.account, @target_account) add_account_note_if_needed!(block.account, 'move_handler.carry_blocks_over_text') end @@ -115,7 +115,7 @@ class MoveWorker def carry_mutes_over! @source_account.muted_by_relationships.where(account: Account.local).find_each do |mute| - MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) unless mute.account.muting?(@target_account) || mute.account.following?(@target_account) + MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) unless skip_mute_move?(mute) add_account_note_if_needed!(mute.account, 'move_handler.carry_mutes_over_text') rescue => e @deferred_error = e @@ -130,4 +130,12 @@ class MoveWorker AccountNote.create!(account: account, target_account: @target_account, comment: text) end end + + def skip_mute_move?(mute) + mute.account.muting?(@target_account) || mute.account.following?(@target_account) + end + + def skip_block_move?(block) + block.account.blocking?(@target_account) || block.account.following?(@target_account) + end end From 916cc1365eb5cdd9bd021e26eb13937c02d7cdb8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:52:59 -0400 Subject: [PATCH 145/660] Fix `Style/GuardClause` in `User#wrap_email_confirmation` (#35524) --- app/models/user.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 762522f2822..781a3482163 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -466,16 +466,17 @@ class User < ApplicationRecord yield - if new_user - # Avoid extremely unlikely race condition when approving and confirming - # the user at the same time - reload unless approved? + after_confirmation_tasks if new_user + end - if approved? - prepare_new_user! - else - notify_staff_about_pending_account! - end + def after_confirmation_tasks + # Handle condition when approving and confirming a user at the same time + reload unless approved? + + if approved? + prepare_new_user! + else + notify_staff_about_pending_account! end end From eb73ae2f86d5dfe8dd328ecac77e93a857b5eeef Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:53:52 -0400 Subject: [PATCH 146/660] Fix `Style/GuardClause` in `User#regenerate_feed!` (#35523) --- app/models/user.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 781a3482163..2ba8c2926d9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -540,10 +540,10 @@ class User < ApplicationRecord def regenerate_feed! home_feed = HomeFeed.new(account) - unless home_feed.regenerating? - home_feed.regeneration_in_progress! - RegenerationWorker.perform_async(account_id) - end + return if home_feed.regenerating? + + home_feed.regeneration_in_progress! + RegenerationWorker.perform_async(account_id) end def needs_feed_update? From 040a638ab953f919c3ea9836e541054d59ce5b3d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 04:54:29 -0400 Subject: [PATCH 147/660] Fix `Style/GuardClause` in `Tag` (#35522) --- app/models/tag.rb | 9 +++++---- spec/models/tag_spec.rb | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/tag.rb b/app/models/tag.rb index 8e21ddca82b..dff10111123 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -164,9 +164,10 @@ class Tag < ApplicationRecord end def validate_display_name_change - unless HashtagNormalizer.new.normalize(display_name).casecmp(name).zero? - errors.add(:display_name, - I18n.t('tags.does_not_match_previous_name')) - end + errors.add(:display_name, I18n.t('tags.does_not_match_previous_name')) unless display_name_matches_name? + end + + def display_name_matches_name? + HashtagNormalizer.new.normalize(display_name).casecmp(name).zero? end end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 0831ac34b8b..18378c000d2 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Tag do subject { Fabricate :tag, name: 'original' } it { is_expected.to_not allow_value('changed').for(:name).with_message(previous_name_error_message) } + it { is_expected.to allow_value('ORIGINAL').for(:name) } end end @@ -31,6 +32,7 @@ RSpec.describe Tag do subject { Fabricate :tag, name: 'original', display_name: 'OriginalDisplayName' } it { is_expected.to_not allow_value('ChangedDisplayName').for(:display_name).with_message(previous_name_error_message) } + it { is_expected.to allow_value('ORIGINAL').for(:display_name) } end end From 513b6289d6293537edaff889cf88e8baaf6c2ca7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 11:34:14 +0200 Subject: [PATCH 148/660] chore(deps): update dependency strong_migrations to v2.5.0 (#35560) 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 dab65477bf8..8c537e52b91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -860,7 +860,7 @@ GEM stoplight (4.1.1) redlock (~> 1.0) stringio (3.1.7) - strong_migrations (2.4.0) + strong_migrations (2.5.0) activerecord (>= 7.1) swd (2.0.3) activesupport (>= 3) From b5cebf45ea122c7516ffc1e4b2d7214aa1cd376f Mon Sep 17 00:00:00 2001 From: Colin Dean Date: Mon, 28 Jul 2025 07:33:11 -0400 Subject: [PATCH 149/660] Swap order of translation restoration and service credit on post card (#33619) --- app/javascript/mastodon/components/status_content.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index e1fd7734e9e..5f0f7079aec 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -48,13 +48,13 @@ class TranslateButton extends PureComponent { return (
-
- -
- + +
+ +
); } From 8d6f033326e3db91d879dd950f589b10313394b7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 08:55:05 -0400 Subject: [PATCH 150/660] Fix `Style/GuardClause` in move worker (#35520) --- app/workers/move_worker.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 667efd69161..58d20ba94b3 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -123,12 +123,12 @@ class MoveWorker end def add_account_note_if_needed!(account, id) - unless AccountNote.exists?(account: account, target_account: @target_account) - text = I18n.with_locale(account.user_locale.presence || I18n.default_locale) do - I18n.t(id, acct: @source_account.acct) - end - AccountNote.create!(account: account, target_account: @target_account, comment: text) + return if AccountNote.exists?(account: account, target_account: @target_account) + + text = I18n.with_locale(account.user_locale.presence || I18n.default_locale) do + I18n.t(id, acct: @source_account.acct) end + AccountNote.create!(account: account, target_account: @target_account, comment: text) end def skip_mute_move?(mute) From f1b9868980b1b86da7034b988b9e62536bdf182f Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 28 Jul 2025 15:25:16 +0200 Subject: [PATCH 151/660] Bypass registration checks for seeded admin user (#35565) --- db/seeds/04_admin.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/db/seeds/04_admin.rb b/db/seeds/04_admin.rb index 887b4a22130..43290c47a46 100644 --- a/db/seeds/04_admin.rb +++ b/db/seeds/04_admin.rb @@ -7,7 +7,17 @@ if Rails.env.development? admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') admin.save(validate: false) - user = User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true) + user = User.where(email: "admin@#{domain}").first_or_initialize( + email: "admin@#{domain}", + password: 'mastodonadmin', + password_confirmation: 'mastodonadmin', + confirmed_at: Time.now.utc, + role: UserRole.find_by(name: 'Owner'), + account: admin, + agreement: true, + approved: true, + bypass_registration_checks: true + ) user.save! user.approve! end From c587c44975b0a4a2cdf1f8589d14a4228bfb27f0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 28 Jul 2025 11:35:37 -0400 Subject: [PATCH 152/660] Fix `Lint/NonLocalExitFromIterator` cop in JSON-LD helper (#34948) --- .rubocop_todo.yml | 4 ---- app/helpers/json_ld_helper.rb | 2 +- spec/helpers/json_ld_helper_spec.rb | 8 ++++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9e69426fcf7..8706ca0ddda 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,10 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -Lint/NonLocalExitFromIterator: - Exclude: - - 'app/helpers/json_ld_helper.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 82 diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/json_ld_helper.rb index 078aba456ae..675d8b87309 100644 --- a/app/helpers/json_ld_helper.rb +++ b/app/helpers/json_ld_helper.rb @@ -134,7 +134,7 @@ module JsonLdHelper patch_for_forwarding!(value, compacted_value) elsif value.is_a?(Array) compacted_value = [compacted_value] unless compacted_value.is_a?(Array) - return if value.size != compacted_value.size + return nil if value.size != compacted_value.size compacted[key] = value.zip(compacted_value).map do |v, vc| if v.is_a?(Hash) && vc.is_a?(Hash) diff --git a/spec/helpers/json_ld_helper_spec.rb b/spec/helpers/json_ld_helper_spec.rb index d76c5167a7d..f216588d978 100644 --- a/spec/helpers/json_ld_helper_spec.rb +++ b/spec/helpers/json_ld_helper_spec.rb @@ -180,6 +180,14 @@ RSpec.describe JsonLdHelper do expect(compacted.dig('object', 'tag', 0, 'href')).to eq ['foo'] expect(safe_for_forwarding?(json, compacted)).to be true end + + context 'when array size mismatch exists' do + subject { helper.patch_for_forwarding!(json, alternate) } + + let(:alternate) { json.merge('to' => %w(one two three)) } + + it { is_expected.to be_nil } + end end describe 'safe_for_forwarding?' do From bedbab74b98e5d2872b6b02461aac3dd54504ec5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 29 Jul 2025 04:22:04 -0400 Subject: [PATCH 153/660] Use bundler version 2.7.1 (#35567) --- Gemfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8c537e52b91..b8813b7211c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,7 +96,7 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.0) - aws-partitions (1.1131.0) + aws-partitions (1.1135.0) aws-sdk-core (3.215.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -233,7 +233,7 @@ GEM fabrication (3.0.0) faker (3.5.2) i18n (>= 1.8.11, < 2) - faraday (2.13.2) + faraday (2.13.4) faraday-net_http (>= 2.0, < 3.5) json logger @@ -345,7 +345,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.13.0) + json (2.13.2) json-canonicalization (1.0.0) json-jwt (1.16.7) activesupport (>= 4.2) @@ -438,7 +438,7 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0715) + mime-types-data (3.2025.0722) mini_mime (1.1.5) mini_portile2 (2.8.9) minitest (5.25.5) @@ -601,13 +601,13 @@ GEM ox (2.14.23) bigdecimal (>= 3.0) parallel (1.27.0) - parser (3.3.8.0) + parser (3.3.9.0) ast (~> 2.4.1) racc parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.5.9) + pg (1.6.0) pghero (3.7.0) activerecord (>= 7.1) playwright-ruby-client (1.54.0) @@ -731,7 +731,7 @@ GEM railties (>= 5.2) rexml (3.4.1) rotp (6.3.0) - rouge (4.5.2) + rouge (4.6.0) rpam2 (4.0.2) rqrcode (3.1.0) chunky_png (~> 1.0) @@ -868,7 +868,7 @@ GEM faraday (~> 2.0) faraday-follow_redirects sysexits (1.2.0) - temple (0.10.3) + temple (0.10.4) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) terrapin (1.1.1) @@ -1108,4 +1108,4 @@ RUBY VERSION ruby 3.4.1p0 BUNDLED WITH - 2.7.0 + 2.7.1 From ea976a5ffb3dd936b5d20c783073c10194bc704d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 29 Jul 2025 04:23:19 -0400 Subject: [PATCH 154/660] Fix unnecessary account note addition for already-muted moved-to users (#35566) --- app/workers/move_worker.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/workers/move_worker.rb b/app/workers/move_worker.rb index 58d20ba94b3..1a5745a86ae 100644 --- a/app/workers/move_worker.rb +++ b/app/workers/move_worker.rb @@ -115,8 +115,10 @@ class MoveWorker def carry_mutes_over! @source_account.muted_by_relationships.where(account: Account.local).find_each do |mute| - MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) unless skip_mute_move?(mute) - add_account_note_if_needed!(mute.account, 'move_handler.carry_mutes_over_text') + unless skip_mute_move?(mute) + MuteService.new.call(mute.account, @target_account, notifications: mute.hide_notifications) + add_account_note_if_needed!(mute.account, 'move_handler.carry_mutes_over_text') + end rescue => e @deferred_error = e end From d299b0d5765f23f33078053aff8ac8f484cb85b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:59:00 +0200 Subject: [PATCH 155/660] New Crowdin Translations (automated) (#35574) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/fa.json | 8 +++-- app/javascript/mastodon/locales/pt-BR.json | 7 ++++ app/javascript/mastodon/locales/sv.json | 2 ++ config/locales/devise.ru.yml | 4 +-- config/locales/fa.yml | 9 +++--- config/locales/ga.yml | 1 + config/locales/ru.yml | 37 +++++++++++++--------- config/locales/simple_form.fa.yml | 7 ++-- config/locales/sv.yml | 1 + 9 files changed, 50 insertions(+), 26 deletions(-) diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index c35687e0317..0217d7bb2fb 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -235,7 +235,7 @@ "confirmations.logout.message": "مطمئنید می‌خواهید خارج شوید؟", "confirmations.logout.title": "خروج؟", "confirmations.missing_alt_text.confirm": "متن جایگزین را اضافه کنید", - "confirmations.missing_alt_text.message": "پست شما حاوی رسانه بدون متن جایگزین است. افزودن توضیحات کمک می کند تا محتوای شما برای افراد بیشتری قابل دسترسی باشد.", + "confirmations.missing_alt_text.message": "فرسته‌تان رسانه‌هایی بدون متن جایگزین دارد. افزودن شرح به دسترس‌پذیر شدن محتوایتان برای افراد بیش‌تری کمک می‌کند.", "confirmations.missing_alt_text.secondary": "به هر حال پست کن", "confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟", "confirmations.mute.confirm": "خموش", @@ -424,7 +424,7 @@ "hints.profiles.see_more_followers": "دیدن پی‌گیرندگان بیش‌تر روی {domain}", "hints.profiles.see_more_follows": "دیدن پی‌گرفته‌های بیش‌تر روی {domain}", "hints.profiles.see_more_posts": "دیدن فرسته‌های بیش‌تر روی {domain}", - "home.column_settings.show_quotes": "نمایش نقل‌قول‌ها", + "home.column_settings.show_quotes": "نمایش نقل‌ها", "home.column_settings.show_reblogs": "نمایش تقویت‌ها", "home.column_settings.show_replies": "نمایش پاسخ‌ها", "home.hide_announcements": "نهفتن اعلامیه‌ها", @@ -845,6 +845,8 @@ "status.bookmark": "نشانک", "status.cancel_reblog_private": "ناتقویت", "status.cannot_reblog": "این فرسته قابل تقویت نیست", + "status.context.load_new_replies": "پاسخ‌های جدیدی موجودند", + "status.context.loading": "بررسی کردن برای پاسخ‌های بیش‌تر", "status.continued_thread": "رشتهٔ دنباله دار", "status.copy": "رونوشت از پیوند فرسته", "status.delete": "حذف", @@ -873,7 +875,7 @@ "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", "status.quote_error.not_found": "این فرسته قابل نمایش نیست.", "status.quote_error.pending_approval": "این فرسته منظر تأیید نگارندهٔ اصلی است.", - "status.quote_error.rejected": "از آن‌جا که نگارندهٔ اصلی فرسته اجازهٔ نقل قولش را نمی‌دهد این فرسته قابل نمایش نیست.", + "status.quote_error.rejected": "از آن‌جا که نگارندهٔ اصلی این فرسته اجازهٔ نقلش را نمی‌دهد قابل نمایش نیست.", "status.quote_error.removed": "این فرسته به دست نگارنده‌اش برداشته شده.", "status.quote_error.unauthorized": "از آن‌جا که اجازهٔ دیدن این فرسته را ندارید قابل نمایش نیست.", "status.quote_post_author": "فرسته توسط {name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index f5762701939..d00f44495d8 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -224,6 +224,8 @@ "confirmations.discard_draft.edit.message": "Continuar vai descartar quaisquer mudanças feitas ao post sendo editado.", "confirmations.discard_draft.edit.title": "Descartar mudanças no seu post?", "confirmations.discard_draft.post.cancel": "Continuar rascunho", + "confirmations.discard_draft.post.message": "Continuar eliminará a publicação que está sendo elaborada no momento.", + "confirmations.discard_draft.post.title": "Eliminar seu esboço de publicação?", "confirmations.discard_edit_media.confirm": "Descartar", "confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?", "confirmations.follow_to_list.confirm": "Seguir e adicionar à lista", @@ -333,9 +335,13 @@ "errors.unexpected_crash.copy_stacktrace": "Copiar dados do erro para área de transferência", "errors.unexpected_crash.report_issue": "Reportar problema", "explore.suggested_follows": "Pessoas", + "explore.title": "Em alta", "explore.trending_links": "Notícias", "explore.trending_statuses": "Publicações", "explore.trending_tags": "Hashtags", + "featured_carousel.header": "{count, plural, one {Postagem fixada} other {Postagens fixadas}}", + "featured_carousel.next": "Próximo", + "featured_carousel.previous": "Anterior", "filter_modal.added.context_mismatch_explanation": "Esta categoria de filtro não se aplica ao contexto no qual você acessou esta publicação. Se quiser que a publicação seja filtrada nesse contexto também, você terá que editar o filtro.", "filter_modal.added.context_mismatch_title": "Incompatibilidade de contexto!", "filter_modal.added.expired_explanation": "Esta categoria de filtro expirou, você precisará alterar a data de expiração para aplicar.", @@ -550,6 +556,7 @@ "navigation_bar.lists": "Listas", "navigation_bar.logout": "Sair", "navigation_bar.moderation": "Moderação", + "navigation_bar.more": "Mais", "navigation_bar.mutes": "Usuários silenciados", "navigation_bar.opened_in_classic_interface": "Publicações, contas e outras páginas específicas são abertas por padrão na interface 'web' clássica.", "navigation_bar.preferences": "Preferências", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 48f15b28b88..1697b0dcb07 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -845,6 +845,8 @@ "status.bookmark": "Bokmärk", "status.cancel_reblog_private": "Sluta boosta", "status.cannot_reblog": "Detta inlägg kan inte boostas", + "status.context.load_new_replies": "Nya svar finns", + "status.context.loading": "Letar efter fler svar", "status.continued_thread": "Fortsatt tråd", "status.copy": "Kopiera inläggslänk", "status.delete": "Radera", diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml index 79a988f1898..cc95f730615 100644 --- a/config/locales/devise.ru.yml +++ b/config/locales/devise.ru.yml @@ -53,7 +53,7 @@ ru: subtitle: Двухфакторная аутентификация отключена для вашей учетной записи. title: 2FA отключена two_factor_enabled: - explanation: Для входа потребуется одноразовый код, сгенерированный сопряжённым приложением TOTP. + explanation: Для входа потребуется одноразовый код, сгенерированный сопряжённым приложением-аутентификатором. subject: 'Mastodon: Двухфакторная аутентификация включена' subtitle: Двухфакторная аутентификация включена для вашей учётной записи. title: 2FA включена @@ -75,7 +75,7 @@ ru: title: Один из ваших электронных ключей удалён webauthn_disabled: explanation: Аутентификация по электронным ключам деактивирована для вашей учетной записи. - extra: Теперь вход возможен с использованием только лишь одноразового кода, сгенерированного сопряжённым приложением TOTP. + extra: Теперь вход возможен с использованием только с помощью одноразового кода, сгенерированного сопряжённым приложением-аутентификатором. subject: 'Mastodon: Аутентификация по электронным ключам деактивирована' title: Вход по электронным ключам деактивирован webauthn_enabled: diff --git a/config/locales/fa.yml b/config/locales/fa.yml index ec0ed1ca997..b9d87d600f2 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -653,8 +653,8 @@ fa: mark_as_sensitive_description_html: رسانهٔ درون فرستهٔ گزارش شده به عنوان حسّاس علامت خورده و شکایتی ضبط خواهد شد تا بتوانید خلاف‌های آینده از همین حساب را بهتر مدیریت کنید. other_description_html: دیدن انتخاب های بیشتر برای کنترل رفتار حساب و سفارشی سازی ارتباط با حساب گزارش شده. resolve_description_html: هیچ کنشی علیه حساب گزارش شده انجام نخواهد شد. هیچ شکایتی ضبط نشده و گزارش بسته خواهد شد. - silence_description_html: این حساب فقط برای کسانی قابل مشاهده خواهد بود که قبلاً آن را دنبال می کنند یا به صورت دستی آن را جستجو می کنند و دسترسی آن را به شدت محدود می کند. همیشه می توان برگرداند. همه گزارش‌های مربوط به این حساب را می‌بندد. - suspend_description_html: اکانت و تمامی محتویات آن غیرقابل دسترسی و در نهایت حذف خواهد شد و تعامل با آن غیر ممکن خواهد بود. قابل برگشت در عرض 30 روز همه گزارش‌های مربوط به این حساب را می‌بندد. + silence_description_html: حساب فقط برای کسانی که از پیش پی می‌گرفتندش یا به صورت دستی به دنیالش گشته‌اند نمایان خواهد بود که رسشش را شدیداً محدود می‌کند. همواره برگشت‌پذیر است. همهٔ گزارش‌ها علیه این حساب را خواهد بست. + suspend_description_html: حساب و همهٔ محتوایش غیرقابل دسترس شده و در نهایت حذف خواهند شد. تعامل با آن ممکن نخواهد بود. بازگشت‌پذیر تا ۳۰ روز. همهٔ گزارش‌ها علیه این حساب را خواهد بست. actions_description_html: تصمیم گیری کنش اقدامی برای حل این گزارش. در صورت انجام کنش تنبیهی روی حساب گزارش شده، غیر از زمان یکه دستهٔ هرزنامه گزیده باشد، برایش آگاهی رایانامه‌ای فرستاده خواهد شد. actions_description_remote_html: تصمیم بگیرید که چه اقدامی برای حل این گزارش انجام دهید. این فقط بر نحوه ارتباط سرور شما با این حساب راه دور و مدیریت محتوای آن تأثیر می گذارد. actions_no_posts: این گزارش هیچ پست مرتبطی برای حذف ندارد @@ -714,7 +714,7 @@ fa: actions: delete_html: پست های توهین آمیز را حذف کنید mark_as_sensitive_html: رسانه پست های توهین آمیز را به عنوان حساس علامت گذاری کنید - silence_html: دسترسی @%{acct} را به شدت محدود کنید و نمایه و محتویات آنها را فقط برای افرادی که قبلاً آنها را دنبال می‌کنند قابل مشاهده کنید یا به صورت دستی نمایه آن را جستجو کنید + silence_html: محدودیت شدید رسش ‪@%{acct}‬ با نمایان کردن نماگر و محتوایش فقط به افرادی که از پیش پی می‌گرفتندش و به صورت دستی به دنبالش گشته‌اند suspend_html: تعلیق @%{acct}، غیرقابل دسترس کردن نمایه و محتوای آنها و تعامل با آنها غیر ممکن close_report: 'علامت گذاری گزارش #%{id} به عنوان حل شده است' close_reports_html: "همه گزارش‌ها در برابر @%{acct} را به‌عنوان حل‌وفصل علامت‌گذاری کنید" @@ -1872,6 +1872,7 @@ fa: edited_at_html: ویراسته در %{date} errors: in_reply_not_found: به نظر نمی‌رسد وضعیتی که می‌خواهید به آن پاسخ دهید، وجود داشته باشد. + quoted_status_not_found: به نظر نمی‌رسد فرسته‌ای که می‌خواهید نقلش کنید وجود داشته باشد. over_character_limit: از حد مجاز %{max} حرف فراتر رفتید pin_errors: direct: فرسته‌هایی که فقط برای کاربران اشاره شده نمایانند نمی‌توانند سنجاق شوند @@ -2002,7 +2003,7 @@ fa: details: 'جزییات ورود:' explanation: ما ورود به حساب شما را از یک آدرس آی پی جدید شناسایی کرده ایم. further_actions_html: اگر این شما نبودید، توصیه می کنیم فورا %{action} را فعال کنید و برای ایمن نگه داشتن حساب خود، احراز هویت دو مرحله ای را فعال کنید. - subject: حساب شما از یک آدرس آی پی جدید قابل دسترسی است + subject: نشانی آی‌پی جدیدی به حسابتان دسترسی پیدا کرده title: یک ورود جدید terms_of_service_changed: agreement: با ادامه استفاده از %{domain}، با این شرایط موافقت می کنید. اگر با شرایط به‌روزرسانی شده مخالف هستید، می‌توانید در هر زمان با حذف حساب خود، قرارداد خود را با %{domain} فسخ کنید. diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 111ae9c56f0..8fff68b3d97 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -2001,6 +2001,7 @@ ga: edited_at_html: "%{date} curtha in eagar" errors: in_reply_not_found: Is cosúil nach ann don phostáil a bhfuil tú ag iarraidh freagra a thabhairt air. + quoted_status_not_found: Is cosúil nach bhfuil an post atá tú ag iarraidh a lua ann. over_character_limit: teorainn carachtar %{max} sáraithe pin_errors: direct: Ní féidir postálacha nach bhfuil le feiceáil ach ag úsáideoirí luaite a phinnáil diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 4cc2e15b5a7..ad4b9d5122e 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1611,7 +1611,7 @@ ru: limit: Вы достигли максимального количества списков login_activities: authentication_methods: - otp: приложения двухфакторной аутентификации + otp: приложения для генерации кодов password: пароля webauthn: электронного ключа description_html: Если вы заметили действия, которых не совершали, вам следует сменить пароль и включить двухфакторную аутентификацию. @@ -1621,11 +1621,18 @@ ru: title: История входов mail_subscriptions: unsubscribe: - action: Да, отписаться + action: Да, я хочу отписаться complete: Подписка отменена - confirmation_html: Вы точно желаете отписаться от всех уведомления типа «%{type}», доставляемых из сервера Mastodon %{domain} на ваш адрес электронной почты %{email}? Вы всегда сможете подписаться снова в настройках e-mail уведомлений. - resubscribe_html: Если вы отписались от рассылки по ошибке, вы можете повторно подписаться на рассылку в настройках настроек почтовых уведомлений. - success_html: Вы больше не будете получать %{type} для Mastodon на %{domain} на вашу электронную почту %{email}. + confirmation_html: Вы уверены в том, что хотите отписаться от всех %{type}, которые вы получаете на адрес %{email} для учётной записи на сервере Mastodon %{domain}? Вы всегда сможете подписаться снова в настройках уведомлений по электронной почте. + emails: + notification_emails: + favourite: уведомлений о добавлении ваших постов в избранное + follow: уведомлений о новых подписчиках + follow_request: уведомлений о новых запросах на подписку + mention: уведомлений о новых упоминаниях + reblog: уведомлений о продвижении ваших постов + resubscribe_html: Если вы отписались по ошибке и хотите подписаться снова, перейдите на страницу настройки уведомлений по электронной почте. + success_html: Вы отказались от %{type}, которые вы получали на адрес %{email} для вашей учётной записи на сервере Mastodon %{domain}. title: Отписаться media_attachments: validations: @@ -1721,13 +1728,13 @@ ru: trillion: трлн unit: '' otp_authentication: - code_hint: Для подтверждения введите код, сгенерированный приложением-аутентификатором - description_html: Подключив двуфакторную авторизацию, для входа в свою учётную запись вам будет необходим смартфон и приложение-аутентификатор на нём, которое будет генерировать специальные временные коды. Без этих кодов войти в учётную запись не получиться, даже если все данные верны, что существенно увеличивает безопасность вашей учётной записи. + code_hint: Для подтверждения введите код из приложения-аутентификатора + description_html: Подключите двухфакторную аутентификацию с использованием специального приложения-аутентификатора, и тогда для входа в вашу учётную запись необходимо будет иметь при себе смартфон, который будет генерировать одноразовые коды. enable: Включить - instructions_html: "Отсканируйте этот QR-код с помощью приложения-аутентификатора, такого как Google Authenticator, Яндекс.Ключ или andOTP. После сканирования и добавления, приложение начнёт генерировать коды, которые потребуется вводить для завершения входа в учётную запись." - manual_instructions: 'Если отсканировать QR-код не получается или не представляется возможным, вы можете ввести ключ настройки вручную:' - setup: Настроить - wrong_code: Введенный код недействителен! Время сервера и время устройства правильно? + instructions_html: "Откройте Google Authenticator или другое приложение-аутентификатор на вашем смартфоне и отсканируйте этот QR-код. В дальнейшем это приложение будет генерировать одноразовые коды, которые потребуется вводить для подтверждения входа в вашу учётную запись." + manual_instructions: 'Если отсканировать QR-код не получается, введите секретный ключ вручную:' + setup: Подключить + wrong_code: Одноразовый код, который вы ввели, не подходит! Совпадает ли время на устройстве с временем на сервере? pagination: newer: Позже next: Вперёд @@ -1751,14 +1758,14 @@ ru: posting_defaults: Предустановки для новых постов public_timelines: Публичные ленты privacy: - hint_html: "Настройте, как вы хотите, чтобы ваш профиль и ваши сообщения были найдены. Различные функции в Mastodon могут помочь вам охватить более широкую аудиторию, если они включены. Уделите время изучению этих настроек, чтобы убедиться, что они подходят для вашего случая использования." + hint_html: "Здесь вы можете определить то, как другие смогут обнаружить ваши посты и ваш профиль. Множество разных функций в Mastodon существуют для того, чтобы помочь вам выйти на более широкую аудиторию, если вы того захотите. Ознакомьтесь с этими настройками и в случае необходимости измените их согласно вашим желаниям." privacy: Приватность - privacy_hint_html: Определите, какую информацию вы хотите раскрыть в интересах других. Люди находят интересные профили и приложения, просматривая список подписчиков других людей и узнавая, из каких приложений они публикуют свои сообщения, но вы можете предпочесть скрыть это. + privacy_hint_html: Решите, сколько данных о себе вы готовы раскрыть ради того, чтобы они пошли на пользу другим. Просматривая ваши подписки, кто-то может обнаружить профили интересных людей, а ещё кто-нибудь может найти своё любимое приложение, увидев его название рядом с вашими постами. Тем не менее вы можете предпочесть не раскрывать эту информацию. reach: Видимость - reach_hint_html: Укажите, хотите ли вы, чтобы новые люди обнаруживали вас и могли следить за вами. Хотите ли вы, чтобы ваши сообщения появлялись на экране Обзора? Хотите ли вы, чтобы другие люди видели вас в своих рекомендациях? Хотите ли вы автоматически принимать всех новых подписчиков или иметь возможность детально контролировать каждого из них? + reach_hint_html: Решите, нужна ли вам новая аудитория и новые подписчики. Настройте по своему желанию, показывать ли ваши посты в разделе «Актуальное», рекомендовать ли ваш профиль другим людям, принимать ли всех новых подписчиков автоматически или рассматривать каждый запрос на подписку в отдельности. search: Поиск search_hint_html: Определите, как вас могут найти. Хотите ли вы, чтобы люди находили вас по тому, о чём вы публично писали? Хотите ли вы, чтобы люди за пределами Mastodon находили ваш профиль при поиске в Интернете? Следует помнить, что полное исключение из всех поисковых систем не может быть гарантировано для публичной информации. - title: Приватность и доступ + title: Приватность и видимость privacy_policy: title: Политика конфиденциальности reactions: diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index f80097832ca..bc7c4703da0 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -56,7 +56,7 @@ fa: scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید. setting_aggregate_reblogs: برای تقویت‌هایی که به تازگی برایتان نمایش داده شده‌اند، تقویت‌های بیشتر را نمایش نده (فقط روی تقویت‌های اخیر تأثیر می‌گذارد) setting_always_send_emails: در حالت عادی آگاهی‌های رایانامه‌ای هنگامی که فعّالانه از ماستودون استفاده می‌کنید فرستاده نمی‌شوند - setting_default_quote_policy: کاربران اشاره شده همواره مجاز به نقل قولند. این تنظیمات تنها روی فرسته‌های ایجاد شده با نگارش بعدی ماستودون موثّر است، ولی می‌توانید ترجیحاتتان را پیشاپیش بگزینید + setting_default_quote_policy: کاربران اشاره شده همواره مجاز به نقلند. این تنظیمات تنها روی فرسته‌های ایجاد شده با نگارش بعدی ماستودون موثّر است، ولی می‌توانید ترجیحاتتان را پیشاپیش بگزینید setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن @@ -150,6 +150,9 @@ fa: min_age: نباید کم‌تر از کمینهٔ زمان لازم از سوی قوانین حقوقیتان باشد. user: chosen_languages: اگر انتخاب کنید، تنها نوشته‌هایی که به زبان‌های برگزیدهٔ شما نوشته شده‌اند در فهرست نوشته‌های عمومی نشان داده می‌شوند + date_of_birth: + one: برای استفاده از %{domain} باید مطمئن شویم کمینه %{count} سال را دارید. این مورد را ذخیره نخواهیم کرد. + other: برای استفاده از %{domain} باید مطمئن شویم کمینه %{count} سال را دارید. این مورد را ذخیره نخواهیم کرد. role: نقش کنترل می کند که کاربر چه مجوزهایی دارد. user_role: color: رنگی که برای نقش در سرتاسر UI استفاده می شود، به عنوان RGB در قالب هگز @@ -230,7 +233,7 @@ fa: setting_boost_modal: نمایش پیغام تأیید پیش از تقویت کردن setting_default_language: زبان نوشته‌های شما setting_default_privacy: حریم خصوصی نوشته‌ها - setting_default_quote_policy: افراد مجاز به نقل قول + setting_default_quote_policy: افراد مجاز به نقل setting_default_sensitive: همیشه تصاویر را به عنوان حساس علامت بزن setting_delete_modal: نمایش پیغام تأیید پیش از پاک کردن یک نوشته setting_disable_hover_cards: از کار انداختن پیش‌نمایش نمایه هنگام رفتن رویش diff --git a/config/locales/sv.yml b/config/locales/sv.yml index ca23e350545..d406eabba32 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1872,6 +1872,7 @@ sv: edited_at_html: 'Ändrad: %{date}' errors: in_reply_not_found: Inlägget du försöker svara på verkar inte existera. + quoted_status_not_found: Inlägget du försöker svara på verkar inte existera. over_character_limit: teckengräns på %{max} har överskridits pin_errors: direct: Inlägg som endast är synliga för nämnda användare kan inte fästas From 3eca8cce1ce501aebcbf140e8c7fe90316d75ff6 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 29 Jul 2025 10:59:16 +0200 Subject: [PATCH 156/660] Add second set of blocked text that applies to accounts regardless of account age (#35563) --- app/lib/antispam.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/lib/antispam.rb b/app/lib/antispam.rb index 4ebf1924854..69c862a5c10 100644 --- a/app/lib/antispam.rb +++ b/app/lib/antispam.rb @@ -33,9 +33,7 @@ class Antispam end def local_preflight_check!(status) - return unless spammy_texts.any? { |spammy_text| status.text.include?(spammy_text) } - return unless suspicious_reply_or_mention?(status) - return unless status.account.created_at >= ACCOUNT_AGE_EXEMPTION.ago + return unless considered_spam?(status) report_if_needed!(status.account) @@ -44,10 +42,26 @@ class Antispam private + def considered_spam?(status) + (all_time_suspicious?(status) || recent_suspicious?(status)) && suspicious_reply_or_mention?(status) + end + + def all_time_suspicious?(status) + all_time_spammy_texts.any? { |spammy_text| status.text.include?(spammy_text) } + end + + def recent_suspicious?(status) + status.account.created_at >= ACCOUNT_AGE_EXEMPTION.ago && spammy_texts.any? { |spammy_text| status.text.include?(spammy_text) } + end + def spammy_texts redis.smembers('antispam:spammy_texts') end + def all_time_spammy_texts + redis.smembers('antispam:all_time_spammy_texts') + end + def suspicious_reply_or_mention?(status) parent = status.thread return true if parent.present? && !Follow.exists?(account_id: parent.account_id, target_account: status.account_id) From d121007927b59831937c0e8352fc394019f2fef9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 Jul 2025 11:00:27 +0200 Subject: [PATCH 157/660] Change "new replies available" notice to be above replies in web UI (#35575) --- .../status/components/refresh_controller.tsx | 16 +++------------- .../mastodon/features/status/index.jsx | 3 +-- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/refresh_controller.tsx b/app/javascript/mastodon/features/status/components/refresh_controller.tsx index 04046302b62..2765d5d9f41 100644 --- a/app/javascript/mastodon/features/status/components/refresh_controller.tsx +++ b/app/javascript/mastodon/features/status/components/refresh_controller.tsx @@ -2,8 +2,6 @@ import { useEffect, useState, useCallback } from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; -import classNames from 'classnames'; - import { fetchContext, completeContextRefresh, @@ -22,8 +20,7 @@ const messages = defineMessages({ export const RefreshController: React.FC<{ statusId: string; - withBorder?: boolean; -}> = ({ statusId, withBorder }) => { +}> = ({ statusId }) => { const refresh = useAppSelector( (state) => state.contexts.refreshing[statusId], ); @@ -78,12 +75,7 @@ export const RefreshController: React.FC<{ if (ready && !loading) { return ( -
- {descendants} {remoteHint} + {descendants}
From 8cf7a77808f69470b55faa762d172c34b5bbc9ff Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 Jul 2025 11:23:32 +0200 Subject: [PATCH 158/660] Fix async refresh never being finished when status cannot be fetched (#35500) --- app/controllers/api/v1/statuses_controller.rb | 6 +++++- .../concerns/status/fetch_replies_concern.rb | 2 +- app/models/worker_batch.rb | 15 +++++---------- .../activitypub/fetch_all_replies_service.rb | 2 +- app/services/activitypub/fetch_replies_service.rb | 7 ++----- .../activitypub/fetch_all_replies_worker.rb | 12 ++++++++++-- app/workers/fetch_reply_worker.rb | 2 +- spec/models/worker_batch_spec.rb | 10 +--------- 8 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index f047ba60466..57977e14b8a 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -66,7 +66,11 @@ class Api::V1::StatusesController < Api::BaseController add_async_refresh_header(async_refresh) elsif !current_account.nil? && @status.should_fetch_replies? add_async_refresh_header(AsyncRefresh.create(refresh_key)) - ActivityPub::FetchAllRepliesWorker.perform_async(@status.id) + + WorkerBatch.new.within do |batch| + batch.connect(refresh_key, threshold: 1.0) + ActivityPub::FetchAllRepliesWorker.perform_async(@status.id, { 'batch_id' => batch.id }) + end end render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) diff --git a/app/models/concerns/status/fetch_replies_concern.rb b/app/models/concerns/status/fetch_replies_concern.rb index cc117cb5ac6..7ab46481747 100644 --- a/app/models/concerns/status/fetch_replies_concern.rb +++ b/app/models/concerns/status/fetch_replies_concern.rb @@ -33,7 +33,7 @@ module Status::FetchRepliesConcern def should_fetch_replies? # we aren't brand new, and we haven't fetched replies since the debounce window - !local? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && ( + !local? && distributable? && created_at <= FETCH_REPLIES_INITIAL_WAIT_MINUTES.ago && ( fetched_replies_at.nil? || fetched_replies_at <= FETCH_REPLIES_COOLDOWN_MINUTES.ago ) end diff --git a/app/models/worker_batch.rb b/app/models/worker_batch.rb index 0a22ce61419..ab9d8d457b2 100644 --- a/app/models/worker_batch.rb +++ b/app/models/worker_batch.rb @@ -24,7 +24,7 @@ class WorkerBatch begin Thread.current[:batch] = self - yield + yield(self) ensure Thread.current[:batch] = nil end @@ -33,10 +33,7 @@ class WorkerBatch # Add jobs to the batch. Usually when the batch is created. # @param [Array] jids def add_jobs(jids) - if jids.blank? - finish! - return - end + return if jids.empty? redis.multi do |pipeline| pipeline.sadd(key('jobs'), jids) @@ -48,7 +45,7 @@ class WorkerBatch # Remove a job from the batch, such as when it's been processed or it has failed. # @param [String] jid - def remove_job(jid) + def remove_job(jid, increment: false) _, pending, processed, async_refresh_key, threshold = redis.multi do |pipeline| pipeline.srem(key('jobs'), jid) pipeline.hincrby(key, 'pending', -1) @@ -57,10 +54,8 @@ class WorkerBatch pipeline.hget(key, 'threshold') end - if async_refresh_key.present? - async_refresh = AsyncRefresh.new(async_refresh_key) - async_refresh.increment_result_count(by: 1) - end + async_refresh = AsyncRefresh.new(async_refresh_key) if async_refresh_key.present? + async_refresh&.increment_result_count(by: 1) if increment if pending.zero? || processed >= (threshold || 1.0).to_f * (processed + pending) async_refresh&.finish! diff --git a/app/services/activitypub/fetch_all_replies_service.rb b/app/services/activitypub/fetch_all_replies_service.rb index e9c1712ed66..b771b845265 100644 --- a/app/services/activitypub/fetch_all_replies_service.rb +++ b/app/services/activitypub/fetch_all_replies_service.rb @@ -6,7 +6,7 @@ class ActivityPub::FetchAllRepliesService < ActivityPub::FetchRepliesService # Limit of replies to fetch per status MAX_REPLIES = (ENV['FETCH_REPLIES_MAX_SINGLE'] || 500).to_i - def call(status_uri, collection_or_uri, max_pages: 1, async_refresh_key: nil, request_id: nil) + def call(status_uri, collection_or_uri, max_pages: 1, batch_id: nil, request_id: nil) @status_uri = status_uri super diff --git a/app/services/activitypub/fetch_replies_service.rb b/app/services/activitypub/fetch_replies_service.rb index 239df0ba584..327c88d846d 100644 --- a/app/services/activitypub/fetch_replies_service.rb +++ b/app/services/activitypub/fetch_replies_service.rb @@ -6,7 +6,7 @@ class ActivityPub::FetchRepliesService < BaseService # Limit of fetched replies MAX_REPLIES = 5 - def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, async_refresh_key: nil, request_id: nil) + def call(reference_uri, collection_or_uri, max_pages: 1, allow_synchronous_requests: true, batch_id: nil, request_id: nil) @reference_uri = reference_uri @allow_synchronous_requests = allow_synchronous_requests @@ -15,10 +15,7 @@ class ActivityPub::FetchRepliesService < BaseService @items = filter_replies(@items) - batch = WorkerBatch.new - batch.connect(async_refresh_key) if async_refresh_key.present? - batch.finish! if @items.empty? - batch.within do + WorkerBatch.new(batch_id).within do |batch| FetchReplyWorker.push_bulk(@items) do |reply_uri| [reply_uri, { 'request_id' => request_id, 'batch_id' => batch.id }] end diff --git a/app/workers/activitypub/fetch_all_replies_worker.rb b/app/workers/activitypub/fetch_all_replies_worker.rb index ab9eebc4ec7..14142b9cd5a 100644 --- a/app/workers/activitypub/fetch_all_replies_worker.rb +++ b/app/workers/activitypub/fetch_all_replies_worker.rb @@ -16,7 +16,9 @@ class ActivityPub::FetchAllRepliesWorker MAX_PAGES = (ENV['FETCH_REPLIES_MAX_PAGES'] || 500).to_i def perform(root_status_id, options = {}) + @batch = WorkerBatch.new(options['batch_id']) @root_status = Status.remote.find_by(id: root_status_id) + return unless @root_status&.should_fetch_replies? @root_status.touch(:fetched_replies_at) @@ -45,6 +47,8 @@ class ActivityPub::FetchAllRepliesWorker # Workers shouldn't be returning anything, but this is used in tests fetched_uris + ensure + @batch.remove_job(jid) end private @@ -53,9 +57,10 @@ class ActivityPub::FetchAllRepliesWorker # status URI, or the prefetched body of the Note object def get_replies(status, max_pages, options = {}) replies_collection_or_uri = get_replies_uri(status) + return if replies_collection_or_uri.nil? - ActivityPub::FetchAllRepliesService.new.call(value_or_id(status), replies_collection_or_uri, max_pages: max_pages, async_refresh_key: "context:#{@root_status.id}:refresh", **options.deep_symbolize_keys) + ActivityPub::FetchAllRepliesService.new.call(value_or_id(status), replies_collection_or_uri, max_pages: max_pages, **options.deep_symbolize_keys) end # Get the URI of the replies collection of a status @@ -78,9 +83,12 @@ class ActivityPub::FetchAllRepliesWorker # @param root_status_uri [String] def get_root_replies(root_status_uri, options = {}) root_status_body = fetch_resource(root_status_uri, true) + return if root_status_body.nil? - FetchReplyWorker.perform_async(root_status_uri, { **options.deep_stringify_keys, 'prefetched_body' => root_status_body }) + @batch.within do + FetchReplyWorker.perform_async(root_status_uri, { **options.deep_stringify_keys, 'prefetched_body' => root_status_body }) + end get_replies(root_status_body, MAX_PAGES, options) end diff --git a/app/workers/fetch_reply_worker.rb b/app/workers/fetch_reply_worker.rb index da3b9a8c131..42e38dc682f 100644 --- a/app/workers/fetch_reply_worker.rb +++ b/app/workers/fetch_reply_worker.rb @@ -10,6 +10,6 @@ class FetchReplyWorker batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id'] FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys) ensure - batch&.remove_job(jid) + batch&.remove_job(jid, increment: true) end end diff --git a/spec/models/worker_batch_spec.rb b/spec/models/worker_batch_spec.rb index b58dc48618a..7c6b8aa8c6d 100644 --- a/spec/models/worker_batch_spec.rb +++ b/spec/models/worker_batch_spec.rb @@ -42,14 +42,6 @@ RSpec.describe WorkerBatch do it 'does not persist the job IDs' do expect(subject.jobs).to eq [] end - - context 'when async refresh is connected' do - let(:async_refresh) { AsyncRefresh.new(async_refresh_key) } - - it 'immediately marks the async refresh as finished' do - expect(async_refresh.reload.finished?).to be true - end - end end context 'when called with an array of job IDs' do @@ -71,7 +63,7 @@ RSpec.describe WorkerBatch do before do subject.connect(async_refresh_key, threshold: 0.5) if async_refresh.present? subject.add_jobs(%w(foo bar baz)) - subject.remove_job('foo') + subject.remove_job('foo', increment: true) end it 'removes the job from pending jobs' do From 20bbd20ef175a5096f19675b7c58753cc6c86daa Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 Jul 2025 12:19:15 +0200 Subject: [PATCH 159/660] Add ability to block words in usernames (#35407) --- .../admin/username_blocks_controller.rb | 77 +++++++++++++++ app/helpers/admin/action_logs_helper.rb | 2 + .../supervised_user_circle_off-fill.svg | 1 + .../400-24px/supervised_user_circle_off.svg | 1 + app/models/admin/action_log_filter.rb | 3 + app/models/form/username_block_batch.rb | 31 ++++++ app/models/user.rb | 6 +- app/models/username_block.rb | 62 ++++++++++++ app/policies/username_block_policy.rb | 19 ++++ .../unreserved_username_validator.rb | 10 +- .../admin/username_blocks/_form.html.haml | 16 +++ .../username_blocks/_username_block.html.haml | 12 +++ .../admin/username_blocks/edit.html.haml | 10 ++ .../admin/username_blocks/index.html.haml | 26 +++++ app/views/admin/username_blocks/new.html.haml | 10 ++ config/i18n-tasks.yml | 2 + config/locales/en.yml | 25 +++++ config/locales/simple_form.en.yml | 8 ++ config/navigation.rb | 1 + config/routes/admin.rb | 6 ++ config/settings.yml | 22 ----- .../20250717003848_create_username_blocks.rb | 23 +++++ db/schema.rb | 13 ++- db/seeds/05_blocked_usernames.rb | 34 +++++++ spec/fabricators/username_block_fabricator.rb | 7 ++ spec/models/username_block_spec.rb | 63 ++++++++++++ spec/requests/admin/username_blocks_spec.rb | 97 +++++++++++++++++++ .../unreserved_username_validator_spec.rb | 7 +- 28 files changed, 560 insertions(+), 34 deletions(-) create mode 100644 app/controllers/admin/username_blocks_controller.rb create mode 100644 app/javascript/material-icons/400-24px/supervised_user_circle_off-fill.svg create mode 100644 app/javascript/material-icons/400-24px/supervised_user_circle_off.svg create mode 100644 app/models/form/username_block_batch.rb create mode 100644 app/models/username_block.rb create mode 100644 app/policies/username_block_policy.rb create mode 100644 app/views/admin/username_blocks/_form.html.haml create mode 100644 app/views/admin/username_blocks/_username_block.html.haml create mode 100644 app/views/admin/username_blocks/edit.html.haml create mode 100644 app/views/admin/username_blocks/index.html.haml create mode 100644 app/views/admin/username_blocks/new.html.haml create mode 100644 db/migrate/20250717003848_create_username_blocks.rb create mode 100644 db/seeds/05_blocked_usernames.rb create mode 100644 spec/fabricators/username_block_fabricator.rb create mode 100644 spec/models/username_block_spec.rb create mode 100644 spec/requests/admin/username_blocks_spec.rb diff --git a/app/controllers/admin/username_blocks_controller.rb b/app/controllers/admin/username_blocks_controller.rb new file mode 100644 index 00000000000..22ac9408178 --- /dev/null +++ b/app/controllers/admin/username_blocks_controller.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class Admin::UsernameBlocksController < Admin::BaseController + before_action :set_username_block, only: [:edit, :update] + + def index + authorize :username_block, :index? + @username_blocks = UsernameBlock.order(username: :asc).page(params[:page]) + @form = Form::UsernameBlockBatch.new + end + + def batch + authorize :username_block, :index? + + @form = Form::UsernameBlockBatch.new(form_username_block_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.username_blocks.no_username_block_selected') + rescue Mastodon::NotPermittedError + flash[:alert] = I18n.t('admin.username_blocks.not_permitted') + ensure + redirect_to admin_username_blocks_path + end + + def new + authorize :username_block, :create? + @username_block = UsernameBlock.new(exact: true) + end + + def edit + authorize @username_block, :update? + end + + def create + authorize :username_block, :create? + + @username_block = UsernameBlock.new(resource_params) + + if @username_block.save + log_action :create, @username_block + redirect_to admin_username_blocks_path, notice: I18n.t('admin.username_blocks.created_msg') + else + render :new + end + end + + def update + authorize @username_block, :update? + + if @username_block.update(resource_params) + log_action :update, @username_block + redirect_to admin_username_blocks_path, notice: I18n.t('admin.username_blocks.updated_msg') + else + render :new + end + end + + private + + def set_username_block + @username_block = UsernameBlock.find(params[:id]) + end + + def form_username_block_batch_params + params + .expect(form_username_block_batch: [username_block_ids: []]) + end + + def resource_params + params + .expect(username_block: [:username, :comparison, :allow_with_approval]) + end + + def action_from_button + 'delete' if params[:delete] + end +end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 859f9246876..4a55a36ecd1 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -13,6 +13,8 @@ module Admin::ActionLogsHelper end when 'UserRole' link_to log.human_identifier, admin_roles_path(log.target_id) + when 'UsernameBlock' + link_to log.human_identifier, edit_admin_username_block_path(log.target_id) when 'Report' link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id) when 'Instance', 'DomainBlock', 'DomainAllow', 'UnavailableDomain' diff --git a/app/javascript/material-icons/400-24px/supervised_user_circle_off-fill.svg b/app/javascript/material-icons/400-24px/supervised_user_circle_off-fill.svg new file mode 100644 index 00000000000..1daf50f858a --- /dev/null +++ b/app/javascript/material-icons/400-24px/supervised_user_circle_off-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/supervised_user_circle_off.svg b/app/javascript/material-icons/400-24px/supervised_user_circle_off.svg new file mode 100644 index 00000000000..060c515ae18 --- /dev/null +++ b/app/javascript/material-icons/400-24px/supervised_user_circle_off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index fd6b4289cee..a5bdd97420f 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -77,6 +77,9 @@ class Admin::ActionLogFilter update_user_role: { target_type: 'UserRole', action: 'update' }.freeze, update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze, unblock_email_account: { target_type: 'Account', action: 'unblock_email' }.freeze, + create_username_block: { target_type: 'UsernameBlock', action: 'create' }.freeze, + update_username_block: { target_type: 'UsernameBlock', action: 'update' }.freeze, + destroy_username_block: { target_type: 'UsernameBlock', action: 'destroy' }.freeze, }.freeze attr_reader :params diff --git a/app/models/form/username_block_batch.rb b/app/models/form/username_block_batch.rb new file mode 100644 index 00000000000..f490391159a --- /dev/null +++ b/app/models/form/username_block_batch.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Form::UsernameBlockBatch < Form::BaseBatch + attr_accessor :username_block_ids + + def save + case action + when 'delete' + delete! + end + end + + private + + def username_blocks + @username_blocks ||= UsernameBlock.where(id: username_block_ids) + end + + def delete! + verify_authorization(:destroy?) + + username_blocks.each do |username_block| + username_block.destroy + log_action :destroy, username_block + end + end + + def verify_authorization(permission) + username_blocks.each { |username_block| authorize(username_block, permission) } + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 2ba8c2926d9..0c876c64bf8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -443,7 +443,7 @@ class User < ApplicationRecord def set_approved self.approved = begin - if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval? + if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval? || sign_up_username_requires_approval? false else open_registrations? || valid_invitation? || external? @@ -499,6 +499,10 @@ class User < ApplicationRecord EmailDomainBlock.requires_approval?(records + [domain], attempt_ip: sign_up_ip) end + def sign_up_username_requires_approval? + account.username? && UsernameBlock.matches?(account.username, allow_with_approval: true) + end + def open_registrations? Setting.registrations_mode == 'open' end diff --git a/app/models/username_block.rb b/app/models/username_block.rb new file mode 100644 index 00000000000..227def66e14 --- /dev/null +++ b/app/models/username_block.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: username_blocks +# +# id :bigint(8) not null, primary key +# allow_with_approval :boolean default(FALSE), not null +# exact :boolean default(FALSE), not null +# normalized_username :string not null +# username :string not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class UsernameBlock < ApplicationRecord + HOMOGLYPHS = { + '1' => 'i', + '2' => 'z', + '3' => 'e', + '4' => 'a', + '5' => 's', + '7' => 't', + '8' => 'b', + '9' => 'g', + '0' => 'o', + }.freeze + + validates :username, presence: true, uniqueness: true + + scope :matches_exactly, ->(str) { where(exact: true).where(normalized_username: str) } + scope :matches_partially, ->(str) { where(exact: false).where(Arel::Nodes.build_quoted(str).matches(Arel::Nodes.build_quoted('%').concat(arel_table[:normalized_username]).concat(Arel::Nodes.build_quoted('%')))) } + + before_save :set_normalized_username + + def comparison + exact? ? 'equals' : 'contains' + end + + def comparison=(val) + self.exact = val == 'equals' + end + + def self.matches?(str, allow_with_approval: false) + normalized_str = str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) + where(allow_with_approval: allow_with_approval).matches_exactly(normalized_str).or(matches_partially(normalized_str)).any? + end + + def to_log_human_identifier + username + end + + private + + def set_normalized_username + self.normalized_username = normalize(username) + end + + def normalize(str) + str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) + end +end diff --git a/app/policies/username_block_policy.rb b/app/policies/username_block_policy.rb new file mode 100644 index 00000000000..9f6b8cfc018 --- /dev/null +++ b/app/policies/username_block_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class UsernameBlockPolicy < ApplicationPolicy + def index? + role.can?(:manage_blocks) + end + + def create? + role.can?(:manage_blocks) + end + + def update? + role.can?(:manage_blocks) + end + + def destroy? + role.can?(:manage_blocks) + end +end diff --git a/app/validators/unreserved_username_validator.rb b/app/validators/unreserved_username_validator.rb index 55a8c835fae..f20f4a7494b 100644 --- a/app/validators/unreserved_username_validator.rb +++ b/app/validators/unreserved_username_validator.rb @@ -28,14 +28,6 @@ class UnreservedUsernameValidator < ActiveModel::Validator end def settings_username_reserved? - settings_has_reserved_usernames? && settings_reserves_username? - end - - def settings_has_reserved_usernames? - Setting.reserved_usernames.present? - end - - def settings_reserves_username? - Setting.reserved_usernames.include?(@username.downcase) + UsernameBlock.matches?(@username, allow_with_approval: false) end end diff --git a/app/views/admin/username_blocks/_form.html.haml b/app/views/admin/username_blocks/_form.html.haml new file mode 100644 index 00000000000..bfbbb18e2f9 --- /dev/null +++ b/app/views/admin/username_blocks/_form.html.haml @@ -0,0 +1,16 @@ +.fields-group + = form.input :username, + wrapper: :with_block_label, + input_html: { autocomplete: 'new-password', pattern: '[a-zA-Z0-9_]+', maxlength: Account::USERNAME_LENGTH_LIMIT } + +.fields-group + = form.input :comparison, + as: :select, + wrapper: :with_block_label, + collection: %w(equals contains), + include_blank: false, + label_method: ->(type) { I18n.t(type, scope: 'admin.username_blocks.comparison') } + +.fields-group + = form.input :allow_with_approval, + wrapper: :with_label diff --git a/app/views/admin/username_blocks/_username_block.html.haml b/app/views/admin/username_blocks/_username_block.html.haml new file mode 100644 index 00000000000..617ec65bc6d --- /dev/null +++ b/app/views/admin/username_blocks/_username_block.html.haml @@ -0,0 +1,12 @@ +.batch-table__row + %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox + = f.check_box :username_block_ids, { multiple: true, include_hidden: false }, username_block.id + .sr-only= username_block.username + .batch-table__row__content.pending-account + .pending-account__header + = t(username_block.exact? ? 'admin.username_blocks.matches_exactly_html' : 'admin.username_blocks.contains_html', string: content_tag(:samp, link_to(username_block.username, edit_admin_username_block_path(username_block)))) + %br/ + - if username_block.allow_with_approval? + = t('admin.email_domain_blocks.allow_registrations_with_approval') + - else + = t('admin.username_blocks.block_registrations') diff --git a/app/views/admin/username_blocks/edit.html.haml b/app/views/admin/username_blocks/edit.html.haml new file mode 100644 index 00000000000..eee0fedef07 --- /dev/null +++ b/app/views/admin/username_blocks/edit.html.haml @@ -0,0 +1,10 @@ +- content_for :page_title do + = t('admin.username_blocks.edit.title') + += simple_form_for @username_block, url: admin_username_block_path(@username_block) do |form| + = render 'shared/error_messages', object: @username_block + + = render form + + .actions + = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/username_blocks/index.html.haml b/app/views/admin/username_blocks/index.html.haml new file mode 100644 index 00000000000..697edfda51a --- /dev/null +++ b/app/views/admin/username_blocks/index.html.haml @@ -0,0 +1,26 @@ +- content_for :page_title do + = t('admin.username_blocks.title') + +- content_for :heading_actions do + = link_to t('admin.username_blocks.add_new'), new_admin_username_block_path, class: 'button' + += form_with model: @form, url: batch_admin_username_blocks_path do |f| + = hidden_field_tag :page, params[:page] || 1 + + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + = f.button safe_join([material_symbol('close'), t('admin.username_blocks.delete')]), + class: 'table-action-link', + data: { confirm: t('admin.reports.are_you_sure') }, + name: :delete, + type: :submit + .batch-table__body + - if @username_blocks.empty? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'username_block', collection: @username_blocks, locals: { f: f } + += paginate @username_blocks diff --git a/app/views/admin/username_blocks/new.html.haml b/app/views/admin/username_blocks/new.html.haml new file mode 100644 index 00000000000..0f5bd27952b --- /dev/null +++ b/app/views/admin/username_blocks/new.html.haml @@ -0,0 +1,10 @@ +- content_for :page_title do + = t('admin.username_blocks.new.title') + += simple_form_for @username_block, url: admin_username_blocks_path do |form| + = render 'shared/error_messages', object: @username_block + + = render form + + .actions + = form.button :button, t('admin.username_blocks.new.create'), type: :submit diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 0dbc0873855..b934696bda6 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -72,6 +72,8 @@ ignore_unused: - 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use - 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use - 'admin.terms_of_service.generate' # temporarily disabled + - 'admin.username_blocks.matches_exactly_html' + - 'admin.username_blocks.contains_html' ignore_inconsistent_interpolations: - '*.one' diff --git a/config/locales/en.yml b/config/locales/en.yml index 204340f504c..a149c18c775 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -190,6 +190,7 @@ en: create_relay: Create Relay create_unavailable_domain: Create Unavailable Domain create_user_role: Create Role + create_username_block: Create Username Rule demote_user: Demote User destroy_announcement: Delete Announcement destroy_canonical_email_block: Delete Email Block @@ -203,6 +204,7 @@ en: destroy_status: Delete Post destroy_unavailable_domain: Delete Unavailable Domain destroy_user_role: Destroy Role + destroy_username_block: Delete Username Rule disable_2fa_user: Disable 2FA disable_custom_emoji: Disable Custom Emoji disable_relay: Disable Relay @@ -237,6 +239,7 @@ en: update_report: Update Report update_status: Update Post update_user_role: Update Role + update_username_block: Update Username Rule actions: approve_appeal_html: "%{name} approved moderation decision appeal from %{target}" approve_user_html: "%{name} approved sign-up from %{target}" @@ -255,6 +258,7 @@ en: create_relay_html: "%{name} created a relay %{target}" create_unavailable_domain_html: "%{name} stopped delivery to domain %{target}" create_user_role_html: "%{name} created %{target} role" + create_username_block_html: "%{name} added rule for usernames containing %{target}" 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}" @@ -268,6 +272,7 @@ en: destroy_status_html: "%{name} removed post by %{target}" destroy_unavailable_domain_html: "%{name} resumed delivery to domain %{target}" destroy_user_role_html: "%{name} deleted %{target} role" + destroy_username_block_html: "%{name} removed rule for usernames containing %{target}" disable_2fa_user_html: "%{name} disabled two factor requirement for user %{target}" disable_custom_emoji_html: "%{name} disabled emoji %{target}" disable_relay_html: "%{name} disabled the relay %{target}" @@ -302,6 +307,7 @@ en: update_report_html: "%{name} updated report %{target}" update_status_html: "%{name} updated post by %{target}" update_user_role_html: "%{name} changed %{target} role" + update_username_block_html: "%{name} updated rule for usernames containing %{target}" deleted_account: deleted account empty: No logs found. filter_by_action: Filter by action @@ -1085,6 +1091,25 @@ en: other: Used by %{count} people over the last week title: Recommendations & Trends trending: Trending + username_blocks: + add_new: Add new + block_registrations: Block registrations + comparison: + contains: Contains + equals: Equals + contains_html: Contains %{string} + created_msg: Successfully created username rule + delete: Delete + edit: + title: Edit username rule + matches_exactly_html: Equals %{string} + new: + create: Create rule + title: Create new username rule + no_username_block_selected: No username rules were changed as none were selected + not_permitted: Not permitted + title: Username rules + updated_msg: Successfully updated username rule warning_presets: add_new: Add new delete: Delete diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 1b410a802d5..86fb4528de4 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -160,6 +160,10 @@ en: name: Public name of the role, if role is set to be displayed as a badge permissions_as_keys: Users with this role will have access to... position: Higher role decides conflict resolution in certain situations. Certain actions can only be performed on roles with a lower priority + username_block: + allow_with_approval: Instead of preventing sign-up outright, matching sign-ups will require your approval + comparison: Please be mindful of the Scunthorpe Problem when blocking partial matches + username: Will be matched regardless of casing and common homoglyphs like "4" for "a" or "3" for "e" webhook: events: Select events to send template: Compose your own JSON payload using variable interpolation. Leave blank for default JSON. @@ -371,6 +375,10 @@ en: name: Name permissions_as_keys: Permissions position: Priority + username_block: + allow_with_approval: Allow registrations with approval + comparison: Method of comparison + username: Word to match webhook: events: Enabled events template: Payload template diff --git a/config/navigation.rb b/config/navigation.rb index d60f8cbc5b3..a8f686fd8b1 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -59,6 +59,7 @@ SimpleNavigation::Configuration.run do |navigation| current_user.can?(:manage_federation) } s.item :email_domain_blocks, safe_join([material_symbol('mail'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) } + s.item :username_blocks, safe_join([material_symbol('supervised_user_circle_off'), t('admin.username_blocks.title')]), admin_username_blocks_path, highlights_on: %r{/admin/username_blocks}, if: -> { current_user.can?(:manage_blocks) } s.item :ip_blocks, safe_join([material_symbol('hide_source'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) } s.item :action_logs, safe_join([material_symbol('list'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) } end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 3d9f24ae838..97f84da44e7 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -230,4 +230,10 @@ namespace :admin do end resources :software_updates, only: [:index] + + resources :username_blocks, except: [:show, :destroy] do + collection do + post :batch + end + end end diff --git a/config/settings.yml b/config/settings.yml index ba81fcb8c6a..7d2f0a00c07 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -20,28 +20,6 @@ defaults: &defaults trends: true trends_as_landing_page: true trendable_by_default: false - reserved_usernames: - - abuse - - account - - accounts - - admin - - administration - - administrator - - admins - - help - - helpdesk - - instance - - mod - - moderator - - moderators - - mods - - owner - - root - - security - - server - - staff - - support - - webmaster disallowed_hashtags: # space separated string or list of hashtags without the hash bootstrap_timeline_accounts: '' activity_api_enabled: true diff --git a/db/migrate/20250717003848_create_username_blocks.rb b/db/migrate/20250717003848_create_username_blocks.rb new file mode 100644 index 00000000000..01649d06574 --- /dev/null +++ b/db/migrate/20250717003848_create_username_blocks.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class CreateUsernameBlocks < ActiveRecord::Migration[8.0] + def change + create_table :username_blocks do |t| + t.string :username, null: false + t.string :normalized_username, null: false + t.boolean :exact, null: false, default: false + t.boolean :allow_with_approval, null: false, default: false + + t.timestamps + end + + add_index :username_blocks, 'lower(username)', unique: true, name: 'index_username_blocks_on_username_lower_btree' + add_index :username_blocks, :normalized_username + + reversible do |dir| + dir.up do + load Rails.root.join('db', 'seeds', '05_blocked_usernames.rb') + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0237b476445..272d6fac182 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: 2025_06_27_132728) do +ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -1238,6 +1238,17 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_27_132728) do 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.datetime "updated_at", 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 diff --git a/db/seeds/05_blocked_usernames.rb b/db/seeds/05_blocked_usernames.rb new file mode 100644 index 00000000000..8bfe536c898 --- /dev/null +++ b/db/seeds/05_blocked_usernames.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +%w( + abuse + account + accounts + admin + administration + administrator + admins + help + helpdesk + instance + mod + moderator + moderators + mods + owner + root + security + server + staff + support + webmaster +).each do |str| + UsernameBlock.create_with(username: str, exact: true).find_or_create_by(username: str) +end + +%w( + mastodon + mastadon +).each do |str| + UsernameBlock.create_with(username: str, exact: false).find_or_create_by(username: str) +end diff --git a/spec/fabricators/username_block_fabricator.rb b/spec/fabricators/username_block_fabricator.rb new file mode 100644 index 00000000000..edca5419aba --- /dev/null +++ b/spec/fabricators/username_block_fabricator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +Fabricator(:username_block) do + username { sequence(:email) { |i| "#{i}#{Faker::Internet.username}" } } + exact false + allow_with_approval false +end diff --git a/spec/models/username_block_spec.rb b/spec/models/username_block_spec.rb new file mode 100644 index 00000000000..72dbe028bdf --- /dev/null +++ b/spec/models/username_block_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UsernameBlock do + describe '.matches?' do + context 'when there is an exact block' do + before do + Fabricate(:username_block, username: 'carriage', exact: true) + end + + it 'returns true on exact match' do + expect(described_class.matches?('carriage')).to be true + end + + it 'returns true on case insensitive match' do + expect(described_class.matches?('CaRRiagE')).to be true + end + + it 'returns true on homoglyph match' do + expect(described_class.matches?('c4rr14g3')).to be true + end + + it 'returns false on partial match' do + expect(described_class.matches?('foo_carriage')).to be false + end + + it 'returns false on no match' do + expect(described_class.matches?('foo')).to be false + end + end + + context 'when there is a partial block' do + before do + Fabricate(:username_block, username: 'carriage', exact: false) + end + + it 'returns true on exact match' do + expect(described_class.matches?('carriage')).to be true + end + + it 'returns true on case insensitive match' do + expect(described_class.matches?('CaRRiagE')).to be true + end + + it 'returns true on homoglyph match' do + expect(described_class.matches?('c4rr14g3')).to be true + end + + it 'returns true on suffix match' do + expect(described_class.matches?('foo_carriage')).to be true + end + + it 'returns true on prefix match' do + expect(described_class.matches?('carriage_foo')).to be true + end + + it 'returns false on no match' do + expect(described_class.matches?('foo')).to be false + end + end + end +end diff --git a/spec/requests/admin/username_blocks_spec.rb b/spec/requests/admin/username_blocks_spec.rb new file mode 100644 index 00000000000..6e17ca2d470 --- /dev/null +++ b/spec/requests/admin/username_blocks_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Username Blocks' do + describe 'GET /admin/username_blocks' do + before { sign_in Fabricate(:admin_user) } + + it 'returns http success' do + get admin_username_blocks_path + + expect(response) + .to have_http_status(200) + end + end + + describe 'POST /admin/username_blocks' do + before { sign_in Fabricate(:admin_user) } + + it 'gracefully handles invalid nested params' do + post admin_username_blocks_path(username_block: 'invalid') + + expect(response) + .to have_http_status(400) + end + + it 'creates a username block' do + post admin_username_blocks_path(username_block: { username: 'banana', comparison: 'contains', allow_with_approval: '0' }) + + expect(response) + .to redirect_to(admin_username_blocks_path) + expect(UsernameBlock.find_by(username: 'banana')) + .to_not be_nil + end + end + + describe 'POST /admin/username_blocks/batch' do + before { sign_in Fabricate(:admin_user) } + + let(:username_blocks) { Fabricate.times(2, :username_block) } + + it 'gracefully handles invalid nested params' do + post batch_admin_username_blocks_path(form_username_block_batch: 'invalid') + + expect(response) + .to redirect_to(admin_username_blocks_path) + end + + it 'deletes selected username blocks' do + post batch_admin_username_blocks_path(form_username_block_batch: { username_block_ids: username_blocks.map(&:id) }, delete: '1') + + expect(response) + .to redirect_to(admin_username_blocks_path) + expect(UsernameBlock.where(id: username_blocks.map(&:id))) + .to be_empty + end + end + + describe 'GET /admin/username_blocks/new' do + before { sign_in Fabricate(:admin_user) } + + it 'returns http success' do + get new_admin_username_block_path + + expect(response) + .to have_http_status(200) + end + end + + describe 'GET /admin/username_blocks/:id/edit' do + before { sign_in Fabricate(:admin_user) } + + let(:username_block) { Fabricate(:username_block) } + + it 'returns http success' do + get edit_admin_username_block_path(username_block) + + expect(response) + .to have_http_status(200) + end + end + + describe 'PUT /admin/username_blocks/:id' do + before { sign_in Fabricate(:admin_user) } + + let(:username_block) { Fabricate(:username_block, username: 'banana') } + + it 'updates username block' do + put admin_username_block_path(username_block, username_block: { username: 'bebebe' }) + + expect(response) + .to redirect_to(admin_username_blocks_path) + expect(username_block.reload.username) + .to eq 'bebebe' + end + end +end diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index 67a2921885e..55dca7db844 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -10,8 +10,13 @@ RSpec.describe UnreservedUsernameValidator do attr_accessor :username validates_with UnreservedUsernameValidator + + def self.name + 'Foo' + end end end + let(:record) { record_class.new } describe '#validate' do @@ -114,7 +119,7 @@ RSpec.describe UnreservedUsernameValidator do end def stub_reserved_usernames(value) - allow(Setting).to receive(:[]).with('reserved_usernames').and_return(value) + value&.each { |str| Fabricate(:username_block, username: str, exact: true) } end end end From a368b29e274e5fbb8d26d28a725036bfef71767d Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 29 Jul 2025 12:47:18 +0200 Subject: [PATCH 160/660] Fix number of new replies increasing even if reply was not fetched (#35577) --- app/workers/fetch_reply_worker.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/workers/fetch_reply_worker.rb b/app/workers/fetch_reply_worker.rb index 42e38dc682f..227d38a5f2d 100644 --- a/app/workers/fetch_reply_worker.rb +++ b/app/workers/fetch_reply_worker.rb @@ -7,9 +7,9 @@ class FetchReplyWorker sidekiq_options queue: 'pull', retry: 3 def perform(child_url, options = {}) - batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id'] - FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys) + batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id'] + result = FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys) ensure - batch&.remove_job(jid, increment: true) + batch&.remove_job(jid, increment: result.present?) end end From 1c89309db04c4c97c0287a16413111b8e8874196 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:37:33 +0200 Subject: [PATCH 161/660] New Crowdin Translations (automated) (#35592) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/en-GB.json | 8 ++---- app/javascript/mastodon/locales/et.json | 2 ++ app/javascript/mastodon/locales/fy.json | 2 ++ app/javascript/mastodon/locales/he.json | 2 +- app/javascript/mastodon/locales/ko.json | 2 +- config/locales/da.yml | 25 ++++++++++++++++ config/locales/de.yml | 25 ++++++++++++++++ config/locales/es-AR.yml | 25 ++++++++++++++++ config/locales/es-MX.yml | 25 ++++++++++++++++ config/locales/es.yml | 27 +++++++++++++++++- config/locales/et.yml | 33 ++++++++++++++++++++++ config/locales/fo.yml | 25 ++++++++++++++++ config/locales/fy.yml | 26 +++++++++++++++++ config/locales/gl.yml | 25 ++++++++++++++++ config/locales/he.yml | 25 ++++++++++++++++ config/locales/is.yml | 25 ++++++++++++++++ config/locales/ko.yml | 8 +++++- config/locales/simple_form.da.yml | 8 ++++++ config/locales/simple_form.de.yml | 8 ++++++ config/locales/simple_form.es-AR.yml | 8 ++++++ config/locales/simple_form.es-MX.yml | 8 ++++++ config/locales/simple_form.es.yml | 8 ++++++ config/locales/simple_form.et.yml | 2 ++ config/locales/simple_form.fo.yml | 8 ++++++ config/locales/simple_form.gl.yml | 8 ++++++ config/locales/simple_form.he.yml | 8 ++++++ config/locales/simple_form.is.yml | 8 ++++++ config/locales/simple_form.vi.yml | 8 ++++++ config/locales/simple_form.zh-TW.yml | 8 ++++++ config/locales/uk.yml | 5 ++++ config/locales/vi.yml | 25 ++++++++++++++++ config/locales/zh-TW.yml | 25 ++++++++++++++++ 32 files changed, 446 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 31d1c71b0ef..441cfee6d10 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -1,7 +1,7 @@ { "about.blocks": "Moderated servers", "about.contact": "Contact:", - "about.default_locale": "Varsayılan", + "about.default_locale": "Default", "about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Reason not available", "about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.", @@ -9,11 +9,11 @@ "about.domain_blocks.silenced.title": "Limited", "about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.", "about.domain_blocks.suspended.title": "Suspended", - "about.language_label": "Dil", + "about.language_label": "Language", "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.account_note_header": "Kişisel not", + "account.account_note_header": "Personal note", "account.add_or_remove_from_list": "Add or Remove from lists", "account.badges.bot": "Automated", "account.badges.group": "Group", @@ -845,8 +845,6 @@ "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "This post cannot be boosted", - "status.context.load_new_replies": "Yeni yanıtlar geldi.", - "status.context.loading": "Daha fazla yanıt kontrol ediliyor", "status.continued_thread": "Continued thread", "status.copy": "Copy link to status", "status.delete": "Delete", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index cf6c39cad48..c4b1c95fd3a 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -845,6 +845,8 @@ "status.bookmark": "Järjehoidja", "status.cancel_reblog_private": "Lõpeta jagamine", "status.cannot_reblog": "Seda postitust ei saa jagada", + "status.context.load_new_replies": "Leidub uusi vastuseid", + "status.context.loading": "Kontrollin täiendavate vastuste olemasolu", "status.continued_thread": "Jätkatud lõim", "status.copy": "Kopeeri postituse link", "status.delete": "Kustuta", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 25f5c0cc01f..1f7f5c0c8ad 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -845,6 +845,8 @@ "status.bookmark": "Blêdwizer tafoegje", "status.cancel_reblog_private": "Net langer booste", "status.cannot_reblog": "Dit berjocht kin net boost wurde", + "status.context.load_new_replies": "Nije reaksjes beskikber", + "status.context.loading": "Op nije reaksjes oan it kontrolearjen", "status.continued_thread": "Ferfolgje it petear", "status.copy": "Copy link to status", "status.delete": "Fuortsmite", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 715ec36f541..d1918aa97dd 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -346,7 +346,7 @@ "featured_carousel.post": "הודעה", "featured_carousel.previous": "הקודם", "featured_carousel.slide": "{index} מתוך {total}", - "filter_modal.added.context_mismatch_explanation": "קטגוריית המסנן הזאת לא חלה על ההקשר שממנו הגעת אל ההודעה הזו. אם תרצה/י שההודעה תסונן גם בהקשר זה, תצטרך/י לערוך את הסנן.", + "filter_modal.added.context_mismatch_explanation": "קטגוריית הסנן הזאת לא חלה על ההקשר שממנו הגעת אל ההודעה הזו. אם תרצה/י שההודעה תסונן גם בהקשר זה, תצטרך/י לערוך את הסנן.", "filter_modal.added.context_mismatch_title": "אין התאמה להקשר!", "filter_modal.added.expired_explanation": "פג תוקפה של קטגוריית הסינון הזו, יש צורך לשנות את תאריך התפוגה כדי שהסינון יוחל.", "filter_modal.added.expired_title": "פג תוקף המסנן!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index cfb3ba8c61d..f0c127956a3 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -513,7 +513,7 @@ "lists.add_to_lists": "리스트에 {name} 추가", "lists.create": "생성", "lists.create_a_list_to_organize": "새 리스트를 만들어 홈 피드를 정리하세요", - "lists.create_list": "리스트 생성", + "lists.create_list": "리스트 만들기", "lists.delete": "리스트 삭제", "lists.done": "완료", "lists.edit": "리스트 편집", diff --git a/config/locales/da.yml b/config/locales/da.yml index d2f6753181b..cd8bd2d90ee 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -190,6 +190,7 @@ da: create_relay: Opret Videresendelse create_unavailable_domain: Opret Utilgængeligt Domæne create_user_role: Opret rolle + create_username_block: Opret brugernavn-regel demote_user: Degradér bruger destroy_announcement: Slet bekendtgørelse destroy_canonical_email_block: Slet e-mailblokering @@ -203,6 +204,7 @@ da: destroy_status: Slet indlæg destroy_unavailable_domain: Slet Utilgængeligt Domæne destroy_user_role: Ødelæg rolle + destroy_username_block: Slet brugernavn-regel disable_2fa_user: Deaktivér 2FA disable_custom_emoji: Deaktivér tilpasset emoji disable_relay: Deaktivér Videresendelse @@ -237,6 +239,7 @@ da: update_report: Opdatér anmeldelse update_status: Opdatér indlæg update_user_role: Opdatér rolle + update_username_block: Opdatér brugernavn-regel actions: approve_appeal_html: "%{name} godkendte moderationsafgørelsesappellen fra %{target}" approve_user_html: "%{name} godkendte tilmeldingen fra %{target}" @@ -255,6 +258,7 @@ da: create_relay_html: "%{name} oprettede videresendelsen %{target}" create_unavailable_domain_html: "%{name} stoppede levering til domænet %{target}" create_user_role_html: "%{name} oprettede %{target}-rolle" + create_username_block_html: "%{name} tilføjede regel for brugernavne indeholdende %{target}" 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}" @@ -268,6 +272,7 @@ da: destroy_status_html: "%{name} fjernede indlægget fra %{target}" destroy_unavailable_domain_html: "%{name} genoptog levering til domænet %{target}" destroy_user_role_html: "%{name} slettede %{target}-rolle" + destroy_username_block_html: "%{name} fjernede regel for brugernavne indeholdende %{target}" disable_2fa_user_html: "%{name} deaktiverede tofaktorkravet for brugeren %{target}" disable_custom_emoji_html: "%{name} deaktiverede emojien %{target}" disable_relay_html: "%{name} deaktiverede videresendelsen %{target}" @@ -302,6 +307,7 @@ da: update_report_html: "%{name} opdaterede rapporten %{target}" update_status_html: "%{name} opdaterede indlægget fra %{target}" update_user_role_html: "%{name} ændrede %{target}-rolle" + update_username_block_html: "%{name} opdaterede regel for brugernavne indeholdende %{target}" deleted_account: slettet konto empty: Ingen logger fundet. filter_by_action: Filtrér efter handling @@ -1085,6 +1091,25 @@ da: other: Brugt af %{count} personer den seneste uge title: Anbefalinger og trends trending: Trender + username_blocks: + add_new: Tilføj ny + block_registrations: Blokér registreringer + comparison: + contains: Indeholder + equals: Er lig + contains_html: Indeholder %{string} + created_msg: Brugernavn-regel er hermed oprettet + delete: Slet + edit: + title: Redigér brugernavn-regel + matches_exactly_html: Er lig med %{string} + new: + create: Opret regel + title: Opret ny brugernavn-regel + no_username_block_selected: Ingen brugernavn-regler ændret (ingen var valgt) + not_permitted: Ikke tilladt + title: Brugernavn-regler + updated_msg: Brugernavn-regel opdateret warning_presets: add_new: Tilføj ny delete: Slet diff --git a/config/locales/de.yml b/config/locales/de.yml index 4b29dbac5de..7b8594fcc5b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -190,6 +190,7 @@ de: create_relay: Relay erstellen create_unavailable_domain: Nicht verfügbare Domain erstellen create_user_role: Rolle erstellen + create_username_block: Regel für Profilnamen erstellen demote_user: Benutzer*in herabstufen destroy_announcement: Ankündigung löschen destroy_canonical_email_block: E-Mail-Sperre entfernen @@ -203,6 +204,7 @@ de: destroy_status: Beitrag entfernen destroy_unavailable_domain: Nicht-verfügbare Domain entfernen destroy_user_role: Rolle entfernen + destroy_username_block: Regel für Profilnamen löschen disable_2fa_user: 2FA deaktivieren disable_custom_emoji: Eigenes Emoji deaktivieren disable_relay: Relay deaktivieren @@ -237,6 +239,7 @@ de: update_report: Meldung aktualisieren update_status: Beitrag aktualisieren update_user_role: Rolle bearbeiten + update_username_block: Regel für Profilnamen aktualisieren actions: approve_appeal_html: "%{name} hat den Einspruch gegen eine Moderationsentscheidung von %{target} genehmigt" approve_user_html: "%{name} genehmigte die Registrierung von %{target}" @@ -255,6 +258,7 @@ de: create_relay_html: "%{name} erstellte ein Relay %{target}" create_unavailable_domain_html: "%{name} beendete die Zustellung an die Domain %{target}" create_user_role_html: "%{name} erstellte die Rolle %{target}" + create_username_block_html: "%{name} erstellte eine Regel für Profilnamen mit %{target}" 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}" @@ -268,6 +272,7 @@ de: destroy_status_html: "%{name} entfernte einen Beitrag von %{target}" destroy_unavailable_domain_html: "%{name} nahm die Zustellung an die Domain %{target} wieder auf" destroy_user_role_html: "%{name} löschte die Rolle %{target}" + destroy_username_block_html: "%{name} entfernte eine Regel für Profilnamen mit %{target}" disable_2fa_user_html: "%{name} deaktivierte die Zwei-Faktor-Authentisierung für %{target}" disable_custom_emoji_html: "%{name} deaktivierte das Emoji %{target}" disable_relay_html: "%{name} deaktivierte das Relay %{target}" @@ -302,6 +307,7 @@ de: update_report_html: "%{name} überarbeitete die Meldung %{target}" update_status_html: "%{name} überarbeitete einen Beitrag von %{target}" update_user_role_html: "%{name} änderte die Rolle von %{target}" + update_username_block_html: "%{name} aktualisierte eine Regel für Profilnamen mit %{target}" deleted_account: gelöschtes Konto empty: Protokolle nicht gefunden. filter_by_action: Nach Aktion filtern @@ -1085,6 +1091,25 @@ de: other: In den vergangenen 7 Tagen von %{count} Profilen verwendet title: Empfehlungen & Trends trending: Angesagt + username_blocks: + add_new: Neue Regel + block_registrations: Registrierungen sperren + comparison: + contains: Enthält + equals: Entspricht + contains_html: Enthält %{string} + created_msg: Regel für Profilnamen erfolgreich erstellt + delete: Löschen + edit: + title: Regel für Profilnamen bearbeiten + matches_exactly_html: Entspricht %{string} + new: + create: Regel erstellen + title: Neue Regel für Profilnamen erstellen + no_username_block_selected: Keine Regeln für Profilnamen wurden geändert, weil keine ausgewählt wurde(n) + not_permitted: Nicht gestattet + title: Regeln für Profilnamen + updated_msg: Regel für Profilnamen erfolgreich aktualisiert warning_presets: add_new: Neu hinzufügen delete: Löschen diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index 93d4c3c31ad..c18172fccaa 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -190,6 +190,7 @@ es-AR: create_relay: Crear relé create_unavailable_domain: Crear dominio no disponible create_user_role: Crear rol + create_username_block: Crear regla de nombre de usuario demote_user: Descender usuario destroy_announcement: Eliminar anuncio destroy_canonical_email_block: Eliminar bloqueo de correo electrónico @@ -203,6 +204,7 @@ es-AR: destroy_status: Eliminar mensaje destroy_unavailable_domain: Eliminar dominio no disponible destroy_user_role: Destruir rol + destroy_username_block: Eliminar regla de nombre de usuario disable_2fa_user: Deshabilitar 2FA disable_custom_emoji: Deshabilitar emoji personalizado disable_relay: Deshabilitar relé @@ -237,6 +239,7 @@ es-AR: update_report: Actualizar denuncia update_status: Actualizar mensaje update_user_role: Actualizar rol + update_username_block: Actualizar regla de nombre de usuario actions: approve_appeal_html: "%{name} aprobó la solicitud de moderación de %{target}" approve_user_html: "%{name} aprobó el registro de %{target}" @@ -255,6 +258,7 @@ es-AR: create_relay_html: "%{name} creó el relé %{target}" create_unavailable_domain_html: "%{name} detuvo la entrega al dominio %{target}" create_user_role_html: "%{name} creó el rol %{target}" + create_username_block_html: "%{name} agregó una regla para los nombres de usuario que contienen %{target}" 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}" @@ -268,6 +272,7 @@ es-AR: destroy_status_html: "%{name} eliminó el mensaje de %{target}" destroy_unavailable_domain_html: "%{name} reanudó la entrega al dominio %{target}" destroy_user_role_html: "%{name} eliminó el rol %{target}" + destroy_username_block_html: "%{name} eliminó una regla para los nombres de usuario que contienen %{target}" disable_2fa_user_html: "%{name} deshabilitó el requerimiento de dos factores para el usuario %{target}" disable_custom_emoji_html: "%{name} deshabilitó el emoji %{target}" disable_relay_html: "%{name} deshabilitó el relé %{target}" @@ -302,6 +307,7 @@ es-AR: update_report_html: "%{name} actualizó la denuncia %{target}" update_status_html: "%{name} actualizó el mensaje de %{target}" update_user_role_html: "%{name} cambió el rol %{target}" + update_username_block_html: "%{name} actualizó una regla para los nombres de usuario que contienen %{target}" deleted_account: cuenta eliminada empty: No se encontraron registros. filter_by_action: Filtrar por acción @@ -1085,6 +1091,25 @@ es-AR: other: Usada por %{count} personas durante la última semana title: Recomendaciones y tendencias trending: En tendencia + username_blocks: + add_new: Agregar nueva + block_registrations: Bloquear registros + comparison: + contains: Contiene + equals: Es igual a + contains_html: Contiene %{string} + created_msg: Regla de nombre de usuario creada exitosamente + delete: Eliminar + edit: + title: Editar regla de nombre de usuario + matches_exactly_html: Es igual a %{string} + new: + create: Crear regla + title: Crear nueva regla de nombre de usuario + no_username_block_selected: No se cambiaron las reglas de nombre de usuario, ya que no se seleccionó ninguna + not_permitted: No permitido + title: Reglas de nombre de usuario + updated_msg: Regla de nombre de usuario actualizada exitosamente warning_presets: add_new: Agregar nuevo delete: Eliminar diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index e839a605e15..c5309f22660 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -190,6 +190,7 @@ es-MX: create_relay: Crear Relé create_unavailable_domain: Crear Dominio No Disponible create_user_role: Crear rol + create_username_block: Crear regla de nombre de usuario demote_user: Degradar Usuario destroy_announcement: Eliminar Anuncio destroy_canonical_email_block: Eliminar bloqueo de correo electrónico @@ -203,6 +204,7 @@ es-MX: destroy_status: Eliminar Publicación destroy_unavailable_domain: Eliminar Dominio No Disponible destroy_user_role: Destruir Rol + destroy_username_block: Eliminar regla de nombre de usuario disable_2fa_user: Deshabilitar 2FA disable_custom_emoji: Deshabilitar Emoji Personalizado disable_relay: Desactivar Relé @@ -237,6 +239,7 @@ es-MX: update_report: Actualizar informe update_status: Actualizar Publicación update_user_role: Actualizar Rol + update_username_block: Actualizar regla de nombre de usuario actions: approve_appeal_html: "%{name} aprobó la solicitud de moderación de %{target}" approve_user_html: "%{name} aprobó el registro de %{target}" @@ -255,6 +258,7 @@ es-MX: create_relay_html: "%{name} creó un relé %{target}" create_unavailable_domain_html: "%{name} detuvo las entregas al dominio %{target}" create_user_role_html: "%{name} creó el rol %{target}" + create_username_block_html: "%{name} añadió regla para nombres de usuario que contienen %{target}" 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}" @@ -268,6 +272,7 @@ es-MX: destroy_status_html: "%{name} eliminó la publicación por %{target}" destroy_unavailable_domain_html: "%{name} reanudó las entregas al dominio %{target}" destroy_user_role_html: "%{name} eliminó el rol %{target}" + destroy_username_block_html: "%{name} eliminó la regla para los nombres de usuario que contienen %{target}" disable_2fa_user_html: "%{name} desactivó el requisito de dos factores para el usuario %{target}" disable_custom_emoji_html: "%{name} desactivó el emoji %{target}" disable_relay_html: "%{name} desactivó el relé %{target}" @@ -302,6 +307,7 @@ es-MX: update_report_html: "%{name} actualizó el informe %{target}" update_status_html: "%{name} actualizó la publicación de %{target}" update_user_role_html: "%{name} cambió el rol %{target}" + update_username_block_html: "%{name} actualizó una regla para los nombres de usuario que contienen %{target}" deleted_account: cuenta eliminada empty: No se encontraron registros. filter_by_action: Filtrar por acción @@ -1085,6 +1091,25 @@ es-MX: other: Usada por %{count} personas durante la última semana title: Recomendaciones y Tendencias trending: En tendencia + username_blocks: + add_new: Añadir nuevo + block_registrations: Bloquear registros + comparison: + contains: Contiene + equals: Igual + contains_html: Contiene %{string} + created_msg: Regla de nombre de usuario creada correctamente + delete: Eliminar + edit: + title: Editar regla de nombre de usuario + matches_exactly_html: Es igual a %{string} + new: + create: Crear regla + title: Crear nueva regla de nombre de usuario + no_username_block_selected: No se modificaron las reglas de nombre de usuario, ya que no se seleccionó ninguna + not_permitted: No permitido + title: Reglas para el nombre de usuario + updated_msg: Regla de nombre de usuario actualizada correctamente warning_presets: add_new: Añadir nuevo delete: Borrar diff --git a/config/locales/es.yml b/config/locales/es.yml index 0381647115a..0481e473874 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -190,6 +190,7 @@ es: create_relay: Crear Relé create_unavailable_domain: Crear Dominio No Disponible create_user_role: Crear Rol + create_username_block: Crear regla de nombre de usuario demote_user: Degradar Usuario destroy_announcement: Eliminar Anuncio destroy_canonical_email_block: Eliminar Bloqueo de Correo Electrónico @@ -203,6 +204,7 @@ es: destroy_status: Eliminar Publicación destroy_unavailable_domain: Eliminar Dominio No Disponible destroy_user_role: Destruir Rol + destroy_username_block: Eliminar regla de nombre de usuario disable_2fa_user: Deshabilitar 2FA disable_custom_emoji: Deshabilitar Emoji Personalizado disable_relay: Desactivar Relé @@ -237,6 +239,7 @@ es: update_report: Actualizar informe update_status: Actualizar Publicación update_user_role: Actualizar Rol + update_username_block: Actualizar regla de nombre de usuario actions: approve_appeal_html: "%{name} aprobó la solicitud de moderación de %{target}" approve_user_html: "%{name} aprobó el registro de %{target}" @@ -255,6 +258,7 @@ es: create_relay_html: "%{name} creó un relé %{target}" create_unavailable_domain_html: "%{name} detuvo las entregas al dominio %{target}" create_user_role_html: "%{name} creó el rol %{target}" + create_username_block_html: "%{name} agregó una regla para los nombres de usuario que contienen %{target}" 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}" @@ -268,6 +272,7 @@ es: destroy_status_html: "%{name} eliminó la publicación de %{target}" destroy_unavailable_domain_html: "%{name} reanudó las entregas al dominio %{target}" destroy_user_role_html: "%{name} eliminó el rol %{target}" + destroy_username_block_html: "%{name} eliminó una regla para los nombres de usuario que contienen %{target}" disable_2fa_user_html: "%{name} desactivó el requisito de dos factores para el usuario %{target}" disable_custom_emoji_html: "%{name} desactivó el emoji %{target}" disable_relay_html: "%{name} desactivó el relé %{target}" @@ -302,6 +307,7 @@ es: update_report_html: "%{name} actualizó el informe %{target}" update_status_html: "%{name} actualizó la publicación de %{target}" update_user_role_html: "%{name} cambió el rol %{target}" + update_username_block_html: "%{name} actualizó una regla para los nombres de usuario que contienen %{target}" deleted_account: cuenta eliminada empty: No se encontraron registros. filter_by_action: Filtrar por acción @@ -1085,6 +1091,25 @@ es: other: Usada por %{count} personas durante la última semana title: Recomendaciones y Tendencias trending: En tendencia + username_blocks: + add_new: Añadir nueva + block_registrations: Bloquear registros + comparison: + contains: Contiene + equals: Es igual a + contains_html: Contiene %{string} + created_msg: Regla de nombre de usuario creada correctamente + delete: Eliminar + edit: + title: Editar regla de nombre de usuario + matches_exactly_html: Es igual a %{string} + new: + create: Crear regla + title: Crear nueva regla de nombre de usuario + no_username_block_selected: No se cambiaron las reglas de nombre de usuario, ya que no se seleccionó ninguno + not_permitted: No permitido + title: Reglas de nombre de usuario + updated_msg: Regla de nombre de usuario actualizada correctamente warning_presets: add_new: Añadir nuevo delete: Borrar @@ -1872,7 +1897,7 @@ es: edited_at_html: Editado %{date} errors: in_reply_not_found: La publicación a la que intentas responder no existe. - quoted_status_not_found: La publicación que estás intentando citar no existe. + quoted_status_not_found: La publicación que estás intentando citar no parece existir. over_character_limit: Límite de caracteres de %{max} superado pin_errors: direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse diff --git a/config/locales/et.yml b/config/locales/et.yml index 962f6be9db8..2e9a7fbf74f 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -190,6 +190,7 @@ et: create_relay: Loo sõnumivahendusserver create_unavailable_domain: Kättesaamatu domeeni lisamine create_user_role: Loo roll + create_username_block: Lisa kasutajanime reegel demote_user: Alandas kasutaja destroy_announcement: Eemaldas teadaande destroy_canonical_email_block: Kustuta e-posti blokeering @@ -203,6 +204,7 @@ et: destroy_status: Kustuta postitus destroy_unavailable_domain: Kättesaamatu domeeni kustutamine destroy_user_role: Rolli kustutamine + destroy_username_block: Kustuta kasutajanime reegel disable_2fa_user: Keela 2FA disable_custom_emoji: Keelas kohandatud emotikoni disable_relay: Lülita sõnumivahendusserver välja @@ -237,6 +239,7 @@ et: update_report: Uuendamise raport update_status: Uuenda postitust update_user_role: Uuenda rolli + update_username_block: Uuenda kasutajanime reeglit actions: approve_appeal_html: "%{name} kiitis heaks modereerimise otsuse vaidlustuse %{target} poolt" approve_user_html: "%{name} kiitis heaks registreerimise %{target} poolt" @@ -255,6 +258,7 @@ et: create_relay_html: "%{name} lõi sõnumivahendusserveri: %{target}" create_unavailable_domain_html: "%{name} lõpetas edastamise domeeni %{target}" create_user_role_html: "%{name} lõi rolli %{target}" + create_username_block_html: "%{name} lisas kasutajanime reegli, milles sisaldub %{target}" demote_user_html: "%{name} alandas kasutajat %{target}" destroy_announcement_html: "%{name} kustutas teadaande %{target}" destroy_canonical_email_block_html: "%{name} eemaldas blokeeringu e-postilt räsiga %{target}" @@ -268,6 +272,7 @@ et: destroy_status_html: "%{name} kustutas %{target} postituse" destroy_unavailable_domain_html: "%{name} taastas edastamise domeeni %{target}" destroy_user_role_html: "%{name} kustutas %{target} rolli" + destroy_username_block_html: "%{name} eemaldas kasutajanime reegli, milles sisaldub %{target}" disable_2fa_user_html: "%{name} eemaldas kasutaja %{target} kahe etapise nõude" disable_custom_emoji_html: "%{name} keelas emotikooni %{target}" disable_relay_html: "%{name} eemaldas sõnumivahendusserveri kasutuselt: %{target}" @@ -302,6 +307,7 @@ et: update_report_html: "%{name} uuendas raportit %{target}" update_status_html: "%{name} muutis %{target} postitust" update_user_role_html: "%{name} muutis %{target} rolli" + update_username_block_html: "%{name} uuendas kasutajanime reeglit, milles sisaldub %{target}" deleted_account: kustutatud konto empty: Logisi ei leitud. filter_by_action: Filtreeri tegevuse järgi @@ -806,6 +812,8 @@ et: preamble: Täpsem kirjeldus, kuidas serverit käitatakse, modereeritakse ja rahastatakse. rules_hint: Reeglitele, millega kasutajad peavad nõustuma, on vastav piirkond. title: Teave + allow_referrer_origin: + title: Luba välistel serveritel näha Mastodoni teenust linkide viitajana appearance: preamble: Kohanda Mastodoni veebiliidest. title: Välimus @@ -993,6 +1001,7 @@ et: other: Saada %{display_count} e-kirja publish: Postita published_on_html: Postitatud %{date} + save_draft: Salvesta kavandina title: Kasutustingimused title: Administreerimine trends: @@ -1069,6 +1078,25 @@ et: other: Kasutatud %{count} kasutaja poolt viimase nädala jooksul title: Soovitused ja trendid trending: Trendid + username_blocks: + add_new: Lisa uus + block_registrations: Blokeeri registreerimisi + comparison: + contains: Sisaldab + equals: Võrdub + contains_html: 'Sisaldab: %{string}' + created_msg: Kasutajanime reegli lisamine õnnestus + delete: Kustuta + edit: + title: Muuda kasutajanime reeglit + matches_exactly_html: 'Võrdub: %{string}' + new: + create: Lisa reegel + title: Lisa uus kasutajanime reegel + no_username_block_selected: Ühtegi kasutajanimereeglit ei muudetud, kuna midagi polnud valitud + not_permitted: Ei ole lubatud + title: Kasutajanime reeglid + updated_msg: Kasutajanime reegli uuendamine õnnestus warning_presets: add_new: Lisa uus delete: Kustuta @@ -1331,6 +1359,10 @@ et: basic_information: Põhiinfo hint_html: "Kohanda, mida inimesed näevad su avalikul profiilil ja postituste kõrval. Inimesed alustavad tõenäolisemalt sinu jälgimist ja interakteeruvad sinuga, kui sul on täidetud profiil ja profiilipilt." other: Muu + emoji_styles: + auto: Automaatne + native: Rakenduseomane + twemoji: Twemoji errors: '400': Esitatud päring oli vigane või valesti vormindatud. '403': Sul puudub õigus seda lehte vaadata. @@ -1850,6 +1882,7 @@ et: edited_at_html: Muudetud %{date} errors: in_reply_not_found: Postitus, millele üritad vastata, ei näi enam eksisteerivat. + quoted_status_not_found: Postitus, mida üritad tsiteerida, ei näi enam eksisteerivat. over_character_limit: tähtmärkide limiit %{max} ületatud pin_errors: direct: Ei saa kinnitada postitusi, mis on nähtavad vaid mainitud kasutajatele diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 9af87098a42..a63b39b5139 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -190,6 +190,7 @@ fo: create_relay: Stovna reiðlag create_unavailable_domain: Stovna navnaøki, sum ikki er tøkt create_user_role: Stovna leiklut + create_username_block: Stovna brúkaranavnsreglu demote_user: Lækka brúkara í tign destroy_announcement: Strika kunngerð destroy_canonical_email_block: Strika t-postablokk @@ -203,6 +204,7 @@ fo: destroy_status: Strika post destroy_unavailable_domain: Strika navnaøki, sum ikki er tøkt destroy_user_role: Bein burtur leiklut + destroy_username_block: Strika brúkaranavnsreglu disable_2fa_user: Ger 2FA óvirkið disable_custom_emoji: Ger serligt kenslutekn óvirkið disable_relay: Ger reiðlag óvirkið @@ -237,6 +239,7 @@ fo: update_report: Dagfør frágreiðing update_status: Dagfør Uppslag update_user_role: Dagfør Leiklut + update_username_block: Dagfør brúkaranavnsreglu actions: approve_appeal_html: "%{name} góðkendi umsjónaráheitan frá %{target}" approve_user_html: "%{name} góðtók umsókn frá %{target}" @@ -255,6 +258,7 @@ fo: create_relay_html: "%{name} gjørdi eitt reiðlag %{target}" create_unavailable_domain_html: "%{name} steðgaði veiting til navnaøkið %{target}" create_user_role_html: "%{name} stovnaði %{target} leiklutin" + create_username_block_html: "%{name} legði reglu afturat fyri brúkaranøvn, sum innihalda %{target}" 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}" @@ -268,6 +272,7 @@ fo: destroy_status_html: "%{name} slettaði upplegg hjá %{target}" destroy_unavailable_domain_html: "%{name} tók upp aftir veiting til navnaøkið %{target}" destroy_user_role_html: "%{name} slettaði leiklutin hjá %{target}" + destroy_username_block_html: "%{name} strikaði reglu fyri brúkaranøvn, sum innihalda %{target}" disable_2fa_user_html: "%{name} slepti kravið um váttan í tveimum stigum fyri brúkaran %{target}" disable_custom_emoji_html: "%{name} gjørdi kensluteknið %{target} óvirkið" disable_relay_html: "%{name} gjørdi reiðlagið %{target} óvirkið" @@ -302,6 +307,7 @@ fo: update_report_html: "%{name} dagførdi meldingina %{target}" update_status_html: "%{name} dagførdi postin hjá %{target}" update_user_role_html: "%{name} broyttir %{target} leiklutir" + update_username_block_html: "%{name} dagførdi reglu fyri brúkaranøvn, sum innihalda %{target}" deleted_account: strikað konta empty: Eingir loggar funnir. filter_by_action: Filtrera eftir atgerð @@ -1085,6 +1091,25 @@ fo: other: Brúkt av %{count} brúkarum seinastu vikuna title: Tilmæli & rák trending: Vælumtókt + username_blocks: + add_new: Legg afturat + block_registrations: Blokera skrásetingar + comparison: + contains: Inniheldur + equals: Er tað sama sum + contains_html: Inniheldur %{string} + created_msg: Eydnaðist at leggja brúkaranavnsreglu afturat + delete: Strika + edit: + title: Broyt brúkaranavnsreglu + matches_exactly_html: Tað sama sum %{string} + new: + create: Stovna reglu + title: Stovna brúkaranavnsreglu + no_username_block_selected: Ongar brúkaranavnsreglur vóru broyttar, tí ongar vóru valdar + not_permitted: Ikki loyvt + title: Brúkaranavnsreglur + updated_msg: Eydnaðist at dagføra brúkaranavnsreglu warning_presets: add_new: Legg afturat delete: Strika diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 38752869da1..05b5b2fa39f 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -190,6 +190,7 @@ fy: create_relay: Relay oanmeitsje create_unavailable_domain: Net beskikber domein oanmeitsje create_user_role: Rol oanmeitsje + create_username_block: Brûkersnammeregel oanmeitsje demote_user: Brûker degradearje destroy_announcement: Meidieling fuortsmite destroy_canonical_email_block: E-mailblokkade fuortsmite @@ -203,6 +204,7 @@ fy: destroy_status: Toot fuortsmite destroy_unavailable_domain: Net beskikber domein fuortsmite destroy_user_role: Rol permanint fuortsmite + destroy_username_block: Brûkersnammeregel fuortsmite disable_2fa_user: Twa-stapsferifikaasje útskeakelje disable_custom_emoji: Lokale emoji útskeakelje disable_relay: Relay útskeakelje @@ -237,6 +239,7 @@ fy: update_report: Rapportaazje bywurkje update_status: Berjocht bywurkje update_user_role: Rol bywurkje + update_username_block: Brûkersnammeregel bywurkje actions: approve_appeal_html: "%{name} hat it beswier tsjin de moderaasjemaatregel fan %{target} goedkard" approve_user_html: "%{name} hat de registraasje fan %{target} goedkard" @@ -255,6 +258,7 @@ fy: create_relay_html: "%{name} hat in relay oanmakke %{target}" create_unavailable_domain_html: "%{name} hat de besoarging foar domein %{target} beëinige" create_user_role_html: "%{name} hat de rol %{target} oanmakke" + create_username_block_html: "%{name} hat in brûkersnammeregel mei %{target} tafoege" demote_user_html: Brûker %{target} is troch %{name} degradearre destroy_announcement_html: "%{name} hat de meidieling %{target} fuortsmiten" destroy_canonical_email_block_html: "%{name} hat it e-mailberjocht mei de hash %{target} deblokkearre" @@ -268,6 +272,7 @@ fy: destroy_status_html: Berjocht fan %{target} is troch %{name} fuortsmiten destroy_unavailable_domain_html: "%{name} hat de besoarging foar domein %{target} opnij starte" destroy_user_role_html: "%{name} hat de rol %{target} fuortsmiten" + destroy_username_block_html: "%{name} hat in brûkersnammeregel mei %{target} fuortsmiten" disable_2fa_user_html: De fereaske twa-stapsferifikaasje foar %{target} is troch %{name} útskeakele disable_custom_emoji_html: Emoji %{target} is troch %{name} útskeakele disable_relay_html: "%{name} hat de relay %{target} útskeakele" @@ -302,6 +307,7 @@ fy: update_report_html: Rapportaazje %{target} is troch %{name} bywurke update_status_html: "%{name} hat de berjochten %{target} bywurke" update_user_role_html: "%{name} hat de rol %{target} wizige" + update_username_block_html: "%{name} hat in brûkersnammeregel mei %{target} bywurke" deleted_account: fuortsmiten account empty: Gjin lochboeken fûn. filter_by_action: Op aksje filterje @@ -1085,6 +1091,25 @@ fy: other: Dizze wike troch %{count} persoanen brûkt title: Oanrekommandaasjes & trends trending: Trending + username_blocks: + add_new: Nije tafoegje + block_registrations: Registraasjes blokkearje + comparison: + contains: Befettet + equals: Is lyk oan + contains_html: Befettet %{string} + created_msg: Brûkersnammeregel mei sukses oanmakke + delete: Fuortsmite + edit: + title: Brûkersnammeregel bywurkje + matches_exactly_html: Is lyk oan %{string} + new: + create: Regel oanmeitsje + title: Brûkersnammeregel oanmeitsje + no_username_block_selected: Der binne gjin brûkersnammeregels wizige, omdat der gjin ien selektearre waard + not_permitted: Net tastien + title: Brûkersnammeregels + updated_msg: Brûkersnammeregel mei sukses bywurke warning_presets: add_new: Nije tafoegje delete: Fuortsmite @@ -1872,6 +1897,7 @@ fy: edited_at_html: Bewurke op %{date} errors: in_reply_not_found: It berjocht wêrop jo probearje te reagearjen liket net te bestean. + quoted_status_not_found: It berjocht dy’t jo probearje te sitearjen liket net te bestean. over_character_limit: Oer de limyt fan %{max} tekens pin_errors: direct: Berjochten dy’t allinnich sichtber binne foar fermelde brûkers kinne net fêstset wurde diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 0b3a4133f55..ceace60dbc8 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -190,6 +190,7 @@ gl: create_relay: Crear Repetidor create_unavailable_domain: Crear dominio Non dispoñible create_user_role: Crear Rol + create_username_block: Crear regra para Identificadores demote_user: Degradar usuaria destroy_announcement: Eliminar anuncio destroy_canonical_email_block: Eliminar bloqueo do correo @@ -203,6 +204,7 @@ gl: destroy_status: Eliminar publicación destroy_unavailable_domain: Eliminar dominio Non dispoñible destroy_user_role: Eliminar Rol + destroy_username_block: Eliminar regra para Identificadores disable_2fa_user: Desactivar 2FA disable_custom_emoji: Desactivar emoticona personalizada disable_relay: Desactivar Repetidor @@ -237,6 +239,7 @@ gl: update_report: Actualización da denuncia update_status: Actualizar publicación update_user_role: Actualizar Rol + update_username_block: Actualizar regra para Identificadores actions: approve_appeal_html: "%{name} aprobou a apelación da decisión da moderación de %{target}" approve_user_html: "%{name} aprobou o rexistro de %{target}" @@ -255,6 +258,7 @@ gl: create_relay_html: "%{name} creou un repetidor en %{target}" create_unavailable_domain_html: "%{name} deixou de interactuar co dominio %{target}" create_user_role_html: "%{name} creou o rol %{target}" + create_username_block_html: "%{name} engadiu unha regra para identificadores que contén %{target}" 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}" @@ -268,6 +272,7 @@ gl: destroy_status_html: "%{name} eliminou a publicación de %{target}" destroy_unavailable_domain_html: "%{name} retomou a interacción co dominio %{target}" destroy_user_role_html: "%{name} eliminou o rol %{target}" + destroy_username_block_html: "%{name} retirou unha regra para identificadores que contén %{target}" disable_2fa_user_html: "%{name} desactivou o requerimento do segundo factor para a usuaria %{target}" disable_custom_emoji_html: "%{name} desactivou o emoji %{target}" disable_relay_html: "%{name} desactivou o repetidor %{target}" @@ -302,6 +307,7 @@ gl: update_report_html: "%{name} actualizou a denuncia %{target}" update_status_html: "%{name} actualizou a publicación de %{target}" update_user_role_html: "%{name} cambiou o rol %{target}" + update_username_block_html: "%{name} actualizou a regra para identificadores que contén %{target}" deleted_account: conta eliminada empty: Non se atoparon rexistros. filter_by_action: Filtrar por acción @@ -1085,6 +1091,25 @@ gl: other: Utilizado por %{count} persoas na última semana title: Recomendacións e Suxestións trending: Popularidade + username_blocks: + add_new: Engadir nova + block_registrations: Bloqueo dos rexistros + comparison: + contains: Contén + equals: É igual a + contains_html: Contén %{string} + created_msg: Creouse correctamente a regra para identificadores + delete: Eliminar + edit: + title: Editar regra para identificadores + matches_exactly_html: É igual a %{string} + new: + create: Crear regra + title: Crear nova regra para identificadores + no_username_block_selected: Non se cambiou ningunha regra porque non se seleccionou ningunha + not_permitted: Non permitido + title: Regras para identificadores + updated_msg: Actualizouse correctamente a regra warning_presets: add_new: Engadir novo delete: Eliminar diff --git a/config/locales/he.yml b/config/locales/he.yml index ed80dfd6c99..737d7464b26 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -196,6 +196,7 @@ he: create_relay: יצירת ממסר create_unavailable_domain: יצירת דומיין בלתי זמין create_user_role: יצירת תפקיד + create_username_block: יצירת חוק שמות משתמש demote_user: הורדת משתמש בדרגה destroy_announcement: מחיקת הכרזה destroy_canonical_email_block: מחיקת חסימת דואל @@ -209,6 +210,7 @@ he: destroy_status: מחיקת הודעה destroy_unavailable_domain: מחיקת דומיין בלתי זמין destroy_user_role: מחיקת תפקיד + destroy_username_block: מחיקת חוק שמות משתמש disable_2fa_user: השעיית זיהוי דו-גורמי disable_custom_emoji: השעיית אמוג'י מיוחד disable_relay: השבתת ממסר @@ -243,6 +245,7 @@ he: update_report: עדכון דו"ח עבירה update_status: סטטוס עדכון update_user_role: עדכון תפקיד + update_username_block: עדכון חוק שמות משתמש actions: approve_appeal_html: "%{name} אישר/ה ערעור על החלטת מנהלי הקהילה מ-%{target}" approve_user_html: "%{name} אישר/ה הרשמה מ-%{target}" @@ -261,6 +264,7 @@ he: create_relay_html: "%{name} יצרו את הממסר %{target}" create_unavailable_domain_html: "%{name} הפסיק/ה משלוח לדומיין %{target}" create_user_role_html: "%{name} יצר את התפקיד של %{target}" + create_username_block_html: "%{name} הוסיפו חוק לשמות משתמש המכילים %{target}" demote_user_html: "%{name} הוריד/ה בדרגה את המשתמש %{target}" destroy_announcement_html: "%{name} מחק/ה את ההכרזה %{target}" destroy_canonical_email_block_html: "%{name} הסירו חסימה מדואל %{target}" @@ -274,6 +278,7 @@ he: destroy_status_html: ההודעה של %{target} הוסרה ע"י %{name} destroy_unavailable_domain_html: "%{name} התחיל/ה מחדש משלוח לדומיין %{target}" destroy_user_role_html: "%{name} ביטל את התפקיד של %{target}" + destroy_username_block_html: "%{name} הסירו חוק לשמות משתמש המכילים %{target}" disable_2fa_user_html: "%{name} ביטל/ה את הדרישה לאימות דו-גורמי למשתמש %{target}" disable_custom_emoji_html: "%{name} השבית/ה את האמוג'י %{target}" disable_relay_html: "%{name} השביתו את הממסר %{target}" @@ -308,6 +313,7 @@ he: update_report_html: '%{name} עדכן/ה דו"ח %{target}' update_status_html: "%{name} עדכן/ה הודעה של %{target}" update_user_role_html: "%{name} שינה את התפקיד של %{target}" + update_username_block_html: "%{name} עידכנו חוק לשמות משתמש המכילים %{target}" deleted_account: חשבון מחוק empty: לא נמצאו יומנים. filter_by_action: סינון לפי פעולה @@ -1121,6 +1127,25 @@ he: two: הוצגה על ידי %{count} משתמשים במשך השבוע שעבר title: המלצות ונושאים חמים trending: נושאים חמים + username_blocks: + add_new: הוספת חדש + block_registrations: חסימת הרשמות + comparison: + contains: מכיל + equals: שווה + contains_html: מכיל %{string} + created_msg: חוק שמות משתמשים נוצר בהצלחה + delete: מחיקה + edit: + title: עריכת חוק שמות משתמש + matches_exactly_html: מתאים ל־%{string} + new: + create: יצירת כלל + title: יצירת חוק חדש לשמות משתמש + no_username_block_selected: לא בוצעו שינויים בחוקי בחירת שמות שכן לא נבחרו כאלו + not_permitted: שמות אסורים + title: חוקי שמות + updated_msg: חוק שמות משתמשים עודכן בהצלחה warning_presets: add_new: הוספת חדש delete: למחוק diff --git a/config/locales/is.yml b/config/locales/is.yml index 2e47f3245a9..11c8a7f568b 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -190,6 +190,7 @@ is: create_relay: Búa til endurvarpa create_unavailable_domain: Útbúa lén sem ekki er tiltækt create_user_role: Útbúa hlutverk + create_username_block: Búa til notandanafnsreglu demote_user: Lækka notanda í tign destroy_announcement: Eyða tilkynningu destroy_canonical_email_block: Eyða útilokunarblokk tölvupósts @@ -203,6 +204,7 @@ is: destroy_status: Eyða færslu destroy_unavailable_domain: Eyða léni sem ekki er tiltækt destroy_user_role: Eyða hlutverki + destroy_username_block: Eyða notandanafnsreglu disable_2fa_user: Gera tveggja-þátta auðkenningu óvirka disable_custom_emoji: Gera sérsniðið tjáningartákn óvirkt disable_relay: Gera endurvarpa óvirkan @@ -237,6 +239,7 @@ is: update_report: Uppfæra kæru update_status: Uppfæra færslu update_user_role: Uppfæra hlutverk + update_username_block: Uppfæra notandanafnsreglu actions: approve_appeal_html: "%{name} samþykkti áfrýjun á ákvörðun umsjónarmanns frá %{target}" approve_user_html: "%{name} samþykkti nýskráningu frá %{target}" @@ -255,6 +258,7 @@ is: create_relay_html: "%{name} bjó til endurvarpa %{target}" create_unavailable_domain_html: "%{name} stöðvaði afhendingu til lénsins %{target}" create_user_role_html: "%{name} útbjó %{target} hlutverk" + create_username_block_html: "%{name} bætti við reglu varðandi notendanöfn sem innihalda %{target}" 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}" @@ -268,6 +272,7 @@ is: destroy_status_html: "%{name} fjarlægði færslu frá %{target}" destroy_unavailable_domain_html: "%{name} hóf aftur afhendingu til lénsins %{target}" destroy_user_role_html: "%{name} eyddi hlutverki %{target}" + destroy_username_block_html: "%{name} fjarlægði reglu varðandi notendanöfn sem innihalda %{target}" disable_2fa_user_html: "%{name} gerði kröfu um tveggja-þátta innskráningu óvirka fyrir notandann %{target}" disable_custom_emoji_html: "%{name} gerði tjáningartáknið %{target} óvirkt" disable_relay_html: "%{name} gerði endurvarpann %{target} óvirkan" @@ -302,6 +307,7 @@ is: update_report_html: "%{name} uppfærði kæru %{target}" update_status_html: "%{name} uppfærði færslu frá %{target}" update_user_role_html: "%{name} breytti hlutverki %{target}" + update_username_block_html: "%{name} uppfærði reglu varðandi notendanöfn sem innihalda %{target}" deleted_account: eyddur notandaaðgangur empty: Engar atvikaskrár fundust. filter_by_action: Sía eftir aðgerð @@ -1087,6 +1093,25 @@ is: other: Notað af %{count} aðilum síðustu vikuna title: Meðmæli og vinsælt trending: Vinsælt + username_blocks: + add_new: Bæta við nýju + block_registrations: Loka á nýskráningar + comparison: + contains: Inniheldur + equals: Er jafnt og + contains_html: Inniheldur %{string} + created_msg: Tókst að útbúa notandanafnsreglu + delete: Eyða + edit: + title: Breyta notandanafnsreglu + matches_exactly_html: Er jafnt og %{string} + new: + create: Búa til reglu + title: Búa til nýja notandanafnsreglu + no_username_block_selected: Engum notandanafnsreglum var breytt þar sem engar voru valdar + not_permitted: Ekki leyft + title: Notendanafnareglur + updated_msg: Tókst að uppfæra notandanafnsreglu warning_presets: add_new: Bæta við nýju delete: Eyða diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 66c16c899df..a3ae182a5c4 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -181,7 +181,7 @@ ko: create_canonical_email_block: 이메일 차단 생성 create_custom_emoji: 커스텀 에모지 생성 create_domain_allow: 도메인 허용 생성 - create_domain_block: 도메인 차단 추가 + create_domain_block: 도메인 차단 만들기 create_email_domain_block: 이메일 도메인 차단 생성 create_ip_block: IP 규칙 만들기 create_relay: 릴레이 생성 @@ -1069,6 +1069,12 @@ ko: other: 지난 주 동안 %{count} 명의 사람들이 사용했습니다 title: 추천과 유행 trending: 유행 중 + username_blocks: + add_new: 새로 추가 + new: + create: 규칙 만들기 + title: 새 유저네임 규칙 만들기 + not_permitted: 허용하지 않음 warning_presets: add_new: 새로 추가 delete: 삭제 diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 025e2decd54..72f98477b71 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -160,6 +160,10 @@ da: name: Offentligt rollennavn, hvis rollen er opsat til fremstå som et badge permissions_as_keys: Brugere med denne rolle vil kunne tilgå... position: Højere rolle bestemmer konfliktløsning i visse situationer. Visse handlinger kan kun udføres på roller med lavere prioritet + username_block: + allow_with_approval: I stedet for at forhindre tilmelding helt, vil matchende tilmeldinger kræve din godkendelse + comparison: Vær opmærksom på Scunthorpe-problemet ved blokering af delvise match + username: Matches uanset minuskel/majuskel-brug og alm. homoglyffer, såsom "4" for "a" eller "3" for "e" webhook: events: Vælg begivenheder at sende template: Skriv din egen JSON nyttelast ved hjælp af variabel interpolation. Lad feltet stå tomt for standard JSON. @@ -371,6 +375,10 @@ da: name: Navn permissions_as_keys: Tilladelser position: Prioritet + username_block: + allow_with_approval: Tillad registreringer med godkendelse + comparison: Sammenligningsmetode + username: Ord, som skal matches webhook: events: Aktive begivenheder template: Payload skabelon diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 04fa49ba576..a6b1bc4f9a3 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -160,6 +160,10 @@ de: name: Name der Rolle, der auch öffentlich als Badge angezeigt wird, sofern dies unten aktiviert ist permissions_as_keys: Benutzer*innen mit dieser Rolle haben Zugriff auf... position: Höhere Rollen entscheiden über Konfliktlösungen zu gewissen Situationen. Bestimmte Aktionen können nur mit geringfügigeren Rollen durchgeführt werden + username_block: + allow_with_approval: Anstatt Registrierungen komplett zu verhindern, benötigen übereinstimmende Treffer eine Genehmigung + comparison: Bitte beachte das Scunthorpe-Problem, wenn teilweise übereinstimmende Treffer gesperrt werden + username: Abgleich erfolgt unabhängig der Groß- und Kleinschreibung sowie gängiger Homoglyphen („4“ für „a“ oder „3“ für „e“) webhook: events: Zu sendende Ereignisse auswählen template: Erstelle deine eigenen JSON-Nutzdaten mit Hilfe von Variablen-Interpolation. Leer lassen für Standard-JSON. @@ -371,6 +375,10 @@ de: name: Name permissions_as_keys: Berechtigungen position: Priorität + username_block: + allow_with_approval: Registrierungen mit Genehmigung zulassen + comparison: Vergleichsmethode + username: Übereinstimmendes Wort webhook: events: Aktivierte Ereignisse template: Nutzdaten-Vorlage diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index e4c8f941ceb..b1b89124d80 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -160,6 +160,10 @@ es-AR: name: Nombre público del rol, si el rol se establece para que se muestre como una insignia permissions_as_keys: Los usuarios con este rol tendrán acceso a… position: Un rol más alto decide la resolución de conflictos en ciertas situaciones. Ciertas acciones sólo pueden llevarse a cabo en roles con prioridad inferior + username_block: + allow_with_approval: En lugar de impedir el registro total, los registros coincidentes requerirán tu aprobación + comparison: Por favor, tené en cuenta el Problema de Scunthorpe al bloquear coincidencias parciales + username: Coincidirá sin importar la capitalización de letras y los homoglifos comunes como «4» para «a», o «3» para «e» webhook: events: Seleccionar eventos para enviar template: Creá tu propio archivo JSON usando interpolación variable. Dejalo en blanco para usar el archivo JSON predeterminado. @@ -371,6 +375,10 @@ es-AR: name: Nombre permissions_as_keys: Permisos position: Prioridad + username_block: + allow_with_approval: Permitir registros con aprobación + comparison: Método de comparación + username: Palabra a coincidir webhook: events: Eventos habilitados template: Plantilla de carga diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index e8080b2a768..dd57c522a11 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -160,6 +160,10 @@ es-MX: name: Nombre público del rol, si el rol se establece para que se muestre como una insignia permissions_as_keys: Los usuarios con este rol tendrán acceso a... position: Un rol superior decide la resolución de conflictos en ciertas situaciones. Ciertas acciones sólo pueden llevarse a cabo en roles con menor prioridad + username_block: + allow_with_approval: En lugar de impedir directamente el registro, los registros coincidentes requerirán tu aprobación + comparison: Por favor ten en cuenta el problema de Scunthorpe al bloquear coincidencias parciales + username: Se emparejará independientemente de las mayúsculas y minúsculas y de los homógrafos comunes como «4» por «a» o «3» por «e» webhook: events: Seleccionar eventos para enviar template: Crea tu propio JSON usando interpolación variable. Déjalo en blanco para el JSON predeterminado. @@ -371,6 +375,10 @@ es-MX: name: Nombre permissions_as_keys: Permisos position: Prioridad + username_block: + allow_with_approval: Permitir registros con aprobación previa + comparison: Método de comparación + username: Palabra a coincidir webhook: events: Eventos habilitados template: Plantilla de carga diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index 31519771e17..6bef4ba62d9 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -160,6 +160,10 @@ es: name: Nombre público del rol, si el rol se establece para que se muestre como una insignia permissions_as_keys: Los usuarios con este rol tendrán acceso a... position: Un rol superior decide la resolución de conflictos en ciertas situaciones. Ciertas acciones sólo pueden llevarse a cabo en roles con menor prioridad + username_block: + allow_with_approval: En lugar de impedir directamente el registro, los registros coincidentes requerirán tu aprobación + comparison: Por favor, ten en cuenta el problema de Scunthorpe al bloquear coincidencias parciales + username: Se emparejará independientemente de la mayúscula o minúscula y de homógrafos comunes como «4» por «a» o «3» por «e» webhook: events: Seleccionar eventos para enviar template: Crea tu propio JSON usando interpolación variable. Déjalo en blanco para el JSON predeterminado. @@ -371,6 +375,10 @@ es: name: Nombre permissions_as_keys: Permisos position: Prioridad + username_block: + allow_with_approval: Permitir registros con aprobación + comparison: Método de comparación + username: Palabra a coincidir webhook: events: Eventos habilitados template: Plantilla de carga diff --git a/config/locales/simple_form.et.yml b/config/locales/simple_form.et.yml index f013f454caa..2217eee7a3e 100644 --- a/config/locales/simple_form.et.yml +++ b/config/locales/simple_form.et.yml @@ -10,6 +10,7 @@ et: indexable: Sinu avalikud postitused võivad ilmuda Mastodoni otsingutulemustes. Inimesed, kes on sinu postitustele reageerinud, saavad neid otsida nii või naa. note: 'Saad @mainida teisi inimesi või #silte.' show_collections: Inimesed saavad sirvida su jälgijaid ja jälgitavaid. Inimesed, keda sa jälgid, näevad seda sõltumata häälestuse valikust. + unlocked: Teised kasutajad saavad sind jälgima hakata nõusolekut küsimata. Eemalda märge, kui soovid jälgimistaotlusi üle vaadata ja valida, kas nõustuda või keelduda uute jälgijatega. account_alias: acct: Sisesta konto kasutajanimi@domeen, mille soovid siia ümber kolida account_migration: @@ -59,6 +60,7 @@ et: setting_display_media_default: Peida tundlikuks märgitud meedia setting_display_media_hide_all: Alati peida kõik meedia setting_display_media_show_all: Alati näita tundlikuks märgistatud meedia + setting_system_scrollbars_ui: Kehtib vaid Safaril ja Chrome'il põhinevatel tavaarvuti veebibrauserite puhul setting_use_blurhash: Värvid põhinevad peidetud visuaalidel, kuid hägustavad igasuguseid detaile setting_use_pending_items: Voo automaatse kerimise asemel peida ajajoone uuendused kliki taha username: Võid kasutada ladina tähti, numbreid ja allkriipsu diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index c15ba7f0dc3..acbd35b9874 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -160,6 +160,10 @@ fo: name: Almenna navnið á leiklutinum, um leikluturin er settur at verða vístur sum eitt tignarmerki permissions_as_keys: Brúkarar við hesum leiklutinum fara at fáa atgongd til... position: Hægri leiklutur er avgerandi fyri loysn av ósemjum í ávísum støðum. Ávísar atgerðir kunnu einans verða gjørdar móti leiklutum, sum hava eina lægri raðfesting + username_block: + allow_with_approval: Í staðin fyri at forða heilt fyri skráseting, fara samsvarandi skrásetingar at krevja, at tú góðkennir tær + comparison: Vinarliga gev Scunthorpe-trupulleikanum gætur, tá tú blokerar lutvís samsvar + username: Samsvar vera funnin óansæð stórar og smáar bókstavir og vanligar homoglyffir, sosum "4" fyri "a" og "3" fyri "e" webhook: events: Vel hendingar at senda template: Samanset títt egna JSON farm við at brúka variabul-interpolasjón. Lat vera blankt fyri sjálvgaldandi JSON. @@ -371,6 +375,10 @@ fo: name: Navn permissions_as_keys: Loyvi position: Raðfesting + username_block: + allow_with_approval: Loyv skrásetingum við góðkenning + comparison: Samanberingarmetoda + username: Orð, sum skal samsvara webhook: events: Virknar hendingar template: Farmaformur diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 58722de43d2..1e64cb94e64 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -160,6 +160,10 @@ gl: name: Nome público do rol, se o rol se mostra como unha insignia permissions_as_keys: As usuarias con este rol terán acceso a... position: O rol superior decide nos conflitos en certas situacións. Algunhas accións só poden aplicarse sobre roles cunha prioridade menor + username_block: + allow_with_approval: No lugar de evitar a cración directa de contas, as contas mediante regras van precisar a túa aprobación + comparison: Ten en conta o Sunthorpe Problem cando se bloquean coincidencias parciais + username: Vai crear unha coincidencia sen importar se son maiúsculas ou minúsculas e homoglifos como «4» por «a» ou «3» por «e». webhook: events: Escoller eventos a enviar template: Crea o teu propio JSON interpolando variables. Déixao en branco para usar o JSON predeterminado. @@ -371,6 +375,10 @@ gl: name: Nome permissions_as_keys: Permisos position: Prioridade + username_block: + allow_with_approval: Permitir crear contas con aprobación + comparison: Método de comparación + username: Palabra a comparar webhook: events: Eventos activados template: Modelo de carga diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index 988195c633a..d6ede9e3073 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -162,6 +162,10 @@ he: name: שם ציבורי של התפקיד, במידה והתפקיד מוגדר ככזה שמופיע כתג permissions_as_keys: למשתמשים בתפקיד זה תהיה גישה ל... position: תפקיד גבוה יותר מכריע בחילוקי דעות במצבים מסוימים. פעולות מסוימות יכולות להתבצע רק על תפקידים בדרגה נמוכה יותר + username_block: + allow_with_approval: במקום למנוע הרשמה לחלוטין, הרשמות חדשות יצטרכו לחכות לאישורך + comparison: יש להזהר מחסימת חלקי שמות קצרים מדי כדי להמנע מהתופעה הידועה בשם Scunthorpe problem + username: ההתאמה תתבצע ללא קשר לגודל אותיות או להומוגליפים נפוצים כגון "4" במקום "a" או "3" במקום "e" webhook: events: בחר אירועים לשליחה template: ניתן להרכיב מטען JSON משלך בשימוש בשילוב משתנים. יש להשאיר ריק בשביל JSON ברירת המחדל. @@ -373,6 +377,10 @@ he: name: שם permissions_as_keys: הרשאות position: עדיפות + username_block: + allow_with_approval: הרשאת הרשמה לאחר אישור + comparison: שיטת השוואה + username: מילה להתאמה webhook: events: אירועים מאופשרים template: תבנית מטען diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index 15e5b92ea67..2a1895bfd76 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -160,6 +160,10 @@ is: name: Opinbert heiti hlutverks, ef birta á hlutverk sem merki permissions_as_keys: Notendur með þetta hlutverk munu hafa aðgang að... position: Rétthærra hlutverk ákvarðar lausn árekstra í ákveðnum tilfellum. Sumar aðgerðir er aðeins hægt að framkvæma á hlutverk með lægri forgangi + username_block: + allow_with_approval: Í stað þess að loka alfarið á nýskráningar, munu samsvarandi nýskráningar þurfa samþykki þitt + comparison: Hafðu í huga Scunthorpe-vandamálið (Scunthorpe inniheldur orð sem ýmsar síur reyna að banna) þegar þú útilokar samsvarandi orðhluta + username: Mun samsvara án tillits til stafstöðu eða algengra táknlíkinga á borð við "4" í stað "a" eða "3" í stað "e" webhook: events: Veldu atburði sem á að senda template: Sníddu eigin JSON með breytilegri brúun. Skiljið eftir autt fyrir sjálfgefið JSON. @@ -371,6 +375,10 @@ is: name: Nafn permissions_as_keys: Heimildir position: Forgangur + username_block: + allow_with_approval: Leyfa skráningar með samþykki + comparison: Aðferð við samanburð + username: Orð sem samsvara webhook: events: Virkjaðir atburðir template: Sniðmát gagna diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index b9b536eece8..70230198967 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -159,6 +159,10 @@ vi: name: Tên công khai của vai trò, nếu vai trò được đặt để hiển thị dưới dạng huy hiệu permissions_as_keys: Người có vai trò này sẽ có quyền truy cập vào... position: Vai trò cao hơn sẽ có quyền quyết định xung đột trong các tình huống. Các vai trò có mức độ ưu tiên thấp hơn chỉ có thể thực hiện một số hành động nhất định + username_block: + allow_with_approval: Thay vì cấm đăng ký ngay lập tức, bạn sẽ duyệt phù hợp trước khi đăng ký + comparison: Xin hãy lưu ý đến Vấn đề Scunthorpe khi chặn các từ trùng khớp một phần + username: Sẽ được khớp bất kể chữ hoa và chữ tượng hình phổ biến như "4" cho "a" hoặc "3" cho "e" webhook: events: Chọn sự kiện để gửi template: Soạn JSON payload của riêng bạn bằng phép nội suy biến. Để trống để dùng JSON mặc định. @@ -370,6 +374,10 @@ vi: name: Tên permissions_as_keys: Quyền position: Mức độ ưu tiên + username_block: + allow_with_approval: Cho đăng ký nhưng duyệt thủ công + comparison: Phương pháp so sánh + username: Khớp từ webhook: events: Những sự kiện đã bật template: Mẫu payload diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 4a48c6c3112..e02e264ce90 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -159,6 +159,10 @@ zh-TW: name: 角色的公開名稱,如果角色設定為顯示為徽章 permissions_as_keys: 有此角色的使用者將有權存取... position: 某些情況下,衝突的解決方式由更高階的角色決定。某些動作只能由優先程度較低的角色執行 + username_block: + allow_with_approval: 不直接禁止註冊,符合規則之註冊將需要您的審核 + comparison: 當您封鎖部分匹配時,請留心 Scunthorpe 問題 + username: 將匹配不限大小寫與常見同形異義字,如以「4」代替「A」或以「3」代替「E」 webhook: events: 請選擇要傳送的事件 template: 使用變數代換組合您自己的 JSON payload。留白以使用預設 JSON 。 @@ -370,6 +374,10 @@ zh-TW: name: 名稱 permissions_as_keys: 權限 position: 優先權 + username_block: + allow_with_approval: 經審核後可註冊 + comparison: 比較方式 + username: 字詞匹配 webhook: events: 已啟用的事件 template: Payload 樣板 diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 88a14b0aa47..ad3926fbebb 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1072,6 +1072,11 @@ uk: other: Використали %{count} людей за минулий тиждень title: Рекомендації та тренди trending: Популярне + username_blocks: + comparison: + contains: Містить + equals: Дорівнює + delete: Видалити warning_presets: add_new: Додати новий delete: Видалити diff --git a/config/locales/vi.yml b/config/locales/vi.yml index adc801c449c..33a779f417d 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -187,6 +187,7 @@ vi: create_relay: Tạo relay create_unavailable_domain: Bỏ liên hợp create_user_role: Tạo vai trò + create_username_block: Tạo quy tắc tên người dùng demote_user: Hạ vai trò destroy_announcement: Xóa thông báo destroy_canonical_email_block: Bỏ chặn địa chỉ email biến thể @@ -200,6 +201,7 @@ vi: destroy_status: Xóa tút destroy_unavailable_domain: Cho phép liên hợp destroy_user_role: Xóa vai trò + destroy_username_block: Xóa quy tắc tên người dùng disable_2fa_user: Vô hiệu hóa 2FA disable_custom_emoji: Vô hiệu hóa emoji disable_relay: Tắt relay @@ -234,6 +236,7 @@ vi: update_report: Cập nhật báo cáo update_status: Cập nhật tút update_user_role: Cập nhật vai trò + update_username_block: Cập nhật quy tắc tên người dùng actions: approve_appeal_html: "%{name} đã chấp nhận khiếu nại từ %{target}" approve_user_html: "%{name} đã chấp nhận đăng ký từ %{target}" @@ -252,6 +255,7 @@ vi: create_relay_html: "%{name} đã tạo relay %{target}" create_unavailable_domain_html: "%{name} đã bỏ liên hợp với máy chủ %{target}" create_user_role_html: "%{name} đã tạo vai trò %{target}" + create_username_block_html: "%{name} thêm quy tắc cho tên người dùng chứa %{target}" 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}" @@ -265,6 +269,7 @@ vi: destroy_status_html: "%{name} đã xóa tút của %{target}" destroy_unavailable_domain_html: "%{name} tiếp tục liên hợp với máy chủ %{target}" destroy_user_role_html: "%{name} đã xóa vai trò %{target}" + destroy_username_block_html: "%{name} xóa quy tắc cho tên người dùng chứa %{target}" disable_2fa_user_html: "%{name} đã vô hiệu hóa xác thực 2 bước của %{target}" disable_custom_emoji_html: "%{name} đã ẩn emoji %{target}" disable_relay_html: "%{name} đã tắt relay %{target}" @@ -299,6 +304,7 @@ vi: update_report_html: "%{name} đã cập nhật báo cáo %{target}" update_status_html: "%{name} đã cập nhật tút của %{target}" update_user_role_html: "%{name} đã cập nhật vai trò %{target}" + update_username_block_html: "%{name} đã cập nhật quy tắc cho tên người dùng chứa %{target}" deleted_account: tài khoản đã xóa empty: Không tìm thấy bản ghi. filter_by_action: Theo hành động @@ -1067,6 +1073,25 @@ vi: other: "%{count} người dùng tuần rồi" title: Đề xuất & Xu hướng trending: Xu hướng + username_blocks: + add_new: Thêm mới + block_registrations: Cấm đăng ký + comparison: + contains: Có chứa + equals: Tương tự + contains_html: Có chứa %{string} + created_msg: Đã tạo quy tắc tên người dùng thành công + delete: Xóa + edit: + title: Sửa quy tắc tên người dùng + matches_exactly_html: Có chứa %{string} + new: + create: Tạo quy tắc + title: Tạo quy tắc tên người dùng mới + no_username_block_selected: Chưa chọn quy tắc tên người dùng nào + not_permitted: Cấm + title: Quy tắc tên người dùng + updated_msg: Đã cập nhật quy tắc tên người dùng thành công warning_presets: add_new: Thêm mới delete: Xóa bỏ diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index bc29a30b332..1dadf43932c 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -187,6 +187,7 @@ zh-TW: create_relay: 新增中繼 create_unavailable_domain: 新增無法存取的網域 create_user_role: 新增角色 + create_username_block: 新增使用者名稱規則 demote_user: 將用戶降級 destroy_announcement: 刪除公告 destroy_canonical_email_block: 刪除電子郵件封鎖 @@ -200,6 +201,7 @@ zh-TW: destroy_status: 刪除狀態 destroy_unavailable_domain: 刪除無法存取的網域 destroy_user_role: 移除角色 + destroy_username_block: 刪除使用者名稱規則 disable_2fa_user: 停用兩階段驗證 disable_custom_emoji: 停用自訂 emoji 表情符號 disable_relay: 停用中繼 @@ -234,6 +236,7 @@ zh-TW: update_report: 更新檢舉報告 update_status: 更新狀態 update_user_role: 更新角色 + update_username_block: 變更使用者名稱規則 actions: approve_appeal_html: "%{name} 已批准來自 %{target} 的審核決定申訴" approve_user_html: "%{name} 已批准自 %{target} 而來的註冊" @@ -252,6 +255,7 @@ zh-TW: create_relay_html: "%{name} 已新增中繼 %{target}" create_unavailable_domain_html: "%{name} 停止發送至網域 %{target}" create_user_role_html: "%{name} 已新增 %{target} 角色" + create_username_block_html: "%{name} 已新增使用者名稱包含 %{target} 之規則" demote_user_html: "%{name} 將使用者 %{target} 降級" destroy_announcement_html: "%{name} 已刪除公告 %{target}" destroy_canonical_email_block_html: "%{name} 已解除封鎖 hash 為 %{target} 之電子郵件" @@ -265,6 +269,7 @@ zh-TW: destroy_status_html: "%{name} 已刪除 %{target} 的嘟文" destroy_unavailable_domain_html: "%{name} 已恢復對網域 %{target} 的發送" destroy_user_role_html: "%{name} 已刪除 %{target} 角色" + destroy_username_block_html: "%{name} 已移除使用者名稱包含 %{target} 之規則" disable_2fa_user_html: "%{name} 已停用使用者 %{target} 的兩階段驗證 (2FA) " disable_custom_emoji_html: "%{name} 已停用自訂 emoji 表情符號 %{target}" disable_relay_html: "%{name} 已停用中繼 %{target}" @@ -299,6 +304,7 @@ zh-TW: update_report_html: "%{name} 已更新 %{target} 的檢舉" update_status_html: "%{name} 已更新 %{target} 的嘟文" update_user_role_html: "%{name} 已變更 %{target} 角色" + update_username_block_html: "%{name} 已變更使用者名稱包含 %{target} 之規則" deleted_account: 已刪除帳號 empty: 找不到 log filter_by_action: 按動作過濾 @@ -1069,6 +1075,25 @@ zh-TW: other: 上週被 %{count} 個人使用 title: 推薦內容與熱門趨勢 trending: 熱門 + username_blocks: + add_new: 新增 + block_registrations: 禁止註冊 + comparison: + contains: 包含 + equals: 等於 + contains_html: 包含 %{string} + created_msg: 已成功新增使用者名稱規則 + delete: 刪除 + edit: + title: 編輯使用者名稱規則 + matches_exactly_html: 等於 %{string} + new: + create: 新增規則 + title: 新增使用者名稱規則 + no_username_block_selected: 因未選取任何使用者名稱規則,所以什麼事都沒發生 + not_permitted: 不允許 + title: 使用者名稱規則 + updated_msg: 已成功變更使用者名稱規則 warning_presets: add_new: 新增 delete: 刪除 From ba9fa54f9c0c7be46ca5ba4d8285b52f7976516a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 04:52:47 -0400 Subject: [PATCH 162/660] Add coverage for more admin/domain_blocks scenarios (#35590) --- spec/system/admin/domain_blocks_spec.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/system/admin/domain_blocks_spec.rb b/spec/system/admin/domain_blocks_spec.rb index 56a5d119844..c309e39a4fb 100644 --- a/spec/system/admin/domain_blocks_spec.rb +++ b/spec/system/admin/domain_blocks_spec.rb @@ -57,6 +57,30 @@ RSpec.describe 'blocking domains through the moderation interface' do end end + context 'when suspending an already suspended domain and using a lower severity' do + before { Fabricate :domain_block, domain: 'example.com', severity: 'silence' } + + it 'warns about downgrade and does not update' do + visit new_admin_domain_block_path + + submit_domain_block('example.com', 'noop') + + expect(page) + .to have_content(/You have already imposed stricter limits on example.com/) + end + end + + context 'when failing to provide a domain value' do + it 'provides an error about the missing value' do + visit new_admin_domain_block_path + + submit_domain_block('', 'noop') + + expect(page) + .to have_content(/review the error below/) + end + end + context 'when suspending a subdomain of an already-silenced domain' do it 'presents a confirmation screen before suspending the domain' do domain_block = Fabricate(:domain_block, domain: 'example.com', severity: 'silence') From ece49baa3817b14e096b4f607cf24408bea199fe Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 05:27:29 -0400 Subject: [PATCH 163/660] Use generated query method for check in admin/domain_blocks (#35589) --- app/controllers/admin/domain_blocks_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index c3443b70776..f88065db8d5 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -129,7 +129,7 @@ module Admin end def requires_confirmation? - @domain_block.valid? && (@domain_block.new_record? || @domain_block.severity_changed?) && @domain_block.severity.to_s == 'suspend' && !params[:confirm] + @domain_block.valid? && (@domain_block.new_record? || @domain_block.severity_changed?) && @domain_block.suspend? && !params[:confirm] end end end From fd779c25b93fbc38ab58f3033aa3803d59feaa9f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 05:28:20 -0400 Subject: [PATCH 164/660] Avoid `return not_found` in statuses controller (#35585) --- app/controllers/statuses_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 341b0e64729..af6bebf36fd 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -11,6 +11,7 @@ class StatusesController < ApplicationController before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? } before_action :set_status before_action :redirect_to_original, only: :show + before_action :verify_embed_allowed, only: :embed after_action :set_link_headers @@ -40,8 +41,6 @@ class StatusesController < ApplicationController end def embed - return not_found if @status.hidden? || @status.reblog? - expires_in 180, public: true response.headers.delete('X-Frame-Options') @@ -50,6 +49,10 @@ class StatusesController < ApplicationController private + def verify_embed_allowed + not_found if @status.hidden? || @status.reblog? + end + def set_link_headers response.headers['Link'] = LinkHeader.new( [[ActivityPub::TagManager.instance.uri_for(@status), [%w(rel alternate), %w(type application/activity+json)]]] From 15b72591d4f3e70b70bc96cce6051d5568d7dd95 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 05:48:18 -0400 Subject: [PATCH 165/660] Use attribute for `User#external?` (#35579) --- app/models/user.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 0c876c64bf8..2f3640b62a6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -143,7 +143,9 @@ class User < ApplicationRecord delegate :can?, to: :role attr_reader :invite_code, :date_of_birth - attr_writer :external, :bypass_registration_checks, :current_account + attr_writer :bypass_registration_checks, :current_account + + attribute :external, :boolean, default: false def self.those_who_can(*any_of_privileges) matching_role_ids = UserRole.that_can(*any_of_privileges).map(&:id) @@ -507,10 +509,6 @@ class User < ApplicationRecord Setting.registrations_mode == 'open' end - def external? - !!@external - end - def bypass_registration_checks? @bypass_registration_checks end From 6dc55a2f4e959b0bd22306780cf765b44889cdb2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 06:33:57 -0400 Subject: [PATCH 166/660] Extract `User::Confirmation` concern (#35582) --- app/models/concerns/user/confirmation.rb | 22 ++++ app/models/user.rb | 17 +-- spec/models/user_spec.rb | 82 +------------ .../models/concerns/user/confirmation.rb | 114 ++++++++++++++++++ 4 files changed, 138 insertions(+), 97 deletions(-) create mode 100644 app/models/concerns/user/confirmation.rb create mode 100644 spec/support/examples/models/concerns/user/confirmation.rb diff --git a/app/models/concerns/user/confirmation.rb b/app/models/concerns/user/confirmation.rb new file mode 100644 index 00000000000..46fdb0210a5 --- /dev/null +++ b/app/models/concerns/user/confirmation.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module User::Confirmation + extend ActiveSupport::Concern + + included do + scope :confirmed, -> { where.not(confirmed_at: nil) } + scope :unconfirmed, -> { where(confirmed_at: nil) } + + def confirm + wrap_email_confirmation { super } + end + end + + def confirmed? + confirmed_at.present? + end + + def unconfirmed? + !confirmed? + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 2f3640b62a6..06a28761190 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -58,6 +58,7 @@ class User < ApplicationRecord include LanguagesHelper include Redisable + include User::Confirmation include User::HasSettings include User::LdapAuthenticable include User::Omniauthable @@ -118,8 +119,6 @@ class User < ApplicationRecord scope :recent, -> { order(id: :desc) } scope :pending, -> { where(approved: false) } scope :approved, -> { where(approved: true) } - scope :confirmed, -> { where.not(confirmed_at: nil) } - scope :unconfirmed, -> { where(confirmed_at: nil) } scope :enabled, -> { where(disabled: false) } scope :disabled, -> { where(disabled: true) } scope :active, -> { confirmed.signed_in_recently.account_not_suspended } @@ -184,10 +183,6 @@ class User < ApplicationRecord current_sign_in_at.present? && current_sign_in_at >= ACTIVE_DURATION.ago end - def confirmed? - confirmed_at.present? - end - def invited? invite_id.present? end @@ -212,12 +207,6 @@ class User < ApplicationRecord account_id end - def confirm - wrap_email_confirmation do - super - end - end - # Mark current email as confirmed, bypassing Devise def mark_email_as_confirmed! wrap_email_confirmation do @@ -264,10 +253,6 @@ class User < ApplicationRecord confirmed? && approved? && !disabled? && !account.unavailable? && !account.memorial? end - def unconfirmed? - !confirmed? - end - def unconfirmed_or_pending? unconfirmed? || pending? end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index c71b7a600ba..438937b1001 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -10,6 +10,7 @@ RSpec.describe User do let(:account) { Fabricate(:account, username: 'alice') } it_behaves_like 'two_factor_backupable' + it_behaves_like 'User::Confirmation' describe 'otp_secret' do it 'encrypts the saved value' do @@ -65,14 +66,6 @@ RSpec.describe User do end end - describe 'confirmed' do - it 'returns an array of users who are confirmed' do - Fabricate(:user, confirmed_at: nil) - confirmed_user = Fabricate(:user, confirmed_at: Time.zone.now) - expect(described_class.confirmed).to contain_exactly(confirmed_user) - end - end - describe 'signed_in_recently' do it 'returns a relation of users who have signed in during the recent period' do recent_sign_in_user = Fabricate(:user, current_sign_in_at: within_duration_window_days.ago) @@ -228,79 +221,6 @@ RSpec.describe User do end end - describe '#confirmed?' do - it 'returns true when a confirmed_at is set' do - user = Fabricate.build(:user, confirmed_at: Time.now.utc) - expect(user.confirmed?).to be true - end - - it 'returns false if a confirmed_at is nil' do - user = Fabricate.build(:user, confirmed_at: nil) - expect(user.confirmed?).to be false - end - end - - describe '#confirm' do - subject { user.confirm } - - let(:new_email) { 'new-email@example.com' } - - before do - allow(TriggerWebhookWorker).to receive(:perform_async) - end - - context 'when the user is already confirmed' do - let!(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: true, unconfirmed_email: new_email) } - - it 'sets email to unconfirmed_email and does not trigger web hook' do - expect { subject }.to change { user.reload.email }.to(new_email) - - expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) - end - end - - context 'when the user is a new user' do - let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) } - - context 'when the user is already approved' do - before do - Setting.registrations_mode = 'approved' - user.approve! - end - - it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do - expect { subject }.to change { user.reload.email }.to(new_email) - - expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once - end - end - - context 'when the user does not require explicit approval' do - before do - Setting.registrations_mode = 'open' - end - - it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do - expect { subject }.to change { user.reload.email }.to(new_email) - - expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once - end - end - - context 'when the user requires explicit approval but is not approved' do - before do - Setting.registrations_mode = 'approved' - end - - it 'sets email to unconfirmed_email and does not trigger web hook' do - expect { subject }.to change { user.reload.email }.to(new_email) - - expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) - end - end - end - end - describe '#approve!' do subject { user.approve! } diff --git a/spec/support/examples/models/concerns/user/confirmation.rb b/spec/support/examples/models/concerns/user/confirmation.rb new file mode 100644 index 00000000000..4edc402f950 --- /dev/null +++ b/spec/support/examples/models/concerns/user/confirmation.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.shared_examples 'User::Confirmation' do + describe 'Scopes' do + let!(:unconfirmed_user) { Fabricate :user, confirmed_at: nil } + let!(:confirmed_user) { Fabricate :user, confirmed_at: Time.now.utc } + + describe '.confirmed' do + it 'returns users who are confirmed' do + expect(described_class.confirmed) + .to contain_exactly(confirmed_user) + end + end + + describe '.unconfirmed' do + it 'returns users who are not confirmed' do + expect(described_class.unconfirmed) + .to contain_exactly(unconfirmed_user) + end + end + end + + describe '#confirmed?' do + subject { Fabricate.build(:user, confirmed_at:) } + + context 'when confirmed_at is set' do + let(:confirmed_at) { Time.now.utc } + + it { is_expected.to be_confirmed } + end + + context 'when confirmed_at is not set' do + let(:confirmed_at) { nil } + + it { is_expected.to_not be_confirmed } + end + end + + describe '#unconfirmed?' do + subject { Fabricate.build(:user, confirmed_at:) } + + context 'when confirmed_at is set' do + let(:confirmed_at) { Time.now.utc } + + it { is_expected.to_not be_unconfirmed } + end + + context 'when confirmed_at is not set' do + let(:confirmed_at) { nil } + + it { is_expected.to be_unconfirmed } + end + end + + describe '#confirm' do + subject { user.confirm } + + let(:new_email) { 'new-email@host.example' } + + before { allow(TriggerWebhookWorker).to receive(:perform_async) } + + context 'when the user is already confirmed' do + let!(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: true, unconfirmed_email: new_email) } + + it 'sets email to unconfirmed_email and does not trigger web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + expect(TriggerWebhookWorker) + .to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + + context 'when the user is a new user' do + let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) } + + context 'when the user does not require explicit approval' do + before { Setting.registrations_mode = 'open' } + + it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + expect(TriggerWebhookWorker) + .to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once + end + end + + context 'when registrations mode is approved' do + before { Setting.registrations_mode = 'approved' } + + context 'when the user is already approved' do + before { user.approve! } + + it 'sets email to unconfirmed_email and triggers `account.approved` web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + expect(TriggerWebhookWorker) + .to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once + end + end + + context 'when the user is not approved' do + it 'sets email to unconfirmed_email and does not trigger web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + expect(TriggerWebhookWorker) + .to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + end + end + end +end From 4042bc959bcfc9d894c1f9e7e008d1a87a349b88 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 06:38:04 -0400 Subject: [PATCH 167/660] Use `increment` for `User#update_sign_in!` optional change (#35573) --- app/models/user.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 06a28761190..669b51c0698 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -228,10 +228,7 @@ class User < ApplicationRecord self.last_sign_in_at = old_current || new_current self.current_sign_in_at = new_current - if new_sign_in - self.sign_in_count ||= 0 - self.sign_in_count += 1 - end + increment(:sign_in_count) if new_sign_in save(validate: false) unless new_record? prepare_returning_user! From 7e6b134222d669ca76289134bd089550b4eb12f7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 07:19:11 -0400 Subject: [PATCH 168/660] Extract `User::Activity` concern (#35581) --- app/models/concerns/user/activity.rb | 28 +++++++++++ app/models/user.rb | 22 +-------- spec/models/user_spec.rb | 29 +---------- .../examples/models/concerns/user/activity.rb | 48 +++++++++++++++++++ 4 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 app/models/concerns/user/activity.rb create mode 100644 spec/support/examples/models/concerns/user/activity.rb diff --git a/app/models/concerns/user/activity.rb b/app/models/concerns/user/activity.rb new file mode 100644 index 00000000000..df2713415b3 --- /dev/null +++ b/app/models/concerns/user/activity.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module User::Activity + extend ActiveSupport::Concern + + # The home and list feeds will be stored for this amount of time, and status + # fan-out to followers will include only people active within this time frame. + # + # Lowering the duration may improve performance if many people sign up, but + # most will not check their feed every day. Raising the duration reduces the + # amount of background processing that happens when people become active. + ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days + + included do + scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) } + scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) } + end + + def signed_in_recently? + current_sign_in_at.present? && current_sign_in_at >= ACTIVE_DURATION.ago + end + + private + + def inactive_since_duration? + last_sign_in_at < ACTIVE_DURATION.ago + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 669b51c0698..7da5876af19 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -58,21 +58,13 @@ class User < ApplicationRecord include LanguagesHelper include Redisable + include User::Activity include User::Confirmation include User::HasSettings include User::LdapAuthenticable include User::Omniauthable include User::PamAuthenticable - # The home and list feeds will be stored in Redis for this amount - # of time, and status fan-out to followers will include only people - # within this time frame. Lowering the duration may improve performance - # if lots of people sign up, but not a lot of them check their feed - # every day. Raising the duration reduces the amount of expensive - # RegenerationWorker jobs that need to be run when those people come - # to check their feed - ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze - devise :two_factor_authenticatable, otp_secret_length: 32 @@ -122,8 +114,6 @@ class User < ApplicationRecord scope :enabled, -> { where(disabled: false) } scope :disabled, -> { where(disabled: true) } scope :active, -> { confirmed.signed_in_recently.account_not_suspended } - scope :signed_in_recently, -> { where(current_sign_in_at: ACTIVE_DURATION.ago..) } - scope :not_signed_in_recently, -> { where(current_sign_in_at: ...ACTIVE_DURATION.ago) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } scope :matches_ip, ->(value) { left_joins(:ips).merge(IpBlock.contained_by(value)).group(users: [:id]) } @@ -179,10 +169,6 @@ class User < ApplicationRecord end end - def signed_in_recently? - current_sign_in_at.present? && current_sign_in_at >= ACTIVE_DURATION.ago - end - def invited? invite_id.present? end @@ -511,7 +497,7 @@ class User < ApplicationRecord return unless confirmed? ActivityTracker.record('activity:logins', id) - regenerate_feed! if needs_feed_update? + regenerate_feed! if inactive_since_duration? end def notify_staff_about_pending_account! @@ -530,10 +516,6 @@ class User < ApplicationRecord RegenerationWorker.perform_async(account_id) end - def needs_feed_update? - last_sign_in_at < ACTIVE_DURATION.ago - end - def validate_email_dns? email_changed? && !external? && !self.class.skip_mx_check? end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 438937b1001..32c9b7f9f8f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -10,6 +10,7 @@ RSpec.describe User do let(:account) { Fabricate(:account, username: 'alice') } it_behaves_like 'two_factor_backupable' + it_behaves_like 'User::Activity' it_behaves_like 'User::Confirmation' describe 'otp_secret' do @@ -66,26 +67,6 @@ RSpec.describe User do end end - describe 'signed_in_recently' do - it 'returns a relation of users who have signed in during the recent period' do - recent_sign_in_user = Fabricate(:user, current_sign_in_at: within_duration_window_days.ago) - Fabricate(:user, current_sign_in_at: exceed_duration_window_days.ago) - - expect(described_class.signed_in_recently) - .to contain_exactly(recent_sign_in_user) - end - end - - describe 'not_signed_in_recently' do - it 'returns a relation of users who have not signed in during the recent period' do - no_recent_sign_in_user = Fabricate(:user, current_sign_in_at: exceed_duration_window_days.ago) - Fabricate(:user, current_sign_in_at: within_duration_window_days.ago) - - expect(described_class.not_signed_in_recently) - .to contain_exactly(no_recent_sign_in_user) - end - end - describe 'account_not_suspended' do it 'returns with linked accounts that are not suspended' do suspended_account = Fabricate(:account, suspended_at: 10.days.ago) @@ -120,14 +101,6 @@ RSpec.describe User do expect(described_class.matches_ip('2160:2160::/32')).to contain_exactly(user1) end end - - def exceed_duration_window_days - described_class::ACTIVE_DURATION + 2.days - end - - def within_duration_window_days - described_class::ACTIVE_DURATION - 2.days - end end describe 'email domains denylist integration' do diff --git a/spec/support/examples/models/concerns/user/activity.rb b/spec/support/examples/models/concerns/user/activity.rb new file mode 100644 index 00000000000..7e647b694a9 --- /dev/null +++ b/spec/support/examples/models/concerns/user/activity.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.shared_examples 'User::Activity' do + before { stub_const 'User::ACTIVE_DURATION', 7.days } + + describe 'Scopes' do + let!(:recent_sign_in_user) { Fabricate(:user, current_sign_in_at: 2.days.ago) } + let!(:no_recent_sign_in_user) { Fabricate(:user, current_sign_in_at: 10.days.ago) } + + describe '.signed_in_recently' do + it 'returns users who have signed in during the recent period' do + expect(described_class.signed_in_recently) + .to contain_exactly(recent_sign_in_user) + end + end + + describe '.not_signed_in_recently' do + it 'returns users who have not signed in during the recent period' do + expect(described_class.not_signed_in_recently) + .to contain_exactly(no_recent_sign_in_user) + end + end + end + + describe '#signed_in_recently?' do + subject { Fabricate.build :user, current_sign_in_at: } + + context 'when current_sign_in_at is nil' do + let(:current_sign_in_at) { nil } + + it { is_expected.to_not be_signed_in_recently } + end + + context 'when current_sign_in_at is before the threshold' do + let(:current_sign_in_at) { 10.days.ago } + + it { is_expected.to_not be_signed_in_recently } + end + + context 'when current_sign_in_at is after the threshold' do + let(:current_sign_in_at) { 2.days.ago } + + it { is_expected.to be_signed_in_recently } + end + end +end From 027657b590943ed71e41b7a099f01d5d0647a11b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:14:33 +0200 Subject: [PATCH 169/660] Update dependency eslint-plugin-jsdoc to v52 (#35561) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1d1952e262f..e1e0017bc53 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.31.0", - "eslint-plugin-jsdoc": "^51.0.0", + "eslint-plugin-jsdoc": "^52.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 746487e0659..b3b7264669f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2676,7 +2676,7 @@ __metadata: eslint-import-resolver-typescript: "npm:^4.2.5" eslint-plugin-formatjs: "npm:^5.3.1" eslint-plugin-import: "npm:~2.31.0" - eslint-plugin-jsdoc: "npm:^51.0.0" + eslint-plugin-jsdoc: "npm:^52.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" @@ -7040,9 +7040,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^51.0.0": - version: 51.3.4 - resolution: "eslint-plugin-jsdoc@npm:51.3.4" +"eslint-plugin-jsdoc@npm:^52.0.0": + version: 52.0.1 + resolution: "eslint-plugin-jsdoc@npm:52.0.1" dependencies: "@es-joy/jsdoccomment": "npm:~0.52.0" are-docs-informative: "npm:^0.0.2" @@ -7056,7 +7056,7 @@ __metadata: spdx-expression-parse: "npm:^4.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/59e5aa972bdd1bd4e2ca2796ed4455dff1069044abc028621e107aa4b0cbb62ce09554c8e7c2ff3a44a1cbd551e54b6970adc420ba3a89adc6236b94310a81ff + checksum: 10c0/0dac74a5ea1db8d15eea5a29a6dadd1361029230f414794abfd143b8cd0129d1cd862481d31eb0374b215d0841f41209fb8b4e36ac69b758094d7c77052432c0 languageName: node linkType: hard From 25add0af316053c79d81d8447771db932d0141bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:15:56 +0000 Subject: [PATCH 170/660] Update dependency cross-env to v10 (#35564) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e1e0017bc53..efe5b45fefb 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "cocoon-js-vanilla": "^1.5.1", "color-blend": "^4.0.0", "core-js": "^3.30.2", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "detect-passive-events": "^2.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", diff --git a/yarn.lock b/yarn.lock index b3b7264669f..9cb29176d8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1939,6 +1939,13 @@ __metadata: languageName: node linkType: hard +"@epic-web/invariant@npm:^1.0.0": + version: 1.0.0 + resolution: "@epic-web/invariant@npm:1.0.0" + checksum: 10c0/72dbeb026e4e4eb3bc9c65739b91408ca77ab7d603a2494fa2eff3790ec22892c4caba751cffdf30f5ccf0e7ba79c1e9c96cf0a357404b9432bf1365baac23ca + languageName: node + linkType: hard + "@es-joy/jsdoccomment@npm:~0.52.0": version: 0.52.0 resolution: "@es-joy/jsdoccomment@npm:0.52.0" @@ -2665,7 +2672,7 @@ __metadata: cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" core-js: "npm:^3.30.2" - cross-env: "npm:^7.0.3" + cross-env: "npm:^10.0.0" detect-passive-events: "npm:^2.0.3" emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" @@ -6104,19 +6111,20 @@ __metadata: languageName: node linkType: hard -"cross-env@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-env@npm:7.0.3" +"cross-env@npm:^10.0.0": + version: 10.0.0 + resolution: "cross-env@npm:10.0.0" dependencies: - cross-spawn: "npm:^7.0.1" + "@epic-web/invariant": "npm:^1.0.0" + cross-spawn: "npm:^7.0.6" bin: - cross-env: src/bin/cross-env.js - cross-env-shell: src/bin/cross-env-shell.js - checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf + cross-env: dist/bin/cross-env.js + cross-env-shell: dist/bin/cross-env-shell.js + checksum: 10c0/d16ffc3734106577d57b6253d81ab50294623bd59f96e161033eaf99c1c308ffbaba8463c23a6c0f72e841eff467cb7007a0a551f27554fcf2bbf6598cd828f9 languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: From 8896d6c1b16cb6edd9b3909f88f2094d022ad478 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 08:55:36 -0400 Subject: [PATCH 171/660] Use attribute for `User#bypass_registration_checks?` (#35580) --- app/models/user.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7da5876af19..ff09acbf796 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -132,9 +132,10 @@ class User < ApplicationRecord delegate :can?, to: :role attr_reader :invite_code, :date_of_birth - attr_writer :bypass_registration_checks, :current_account + attr_writer :current_account attribute :external, :boolean, default: false + attribute :bypass_registration_checks, :boolean, default: false def self.those_who_can(*any_of_privileges) matching_role_ids = UserRole.that_can(*any_of_privileges).map(&:id) @@ -477,10 +478,6 @@ class User < ApplicationRecord Setting.registrations_mode == 'open' end - def bypass_registration_checks? - @bypass_registration_checks - end - def sanitize_role self.role = nil if role.present? && role.everyone? end From 3146109b084026133c92137f57ed9e74ad9f7246 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 08:57:51 -0400 Subject: [PATCH 172/660] Add `MediaAttachment.combined_media_file_size` method (#35570) --- app/lib/admin/metrics/dimension/space_usage_dimension.rb | 2 +- .../metrics/measure/instance_media_attachments_measure.rb | 8 ++++++-- app/models/media_attachment.rb | 4 ++++ lib/mastodon/cli/media.rb | 4 +--- spec/models/media_attachment_spec.rb | 6 ++++++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb index 3fd8d86856e..c03464ecaa9 100644 --- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb +++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb @@ -40,7 +40,7 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension def media_size value = [ - MediaAttachment.sum(Arel.sql('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)')), + MediaAttachment.sum(MediaAttachment.combined_media_file_size), CustomEmoji.sum(:image_file_size), PreviewCard.sum(:image_file_size), Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')), diff --git a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb index 996ca52e0bb..00836191f1d 100644 --- a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb +++ b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb @@ -29,7 +29,7 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics: def perform_total_query domain = params[:domain] domain = Instance.by_domain_and_subdomains(params[:domain]).select(:domain) if params[:include_subdomains] - MediaAttachment.joins(:account).merge(Account.where(domain: domain)).sum('COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)') + MediaAttachment.joins(:account).merge(Account.where(domain: domain)).sum(MediaAttachment.combined_media_file_size) end def perform_previous_total_query @@ -44,7 +44,7 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics: <<~SQL.squish SELECT axis.*, ( WITH new_media_attachments AS ( - SELECT COALESCE(media_attachments.file_file_size, 0) + COALESCE(media_attachments.thumbnail_file_size, 0) AS size + SELECT #{media_size_total} AS size FROM media_attachments INNER JOIN accounts ON accounts.id = media_attachments.account_id WHERE date_trunc('day', media_attachments.created_at)::date = axis.period @@ -58,6 +58,10 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics: SQL end + def media_size_total + MediaAttachment.combined_media_file_size.to_sql + end + def params @params.permit(:domain, :include_subdomains) end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 2e60f732b81..13ca0d7e3ab 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -298,6 +298,10 @@ class MediaAttachment < ApplicationRecord IMAGE_FILE_EXTENSIONS + VIDEO_FILE_EXTENSIONS + AUDIO_FILE_EXTENSIONS end + def combined_media_file_size + arel_table.coalesce(arel_table[:file_file_size], 0) + arel_table.coalesce(arel_table[:thumbnail_file_size], 0) + end + private def file_styles(attachment) diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb index 1059eb60660..02c9894c36d 100644 --- a/lib/mastodon/cli/media.rb +++ b/lib/mastodon/cli/media.rb @@ -313,9 +313,7 @@ module Mastodon::CLI end def combined_media_sum - Arel.sql(<<~SQL.squish) - COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0) - SQL + MediaAttachment.combined_media_file_size end def preload_records_from_mixed_objects(objects) diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 910eac4e9a4..a712cdde1dc 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -313,6 +313,12 @@ RSpec.describe MediaAttachment, :attachment_processing do end end + describe '.combined_media_file_size' do + subject { described_class.combined_media_file_size } + + it { is_expected.to be_an(Arel::Nodes::Grouping) } + end + private def media_metadata From 139025fce0a9c3ea6a8d82aad512a25e7c843d4c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 09:17:53 -0400 Subject: [PATCH 173/660] Fix wrong policy authorization in admin controllers (#35588) --- app/controllers/admin/disputes/appeals_controller.rb | 2 +- app/controllers/admin/domain_blocks_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/disputes/appeals_controller.rb b/app/controllers/admin/disputes/appeals_controller.rb index 0c415536767..7c70603e231 100644 --- a/app/controllers/admin/disputes/appeals_controller.rb +++ b/app/controllers/admin/disputes/appeals_controller.rb @@ -18,7 +18,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController end def reject - authorize @appeal, :approve? + authorize @appeal, :reject? log_action :reject, @appeal @appeal.reject!(current_account) UserMailer.appeal_rejected(@appeal.account.user, @appeal).deliver_later diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index f88065db8d5..5e1074b224a 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -36,7 +36,7 @@ module Admin end def edit - authorize :domain_block, :create? + authorize :domain_block, :update? end def create From e8e6cf951013cdda3e748653bf3dee582e4557c0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 30 Jul 2025 09:46:58 -0400 Subject: [PATCH 174/660] Add coverage to user spec for missing `last_sign_in_at` scenario (#35587) --- app/models/user.rb | 4 +--- spec/models/user_spec.rb | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index ff09acbf796..aca72daff04 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -209,10 +209,8 @@ class User < ApplicationRecord end def update_sign_in!(new_sign_in: false) - old_current = current_sign_in_at new_current = Time.now.utc - - self.last_sign_in_at = old_current || new_current + self.last_sign_in_at = current_sign_in_at || new_current self.current_sign_in_at = new_current increment(:sign_in_count) if new_sign_in diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 32c9b7f9f8f..a9ab15a956e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -163,12 +163,13 @@ RSpec.describe User do describe '#update_sign_in!' do context 'with an existing user' do - let!(:user) { Fabricate :user, last_sign_in_at: 10.days.ago, current_sign_in_at: 1.hour.ago, sign_in_count: 123 } + let!(:user) { Fabricate :user, last_sign_in_at: 10.days.ago, current_sign_in_at:, sign_in_count: 123 } + let(:current_sign_in_at) { 1.hour.ago } context 'with new sign in false' do it 'updates timestamps but not counts' do expect { user.update_sign_in!(new_sign_in: false) } - .to change(user, :last_sign_in_at) + .to change(user, :last_sign_in_at).to(current_sign_in_at) .and change(user, :current_sign_in_at) .and not_change(user, :sign_in_count) end @@ -177,11 +178,22 @@ RSpec.describe User do context 'with new sign in true' do it 'updates timestamps and counts' do expect { user.update_sign_in!(new_sign_in: true) } - .to change(user, :last_sign_in_at) + .to change(user, :last_sign_in_at).to(current_sign_in_at) .and change(user, :current_sign_in_at) .and change(user, :sign_in_count).by(1) end end + + context 'when the user does not have a current_sign_in_at value' do + let(:current_sign_in_at) { nil } + + before { travel_to(1.minute.ago) } + + it 'updates last sign in to now' do + expect { user.update_sign_in! } + .to change(user, :last_sign_in_at).to(Time.now.utc) + end + end end context 'with a new user' do From 31ba52a57bc3a9166246d9f27fb2890026c9170b Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 30 Jul 2025 16:07:01 +0200 Subject: [PATCH 175/660] Change `StatusReachFinder` to consider quotes as well as reblogs (#35601) --- app/lib/status_reach_finder.rb | 11 +++++++++++ app/models/status.rb | 1 + 2 files changed, 12 insertions(+) diff --git a/app/lib/status_reach_finder.rb b/app/lib/status_reach_finder.rb index 5fb19643378..3ff04c4b69b 100644 --- a/app/lib/status_reach_finder.rb +++ b/app/lib/status_reach_finder.rb @@ -30,8 +30,10 @@ class StatusReachFinder [ replied_to_account_id, reblog_of_account_id, + quote_of_account_id, mentioned_account_ids, reblogs_account_ids, + quotes_account_ids, favourites_account_ids, replies_account_ids, ].tap do |arr| @@ -46,6 +48,10 @@ class StatusReachFinder @status.in_reply_to_account_id if distributable? end + def quote_of_account_id + @status.quote&.quoted_account_id + end + def reblog_of_account_id @status.reblog.account_id if @status.reblog? end @@ -54,6 +60,11 @@ class StatusReachFinder @status.mentions.pluck(:account_id) end + # Beware: Quotes can be created without the author having had access to the status + def quotes_account_ids + @status.quotes.pluck(:account_id) if distributable? || unsafe? + end + # Beware: Reblogs can be created without the author having had access to the status def reblogs_account_ids @status.reblogs.rewhere(deleted_at: [nil, @status.deleted_at]).pluck(:account_id) if distributable? || unsafe? diff --git a/app/models/status.rb b/app/models/status.rb index 51150bec49e..c99a1f8df56 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -78,6 +78,7 @@ class Status < ApplicationRecord has_many :mentions, dependent: :destroy, inverse_of: :status has_many :mentioned_accounts, through: :mentions, source: :account, class_name: 'Account' has_many :media_attachments, dependent: :nullify + has_many :quotes, foreign_key: 'quoted_status_id', inverse_of: :quoted_status, dependent: :nullify # The `dependent` option is enabled by the initial `mentions` association declaration has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent From efc0d237afc71681c0e56cf8910c1e0f9c6f56ff Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 30 Jul 2025 16:39:30 +0200 Subject: [PATCH 176/660] Fix synchronous recursive fetching of deeply-nested quoted posts (#35600) --- app/lib/activitypub/activity/create.rb | 4 ++-- .../activitypub/fetch_remote_status_service.rb | 5 +++-- app/services/activitypub/verify_quote_service.rb | 13 +++++++++---- lib/exceptions.rb | 1 + 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index f7c723757ef..3fc9269dd3a 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -206,8 +206,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity @quote.save embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context']) - ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id]) - rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS + ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id], depth: @options[:depth]) + rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] }) end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index 7173746f2de..0473bb5939f 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -8,9 +8,10 @@ class ActivityPub::FetchRemoteStatusService < BaseService DISCOVERIES_PER_REQUEST = 1000 # Should be called when uri has already been checked for locality - def call(uri, prefetched_body: nil, on_behalf_of: nil, expected_actor_uri: nil, request_id: nil) + def call(uri, prefetched_body: nil, on_behalf_of: nil, expected_actor_uri: nil, request_id: nil, depth: nil) return if domain_not_allowed?(uri) + @depth = depth || 0 @request_id = request_id || "#{Time.now.utc.to_i}-status-#{uri}" @json = if prefetched_body.nil? fetch_status(uri, true, on_behalf_of) @@ -52,7 +53,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService return nil if discoveries > DISCOVERIES_PER_REQUEST end - ActivityPub::Activity.factory(activity_json, actor, request_id: @request_id).perform + ActivityPub::Activity.factory(activity_json, actor, request_id: @request_id, depth: @depth).perform end private diff --git a/app/services/activitypub/verify_quote_service.rb b/app/services/activitypub/verify_quote_service.rb index ad4dfbe310c..a83a3c1155a 100644 --- a/app/services/activitypub/verify_quote_service.rb +++ b/app/services/activitypub/verify_quote_service.rb @@ -3,9 +3,12 @@ class ActivityPub::VerifyQuoteService < BaseService include JsonLdHelper + MAX_SYNCHRONOUS_DEPTH = 2 + # Optionally fetch quoted post, and verify the quote is authorized - def call(quote, fetchable_quoted_uri: nil, prefetched_quoted_object: nil, prefetched_approval: nil, request_id: nil) + def call(quote, fetchable_quoted_uri: nil, prefetched_quoted_object: nil, prefetched_approval: nil, request_id: nil, depth: nil) @request_id = request_id + @depth = depth || 0 @quote = quote @fetching_error = nil @@ -72,10 +75,12 @@ class ActivityPub::VerifyQuoteService < BaseService return if uri.nil? || @quote.quoted_status.present? status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status) - status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id) + raise Mastodon::RecursionLimitExceededError if @depth > MAX_SYNCHRONOUS_DEPTH && status.nil? + + status ||= ActivityPub::FetchRemoteStatusService.new.call(uri, on_behalf_of: @quote.account.followers.local.first, prefetched_body:, request_id: @request_id, depth: @depth + 1) @quote.update(quoted_status: status) if status.present? - rescue Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e + rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS => e @fetching_error = e end @@ -90,7 +95,7 @@ class ActivityPub::VerifyQuoteService < BaseService # It's not safe to fetch if the inlined object is cross-origin or doesn't match expectations return if object['id'] != uri || non_matching_uri_hosts?(@quote.approval_uri, object['id']) - status = ActivityPub::FetchRemoteStatusService.new.call(object['id'], prefetched_body: object, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id) + status = ActivityPub::FetchRemoteStatusService.new.call(object['id'], prefetched_body: object, on_behalf_of: @quote.account.followers.local.first, request_id: @request_id, depth: @depth) if status.present? @quote.update(quoted_status: status) diff --git a/lib/exceptions.rb b/lib/exceptions.rb index c8c81983825..18a99ace2a4 100644 --- a/lib/exceptions.rb +++ b/lib/exceptions.rb @@ -14,6 +14,7 @@ module Mastodon class InvalidParameterError < Error; end class SignatureVerificationError < Error; end class MalformedHeaderError < Error; end + class RecursionLimitExceededError < Error; end class UnexpectedResponseError < Error attr_reader :response From 39250ab9619e3f6450a5ad70bd21aa80507f4115 Mon Sep 17 00:00:00 2001 From: Osman <1126756+breadtk@users.noreply.github.com> Date: Wed, 30 Jul 2025 08:08:05 -0700 Subject: [PATCH 177/660] Sort auditable accounts (#35272) --- app/controllers/admin/action_logs_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb index 8b8e83fde77..61ca1661898 100644 --- a/app/controllers/admin/action_logs_controller.rb +++ b/app/controllers/admin/action_logs_controller.rb @@ -6,7 +6,7 @@ module Admin def index authorize :audit_log, :index? - @auditable_accounts = Account.auditable.select(:id, :username) + @auditable_accounts = Account.auditable.select(:id, :username).order(username: :asc) end private From 92bf55afd08adadae40469ff9f6b6cc76aaf36ca Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 Jul 2025 17:53:42 +0200 Subject: [PATCH 178/660] Change design of quote posts in web UI (#35584) --- .../mastodon/components/learn_more_link.tsx | 63 ++++++++ .../mastodon/components/status_content.jsx | 10 ++ .../mastodon/components/status_quoted.tsx | 85 +++++----- app/javascript/mastodon/locales/en.json | 13 +- .../styles/mastodon-light/css_variables.scss | 2 + .../styles/mastodon/components.scss | 145 +++++++++++------- .../styles/mastodon/css_variables.scss | 1 + 7 files changed, 210 insertions(+), 109 deletions(-) create mode 100644 app/javascript/mastodon/components/learn_more_link.tsx diff --git a/app/javascript/mastodon/components/learn_more_link.tsx b/app/javascript/mastodon/components/learn_more_link.tsx new file mode 100644 index 00000000000..b5337794c95 --- /dev/null +++ b/app/javascript/mastodon/components/learn_more_link.tsx @@ -0,0 +1,63 @@ +import { useState, useRef, useCallback, useId } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import Overlay from 'react-overlays/Overlay'; + +export const LearnMoreLink: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const accessibilityId = useId(); + const [open, setOpen] = useState(false); + const triggerRef = useRef(null); + + const handleClick = useCallback(() => { + setOpen(!open); + }, [open, setOpen]); + + return ( + <> + + + + {({ props }) => ( +
+
{children}
+ +
+ +
+
+ )} +
+ + ); +}; diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 5f0f7079aec..38d24921c5e 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -138,6 +138,16 @@ class StatusContent extends PureComponent { onCollapsedToggle(collapsed); } + + // Remove quote fallback link from the DOM so it doesn't + // mess with paragraph margins + if (!!status.get('quote')) { + const inlineQuote = node.querySelector('.quote-inline'); + + if (inlineQuote) { + inlineQuote.remove(); + } + } } handleMouseEnter = ({ currentTarget }) => { diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index d3d8b58c33d..8d43ea1819a 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -3,19 +3,15 @@ import { useEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import { Link } from 'react-router-dom'; import type { Map as ImmutableMap } from 'immutable'; -import ArticleIcon from '@/material-icons/400-24px/article.svg?react'; -import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; -import { Icon } from 'mastodon/components/icon'; +import { LearnMoreLink } from 'mastodon/components/learn_more_link'; import StatusContainer from 'mastodon/containers/status_container'; import type { Status } from 'mastodon/models/status'; import type { RootState } from 'mastodon/store'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; -import QuoteIcon from '../../images/quote.svg?react'; import { fetchStatus } from '../actions/statuses'; import { makeGetStatus } from '../selectors'; @@ -31,7 +27,6 @@ const QuoteWrapper: React.FC<{ 'status__quote--error': isError, })} > - {children}
); @@ -45,27 +40,20 @@ const NestedQuoteLink: React.FC<{ accountId ? state.accounts.get(accountId) : undefined, ); - const quoteAuthorName = account?.display_name_html; + const quoteAuthorName = account?.acct; if (!quoteAuthorName) { return null; } - const quoteAuthorElement = ( - - ); - const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`; - return ( - +
- - - +
); }; @@ -112,39 +100,42 @@ export const QuotedStatus: React.FC<{ defaultMessage='Hidden due to one of your filters' /> ); - } else if (quoteState === 'deleted') { - quoteError = ( - - ); - } else if (quoteState === 'unauthorized') { - quoteError = ( - - ); } else if (quoteState === 'pending') { quoteError = ( - + <> + + + +
+ +
+

+ +

+
+ ); - } else if (quoteState === 'rejected' || quoteState === 'revoked') { + } else if ( + !status || + !quotedStatusId || + quoteState === 'deleted' || + quoteState === 'rejected' || + quoteState === 'revoked' || + quoteState === 'unauthorized' + ) { quoteError = ( - ); - } else if (!status || !quotedStatusId) { - quoteError = ( - ); } @@ -168,7 +159,7 @@ export const QuotedStatus: React.FC<{ isQuotedPost id={quotedStatusId} contextType={contextType} - avatarSize={40} + avatarSize={32} > {canRenderChildQuote && ( [data-popper-placement] { } } -.status--has-quote .quote-inline { - display: none; -} - .status { padding: 16px; min-height: 54px; @@ -1470,10 +1466,6 @@ body > [data-popper-placement] { margin-top: 16px; } - &--is-quote { - border: none; - } - &--in-thread { --thread-margin: calc(46px + 8px); @@ -1860,79 +1852,99 @@ body > [data-popper-placement] { // --status-gutter-width is currently only set inside of // .notification-ungrouped, so everywhere else this will fall back // to the pixel values - --quote-margin: var(--status-gutter-width, 36px); + --quote-margin: var(--status-gutter-width); position: relative; margin-block-start: 16px; margin-inline-start: calc(var(--quote-margin) + var(--thread-margin, 0px)); - border-radius: 8px; + border-radius: 12px; color: var(--nested-card-text); - background: var(--nested-card-background); - border: var(--nested-card-border); - - @container (width > 460px) { - --quote-margin: var(--status-gutter-width, 56px); - } + border: 1px solid var(--surface-border-color); } .status__quote--error { + box-sizing: border-box; display: flex; align-items: center; + justify-content: space-between; gap: 8px; padding: 12px; - font-size: 15px; + font-size: 14px; + line-height: 20px; + letter-spacing: 0.25px; + min-height: 56px; + + .link-button { + font-size: inherit; + line-height: inherit; + letter-spacing: inherit; + } } .status__quote-author-button { position: relative; overflow: hidden; - display: inline-flex; - width: auto; - margin-block-start: 10px; - padding: 5px 12px; + display: flex; + margin-top: 8px; + padding: 8px 12px; align-items: center; - gap: 6px; font-family: inherit; font-size: 14px; - font-weight: 700; - line-height: normal; - letter-spacing: 0; - text-decoration: none; - color: $highlight-text-color; - background: var(--nested-card-background); - border: var(--nested-card-border); - border-radius: 4px; - - &:active, - &:focus, - &:hover { - border-color: lighten($highlight-text-color, 4%); - color: lighten($highlight-text-color, 4%); - } - - &:focus-visible { - outline: $ui-button-icon-focus-outline; - } + font-weight: 400; + line-height: 20px; + letter-spacing: 0.25px; + color: $darker-text-color; + background: var(--surface-variant-background-color); + border-radius: 8px; + cursor: default; } -.status__quote-icon { - position: absolute; - inset-block-start: 18px; - inset-inline-start: -40px; - display: block; - width: 26px; - height: 26px; - padding: 5px; - color: #6a49ba; - z-index: 10; +.status--is-quote { + border: none; + padding: 12px; - .status__quote--error & { - inset-block-start: 50%; - transform: translateY(-50%); + .status__info { + padding-bottom: 8px; } - @container (width > 460px) { - inset-inline-start: -50px; + .display-name, + .status__relative-time { + font-size: 14px; + line-height: 20px; + letter-spacing: 0.1px; + } + + .display-name__account { + font-size: 12px; + line-height: 16px; + letter-spacing: 0.5px; + } + + .status__content { + display: -webkit-box; + font-size: 14px; + letter-spacing: 0.25px; + line-height: 20px; + -webkit-line-clamp: 4; + line-clamp: 4; + -webkit-box-orient: vertical; + overflow: hidden; + + p { + margin-bottom: 20px; + + &:last-child { + margin-bottom: 0; + } + } + } + + .media-gallery, + .video-player, + .audio-player, + .attachment-list, + .poll { + margin-top: 8px; } } @@ -2152,6 +2164,27 @@ body > [data-popper-placement] { } } +.learn-more__popout { + gap: 8px; + + &__content { + display: flex; + flex-direction: column; + gap: 4px; + } + + h6 { + font-size: inherit; + font-weight: 500; + line-height: inherit; + letter-spacing: 0.1px; + } + + .link-button { + font-weight: 500; + } +} + .account__wrapper { display: flex; gap: 10px; diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/css_variables.scss index 431cdd7a8e8..16ed033b968 100644 --- a/app/javascript/styles/mastodon/css_variables.scss +++ b/app/javascript/styles/mastodon/css_variables.scss @@ -16,6 +16,7 @@ --surface-background-color: #{darken($ui-base-color, 4%)}; --surface-variant-background-color: #{$ui-base-color}; --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; + --surface-border-color: #{lighten($ui-base-color, 8%)}; --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)}; --avatar-border-radius: 8px; --media-outline-color: #{rgba(#fcf8ff, 0.15)}; From 2257612deb518ff74a5b12b08173a09a843cb975 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 Jul 2025 18:09:16 +0200 Subject: [PATCH 179/660] Fix "new replies available" reporting a false positive for re-fetched root status (#35602) --- app/workers/activitypub/fetch_all_replies_worker.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/workers/activitypub/fetch_all_replies_worker.rb b/app/workers/activitypub/fetch_all_replies_worker.rb index 14142b9cd5a..128bfe7e8af 100644 --- a/app/workers/activitypub/fetch_all_replies_worker.rb +++ b/app/workers/activitypub/fetch_all_replies_worker.rb @@ -86,9 +86,7 @@ class ActivityPub::FetchAllRepliesWorker return if root_status_body.nil? - @batch.within do - FetchReplyWorker.perform_async(root_status_uri, { **options.deep_stringify_keys, 'prefetched_body' => root_status_body }) - end + FetchReplyWorker.perform_async(root_status_uri, { **options.deep_stringify_keys.except('batch_id'), 'prefetched_body' => root_status_body }) get_replies(root_status_body, MAX_PAGES, options) end From b80e95b2aaeef7a274f92293e00ccd52892c10be Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 30 Jul 2025 18:18:58 +0200 Subject: [PATCH 180/660] Change new replies to be loaded automatically if thread previously empty (#35603) --- .../status/components/refresh_controller.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/status/components/refresh_controller.tsx b/app/javascript/mastodon/features/status/components/refresh_controller.tsx index 2765d5d9f41..9788b2849f3 100644 --- a/app/javascript/mastodon/features/status/components/refresh_controller.tsx +++ b/app/javascript/mastodon/features/status/components/refresh_controller.tsx @@ -24,6 +24,11 @@ export const RefreshController: React.FC<{ const refresh = useAppSelector( (state) => state.contexts.refreshing[statusId], ); + const autoRefresh = useAppSelector( + (state) => + !state.contexts.replies[statusId] || + state.contexts.replies[statusId].length === 0, + ); const dispatch = useAppDispatch(); const intl = useIntl(); const [ready, setReady] = useState(false); @@ -39,6 +44,11 @@ export const RefreshController: React.FC<{ dispatch(completeContextRefresh({ statusId })); if (result.async_refresh.result_count > 0) { + if (autoRefresh) { + void dispatch(fetchContext({ statusId })); + return ''; + } + setReady(true); } } else { @@ -57,7 +67,7 @@ export const RefreshController: React.FC<{ return () => { clearTimeout(timeoutId); }; - }, [dispatch, setReady, statusId, refresh]); + }, [dispatch, setReady, statusId, refresh, autoRefresh]); const handleClick = useCallback(() => { setLoading(true); From e5826777b6c06a32b97388657beaca1e5eccb421 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 30 Jul 2025 18:28:26 +0200 Subject: [PATCH 181/660] Fix friends-of-friends recommendations suggesting already-requested accounts (#35604) --- .../account_suggestions/friends_of_friends_source.rb | 1 + .../friends_of_friends_source_spec.rb | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/account_suggestions/friends_of_friends_source.rb b/app/models/account_suggestions/friends_of_friends_source.rb index 707c6ccaec2..d4accd2cea6 100644 --- a/app/models/account_suggestions/friends_of_friends_source.rb +++ b/app/models/account_suggestions/friends_of_friends_source.rb @@ -26,6 +26,7 @@ class AccountSuggestions::FriendsOfFriendsSource < AccountSuggestions::Source AND NOT EXISTS (SELECT 1 FROM mutes m WHERE m.target_account_id = follows.target_account_id AND m.account_id = :id) AND (accounts.domain IS NULL OR NOT EXISTS (SELECT 1 FROM account_domain_blocks b WHERE b.account_id = :id AND b.domain = accounts.domain)) AND NOT EXISTS (SELECT 1 FROM follows f WHERE f.target_account_id = follows.target_account_id AND f.account_id = :id) + AND NOT EXISTS (SELECT 1 FROM follow_requests f WHERE f.target_account_id = follows.target_account_id AND f.account_id = :id) AND follows.target_account_id <> :id AND accounts.discoverable AND accounts.suspended_at IS NULL diff --git a/spec/models/account_suggestions/friends_of_friends_source_spec.rb b/spec/models/account_suggestions/friends_of_friends_source_spec.rb index 9daaa233bfd..af1e6e98896 100644 --- a/spec/models/account_suggestions/friends_of_friends_source_spec.rb +++ b/spec/models/account_suggestions/friends_of_friends_source_spec.rb @@ -16,10 +16,12 @@ RSpec.describe AccountSuggestions::FriendsOfFriendsSource do let!(:jerk) { Fabricate(:account, discoverable: true, hide_collections: false) } let!(:larry) { Fabricate(:account, discoverable: true, hide_collections: false) } let!(:morty) { Fabricate(:account, discoverable: true, hide_collections: false, memorial: true) } + let!(:joyce) { Fabricate(:account, discoverable: true, hide_collections: false) } context 'with follows and blocks' do before do bob.block!(jerk) + bob.request_follow!(joyce) FollowRecommendationMute.create!(account: bob, target_account: neil) # bob follows eugen, alice and larry @@ -28,8 +30,8 @@ RSpec.describe AccountSuggestions::FriendsOfFriendsSource do # alice follows eve and mallory [john, mallory].each { |account| alice.follow!(account) } - # eugen follows eve, john, jerk, larry, neil and morty - [eve, mallory, jerk, larry, neil, morty].each { |account| eugen.follow!(account) } + # eugen follows eve, john, jerk, larry, neil, morty and joyce + [eve, mallory, jerk, larry, neil, morty, joyce].each { |account| eugen.follow!(account) } end it 'returns eligible accounts', :aggregate_failures do @@ -55,6 +57,9 @@ RSpec.describe AccountSuggestions::FriendsOfFriendsSource do # morty is not included because his account is in memoriam expect(results).to_not include([morty.id, :friends_of_friends]) + + # joyce is not included because there is already a pending follow request + expect(results).to_not include([joyce.id, :friends_of_friends]) end end From fc1abed0dc19f6ee4fff45c89eed1a5ce79e4e00 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:14:48 +0200 Subject: [PATCH 182/660] Update dependency database_cleaner-active_record to v2.2.2 (#35610) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b8813b7211c..9123197e5a3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,9 +175,9 @@ GEM css_parser (1.21.1) addressable csv (3.3.5) - database_cleaner-active_record (2.2.1) + database_cleaner-active_record (2.2.2) activerecord (>= 5.a) - database_cleaner-core (~> 2.0.0) + database_cleaner-core (~> 2.0) database_cleaner-core (2.0.1) date (3.4.1) debug (1.11.0) From 2131d1ff236373a0c8ac5f4934b10afca92d5236 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:26:24 +0200 Subject: [PATCH 183/660] New Crowdin Translations (automated) (#35611) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ar.json | 6 ----- app/javascript/mastodon/locales/be.json | 1 - app/javascript/mastodon/locales/bg.json | 6 ----- app/javascript/mastodon/locales/br.json | 1 - app/javascript/mastodon/locales/ca.json | 6 ----- app/javascript/mastodon/locales/cs.json | 6 ----- app/javascript/mastodon/locales/cy.json | 6 ----- app/javascript/mastodon/locales/da.json | 13 ++++++----- app/javascript/mastodon/locales/de.json | 13 ++++++----- app/javascript/mastodon/locales/el.json | 13 ++++++----- app/javascript/mastodon/locales/en-GB.json | 6 ----- app/javascript/mastodon/locales/eo.json | 3 --- app/javascript/mastodon/locales/es-AR.json | 13 ++++++----- app/javascript/mastodon/locales/es-MX.json | 6 ----- app/javascript/mastodon/locales/es.json | 6 ----- app/javascript/mastodon/locales/et.json | 6 ----- app/javascript/mastodon/locales/eu.json | 2 -- app/javascript/mastodon/locales/fa.json | 6 ----- app/javascript/mastodon/locales/fi.json | 13 ++++++----- app/javascript/mastodon/locales/fo.json | 13 ++++++----- app/javascript/mastodon/locales/fr-CA.json | 3 --- app/javascript/mastodon/locales/fr.json | 3 --- app/javascript/mastodon/locales/fy.json | 6 ----- app/javascript/mastodon/locales/ga.json | 6 ----- app/javascript/mastodon/locales/gd.json | 6 ----- app/javascript/mastodon/locales/gl.json | 13 ++++++----- app/javascript/mastodon/locales/he.json | 6 ----- app/javascript/mastodon/locales/hu.json | 13 ++++++----- app/javascript/mastodon/locales/is.json | 6 ----- app/javascript/mastodon/locales/it.json | 13 ++++++----- app/javascript/mastodon/locales/ja.json | 6 ----- app/javascript/mastodon/locales/kab.json | 1 - app/javascript/mastodon/locales/ko.json | 6 ----- app/javascript/mastodon/locales/lv.json | 6 ----- app/javascript/mastodon/locales/nan.json | 6 ----- app/javascript/mastodon/locales/nl.json | 6 ----- app/javascript/mastodon/locales/nn.json | 6 ----- app/javascript/mastodon/locales/no.json | 3 --- app/javascript/mastodon/locales/pt-BR.json | 6 ----- app/javascript/mastodon/locales/pt-PT.json | 6 ----- app/javascript/mastodon/locales/ru.json | 6 ----- app/javascript/mastodon/locales/sq.json | 6 ----- app/javascript/mastodon/locales/sv.json | 6 ----- app/javascript/mastodon/locales/th.json | 1 - app/javascript/mastodon/locales/tr.json | 6 ----- app/javascript/mastodon/locales/uk.json | 5 ++++- app/javascript/mastodon/locales/vi.json | 13 ++++++----- app/javascript/mastodon/locales/zh-CN.json | 6 ----- app/javascript/mastodon/locales/zh-TW.json | 13 ++++++----- config/locales/da.yml | 2 +- config/locales/el.yml | 25 +++++++++++++++++++++ config/locales/fi.yml | 26 ++++++++++++++++++++++ config/locales/hu.yml | 25 +++++++++++++++++++++ config/locales/it.yml | 26 ++++++++++++++++++++++ config/locales/nl.yml | 25 +++++++++++++++++++++ config/locales/ru.yml | 14 ++++++------ config/locales/simple_form.el.yml | 8 +++++++ config/locales/simple_form.fi.yml | 13 +++++++++++ config/locales/simple_form.hu.yml | 8 +++++++ config/locales/simple_form.it.yml | 8 +++++++ config/locales/simple_form.nl.yml | 8 +++++++ 61 files changed, 261 insertions(+), 261 deletions(-) diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 0443710638c..80056d1f6c9 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -872,12 +872,6 @@ "status.open": "وسّع هذا المنشور", "status.pin": "دبّسه على الصفحة التعريفية", "status.quote_error.filtered": "مُخفي بسبب إحدى إعدادات التصفية خاصتك", - "status.quote_error.not_found": "لا يمكن عرض هذا المنشور.", - "status.quote_error.pending_approval": "هذا المنشور ينتظر موافقة صاحب المنشور الأصلي.", - "status.quote_error.rejected": "لا يمكن عرض هذا المنشور لأن صاحب المنشور الأصلي لا يسمح له بأن يكون مقتبس.", - "status.quote_error.removed": "تمت إزالة المنشور من قبل صاحبه.", - "status.quote_error.unauthorized": "لا يمكن عرض هذا المنشور لأنك لست مخولاً برؤيته.", - "status.quote_post_author": "منشور من {name}", "status.read_more": "اقرأ المزيد", "status.reblog": "إعادة النشر", "status.reblog_private": "إعادة النشر إلى الجمهور الأصلي", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 0f49cc69799..706e746988f 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -821,7 +821,6 @@ "status.mute_conversation": "Ігнараваць размову", "status.open": "Разгарнуць гэты допіс", "status.pin": "Замацаваць у профілі", - "status.quote_post_author": "Допіс карыстальніка @{name}", "status.read_more": "Чытаць болей", "status.reblog": "Пашырыць", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 392f9470c0e..6f0cf6f54fe 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -860,12 +860,6 @@ "status.open": "Разширяване на публикацията", "status.pin": "Закачане в профила", "status.quote_error.filtered": "Скрито поради един от филтрите ви", - "status.quote_error.not_found": "Публикацията не може да се показва.", - "status.quote_error.pending_approval": "Публикацията чака одобрение от първоначалния автор.", - "status.quote_error.rejected": "Публикацията не може да се показва като първоначалния автор не позволява цитирането ѝ.", - "status.quote_error.removed": "Публикацията е премахната от автора ѝ.", - "status.quote_error.unauthorized": "Публикацията не може да се показва, тъй като не сте упълномощени да я гледате.", - "status.quote_post_author": "Публикация от {name}", "status.read_more": "Още за четене", "status.reblog": "Подсилване", "status.reblog_private": "Подсилване с оригиналната видимост", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index 8882ff916e3..cdca2446ca0 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -582,7 +582,6 @@ "status.mute_conversation": "Kuzhat ar gaozeadenn", "status.open": "Digeriñ ar c'hannad-mañ", "status.pin": "Spilhennañ d'ar profil", - "status.quote_post_author": "Embannadenn gant {name}", "status.read_more": "Lenn muioc'h", "status.reblog": "Skignañ", "status.reblog_private": "Skignañ gant ar weledenn gentañ", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 645a3751243..5bee7f604c7 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -872,12 +872,6 @@ "status.open": "Amplia el tut", "status.pin": "Fixa en el perfil", "status.quote_error.filtered": "No es mostra a causa d'un dels vostres filtres", - "status.quote_error.not_found": "No es pot mostrar aquesta publicació.", - "status.quote_error.pending_approval": "Aquesta publicació està pendent d'aprovació per l'autor original.", - "status.quote_error.rejected": "No es pot mostrar aquesta publicació perquè l'autor original no en permet la citació.", - "status.quote_error.removed": "Aquesta publicació ha estat eliminada per l'autor.", - "status.quote_error.unauthorized": "No es pot mostrar aquesta publicació perquè no teniu autorització per a veure-la.", - "status.quote_post_author": "Publicació de {name}", "status.read_more": "Més informació", "status.reblog": "Impulsa", "status.reblog_private": "Impulsa amb la visibilitat original", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 24486800f30..40273be1bfa 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -873,12 +873,6 @@ "status.open": "Rozbalit tento příspěvek", "status.pin": "Připnout na profil", "status.quote_error.filtered": "Skryté kvůli jednomu z vašich filtrů", - "status.quote_error.not_found": "Tento příspěvek nelze zobrazit.", - "status.quote_error.pending_approval": "Tento příspěvek čeká na schválení od původního autora.", - "status.quote_error.rejected": "Tento příspěvek nemůže být zobrazen, protože původní autor neumožňuje, aby byl citován.", - "status.quote_error.removed": "Tento příspěvek byl odstraněn jeho autorem.", - "status.quote_error.unauthorized": "Tento příspěvek nelze zobrazit, protože nemáte oprávnění k jeho zobrazení.", - "status.quote_post_author": "Příspěvek od {name}", "status.read_more": "Číst více", "status.reblog": "Boostnout", "status.reblog_private": "Boostnout s původní viditelností", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index dfd900bece9..9dfc6b35a60 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -871,12 +871,6 @@ "status.open": "Ehangu'r post hwn", "status.pin": "Pinio ar y proffil", "status.quote_error.filtered": "Wedi'i guddio oherwydd un o'ch hidlwyr", - "status.quote_error.not_found": "Does dim modd dangos y postiad hwn.", - "status.quote_error.pending_approval": "Mae'r postiad hwn yn aros am gymeradwyaeth yr awdur gwreiddiol.", - "status.quote_error.rejected": "Does dim modd dangos y postiad hwn gan nad yw'r awdur gwreiddiol yn caniatáu iddo gael ei ddyfynnu.", - "status.quote_error.removed": "Cafodd y postiad hwn ei ddileu gan ei awdur.", - "status.quote_error.unauthorized": "Does dim modd dangos y postiad hwn gan nad oes gennych awdurdod i'w weld.", - "status.quote_post_author": "Postiad gan {name}", "status.read_more": "Darllen rhagor", "status.reblog": "Hybu", "status.reblog_private": "Hybu i'r gynulleidfa wreiddiol", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 15597e9a279..ff3ba99e8d6 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "for at oversætte et indlæg", "keyboard_shortcuts.unfocus": "Fjern fokus fra tekstskrivningsområde/søgning", "keyboard_shortcuts.up": "Flyt opad på listen", + "learn_more_link.got_it": "Forstået", + "learn_more_link.learn_more": "Få mere at vide", "lightbox.close": "Luk", "lightbox.next": "Næste", "lightbox.previous": "Forrige", @@ -873,12 +875,11 @@ "status.open": "Udvid dette indlæg", "status.pin": "Fastgør til profil", "status.quote_error.filtered": "Skjult grundet et af filterne", - "status.quote_error.not_found": "Dette indlæg kan ikke vises.", - "status.quote_error.pending_approval": "Dette indlæg afventer godkendelse fra den oprindelige forfatter.", - "status.quote_error.rejected": "Dette indlæg kan ikke vises, da den oprindelige forfatter ikke tillader citering heraf.", - "status.quote_error.removed": "Dette indlæg er fjernet af forfatteren.", - "status.quote_error.unauthorized": "Dette indlæg kan ikke vises, da man ikke har tilladelse til at se det.", - "status.quote_post_author": "Indlæg fra {name}", + "status.quote_error.not_available": "Indlæg utilgængeligt", + "status.quote_error.pending_approval": "Afventende indlæg", + "status.quote_error.pending_approval_popout.body": "Citater delt på tværs af Fediverset kan tage tid at vise, da forskellige servere har forskellige protokoller.", + "status.quote_error.pending_approval_popout.title": "Afventende citat? Tag det roligt", + "status.quote_post_author": "Citerede et indlæg fra @{name}", "status.read_more": "Læs mere", "status.reblog": "Fremhæv", "status.reblog_private": "Fremhæv med oprindelig synlighed", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index fc34c0fe191..90251442849 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "Beitrag übersetzen", "keyboard_shortcuts.unfocus": "Eingabefeld/Suche nicht mehr fokussieren", "keyboard_shortcuts.up": "Ansicht nach oben bewegen", + "learn_more_link.got_it": "Verstanden", + "learn_more_link.learn_more": "Mehr erfahren", "lightbox.close": "Schließen", "lightbox.next": "Vor", "lightbox.previous": "Zurück", @@ -873,12 +875,11 @@ "status.open": "Beitrag öffnen", "status.pin": "Im Profil anheften", "status.quote_error.filtered": "Ausgeblendet wegen eines deiner Filter", - "status.quote_error.not_found": "Dieser Beitrag kann nicht angezeigt werden.", - "status.quote_error.pending_approval": "Dieser Beitrag muss noch durch das ursprüngliche Profil genehmigt werden.", - "status.quote_error.rejected": "Dieser Beitrag kann nicht angezeigt werden, weil das ursprüngliche Profil das Zitieren nicht erlaubt.", - "status.quote_error.removed": "Dieser Beitrag wurde durch das Profil entfernt.", - "status.quote_error.unauthorized": "Dieser Beitrag kann nicht angezeigt werden, weil du zum Ansehen nicht berechtigt bist.", - "status.quote_post_author": "Beitrag von {name}", + "status.quote_error.not_available": "Beitrag nicht verfügbar", + "status.quote_error.pending_approval": "Beitragsveröffentlichung ausstehend", + "status.quote_error.pending_approval_popout.body": "Zitierte Beiträge, die im Fediverse geteilt werden, benötigen einige Zeit, bis sie überall angezeigt werden, da die verschiedenen Server unterschiedliche Protokolle nutzen.", + "status.quote_error.pending_approval_popout.title": "Zitierter Beitrag noch nicht freigegeben? Immer mit der Ruhe", + "status.quote_post_author": "Zitierte einen Beitrag von @{name}", "status.read_more": "Gesamten Beitrag anschauen", "status.reblog": "Teilen", "status.reblog_private": "Mit der ursprünglichen Zielgruppe teilen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index b18897576c5..79aa5874b5b 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "για να μεταφραστεί μια ανάρτηση", "keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης", "keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα", + "learn_more_link.got_it": "Το κατάλαβα", + "learn_more_link.learn_more": "Μάθε περισσότερα", "lightbox.close": "Κλείσιμο", "lightbox.next": "Επόμενο", "lightbox.previous": "Προηγούμενο", @@ -873,12 +875,11 @@ "status.open": "Επέκταση ανάρτησης", "status.pin": "Καρφίτσωσε στο προφίλ", "status.quote_error.filtered": "Κρυφό λόγω ενός από τα φίλτρα σου", - "status.quote_error.not_found": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί.", - "status.quote_error.pending_approval": "Αυτή η ανάρτηση εκκρεμεί έγκριση από τον αρχικό συντάκτη.", - "status.quote_error.rejected": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί καθώς ο αρχικός συντάκτης δεν επιτρέπει τις παραθέσεις.", - "status.quote_error.removed": "Αυτή η ανάρτηση αφαιρέθηκε από τον συντάκτη της.", - "status.quote_error.unauthorized": "Αυτή η ανάρτηση δεν μπορεί να εμφανιστεί καθώς δεν έχεις εξουσιοδότηση για να τη δεις.", - "status.quote_post_author": "Ανάρτηση από {name}", + "status.quote_error.not_available": "Ανάρτηση μη διαθέσιμη", + "status.quote_error.pending_approval": "Ανάρτηση σε αναμονή", + "status.quote_error.pending_approval_popout.body": "Οι παραθέσεις που μοιράζονται στο Fediverse μπορεί να χρειαστούν χρόνο για να εμφανιστούν, καθώς διαφορετικοί διακομιστές έχουν διαφορετικά πρωτόκολλα.", + "status.quote_error.pending_approval_popout.title": "Παράθεση σε εκκρεμότητα; Μείνετε ψύχραιμοι", + "status.quote_post_author": "Παρατίθεται μια ανάρτηση από @{name}", "status.read_more": "Διάβασε περισότερα", "status.reblog": "Ενίσχυση", "status.reblog_private": "Ενίσχυση με αρχική ορατότητα", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 441cfee6d10..1bb482d9888 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -871,12 +871,6 @@ "status.open": "Expand this post", "status.pin": "Pin on profile", "status.quote_error.filtered": "Hidden due to one of your filters", - "status.quote_error.not_found": "This post cannot be displayed.", - "status.quote_error.pending_approval": "This post is pending approval from the original author.", - "status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.", - "status.quote_error.removed": "This post was removed by its author.", - "status.quote_error.unauthorized": "This post cannot be displayed as you are not authorised", - "status.quote_post_author": "Post by {name}", "status.read_more": "Read more", "status.reblog": "Boost", "status.reblog_private": "Boost with original visibility", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 92f529a8f41..5027ce48b39 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -851,9 +851,6 @@ "status.mute_conversation": "Silentigi konversacion", "status.open": "Pligrandigu ĉi tiun afiŝon", "status.pin": "Alpingli al la profilo", - "status.quote_error.not_found": "Ĉi tiu afiŝo ne povas esti montrata.", - "status.quote_error.rejected": "Ĉi tiu afiŝo ne povas esti montrata ĉar la originala aŭtoro ne permesas ĝian citadon.", - "status.quote_error.removed": "Ĉi tiu afiŝo estis forigita de ĝia aŭtoro.", "status.read_more": "Legi pli", "status.reblog": "Diskonigi", "status.reblog_private": "Diskonigi kun la sama videbleco", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 3eca9eab37c..84536740b2e 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "para traducir un mensaje", "keyboard_shortcuts.unfocus": "Quitar el foco del área de texto de redacción o de búsqueda", "keyboard_shortcuts.up": "Subir en la lista", + "learn_more_link.got_it": "Entendido", + "learn_more_link.learn_more": "Aprendé más", "lightbox.close": "Cerrar", "lightbox.next": "Siguiente", "lightbox.previous": "Anterior", @@ -873,12 +875,11 @@ "status.open": "Expandir este mensaje", "status.pin": "Fijar en el perfil", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", - "status.quote_error.not_found": "No se puede mostrar este mensaje.", - "status.quote_error.pending_approval": "Este mensaje está pendiente de aprobación del autor original.", - "status.quote_error.rejected": "No se puede mostrar este mensaje, ya que el autor original no permite que se cite.", - "status.quote_error.removed": "Este mensaje fue eliminado por su autor.", - "status.quote_error.unauthorized": "No se puede mostrar este mensaje, ya que no tenés autorización para verlo.", - "status.quote_post_author": "Mensaje de @{name}", + "status.quote_error.not_available": "Mensaje no disponible", + "status.quote_error.pending_approval": "Mensaje pendiente", + "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que diferentes servidores tienen diferentes protocolos.", + "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Esperá un momento", + "status.quote_post_author": "Se citó un mensaje de @{name}", "status.read_more": "Leé más", "status.reblog": "Adherir", "status.reblog_private": "Adherir a la audiencia original", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 67a91402f87..55d327b4977 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -873,12 +873,6 @@ "status.open": "Expandir estado", "status.pin": "Fijar", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", - "status.quote_error.not_found": "No se puede mostrar esta publicación.", - "status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.", - "status.quote_error.rejected": "No se puede mostrar esta publicación, puesto que el autor original no permite que sea citado.", - "status.quote_error.removed": "Esta publicación fue eliminada por su autor.", - "status.quote_error.unauthorized": "No se puede mostrar esta publicación, puesto que no estás autorizado a verla.", - "status.quote_post_author": "Publicado por {name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Implusar a la audiencia original", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index cbd79ea10fd..5732da2735e 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -873,12 +873,6 @@ "status.open": "Expandir publicación", "status.pin": "Fijar", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", - "status.quote_error.not_found": "No se puede mostrar esta publicación.", - "status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.", - "status.quote_error.rejected": "Esta publicación no puede mostrarse porque el autor original no permite que se cite.", - "status.quote_error.removed": "Esta publicación fue eliminada por su autor.", - "status.quote_error.unauthorized": "Esta publicación no puede mostrarse, ya que no estás autorizado a verla.", - "status.quote_post_author": "Publicación de {name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Impulsar a la audiencia original", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index c4b1c95fd3a..6d9946985d1 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -873,12 +873,6 @@ "status.open": "Laienda postitus", "status.pin": "Kinnita profiilile", "status.quote_error.filtered": "Peidetud mõne kasutatud filtri tõttu", - "status.quote_error.not_found": "Seda postitust ei saa näidata.", - "status.quote_error.pending_approval": "See postitus on algse autori kinnituse ootel.", - "status.quote_error.rejected": "Seda postitust ei saa näidata, kuina algne autor ei luba teda tsiteerida.", - "status.quote_error.removed": "Autor kustutas selle postituse.", - "status.quote_error.unauthorized": "Kuna sul pole luba selle postituse nägemiseks, siis seda ei saa kuvada.", - "status.quote_post_author": "Postitajaks {name}", "status.read_more": "Loe veel", "status.reblog": "Jaga", "status.reblog_private": "Jaga algse nähtavusega", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 65f3e87ac15..316dc43a4d2 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -844,8 +844,6 @@ "status.mute_conversation": "Mututu elkarrizketa", "status.open": "Hedatu bidalketa hau", "status.pin": "Finkatu profilean", - "status.quote_error.not_found": "Bidalketa hau ezin da erakutsi.", - "status.quote_error.pending_approval": "Bidalketa hau egile originalak onartzeko zain dago.", "status.read_more": "Irakurri gehiago", "status.reblog": "Bultzada", "status.reblog_private": "Bultzada jatorrizko hartzaileei", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 0217d7bb2fb..9677454e0b8 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -873,12 +873,6 @@ "status.open": "گسترش این فرسته", "status.pin": "سنجاق به نمایه", "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", - "status.quote_error.not_found": "این فرسته قابل نمایش نیست.", - "status.quote_error.pending_approval": "این فرسته منظر تأیید نگارندهٔ اصلی است.", - "status.quote_error.rejected": "از آن‌جا که نگارندهٔ اصلی این فرسته اجازهٔ نقلش را نمی‌دهد قابل نمایش نیست.", - "status.quote_error.removed": "این فرسته به دست نگارنده‌اش برداشته شده.", - "status.quote_error.unauthorized": "از آن‌جا که اجازهٔ دیدن این فرسته را ندارید قابل نمایش نیست.", - "status.quote_post_author": "فرسته توسط {name}", "status.read_more": "بیشتر بخوانید", "status.reblog": "تقویت", "status.reblog_private": "تقویت برای مخاطبان نخستین", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 36fcfd4e947..b9898a8c9e9 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "Käännä julkaisu", "keyboard_shortcuts.unfocus": "Poistu kirjoitus- tai hakukentästä", "keyboard_shortcuts.up": "Siirry luettelossa taaksepäin", + "learn_more_link.got_it": "Selvä", + "learn_more_link.learn_more": "Lue lisää", "lightbox.close": "Sulje", "lightbox.next": "Seuraava", "lightbox.previous": "Edellinen", @@ -873,12 +875,11 @@ "status.open": "Laajenna julkaisu", "status.pin": "Kiinnitä profiiliin", "status.quote_error.filtered": "Piilotettu jonkin asettamasi suodattimen takia", - "status.quote_error.not_found": "Tätä julkaisua ei voi näyttää.", - "status.quote_error.pending_approval": "Tämä julkaisu odottaa alkuperäisen tekijänsä hyväksyntää.", - "status.quote_error.rejected": "Tätä julkaisua ei voi näyttää, sillä sen alkuperäinen tekijä ei salli lainattavan julkaisua.", - "status.quote_error.removed": "Tekijä on poistanut julkaisun.", - "status.quote_error.unauthorized": "Tätä julkaisua ei voi näyttää, koska sinulla ei ole oikeutta tarkastella sitä.", - "status.quote_post_author": "Julkaisu käyttäjältä {name}", + "status.quote_error.not_available": "Julkaisu ei saatavilla", + "status.quote_error.pending_approval": "Julkaisu odottaa", + "status.quote_error.pending_approval_popout.body": "Saattaa viedä jonkin ainaa ennen kuin fediversumin kautta jaetut julkaisut tulevat näkyviin, sillä eri palvelimet käyttävät eri protokollia.", + "status.quote_error.pending_approval_popout.title": "Odottava lainaus? Pysy rauhallisena", + "status.quote_post_author": "Lainaa käyttäjän @{name} julkaisua", "status.read_more": "Näytä enemmän", "status.reblog": "Tehosta", "status.reblog_private": "Tehosta alkuperäiselle yleisölle", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 1bdccaeae86..808a1147be2 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "at umseta ein post", "keyboard_shortcuts.unfocus": "Tak skrivi-/leiti-økið úr miðdeplinum", "keyboard_shortcuts.up": "Flyt upp á listanum", + "learn_more_link.got_it": "Eg skilji", + "learn_more_link.learn_more": "Lær meira", "lightbox.close": "Lat aftur", "lightbox.next": "Fram", "lightbox.previous": "Aftur", @@ -873,12 +875,11 @@ "status.open": "Víðka henda postin", "status.pin": "Ger fastan í vangan", "status.quote_error.filtered": "Eitt av tínum filtrum fjalir hetta", - "status.quote_error.not_found": "Tað ber ikki til at vísa hendan postin.", - "status.quote_error.pending_approval": "Hesin posturin bíðar eftir góðkenning frá upprunahøvundinum.", - "status.quote_error.rejected": "Hesin posturin kann ikki vísast, tí upprunahøvundurin loyvir ikki at posturin verður siteraður.", - "status.quote_error.removed": "Hesin posturin var strikaður av høvundinum.", - "status.quote_error.unauthorized": "Hesin posturin kann ikki vísast, tí tú hevur ikki rættindi at síggja hann.", - "status.quote_post_author": "Postur hjá @{name}", + "status.quote_error.not_available": "Postur ikki tøkur", + "status.quote_error.pending_approval": "Postur bíðar", + "status.quote_error.pending_approval_popout.body": "Sitatir, sum eru deild tvørtur um fediversið, kunnu taka nakað av tíð at vísast, tí ymiskir ambætarar hava ymiskar protokollir.", + "status.quote_error.pending_approval_popout.title": "Bíðar eftir sitati? Tak tað róligt", + "status.quote_post_author": "Siteraði ein post hjá @{name}", "status.read_more": "Les meira", "status.reblog": "Stimbra", "status.reblog_private": "Stimbra við upprunasýni", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 840b0e508d5..7803004869e 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -864,9 +864,6 @@ "status.mute_conversation": "Masquer la conversation", "status.open": "Afficher la publication entière", "status.pin": "Épingler sur profil", - "status.quote_error.removed": "Ce message a été retiré par son auteur·ice.", - "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé·e à le voir.", - "status.quote_post_author": "Message par {name}", "status.read_more": "En savoir plus", "status.reblog": "Booster", "status.reblog_private": "Booster avec visibilité originale", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index c0c9580a46d..65b97b498b3 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -864,9 +864,6 @@ "status.mute_conversation": "Masquer la conversation", "status.open": "Afficher le message entier", "status.pin": "Épingler sur le profil", - "status.quote_error.removed": "Ce message a été retiré par son auteur·ice.", - "status.quote_error.unauthorized": "Ce message ne peut pas être affiché car vous n'êtes pas autorisé·e à le voir.", - "status.quote_post_author": "Message par {name}", "status.read_more": "En savoir plus", "status.reblog": "Partager", "status.reblog_private": "Partager à l’audience originale", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 1f7f5c0c8ad..f28fbb2ff86 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -873,12 +873,6 @@ "status.open": "Dit berjocht útklappe", "status.pin": "Op profylside fêstsette", "status.quote_error.filtered": "Ferburgen troch ien fan jo filters", - "status.quote_error.not_found": "Dit berjocht kin net toand wurde.", - "status.quote_error.pending_approval": "Dit berjocht is yn ôfwachting fan goedkarring troch de oarspronklike auteur.", - "status.quote_error.rejected": "Dit berjocht kin net toand wurde, omdat de oarspronklike auteur net tastiet dat it sitearre wurdt.", - "status.quote_error.removed": "Dit berjocht is fuotsmiten troch de auteur.", - "status.quote_error.unauthorized": "Dit berjocht kin net toand wurde, omdat jo net it foech hawwe om it te besjen.", - "status.quote_post_author": "Berjocht fan {name}", "status.read_more": "Mear ynfo", "status.reblog": "Booste", "status.reblog_private": "Boost nei oarspronklike ûntfangers", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 2c0b709bdc3..a710043a887 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -873,12 +873,6 @@ "status.open": "Leathnaigh an post seo", "status.pin": "Pionnáil ar do phróifíl", "status.quote_error.filtered": "I bhfolach mar gheall ar cheann de do scagairí", - "status.quote_error.not_found": "Ní féidir an post seo a thaispeáint.", - "status.quote_error.pending_approval": "Tá an post seo ag feitheamh ar cheadú ón údar bunaidh.", - "status.quote_error.rejected": "Ní féidir an post seo a thaispeáint mar ní cheadaíonn an t-údar bunaidh é a lua.", - "status.quote_error.removed": "Baineadh an post seo ag a údar.", - "status.quote_error.unauthorized": "Ní féidir an post seo a thaispeáint mar níl údarú agat é a fheiceáil.", - "status.quote_post_author": "Postáil le {name}", "status.read_more": "Léan a thuilleadh", "status.reblog": "Treisiú", "status.reblog_private": "Mol le léargas bunúsach", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index c783182cd5f..be02d0a67a7 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -871,12 +871,6 @@ "status.open": "Leudaich am post seo", "status.pin": "Prìnich ris a’ phròifil", "status.quote_error.filtered": "Falaichte le criathrag a th’ agad", - "status.quote_error.not_found": "Chan urrainn dhuinn am post seo a shealltainn.", - "status.quote_error.pending_approval": "Tha am post seo a’ feitheamh air aontachadh leis an ùghdar tùsail.", - "status.quote_error.rejected": "Chan urrainn dhuinn am post seo a shealltainn air sgàth ’s nach ceadaich an t-ùghdar tùsail aige gun dèid a luaidh.", - "status.quote_error.removed": "Chaidh am post seo a thoirt air falbh le ùghdar.", - "status.quote_error.unauthorized": "Chan urrainn dhuinn am post seo a shealltainn air sgàth ’s nach eil cead agad fhaicinn.", - "status.quote_post_author": "Post le {name}", "status.read_more": "Leugh an còrr", "status.reblog": "Brosnaich", "status.reblog_private": "Brosnaich leis an t-so-fhaicsinneachd tùsail", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 6a8570c1d05..4cd1f9718f6 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "para traducir unha publicación", "keyboard_shortcuts.unfocus": "Para deixar de destacar a área de escritura/procura", "keyboard_shortcuts.up": "Para mover cara arriba na listaxe", + "learn_more_link.got_it": "Entendo", + "learn_more_link.learn_more": "Saber máis", "lightbox.close": "Fechar", "lightbox.next": "Seguinte", "lightbox.previous": "Anterior", @@ -873,12 +875,11 @@ "status.open": "Estender esta publicación", "status.pin": "Fixar no perfil", "status.quote_error.filtered": "Oculto debido a un dos teus filtros", - "status.quote_error.not_found": "Non se pode mostrar a publicación.", - "status.quote_error.pending_approval": "A publicación está pendente da aprobación pola autora orixinal.", - "status.quote_error.rejected": "Non se pode mostrar esta publicación xa que a autora orixinal non permite que se cite.", - "status.quote_error.removed": "Publicación eliminada pola autora.", - "status.quote_error.unauthorized": "Non se pode mostrar esta publicación porque non tes permiso para vela.", - "status.quote_post_author": "Publicación de {name}", + "status.quote_error.not_available": "Publicación non dispoñible", + "status.quote_error.pending_approval": "Publicación pendente", + "status.quote_error.pending_approval_popout.body": "As citas compartidas no Fediverso poderían tardar en mostrarse, xa que os diferentes servidores teñen diferentes protocolos.", + "status.quote_error.pending_approval_popout.title": "Cita pendente? Non te apures", + "status.quote_post_author": "Citou unha publicación de @{name}", "status.read_more": "Ler máis", "status.reblog": "Promover", "status.reblog_private": "Compartir coa audiencia orixinal", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index d1918aa97dd..e185490de93 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -873,12 +873,6 @@ "status.open": "הרחבת הודעה זו", "status.pin": "הצמדה לפרופיל שלי", "status.quote_error.filtered": "מוסתר בהתאם לסננים שלך", - "status.quote_error.not_found": "לא ניתן להציג הודעה זו.", - "status.quote_error.pending_approval": "הודעה זו מחכה לאישור מידי היוצר המקורי.", - "status.quote_error.rejected": "לא ניתן להציג הודעה זו שכן המחבר.ת המקוריים לא הרשו לצטט אותה.", - "status.quote_error.removed": "הודעה זו הוסרה על ידי השולחים המקוריים.", - "status.quote_error.unauthorized": "הודעה זו לא מוצגת כיוון שאין לך רשות לראותה.", - "status.quote_post_author": "פרסום מאת {name}", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", "status.reblog_private": "להדהד ברמת הנראות המקורית", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 06a8bf88465..8f93fab6d4c 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "Bejegyzés lefordítása", "keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele", "keyboard_shortcuts.up": "Mozgás felfelé a listában", + "learn_more_link.got_it": "Rendben", + "learn_more_link.learn_more": "További tudnivalók", "lightbox.close": "Bezárás", "lightbox.next": "Következő", "lightbox.previous": "Előző", @@ -873,12 +875,11 @@ "status.open": "Bejegyzés kibontása", "status.pin": "Kitűzés a profilodra", "status.quote_error.filtered": "A szűrőid miatt rejtett", - "status.quote_error.not_found": "Ez a bejegyzés nem jeleníthető meg.", - "status.quote_error.pending_approval": "Ez a bejegyzés az eredeti szerző jóváhagyására vár.", - "status.quote_error.rejected": "Ez a bejegyzés nem jeleníthető meg, mert az eredeti szerzője nem engedélyezi az idézését.", - "status.quote_error.removed": "Ezt a bejegyzés eltávolította a szerzője.", - "status.quote_error.unauthorized": "Ez a bejegyzés nem jeleníthető meg, mert nem jogosult a megtekintésére.", - "status.quote_post_author": "Szerző: {name}", + "status.quote_error.not_available": "A bejegyzés nem érhető el", + "status.quote_error.pending_approval": "A bejegyzés függőben van", + "status.quote_error.pending_approval_popout.body": "A Födiverzumon keresztül megosztott idézetek megjelenítése eltarthat egy darabig, mivel a különböző kiszolgálók különböző protokollokat használnak.", + "status.quote_error.pending_approval_popout.title": "Függőben lévő idézet? Maradj nyugodt.", + "status.quote_post_author": "Idézte @{name} bejegyzését", "status.read_more": "Bővebben", "status.reblog": "Megtolás", "status.reblog_private": "Megtolás az eredeti közönségnek", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 54aec7cbee8..96379263314 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -873,12 +873,6 @@ "status.open": "Opna þessa færslu", "status.pin": "Festa á notandasnið", "status.quote_error.filtered": "Falið vegna einnar síu sem er virk", - "status.quote_error.not_found": "Þessa færslu er ekki hægt að birta.", - "status.quote_error.pending_approval": "Þessi færsla bíður eftir samþykki frá upprunalegum höfundi hennar.", - "status.quote_error.rejected": "Þessa færslu er ekki hægt að birta þar sem upphaflegur höfundur hennar leyfir ekki að vitnað sé til hennar.", - "status.quote_error.removed": "Þessi færsla var fjarlægð af höfundi hennar.", - "status.quote_error.unauthorized": "Þessa færslu er ekki hægt að birta þar sem þú hefur ekki heimild til að skoða hana.", - "status.quote_post_author": "Færsla frá {name}", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", "status.reblog_private": "Endurbirta til upphaflegra lesenda", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5134b39e673..5168665f6a2 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "Traduce un post", "keyboard_shortcuts.unfocus": "Rimuove il focus sull'area di composizione testuale/ricerca", "keyboard_shortcuts.up": "Scorre in su nell'elenco", + "learn_more_link.got_it": "Ho capito", + "learn_more_link.learn_more": "Scopri di più", "lightbox.close": "Chiudi", "lightbox.next": "Successivo", "lightbox.previous": "Precedente", @@ -873,12 +875,11 @@ "status.open": "Espandi questo post", "status.pin": "Fissa in cima sul profilo", "status.quote_error.filtered": "Nascosto a causa di uno dei tuoi filtri", - "status.quote_error.not_found": "Questo post non può essere visualizzato.", - "status.quote_error.pending_approval": "Questo post è in attesa di approvazione dell'autore originale.", - "status.quote_error.rejected": "Questo post non può essere visualizzato perché l'autore originale non consente che venga citato.", - "status.quote_error.removed": "Questo post è stato rimosso dal suo autore.", - "status.quote_error.unauthorized": "Questo post non può essere visualizzato in quanto non sei autorizzato a visualizzarlo.", - "status.quote_post_author": "Post di @{name}", + "status.quote_error.not_available": "Post non disponibile", + "status.quote_error.pending_approval": "Post in attesa", + "status.quote_error.pending_approval_popout.body": "Le citazioni condivise in tutto il Fediverso possono richiedere del tempo per la visualizzazione, poiché server diversi hanno protocolli diversi.", + "status.quote_error.pending_approval_popout.title": "Citazione in attesa? Resta calmo", + "status.quote_post_author": "Citato un post di @{name}", "status.read_more": "Leggi di più", "status.reblog": "Reblog", "status.reblog_private": "Reblog con visibilità originale", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 59ed074012a..b7af03c1099 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -868,12 +868,6 @@ "status.open": "詳細を表示", "status.pin": "プロフィールに固定表示", "status.quote_error.filtered": "あなたのフィルター設定によって非表示になっています", - "status.quote_error.not_found": "この投稿は表示できません。", - "status.quote_error.pending_approval": "この投稿は投稿者の承認待ちです。", - "status.quote_error.rejected": "この投稿は、オリジナルの投稿者が引用することを許可していないため、表示できません。", - "status.quote_error.removed": "この投稿は投稿者によって削除されました。", - "status.quote_error.unauthorized": "この投稿を表示する権限がないため、表示できません。", - "status.quote_post_author": "{name} の投稿", "status.read_more": "もっと見る", "status.reblog": "ブースト", "status.reblog_private": "ブースト", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 0bc4665a253..52305fec2b6 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -623,7 +623,6 @@ "status.mute_conversation": "Sgugem adiwenni", "status.open": "Semɣeṛ tasuffeɣt-ayi", "status.pin": "Senteḍ-itt deg umaɣnu", - "status.quote_post_author": "Izen sɣur {name}", "status.read_more": "Issin ugar", "status.reblog": "Bḍu", "status.reblogged_by": "Yebḍa-tt {name}", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index f0c127956a3..e6cdc4e7800 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -871,12 +871,6 @@ "status.open": "상세 정보 표시", "status.pin": "고정", "status.quote_error.filtered": "필터에 의해 가려짐", - "status.quote_error.not_found": "이 게시물은 표시할 수 없습니다.", - "status.quote_error.pending_approval": "이 게시물은 원작자의 승인을 기다리고 있습니다.", - "status.quote_error.rejected": "이 게시물은 원작자가 인용을 허용하지 않았기 때문에 표시할 수 없습니다.", - "status.quote_error.removed": "이 게시물은 작성자에 의해 삭제되었습니다.", - "status.quote_error.unauthorized": "이 게시물은 권한이 없기 때문에 볼 수 없습니다.", - "status.quote_post_author": "{name} 님의 게시물", "status.read_more": "더 보기", "status.reblog": "부스트", "status.reblog_private": "원래의 수신자들에게 부스트", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index ee3c143412f..cb77337a658 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -738,12 +738,6 @@ "status.mute_conversation": "Apklusināt sarunu", "status.open": "Izvērst šo ierakstu", "status.pin": "Piespraust profilam", - "status.quote_error.not_found": "Šo ierakstu nevar parādīt.", - "status.quote_error.pending_approval": "Šis ieraksts gaida apstiprinājumu no tā autora.", - "status.quote_error.rejected": "Šo ierakstu nevar parādīt, jo tā autors neļauj to citēt.", - "status.quote_error.removed": "Šo ierakstu noņēma tā autors.", - "status.quote_error.unauthorized": "Šo ierakstu nevar parādīt, jo jums nav atļaujas to skatīt.", - "status.quote_post_author": "Publicēja {name}", "status.read_more": "Lasīt vairāk", "status.reblog": "Pastiprināt", "status.reblog_private": "Pastiprināt ar sākotnējo redzamību", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 8120312d80f..f6ca3c4c31a 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -872,12 +872,6 @@ "status.mute_conversation": "Kā對話消音", "status.open": "Kā PO文展開", "status.quote_error.filtered": "Lí所設定ê過濾器kā tse khàm起來", - "status.quote_error.not_found": "Tsit篇PO文bē當顯示。", - "status.quote_error.pending_approval": "Tsit篇PO文teh等原作者審查。", - "status.quote_error.rejected": "因為原作者無允准引用,tsit篇PO文bē當顯示。", - "status.quote_error.removed": "Tsit篇hōo作者thâi掉ah。", - "status.quote_error.unauthorized": "因為lí無得著讀tse ê權限,tsit篇PO文bē當顯示。", - "status.quote_post_author": "{name} 所PO ê", "status.read_more": "讀詳細", "status.reblog": "轉送", "status.reblog_private": "照原PO ê通看見ê範圍轉送", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 04ceb2591ba..54d171d38d5 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -873,12 +873,6 @@ "status.open": "Volledig bericht tonen", "status.pin": "Aan profielpagina vastmaken", "status.quote_error.filtered": "Verborgen door een van je filters", - "status.quote_error.not_found": "Dit bericht kan niet worden weergegeven.", - "status.quote_error.pending_approval": "Dit bericht is in afwachting van goedkeuring door de oorspronkelijke auteur.", - "status.quote_error.rejected": "Dit bericht kan niet worden weergegeven omdat de oorspronkelijke auteur niet toestaat dat het wordt geciteerd.", - "status.quote_error.removed": "Dit bericht is verwijderd door de auteur.", - "status.quote_error.unauthorized": "Dit bericht kan niet worden weergegeven omdat je niet bevoegd bent om het te bekijken.", - "status.quote_post_author": "Bericht van {name}", "status.read_more": "Meer lezen", "status.reblog": "Boosten", "status.reblog_private": "Boost naar oorspronkelijke ontvangers", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index b5b97007c8c..4d7b6d401f0 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -871,12 +871,6 @@ "status.open": "Utvid denne statusen", "status.pin": "Fest på profil", "status.quote_error.filtered": "Gøymt på grunn av eitt av filtra dine", - "status.quote_error.not_found": "Du kan ikkje visa dette innlegget.", - "status.quote_error.pending_approval": "Dette innlegget ventar på at skribenten skal godkjenna det.", - "status.quote_error.rejected": "Du kan ikkje visa dette innlegget fordi skribenten ikkje vil at det skal siterast.", - "status.quote_error.removed": "Skribenten sletta dette innlegget.", - "status.quote_error.unauthorized": "Du kan ikkje visa dette innlegget fordi du ikkje har løyve til det.", - "status.quote_post_author": "Innlegg av {name}", "status.read_more": "Les meir", "status.reblog": "Framhev", "status.reblog_private": "Framhev til dei originale mottakarane", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 8ef9b6a4818..f96377caa28 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -839,9 +839,6 @@ "status.open": "Utvid dette innlegget", "status.pin": "Fest på profilen", "status.quote_error.filtered": "Skjult på grunn av et av filterne dine", - "status.quote_error.not_found": "Dette innlegget kan ikke vises.", - "status.quote_error.pending_approval": "Dette innlegget venter på godkjenning fra den opprinnelige forfatteren.", - "status.quote_error.rejected": "Dette innlegget kan ikke vises fordi den opprinnelige forfatteren ikke har tillatt at det blir sitert.", "status.read_more": "Les mer", "status.reblog": "Fremhev", "status.reblog_private": "Fremhev til det opprinnelige publikummet", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index d00f44495d8..7844de56339 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -854,12 +854,6 @@ "status.open": "Abrir toot", "status.pin": "Fixar", "status.quote_error.filtered": "Oculto devido a um dos seus filtros", - "status.quote_error.not_found": "Esta postagem não pode ser exibida.", - "status.quote_error.pending_approval": "Esta postagem está pendente de aprovação do autor original.", - "status.quote_error.rejected": "Esta publicação não pode ser exibida porque o autor original não permite que seja citada.", - "status.quote_error.removed": "Esta postagem foi removida pelo autor.", - "status.quote_error.unauthorized": "Esta publicação não pode ser exibida, pois, você não está autorizado a vê-la.", - "status.quote_post_author": "Publicação por {name}", "status.read_more": "Ler mais", "status.reblog": "Dar boost", "status.reblog_private": "Dar boost para o mesmo público", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 6cc740f755f..8b0f2cbddf2 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -873,12 +873,6 @@ "status.open": "Expandir esta publicação", "status.pin": "Afixar no perfil", "status.quote_error.filtered": "Oculto devido a um dos seus filtros", - "status.quote_error.not_found": "Esta publicação não pode ser exibida.", - "status.quote_error.pending_approval": "Esta publicação está a aguardar a aprovação do autor original.", - "status.quote_error.rejected": "Esta publicação não pode ser exibida porque o autor original não permite que seja citada.", - "status.quote_error.removed": "Esta publicação foi removida pelo seu autor.", - "status.quote_error.unauthorized": "Esta publicação não pode ser exibida porque o utilizador não está autorizado a visualizá-la.", - "status.quote_post_author": "Publicação de {name}", "status.read_more": "Ler mais", "status.reblog": "Impulsionar", "status.reblog_private": "Impulsionar com a visibilidade original", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 40d8341713f..ada27cea5fd 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -871,12 +871,6 @@ "status.open": "Открыть пост", "status.pin": "Закрепить в профиле", "status.quote_error.filtered": "Скрыто одним из ваших фильтров", - "status.quote_error.not_found": "Пост не может быть показан.", - "status.quote_error.pending_approval": "Разрешение на цитирование от автора оригинального поста пока не получено.", - "status.quote_error.rejected": "Автор оригинального поста запретил его цитировать.", - "status.quote_error.removed": "Пост был удалён его автором.", - "status.quote_error.unauthorized": "Этот пост для вас недоступен.", - "status.quote_post_author": "Пост пользователя {name}", "status.read_more": "Читать далее", "status.reblog": "Продвинуть", "status.reblog_private": "Продвинуть для своей аудитории", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 75c00681603..d871d1bb278 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -864,12 +864,6 @@ "status.open": "Zgjeroje këtë mesazh", "status.pin": "Fiksoje në profil", "status.quote_error.filtered": "Fshehur për shkak të njërit nga filtrat tuaj", - "status.quote_error.not_found": "Ky postim s’mund të shfaqet.", - "status.quote_error.pending_approval": "Ky postim është në pritje të miratimit nga autori origjinal.", - "status.quote_error.rejected": "Ky postim s’mund të shfaqet, ngaqë autori origjinal nuk lejon citim të tij.", - "status.quote_error.removed": "Ky postim u hoq nga autori i tij.", - "status.quote_error.unauthorized": "Ky postim s’mund të shfaqet, ngaqë s’jeni i autorizuar ta shihni.", - "status.quote_post_author": "Postim nga {name}", "status.read_more": "Lexoni më tepër", "status.reblog": "Përforcojeni", "status.reblog_private": "Përforcim për publikun origjinal", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 1697b0dcb07..45ca92bebeb 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -873,12 +873,6 @@ "status.open": "Utvidga detta inlägg", "status.pin": "Fäst i profil", "status.quote_error.filtered": "Dolt på grund av ett av dina filter", - "status.quote_error.not_found": "Detta inlägg kan inte boostas.", - "status.quote_error.pending_approval": "Det här inlägget väntar på godkännande från originalförfattaren.", - "status.quote_error.rejected": "Det här inlägget kan inte visas eftersom originalförfattaren inte tillåter att det citeras.", - "status.quote_error.removed": "Detta inlägg har tagits bort av författaren.", - "status.quote_error.unauthorized": "Det här inlägget kan inte visas eftersom du inte har behörighet att se det.", - "status.quote_post_author": "Inlägg av @{name}", "status.read_more": "Läs mer", "status.reblog": "Boosta", "status.reblog_private": "Boosta med ursprunglig synlighet", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 03faba8a660..545371e56fb 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -832,7 +832,6 @@ "status.mute_conversation": "ซ่อนการสนทนา", "status.open": "ขยายโพสต์นี้", "status.pin": "ปักหมุดในโปรไฟล์", - "status.quote_post_author": "โพสต์โดย {name}", "status.read_more": "อ่านเพิ่มเติม", "status.reblog": "ดัน", "status.reblog_private": "ดันด้วยการมองเห็นดั้งเดิม", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 9a8354d052e..fb2a9237bcb 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -873,12 +873,6 @@ "status.open": "Bu gönderiyi genişlet", "status.pin": "Profile sabitle", "status.quote_error.filtered": "Bazı filtrelerinizden dolayı gizlenmiştir", - "status.quote_error.not_found": "Bu gönderi görüntülenemez.", - "status.quote_error.pending_approval": "Bu gönderi özgün yazarın onayını bekliyor.", - "status.quote_error.rejected": "Bu gönderi, özgün yazar alıntılanmasına izin vermediği için görüntülenemez.", - "status.quote_error.removed": "Bu gönderi yazarı tarafından kaldırıldı.", - "status.quote_error.unauthorized": "Bu gönderiyi, yetkiniz olmadığı için görüntüleyemiyorsunuz.", - "status.quote_post_author": "{name} gönderisi", "status.read_more": "Devamını okuyun", "status.reblog": "Yeniden paylaş", "status.reblog_private": "Özgün görünürlük ile yeniden paylaş", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 2e118f3589e..38401f18fa5 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -468,6 +468,8 @@ "keyboard_shortcuts.translate": "перекласти допис", "keyboard_shortcuts.unfocus": "Розфокусуватися з нового допису чи пошуку", "keyboard_shortcuts.up": "Рухатися вгору списком", + "learn_more_link.got_it": "Зрозуміло", + "learn_more_link.learn_more": "Докладніше", "lightbox.close": "Закрити", "lightbox.next": "Далі", "lightbox.previous": "Назад", @@ -843,7 +845,8 @@ "status.open": "Розгорнути допис", "status.pin": "Закріпити у профілі", "status.quote_error.filtered": "Приховано через один з ваших фільтрів", - "status.quote_post_author": "@{name} опублікував допис", + "status.quote_error.not_available": "Пост недоступний", + "status.quote_post_author": "Цитований допис @{name}", "status.read_more": "Дізнатися більше", "status.reblog": "Поширити", "status.reblog_private": "Поширити для початкової аудиторії", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index b5c8c3ec235..5062434ab99 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "dịch tút", "keyboard_shortcuts.unfocus": "đưa con trỏ ra khỏi ô soạn thảo hoặc ô tìm kiếm", "keyboard_shortcuts.up": "di chuyển lên trên danh sách", + "learn_more_link.got_it": "Đã hiểu", + "learn_more_link.learn_more": "Tìm hiểu thêm", "lightbox.close": "Đóng", "lightbox.next": "Tiếp", "lightbox.previous": "Trước", @@ -873,12 +875,11 @@ "status.open": "Mở tút", "status.pin": "Ghim lên hồ sơ", "status.quote_error.filtered": "Bị ẩn vì một bộ lọc của bạn", - "status.quote_error.not_found": "Tút này không thể hiển thị.", - "status.quote_error.pending_approval": "Tút này cần chờ cho phép từ người đăng.", - "status.quote_error.rejected": "Tút này không thể hiển thị vì người đăng không cho phép trích dẫn nó.", - "status.quote_error.removed": "Tút này đã bị người đăng xóa.", - "status.quote_error.unauthorized": "Tút này không thể hiển thị vì bạn không được cấp quyền truy cập nó.", - "status.quote_post_author": "Tút của {name}", + "status.quote_error.not_available": "Tút không khả dụng", + "status.quote_error.pending_approval": "Tút đang chờ duyệt", + "status.quote_error.pending_approval_popout.body": "Các trích dẫn được chia sẻ trên Fediverse có thể mất thời gian để hiển thị vì các máy chủ khác nhau có giao thức khác nhau.", + "status.quote_error.pending_approval_popout.title": "Đang chờ trích dẫn? Hãy bình tĩnh", + "status.quote_post_author": "Trích dẫn từ tút của @{name}", "status.read_more": "Đọc tiếp", "status.reblog": "Đăng lại", "status.reblog_private": "Đăng lại (Riêng tư)", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 127ae87f637..43a7cf0ff84 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -862,12 +862,6 @@ "status.open": "展开嘟文", "status.pin": "在个人资料页面置顶", "status.quote_error.filtered": "已根据你的筛选器过滤", - "status.quote_error.not_found": "无法显示这篇贴文。", - "status.quote_error.pending_approval": "此嘟文正在等待原作者批准。", - "status.quote_error.rejected": "由于原作者不允许引用转发,无法显示这篇贴文。", - "status.quote_error.removed": "该帖子已被作者删除。", - "status.quote_error.unauthorized": "你无权查看此嘟文,因此无法显示。", - "status.quote_post_author": "{name} 的嘟文", "status.read_more": "查看更多", "status.reblog": "转嘟", "status.reblog_private": "以相同可见性转嘟", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 40eed25a495..b086b55b833 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "翻譯嘟文", "keyboard_shortcuts.unfocus": "跳離文字撰寫區塊或搜尋框", "keyboard_shortcuts.up": "向上移動", + "learn_more_link.got_it": "了解", + "learn_more_link.learn_more": "了解更多", "lightbox.close": "關閉", "lightbox.next": "下一步", "lightbox.previous": "上一步", @@ -873,12 +875,11 @@ "status.open": "展開此嘟文", "status.pin": "釘選至個人檔案頁面", "status.quote_error.filtered": "由於您的過濾器,該嘟文被隱藏", - "status.quote_error.not_found": "這則嘟文無法被顯示。", - "status.quote_error.pending_approval": "此嘟文正在等待原作者審核。", - "status.quote_error.rejected": "由於原作者不允許引用,此嘟文無法被顯示。", - "status.quote_error.removed": "此嘟文已被其作者移除。", - "status.quote_error.unauthorized": "由於您未被授權檢視,此嘟文無法被顯示。", - "status.quote_post_author": "由 {name} 發嘟", + "status.quote_error.not_available": "無法取得該嘟文", + "status.quote_error.pending_approval": "嘟文正在發送中", + "status.quote_error.pending_approval_popout.body": "因為伺服器間可能運行不同協定,顯示聯邦宇宙間之引用嘟文會有些許延遲。", + "status.quote_error.pending_approval_popout.title": "引用嘟文正在發送中?別著急,請稍候片刻", + "status.quote_post_author": "已引用 @{name} 之嘟文", "status.read_more": "閱讀更多", "status.reblog": "轉嘟", "status.reblog_private": "依照原嘟可見性轉嘟", diff --git a/config/locales/da.yml b/config/locales/da.yml index cd8bd2d90ee..6f46fe6d5c6 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1230,7 +1230,7 @@ da: prefix_invited_by_user: "@%{name} inviterer dig ind på denne Mastodon-server!" prefix_sign_up: Tilmeld dig Mastodon i dag! suffix: Du vil med en konto kunne følge personer, indsende opdateringer og udveksle beskeder med brugere fra enhver Mastodon-server, og meget mere! - didnt_get_confirmation: Intet bekræftelseslink modtaget? + didnt_get_confirmation: Ikke modtaget bekræftelseslink? dont_have_your_security_key: Har ikke din sikkerhedsnøgle? forgot_password: Glemt din adgangskode? invalid_reset_password_token: Adgangskodenulstillingstoken ugyldigt eller udløbet. Anmod om et nyt. diff --git a/config/locales/el.yml b/config/locales/el.yml index b1df6962ecf..00ef7fdd56f 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -190,6 +190,7 @@ el: create_relay: Δημιουργία Relay create_unavailable_domain: Δημιουργία Μη Διαθέσιμου Τομέα create_user_role: Δημιουργία Ρόλου + create_username_block: Δημιουργία Κανόνα Ονόματος Χρήστη demote_user: Υποβιβασμός Χρήστη destroy_announcement: Διαγραφή Ανακοίνωσης destroy_canonical_email_block: Διαγραφή Αποκλεισμού Email @@ -203,6 +204,7 @@ el: destroy_status: Διαγραφή Ανάρτησης destroy_unavailable_domain: Διαγραφή Μη Διαθέσιμου Τομέα destroy_user_role: Καταστροφή Ρόλου + destroy_username_block: Διαγραφή Κανόνα Ονόματος Χρήστη disable_2fa_user: Απενεργοποίηση 2FA disable_custom_emoji: Απενεργοποίηση Προσαρμοσμένων Emoji disable_relay: Απενεργοποίηση Relay @@ -237,6 +239,7 @@ el: update_report: Ενημέρωση Αναφοράς update_status: Ενημέρωση Ανάρτησης update_user_role: Ενημέρωση ρόλου + update_username_block: Ενημέρωση Κανόνα Ονόματος Χρήστη actions: approve_appeal_html: Ο/Η %{name} ενέκρινε την ένσταση της απόφασης των συντονιστών από %{target} approve_user_html: ο/η %{name} ενέκρινε την εγγραφή του %{target} @@ -255,6 +258,7 @@ el: create_relay_html: Ο χρήστης %{name} δημιούργησε ένα relay %{target} create_unavailable_domain_html: Ο/Η %{name} σταμάτησε να τροφοδοτεί τον τομέα %{target} create_user_role_html: Ο/Η %{name} δημιούργησε ρόλο %{target} + create_username_block_html: "%{name} πρόσθεσε κανόνα για ονόματα χρηστών που περιέχουν %{target}" demote_user_html: Ο/Η %{name} υποβίβασε τον χρήστη %{target} destroy_announcement_html: Ο/Η %{name} διέγραψε την ανακοίνωση %{target} destroy_canonical_email_block_html: Ο χρήστης %{name} έκανε άρση αποκλεισμού email με το hash %{target} @@ -268,6 +272,7 @@ el: destroy_status_html: Ο/Η %{name} αφαίρεσε την ανάρτηση του/της %{target} destroy_unavailable_domain_html: Ο/Η %{name} ξανάρχισε να τροφοδοτεί το domain %{target} destroy_user_role_html: Ο/Η %{name} διέγραψε τον ρόλο του %{target} + destroy_username_block_html: "%{name} αφαίρεσε κανόνα για ονόματα χρηστών που περιέχουν %{target}" disable_2fa_user_html: Ο/Η %{name} απενεργοποίησε την απαίτηση για ταυτοποίηση δύο παραγόντων για τον χρήστη %{target} disable_custom_emoji_html: Ο/Η %{name} απενεργοποίησε το emoji %{target} disable_relay_html: Ο χρήστης %{name} απενεργοποίησε το relay %{target} @@ -302,6 +307,7 @@ el: update_report_html: Ο χρήστης %{name} ενημέρωσε την αναφορά %{target} update_status_html: Ο/Η %{name} ενημέρωσε την ανάρτηση του/της %{target} update_user_role_html: Ο/Η %{name} άλλαξε τον ρόλο %{target} + update_username_block_html: "%{name} ενημέρωσε κανόνα για ονόματα χρηστών που περιέχουν %{target}" deleted_account: διαγεγραμμένος λογαριασμός empty: Δεν βρέθηκαν αρχεία καταγραφής. filter_by_action: Φιλτράρισμα ανά ενέργεια @@ -1085,6 +1091,25 @@ el: other: Χρησιμοποιήθηκε από %{count} άτομα την τελευταία εβδομάδα title: Προτάσεις και τάσεις trending: Τάσεις + username_blocks: + add_new: Προσθήκη νέου + block_registrations: Φραγή εγγραφών + comparison: + contains: Περιέχει + equals: Ισούται + contains_html: Περιέχει %{string} + created_msg: Ο κανόνας ονόματος χρήστη δημιουργήθηκε με επιτυχία + delete: Διαγραφή + edit: + title: Επεξεργασία κανόνα ονόματος χρήστη + matches_exactly_html: Ισούται με %{string} + new: + create: Δημιουργία κανόνα + title: Δημιουργία νέου κανόνα ονόματος χρήστη + no_username_block_selected: Δεν άλλαξαν οι κανόνες ονόματος χρήστη καθώς κανένας δεν επιλέχθηκε + not_permitted: Δεν επιτρέπεται + title: Κανόνες ονόματος χρήστη + updated_msg: Ο κανόνας ονόματος χρήστη ενημερώθηκε με επιτυχία warning_presets: add_new: Πρόσθεση νέου delete: Διαγραφή diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 54aadf60f32..dc4a98c49bf 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -190,6 +190,7 @@ fi: create_relay: Luo välittäjä create_unavailable_domain: Luo ei-saatavilla oleva verkkotunnus create_user_role: Luo rooli + create_username_block: Luo käyttäjänimisääntö demote_user: Alenna käyttäjä destroy_announcement: Poista tiedote destroy_canonical_email_block: Poista sähköpostiosoitteen esto @@ -203,6 +204,7 @@ fi: destroy_status: Poista julkaisu destroy_unavailable_domain: Poista ei-saatavilla oleva verkkotunnus destroy_user_role: Hävitä rooli + destroy_username_block: Poista käyttäjänimisääntö disable_2fa_user: Poista kaksivaiheinen todennus käytöstä disable_custom_emoji: Poista mukautettu emoji käytöstä disable_relay: Poista välittäjä käytöstä @@ -237,6 +239,7 @@ fi: update_report: Päivitä raportti update_status: Päivitä julkaisu update_user_role: Päivitä rooli + update_username_block: Päivitä käyttäjänimisääntö actions: approve_appeal_html: "%{name} hyväksyi käyttäjän %{target} valituksen moderointipäätöksestä" approve_user_html: "%{name} hyväksyi käyttäjän %{target} rekisteröitymisen" @@ -255,6 +258,7 @@ fi: create_relay_html: "%{name} loi välittäjän %{target}" create_unavailable_domain_html: "%{name} pysäytti toimituksen verkkotunnukseen %{target}" create_user_role_html: "%{name} loi roolin %{target}" + create_username_block_html: "%{name} lisäsi säännön käyttäjänimille, joihin sisältyy %{target}" 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" @@ -268,6 +272,7 @@ fi: destroy_status_html: "%{name} poisti käyttäjän %{target} julkaisun" destroy_unavailable_domain_html: "%{name} jatkoi toimitusta verkkotunnukseen %{target}" destroy_user_role_html: "%{name} poisti roolin %{target}" + destroy_username_block_html: "%{name} poisti säännön käyttäjänimiltä, joihin sisältyy %{target}" disable_2fa_user_html: "%{name} poisti käyttäjältä %{target} vaatimuksen kaksivaiheiseen todentamiseen" disable_custom_emoji_html: "%{name} poisti emojin %{target} käytöstä" disable_relay_html: "%{name} poisti välittäjän %{target} käytöstä" @@ -302,6 +307,7 @@ fi: update_report_html: "%{name} päivitti raportin %{target}" update_status_html: "%{name} päivitti käyttäjän %{target} julkaisun" update_user_role_html: "%{name} muutti roolia %{target}" + update_username_block_html: "%{name} päivitti säännön käyttäjänimille, joihin sisältyy %{target}" deleted_account: poisti tilin empty: Lokeja ei löytynyt. filter_by_action: Suodata toimen mukaan @@ -1083,6 +1089,25 @@ fi: other: Käyttänyt %{count} käyttäjää viimeisen viikon aikana title: Suositukset ja trendit trending: Trendaus + username_blocks: + add_new: Lisää uusi + block_registrations: Estä rekisteröitymiset + comparison: + contains: Sisältää + equals: Vastaa + contains_html: Sisältää merkkijonon %{string} + created_msg: Käyttäjänimisääntö luotiin onnistuneesti + delete: Poista + edit: + title: Muokkaa käyttäjänimisääntöä + matches_exactly_html: Vastaa merkkijonoa %{string} + new: + create: Luo sääntö + title: Luo uusi käyttäjänimisääntö + no_username_block_selected: Käyttäjänimisääntöjä ei muutettu, koska yhtään ei ollut valittuna + not_permitted: Ei sallittu + title: Käyttäjänimisäännöt + updated_msg: Käyttäjänimisääntö päivitettiin onnistuneesti warning_presets: add_new: Lisää uusi delete: Poista @@ -1870,6 +1895,7 @@ fi: edited_at_html: Muokattu %{date} errors: in_reply_not_found: Julkaisua, johon yrität vastata, ei näytä olevan olemassa. + quoted_status_not_found: Julkaisua, jota yrität lainata, ei näytä olevan olemassa. over_character_limit: merkkimäärän rajoitus %{max} ylitetty pin_errors: direct: Vain mainituille käyttäjille näkyviä julkaisuja ei voi kiinnittää diff --git a/config/locales/hu.yml b/config/locales/hu.yml index ddb1841f1eb..d088cb3b6e8 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -190,6 +190,7 @@ hu: create_relay: Továbbító létrehozása create_unavailable_domain: Elérhetetlen domain létrehozása create_user_role: Szerepkör létrehozása + create_username_block: Felhasználónév-szabály létrehozása demote_user: Felhasználó lefokozása destroy_announcement: Közlemény törlése destroy_canonical_email_block: E-mail-tiltás törlése @@ -203,6 +204,7 @@ hu: destroy_status: Bejegyzés törlése destroy_unavailable_domain: Elérhetetlen domain törlése destroy_user_role: Szerepkör eltávolítása + destroy_username_block: Felhasználónév-szabály törlése disable_2fa_user: Kétlépcsős hitelesítés letiltása disable_custom_emoji: Egyéni emodzsi letiltása disable_relay: Továbbító letiltása @@ -237,6 +239,7 @@ hu: update_report: Bejelentés frissítése update_status: Bejegyzés frissítése update_user_role: Szerepkör frissítése + update_username_block: Felhasználónév-szabály frissítése actions: approve_appeal_html: "%{name} jóváhagyott egy fellebbezést %{target} moderátori döntéséről" approve_user_html: "%{name} jóváhagyta %{target} regisztrációját" @@ -255,6 +258,7 @@ hu: create_relay_html: "%{name} létrehozta az átirányítót: %{target}" create_unavailable_domain_html: "%{name} leállította a kézbesítést a %{target} domainbe" create_user_role_html: "%{name} létrehozta a(z) %{target} szerepkört" + create_username_block_html: "%{name} az ezt tartalmazó felhasználónevekre vonatkozó szabályt hozott létre: %{target}" demote_user_html: "%{name} lefokozta %{target} felhasználót" destroy_announcement_html: "%{name} törölte a %{target} közleményt" destroy_canonical_email_block_html: "%{name} engedélyezte a(z) %{target} hashű e-mailt" @@ -268,6 +272,7 @@ hu: destroy_status_html: "%{name} eltávolította %{target} felhasználó bejegyzését" destroy_unavailable_domain_html: "%{name} újraindította a kézbesítést a %{target} domainbe" destroy_user_role_html: "%{name} törölte a(z) %{target} szerepkört" + destroy_username_block_html: "%{name} az ezt tartalmazó felhasználónevekre vonatkozó szabályt törölt: %{target}" disable_2fa_user_html: "%{name} kikapcsolta a kétlépcsős hitelesítést %{target} felhasználó fiókján" disable_custom_emoji_html: "%{name} letiltotta az emodzsit: %{target}" disable_relay_html: "%{name} letiltotta az átirányítót: %{target}" @@ -302,6 +307,7 @@ hu: update_report_html: "%{name} frissítette a %{target} bejelentést" update_status_html: "%{name} frissítette %{target} felhasználó bejegyzését" update_user_role_html: "%{name} módosította a(z) %{target} szerepkört" + update_username_block_html: "%{name} az ezt tartalmazó felhasználónevekre vonatkozó szabályt frissített: %{target}" deleted_account: törölt fiók empty: Nem található napló. filter_by_action: Szűrés művelet alapján @@ -1085,6 +1091,25 @@ hu: other: "%{count} ember használta a múlt héten" title: Ajánlások és trendek trending: Felkapott + username_blocks: + add_new: Új hozzáadása + block_registrations: Regisztrációk blokkolása + comparison: + contains: Tartalmazza + equals: Megegyezik vele + contains_html: 'Tartalmazza ezt: %{string}' + created_msg: Felhasználónév-szabály sikeresen létrehozva + delete: Törlés + edit: + title: Felhasználónév-szabály szerkesztése + matches_exactly_html: 'Megegyezik ezzel: %{string}' + new: + create: Szabály létrehozása + title: Új felhasználónév-szabály létrehozása + no_username_block_selected: Nem változott meg egy felhasználónév-szabály sem, mert semmi sem volt kiválasztva + not_permitted: Nem engedélyezett + title: Felhasználónév-szabályok + updated_msg: Felhasználónév-szabály sikeresen frissítve warning_presets: add_new: Új hozzáadása delete: Törlés diff --git a/config/locales/it.yml b/config/locales/it.yml index 2bf9f656ba0..c9d04bc33d5 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -190,6 +190,7 @@ it: create_relay: Crea Relay create_unavailable_domain: Crea Dominio Non Disponibile create_user_role: Crea Ruolo + create_username_block: Crea regola del nome utente demote_user: Retrocedi Utente destroy_announcement: Elimina Annuncio destroy_canonical_email_block: Elimina il blocco dell'e-mail @@ -203,6 +204,7 @@ it: destroy_status: Elimina Toot destroy_unavailable_domain: Elimina Dominio Non Disponibile destroy_user_role: Distruggi Ruolo + destroy_username_block: Cancella regola del nome utente disable_2fa_user: Disabilita A2F disable_custom_emoji: Disabilita Emoji Personalizzata disable_relay: Disabilita Relay @@ -237,6 +239,7 @@ it: update_report: Aggiorna segnalazione update_status: Aggiorna Toot update_user_role: Aggiorna Ruolo + update_username_block: Aggiorna regola del nome utente actions: approve_appeal_html: "%{name} ha approvato il ricorso sulla decisione di moderazione da %{target}" approve_user_html: "%{name} ha approvato l'iscrizione da %{target}" @@ -255,6 +258,7 @@ it: create_relay_html: "%{name} ha creato un relay %{target}" create_unavailable_domain_html: "%{name} ha interrotto la consegna al dominio %{target}" create_user_role_html: "%{name} ha creato il ruolo %{target}" + create_username_block_html: "%{name} ha aggiunto la regola per i nomi utente contenenti %{target}" 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}" @@ -268,6 +272,7 @@ it: destroy_status_html: "%{name} ha rimosso il toot di %{target}" destroy_unavailable_domain_html: "%{name} ha ripreso la consegna al dominio %{target}" destroy_user_role_html: "%{name} ha eliminato il ruolo %{target}" + destroy_username_block_html: "%{name} ha rimosso la regola per i nomi utente contenenti %{target}" disable_2fa_user_html: "%{name} ha disabilitato l'autenticazione a due fattori per l'utente %{target}" disable_custom_emoji_html: "%{name} ha disabilitato emoji %{target}" disable_relay_html: "%{name} ha disabilitato il relay %{target}" @@ -302,6 +307,7 @@ it: update_report_html: "%{name} ha aggiornato la segnalazione %{target}" update_status_html: "%{name} ha aggiornato lo status di %{target}" update_user_role_html: "%{name} ha modificato il ruolo %{target}" + update_username_block_html: "%{name} ha aggiornato la regola per i nomi utente contenenti %{target}" deleted_account: account eliminato empty: Nessun log trovato. filter_by_action: Filtra per azione @@ -1085,6 +1091,25 @@ it: other: Usato da %{count} persone nell'ultima settimana title: Raccomandazioni & Tendenze trending: Di tendenza + username_blocks: + add_new: Aggiungi una nuova + block_registrations: Blocco delle registrazioni + comparison: + contains: Contiene + equals: Uguale a + contains_html: Contiene %{string} + created_msg: Regola del nome utente creata con successo + delete: Elimina + edit: + title: Modifica regola del nome utente + matches_exactly_html: Uguale a %{string} + new: + create: Crea regola + title: Crea una nuova regola del nome utente + no_username_block_selected: Non sono state modificate le regole del nome utente in quanto non sono state selezionate + not_permitted: Non consentito + title: Regole del nome utente + updated_msg: Regola del nome utente aggiornata con successo warning_presets: add_new: Aggiungi nuovo delete: Cancella @@ -1874,6 +1899,7 @@ it: edited_at_html: Modificato il %{date} errors: in_reply_not_found: Il post a cui stai tentando di rispondere non sembra esistere. + quoted_status_not_found: Il post che stai cercando di citare non sembra esistere. over_character_limit: Limite caratteri superato di %{max} pin_errors: direct: I messaggi visibili solo agli utenti citati non possono essere fissati in cima diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6d26903a20b..8e9cbaabbd7 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -190,6 +190,7 @@ nl: create_relay: Relay aanmaken create_unavailable_domain: Niet beschikbaar domein aanmaken create_user_role: Rol aanmaken + create_username_block: Gebruikersnaam-regel aanmaken demote_user: Gebruiker degraderen destroy_announcement: Mededeling verwijderen destroy_canonical_email_block: E-mailblokkade verwijderen @@ -203,6 +204,7 @@ nl: destroy_status: Toot verwijderen destroy_unavailable_domain: Niet beschikbaar domein verwijderen destroy_user_role: Rol permanent verwijderen + destroy_username_block: Gebruikersnaam-regel verwijderen disable_2fa_user: Tweestapsverificatie uitschakelen disable_custom_emoji: Lokale emojij uitschakelen disable_relay: Relay uitschakelen @@ -237,6 +239,7 @@ nl: update_report: Rapportage bijwerken update_status: Bericht bijwerken update_user_role: Rol bijwerken + update_username_block: Gebruikersnaam-regel bijwerken actions: approve_appeal_html: "%{name} heeft het bezwaar tegen de moderatiemaatregel van %{target} goedgekeurd" approve_user_html: "%{name} heeft de registratie van %{target} goedgekeurd" @@ -255,6 +258,7 @@ nl: create_relay_html: "%{name} heeft een relay aangemaakt %{target}" create_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} beëindigd" create_user_role_html: "%{name} maakte de rol %{target} aan" + create_username_block_html: "%{name} heeft regel toegevoegd voor gebruikersnamen die %{target} bevatten" 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}" @@ -268,6 +272,7 @@ nl: destroy_status_html: Bericht van %{target} is door %{name} verwijderd destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat" destroy_user_role_html: "%{name} verwijderde de rol %{target}" + destroy_username_block_html: "%{name} heeft regel verwijderd voor gebruikersnamen die %{target} bevatten" disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld disable_relay_html: "%{name} heeft de relay %{target} uitgeschakeld" @@ -302,6 +307,7 @@ nl: update_report_html: Rapportage %{target} is door %{name} bijgewerkt update_status_html: "%{name} heeft de berichten van %{target} bijgewerkt" update_user_role_html: "%{name} wijzigde de rol %{target}" + update_username_block_html: "%{name} heeft regel bijgewerkt voor gebruikersnamen die %{target} bevatten" deleted_account: verwijderd account empty: Geen logs gevonden. filter_by_action: Op actie filteren @@ -1085,6 +1091,25 @@ nl: other: Door %{count} mensen tijdens de afgelopen week gebruikt title: Aanbevelingen & trends trending: Trending + username_blocks: + add_new: Nieuw toevoegen + block_registrations: Registraties blokkeren + comparison: + contains: Bevat + equals: Is gelijk aan + contains_html: Bevat %{string} + created_msg: Gebruikersnaam-regel succesvol aangemaakt + delete: Verwijderen + edit: + title: Gebruikersnaam-regel bewerken + matches_exactly_html: Is gelijk aan %{string} + new: + create: Regel aanmaken + title: Nieuwe gebruikersnaam-regel aanmaken + no_username_block_selected: Er zijn geen gebruikersnaam-regels gewijzigd omdat er geen zijn geselecteerd + not_permitted: Niet toegestaan + title: Gebruikersnaam-regels + updated_msg: Gebruikersnaam-regel succesvol bijgewerkt warning_presets: add_new: Nieuwe toevoegen delete: Verwijderen diff --git a/config/locales/ru.yml b/config/locales/ru.yml index ad4b9d5122e..b04e99f5fed 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1653,9 +1653,9 @@ ru: on_cooldown: Вы пока не можете переезжать followers_count: Подписчиков на момент переезда incoming_migrations: Переезд со старой учётной записи - incoming_migrations_html: Переезжаете с другой учётной записи? Настройте псевдоним для переноса подписчиков. + incoming_migrations_html: Вы можете добавить связанную учётную запись, если собираетесь перенести оттуда подписчиков. moved_msg: Теперь ваша учётная запись перенаправляет к %{acct}, туда же перемещаются подписчики. - not_redirecting: Для вашей учётной записи пока не настроено перенаправление. + not_redirecting: Прямо сейчас ваша учётная запись никуда не перенаправлена. on_cooldown: Вы уже недавно переносили свою учётную запись. Эта возможность будет снова доступна через %{count} дн. past_migrations: История переездов proceed_with_move: Перенести подписчиков @@ -1666,7 +1666,7 @@ ru: backreference_required: Текущая учётная запись сначала должна быть добавлена как связанная в настройках новой учётной записи before: 'Внимательно ознакомьтесь со следующими замечаниями перед тем как продолжить:' cooldown: После переезда наступит период ожидания, в течение которого переезд будет невозможен - disabled_account: Вашу текущую учётная запись впоследствии нельзя будет больше использовать. При этом, у вас будет доступ к экспорту данных, а также к повторной активации учётной записи. + disabled_account: Переезд приведёт к тому, что вашу текущую учётную запись нельзя будет полноценно использовать. Тем не менее у вас останется доступ к экспорту данных и повторной активации учётной записи. followers: В результате переезда все ваши подписчики будут перенесены с текущей учётной записи на новую only_redirect_html: Также вы можете настроить перенаправление без переноса подписчиков. other_data: Никакие другие данные не будут автоматически перенесены @@ -1764,16 +1764,16 @@ ru: reach: Видимость reach_hint_html: Решите, нужна ли вам новая аудитория и новые подписчики. Настройте по своему желанию, показывать ли ваши посты в разделе «Актуальное», рекомендовать ли ваш профиль другим людям, принимать ли всех новых подписчиков автоматически или рассматривать каждый запрос на подписку в отдельности. search: Поиск - search_hint_html: Определите, как вас могут найти. Хотите ли вы, чтобы люди находили вас по тому, о чём вы публично писали? Хотите ли вы, чтобы люди за пределами Mastodon находили ваш профиль при поиске в Интернете? Следует помнить, что полное исключение из всех поисковых систем не может быть гарантировано для публичной информации. + search_hint_html: Решите, нужно ли вам скрыть себя из поиска. Настройте по своему желанию то, можно ли будет найти вас по публичным постам, а также то, можно ли будет кому угодно в интернете найти ваш профиль с помощью поисковых сайтов. Имейте в виду, что невозможно гарантировать полное исключение общедоступной информации из всех поисковых систем. title: Приватность и видимость privacy_policy: title: Политика конфиденциальности reactions: errors: - limit_reached: Достигнут лимит разных реакций - unrecognized_emoji: не является распознанным эмодзи + limit_reached: К этому объявлению уже добавлено максимальное количество уникальных реакций + unrecognized_emoji: не соответствует ни одному известному названию эмодзи redirects: - prompt: Если вы доверяете этой ссылке, нажмите на нее, чтобы продолжить. + prompt: Переходите по ссылке только в том случае, если доверяете сайту, на который она ведёт. title: Вы покидаете %{instance}. relationships: activity: Активность учётной записи diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index fd32c087440..47dc24583ef 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -160,6 +160,10 @@ el: name: Δημόσιο όνομα του ρόλου, εάν ο ρόλος έχει οριστεί να εμφανίζεται ως σήμα permissions_as_keys: Οι χρήστες με αυτόν τον ρόλο θα έχουν πρόσβαση σε... position: Ανώτεροι ρόλοι αποφασίζει την επίλυση συγκρούσεων σε ορισμένες περιπτώσεις. Ορισμένες ενέργειες μπορούν να εκτελεστούν μόνο σε ρόλους με χαμηλότερη προτεραιότητα + username_block: + allow_with_approval: Αντί να αποτρέψετε την οριστική εγγραφή, η αντιστοίχιση εγγραφών θα απαιτήσει την έγκρισή σας + comparison: Παρακαλώ να λάβετε υπόψη το Πρόβλημα Scunthorpe κατά τη φραγή μερικών αντιστοιχίσεων + username: Θα αντιστοιχηθεί ανεξάρτητα από τα κεφαλαία/πεζά και τα κοινά ομόγλυφα όπως "4" για "α" ή "3" για "e" webhook: events: Επιλέξτε συμβάντα για αποστολή template: Σύνθεσε το δικό σου JSON payload χρησιμοποιώντας μεταβλητή παρεμβολή. Άφησε κενό για προεπιλογή JSON. @@ -371,6 +375,10 @@ el: name: Όνομα permissions_as_keys: Δικαιώματα position: Προτεραιότητα + username_block: + allow_with_approval: Να επιτρέπονται εγγραφές με έγκριση + comparison: Μέθοδος σύγκρισης + username: Λέξη για αντιστοίχιση webhook: events: Ενεργοποιημένα συμβάντα template: Πρότυπο payload diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 5a806fb8e76..7d6c97d8deb 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -61,6 +61,7 @@ fi: setting_display_media_default: Piilota arkaluonteiseksi merkitty mediasisältö setting_display_media_hide_all: Piilota mediasisältö aina setting_display_media_show_all: Näytä mediasisältö aina + setting_emoji_style: Miten emojit näkyvät. ”Automaattinen” pyrkii käyttämään natiiveja emojeita, mutta Twemoji-emojeita käytetään varavaihtoehtoina vanhoissa selaimissa. setting_system_scrollbars_ui: Koskee vain Safari- ja Chrome-pohjaisia työpöytäselaimia setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan @@ -148,6 +149,9 @@ fi: min_age: Ei pidä alittaa lainkäyttöalueesi lakien vaatimaa vähimmäisikää. user: chosen_languages: Jos valitset kieliä oheisesta luettelosta, vain niidenkieliset julkaisut näkyvät sinulle julkisilla aikajanoilla + date_of_birth: + one: Meidän tulee varmistaa, että olet vähintään %{count}, jotta voit käyttää %{domain}. Emme tallenna tätä. + other: Meidän tulee varmistaa, että olet vähintään %{count}, jotta voit käyttää %{domain}. Emme tallenna tätä. role: Rooli määrää, millaiset käyttöoikeudet käyttäjällä on. user_role: color: Väri, jota käytetään roolille kaikkialla käyttöliittymässä, RGB-heksadesimaalimuodossa @@ -155,6 +159,10 @@ fi: name: Roolin julkinen nimi, jos rooli on asetettu näytettäväksi merkkinä permissions_as_keys: Käyttäjillä, joilla on tämä rooli, on käyttöoikeus… position: Korkeampi rooli ratkaisee konfliktit tietyissä tilanteissa. Tiettyjä toimia voidaan suorittaa vain rooleilla, joiden prioriteetti on pienempi + username_block: + allow_with_approval: Sen sijaan, että rekisteröityminen estetään kokonaan, sääntöä vastaavat rekisteröitymiset edellyttävät hyväksyntääsi + comparison: Ota Scunthorpe-ongelma huomioon, kun estät osittaisia osumia + username: Vastaa riippumatta aakkoskoosta ja yleisistä homoglyyfeistä kuten ”4” merkille ”a” ja ”3” merkille ”e” webhook: events: Valitse lähetettävät tapahtumat template: Luo oma JSON-hyötykuorma käyttäen muuttujien interpolointia. Jätä kenttä tyhjäksi käyttääksesi vakio-JSON-kuormaa. @@ -348,6 +356,7 @@ fi: admin_email: Sähköpostiosoite oikeudellisille ilmoituksille arbitration_address: Fyysinen osoite välimiesmenettelyn ilmoituksille arbitration_website: Sähköpostiosoite välimiesmenettelyn ilmoituksille + choice_of_law: Sovellettava lainsäädäntö dmca_address: Fyysinen osoite DMCA-/tekijänoikeusilmoituksille dmca_email: Sähköpostiosoite DMCA-/tekijänoikeusilmoituksille domain: Verkkotunnus @@ -365,6 +374,10 @@ fi: name: Nimi permissions_as_keys: Käyttöoikeudet position: Prioriteetti + username_block: + allow_with_approval: Salli rekisteröitymiset hyväksynnällä + comparison: Vertailumenetelmä + username: Vastattava sana webhook: events: Käytössä olevat tapahtumat template: Hyötykuormapohja diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 61ca09b951f..79c163ebb7c 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -160,6 +160,10 @@ hu: name: A szerep nyilvános neve, ha a szerepet úgy állították be, hogy jelvényként látható legyen permissions_as_keys: A felhasználók ezzel a szereppel elérhetik a... position: A magasabb szerepkör oldja fel az ütközéseket bizonyos helyzetekben. Bizonyos műveleteket csak alacsonyabb prioritású szerepkörrel lehet elvégezni. + username_block: + allow_with_approval: A regisztráció azonnali megakadályozása helyett az illeszkedő regisztrációkhoz jóváhagyás szükséges + comparison: Vegye figyelembe a Scunthorpe-problémát, amikor részleges egyezéseket blokkol + username: Az illeszkedés egyezőnek tekinti a kis- és nagybetűket, valamint a gyakori homoglifákat, mint a „4” és az „a” vagy a „3” és az „e” webhook: events: Válaszd ki a küldendő eseményeket template: Saját JSON adatcsomagot állíthatsz össze változó-behelyettesítés használatával. Hagyd üresen az alapértelmezett JSON adatcsomaghoz. @@ -371,6 +375,10 @@ hu: name: Név permissions_as_keys: Engedélyek position: Prioritás + username_block: + allow_with_approval: Regisztráció engedélyezése jóváhagyással + comparison: Összehasonlítás módja + username: Ellenőrzendő szó webhook: events: Engedélyezett események template: Adatcsomag sablon diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 7410cd4ae45..b07a1fc1dc3 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -160,6 +160,10 @@ it: name: Nome pubblico del ruolo, se il ruolo è impostato per essere visualizzato come distintivo permissions_as_keys: Gli utenti con questo ruolo avranno accesso a... position: Un ruolo più alto decide la risoluzione dei conflitti in determinate situazioni. Alcune azioni possono essere eseguite solo su ruoli con priorità più bassa + username_block: + allow_with_approval: Invece di impedire del tutto l'iscrizione, le iscrizioni corrispondenti richiederanno la tua approvazione + comparison: Si prega di tenere presente il problema di Scunthorpe quando si bloccano corrispondenze parziali + username: Coinciderà indipendentemente da lettere e omoglifi comuni come "4" per "a" o "3" per "e" webhook: events: Seleziona eventi da inviare template: Componi il tuo carico utile JSON utilizzando l'interpolazione variabile. Lascia vuoto per il JSON predefinito. @@ -371,6 +375,10 @@ it: name: Nome permissions_as_keys: Permessi position: Priorità + username_block: + allow_with_approval: Consenti le registrazioni con approvazione + comparison: Metodo di confronto + username: Parola da abbinare webhook: events: Eventi abilitati template: Modello di carico utile diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 70e302da031..53945af5313 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -160,6 +160,10 @@ nl: name: Openbare naam van de rol, wanneer de rol als badge op profielpagina's wordt getoond permissions_as_keys: Gebruikers met deze rol hebben toegang tot... position: Een hogere rol beslist in bepaalde situaties over het oplossen van conflicten. Bepaalde acties kunnen alleen worden uitgevoerd op rollen met een lagere prioriteit + username_block: + allow_with_approval: In plaats van inschrijving helemaal te voorkomen, zullen overeenkomstige inschrijvingen jouw goedkeuring vereisen + comparison: Houd rekening met het Scunthorpe-probleem wanneer je gedeeltelijke overeenkomsten blokkeert + username: Wordt gezien als overeenkomst ongeacht de lettergrootte en gangbare lettervervangingen zoals "4" voor "a" of "3" voor "e" webhook: events: Selecteer de te verzenden gebeurtenissen template: Maak een eigen JSON payload aan met variabele interpolatie. Laat leeg voor standaard JSON. @@ -371,6 +375,10 @@ nl: name: Naam permissions_as_keys: Rechten position: Prioriteit + username_block: + allow_with_approval: Registraties met goedkeuring toestaan + comparison: Methode van vergelijking + username: Overeen te komen woord webhook: events: Ingeschakelde gebeurtenissen template: Sjabloon Payload From 572a0e128d74c16d0715e646a5895dac4c34eca4 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 31 Jul 2025 09:39:53 +0200 Subject: [PATCH 184/660] Change quote verification to not bypass authorization flow for mentions (#35528) --- app/lib/activitypub/parser/status_parser.rb | 3 --- .../concerns/status/interaction_policy_concern.rb | 8 -------- app/services/activitypub/verify_quote_service.rb | 9 +-------- config/locales/en.yml | 4 ++-- config/locales/simple_form.en.yml | 2 +- spec/policies/status_policy_spec.rb | 14 +++++++------- .../activitypub/verify_quote_service_spec.rb | 4 ++-- 7 files changed, 13 insertions(+), 31 deletions(-) diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index ad3ef72be8a..5a434ed915a 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -152,9 +152,6 @@ class ActivityPub::Parser::StatusParser # Remove the special-meaning actor URI allowed_actors.delete(@options[:actor_uri]) - # Tagged users are always allowed, so remove them - allowed_actors -= as_array(@object['tag']).filter_map { |tag| tag['href'] if equals_or_includes?(tag['type'], 'Mention') } - # Any unrecognized actor is marked as unknown flags |= Status::QUOTE_APPROVAL_POLICY_FLAGS[:unknown] unless allowed_actors.empty? diff --git a/app/models/concerns/status/interaction_policy_concern.rb b/app/models/concerns/status/interaction_policy_concern.rb index 7e7642209db..6ad047fd8d5 100644 --- a/app/models/concerns/status/interaction_policy_concern.rb +++ b/app/models/concerns/status/interaction_policy_concern.rb @@ -33,16 +33,8 @@ module Status::InteractionPolicyConcern automatic_policy = quote_approval_policy >> 16 manual_policy = quote_approval_policy & 0xFFFF - # Checking for public policy first because it's less expensive than looking at mentions return :automatic if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:public]) - # Mentioned users are always allowed to quote - if active_mentions.loaded? - return :automatic if active_mentions.any? { |mention| mention.account_id == other_account.id } - elsif active_mentions.exists?(account: other_account) - return :automatic - end - if automatic_policy.anybits?(QUOTE_APPROVAL_POLICY_FLAGS[:followers]) following_author = preloaded_relations[:following] ? preloaded_relations[:following][account_id] : other_account.following?(account) if following_author.nil? return :automatic if following_author diff --git a/app/services/activitypub/verify_quote_service.rb b/app/services/activitypub/verify_quote_service.rb index a83a3c1155a..822abcf4022 100644 --- a/app/services/activitypub/verify_quote_service.rb +++ b/app/services/activitypub/verify_quote_service.rb @@ -45,14 +45,7 @@ class ActivityPub::VerifyQuoteService < BaseService true end - # Always allow someone to quote posts in which they are mentioned - if @quote.quoted_status.active_mentions.exists?(mentions: { account_id: @quote.account_id }) - @quote.accept! - - true - else - false - end + false end def fetch_approval_object(uri, prefetched_body: nil) diff --git a/config/locales/en.yml b/config/locales/en.yml index a149c18c775..59272bb3542 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1906,8 +1906,8 @@ en: ownership: Someone else's post cannot be pinned reblog: A boost cannot be pinned quote_policies: - followers: Followers and mentioned users - nobody: Only mentioned users + followers: Only your followers + nobody: Nobody public: Everyone title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 86fb4528de4..8da54f626f7 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -56,7 +56,7 @@ en: scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon - setting_default_quote_policy: Mentioned users are always allowed to quote. This setting will only take effect for posts created with the next Mastodon version, but you can select your preference in preparation + setting_default_quote_policy: This setting will only take effect for posts created with the next Mastodon version, but you can select your preference in preparation. setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 63622970455..af6958b68dc 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -94,19 +94,19 @@ RSpec.describe StatusPolicy, type: :model do expect(subject).to permit(status.account, status) end - it 'grants access when direct and viewer is mentioned' do + it 'does not grant access access when direct and viewer is mentioned but not explicitly allowed' do status.visibility = :direct - status.mentions = [Fabricate(:mention, account: alice)] + status.mentions = [Fabricate(:mention, account: bob)] - expect(subject).to permit(alice, status) + expect(subject).to_not permit(bob, status) end - it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do + it 'does not grant access access when direct and viewer is mentioned but not explicitly allowed and mentions are loaded' do status.visibility = :direct status.mentions = [Fabricate(:mention, account: bob)] status.active_mentions.load - expect(subject).to permit(bob, status) + expect(subject).to_not permit(bob, status) end it 'denies access when direct and viewer is not mentioned' do @@ -123,11 +123,11 @@ RSpec.describe StatusPolicy, type: :model do expect(subject).to_not permit(viewer, status) end - it 'grants access when private and viewer is mentioned' do + it 'grants access when private and viewer is mentioned but not otherwise allowed' do status.visibility = :private status.mentions = [Fabricate(:mention, account: bob)] - expect(subject).to permit(bob, status) + expect(subject).to_not permit(bob, status) end it 'denies access when private and non-viewer is mentioned' do diff --git a/spec/services/activitypub/verify_quote_service_spec.rb b/spec/services/activitypub/verify_quote_service_spec.rb index ae4ffae9bb2..94b9e33ed3b 100644 --- a/spec/services/activitypub/verify_quote_service_spec.rb +++ b/spec/services/activitypub/verify_quote_service_spec.rb @@ -267,9 +267,9 @@ RSpec.describe ActivityPub::VerifyQuoteService do quoted_status.mentions << Mention.new(account: account) end - it 'updates the status' do + it 'does not the status' do expect { subject.call(quote) } - .to change(quote, :state).to('accepted') + .to_not change(quote, :state).from('pending') end end end From 2dfdcc7dcb63e0a480e5a5b80b4f56344540d018 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 31 Jul 2025 11:36:51 +0200 Subject: [PATCH 185/660] Add API endpoints to view and revoke one's quoted posts (#35578) --- .../api/v1/statuses/quotes_controller.rb | 72 ++++++++++ app/models/quote.rb | 2 + app/policies/quote_policy.rb | 7 + app/policies/status_policy.rb | 4 + .../delete_quote_authorization_serializer.rb | 28 ++++ app/services/revoke_quote_service.rb | 32 +++++ config/routes/api.rb | 6 + spec/requests/api/v1/statuses/quotes_spec.rb | 126 ++++++++++++++++++ ...ete_quote_authorization_serializer_spec.rb | 21 +++ spec/services/revoke_quote_service_spec.rb | 26 ++++ 10 files changed, 324 insertions(+) create mode 100644 app/controllers/api/v1/statuses/quotes_controller.rb create mode 100644 app/policies/quote_policy.rb create mode 100644 app/serializers/activitypub/delete_quote_authorization_serializer.rb create mode 100644 app/services/revoke_quote_service.rb create mode 100644 spec/requests/api/v1/statuses/quotes_spec.rb create mode 100644 spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb create mode 100644 spec/services/revoke_quote_service_spec.rb diff --git a/app/controllers/api/v1/statuses/quotes_controller.rb b/app/controllers/api/v1/statuses/quotes_controller.rb new file mode 100644 index 00000000000..7dd91e9a2ee --- /dev/null +++ b/app/controllers/api/v1/statuses/quotes_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: :revoke + + before_action :check_owner! + before_action :set_quote, only: :revoke + after_action :insert_pagination_headers, only: :index + + def index + cache_if_unauthenticated! + @statuses = load_statuses + render json: @statuses, each_serializer: REST::StatusSerializer + end + + def revoke + authorize @quote, :revoke? + + RevokeQuoteService.new.call(@quote) + + render_empty # TODO: do we want to return something? an updated status? + end + + private + + def check_owner! + authorize @status, :list_quotes? + end + + def set_quote + @quote = @status.quotes.find_by!(status_id: params[:id]) + end + + def load_statuses + scope = default_statuses + scope = scope.not_excluded_by_account(current_account) unless current_account.nil? + scope.merge(paginated_quotes).to_a + end + + def default_statuses + Status.includes(:quote).references(:quote) + end + + def paginated_quotes + @status.quotes.accepted.paginate_by_max_id( + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id] + ) + end + + def next_path + api_v1_status_quotes_url pagination_params(max_id: pagination_max_id) if records_continue? + end + + def prev_path + api_v1_status_quotes_url pagination_params(since_id: pagination_since_id) unless @statuses.empty? + end + + def pagination_max_id + @statuses.last.quote.id + end + + def pagination_since_id + @statuses.first.quote.id + end + + def records_continue? + @statuses.size == limit_param(DEFAULT_STATUSES_LIMIT) + end +end diff --git a/app/models/quote.rb b/app/models/quote.rb index 89845ed9f49..b844805da73 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -17,6 +17,8 @@ # status_id :bigint(8) not null # class Quote < ApplicationRecord + include Paginable + BACKGROUND_REFRESH_INTERVAL = 1.week.freeze REFRESH_DEADLINE = 6.hours diff --git a/app/policies/quote_policy.rb b/app/policies/quote_policy.rb new file mode 100644 index 00000000000..a8be64a7792 --- /dev/null +++ b/app/policies/quote_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class QuotePolicy < ApplicationPolicy + def revoke? + record.quoted_account_id.present? && record.quoted_account_id == current_account&.id + end +end diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index d9bb7201c00..5d01da42c61 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -36,6 +36,10 @@ class StatusPolicy < ApplicationPolicy owned? end + def list_quotes? + owned? + end + alias unreblog? destroy? def update? diff --git a/app/serializers/activitypub/delete_quote_authorization_serializer.rb b/app/serializers/activitypub/delete_quote_authorization_serializer.rb new file mode 100644 index 00000000000..ab914711650 --- /dev/null +++ b/app/serializers/activitypub/delete_quote_authorization_serializer.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class ActivityPub::DeleteQuoteAuthorizationSerializer < ActivityPub::Serializer + attributes :id, :type, :actor, :to + + # TODO: change the `object` to a `QuoteAuthorization` object instead of just the URI? + attribute :virtual_object, key: :object + + def id + [object.approval_uri, '#delete'].join + end + + def virtual_object + object.approval_uri + end + + def type + 'Delete' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.quoted_account) + end + + def to + [ActivityPub::TagManager::COLLECTIONS[:public]] + end +end diff --git a/app/services/revoke_quote_service.rb b/app/services/revoke_quote_service.rb new file mode 100644 index 00000000000..8f5dc8f9105 --- /dev/null +++ b/app/services/revoke_quote_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class RevokeQuoteService < BaseService + include Payloadable + + def call(quote) + @quote = quote + @account = quote.quoted_account + + @quote.reject! + distribute_stamp_deletion! + end + + private + + def distribute_stamp_deletion! + ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url| + [signed_activity_json, @account.id, inbox_url] + end + end + + def inboxes + [ + @quote.status, + @quote.quoted_status, + ].compact.map { |status| StatusReachFinder.new(status, unsafe: true).inboxes }.flatten.uniq + end + + def signed_activity_json + @signed_activity_json ||= Oj.dump(serialize_payload(@quote, ActivityPub::DeleteQuoteAuthorizationSerializer, signer: @account, always_sign: true)) + end +end diff --git a/config/routes/api.rb b/config/routes/api.rb index 4040a4350fa..83190610d0b 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -18,6 +18,12 @@ namespace :api, format: false do resource :reblog, only: :create post :unreblog, to: 'reblogs#destroy' + resources :quotes, only: :index do + member do + post :revoke + end + end + resource :favourite, only: :create post :unfavourite, to: 'favourites#destroy' diff --git a/spec/requests/api/v1/statuses/quotes_spec.rb b/spec/requests/api/v1/statuses/quotes_spec.rb new file mode 100644 index 00000000000..bbf697ce323 --- /dev/null +++ b/spec/requests/api/v1/statuses/quotes_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'API V1 Statuses Quotes' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + describe 'GET /api/v1/statuses/:status_id/quotes' do + subject do + get "/api/v1/statuses/#{status.id}/quotes", headers: headers, params: { limit: 2 } + end + + let(:scopes) { 'read:statuses' } + + let(:status) { Fabricate(:status, account: user.account) } + let!(:accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + let!(:rejected_quote) { Fabricate(:quote, quoted_status: status, state: :rejected) } + let!(:pending_quote) { Fabricate(:quote, quoted_status: status, state: :pending) } + let!(:another_accepted_quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + + context 'with an OAuth token' do + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + it 'returns http success and statuses quoting this post' do + subject + + expect(response) + .to have_http_status(200) + .and include_pagination_headers( + prev: api_v1_status_quotes_url(limit: 2, since_id: another_accepted_quote.id), + next: api_v1_status_quotes_url(limit: 2, max_id: accepted_quote.id) + ) + expect(response.content_type) + .to start_with('application/json') + + expect(response.parsed_body) + .to contain_exactly( + include(id: accepted_quote.status.id.to_s), + include(id: another_accepted_quote.status.id.to_s) + ) + + expect(response.parsed_body) + .to_not include( + include(id: rejected_quote.status.id.to_s), + include(id: pending_quote.status.id.to_s) + ) + end + + context 'with a different user than the post owner' do + let(:status) { Fabricate(:status) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') + end + end + end + + context 'without an OAuth token' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') + end + end + end + + describe 'POST /api/v1/statuses/:status_id/quotes/:id/revoke' do + subject do + post "/api/v1/statuses/#{status.id}/quotes/#{quote.status.id}/revoke", headers: headers + end + + let(:scopes) { 'write:statuses' } + + let(:status) { Fabricate(:status, account: user.account) } + let!(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + + context 'with an OAuth token' do + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it_behaves_like 'forbidden for wrong scope', 'read read:statuses' + + context 'with a different user than the post owner' do + let(:status) { Fabricate(:status) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') + end + end + + it 'revokes the quote and returns HTTP success' do + expect { subject } + .to change { quote.reload.state }.from('accepted').to('revoked') + + expect(response) + .to have_http_status(200) + end + end + + context 'without an OAuth token' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') + end + end + end +end diff --git a/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb new file mode 100644 index 00000000000..a7609644f17 --- /dev/null +++ b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::DeleteQuoteAuthorizationSerializer do + subject { serialized_record_json(quote, described_class, adapter: ActivityPub::Adapter) } + + describe 'serializing an object' do + let(:status) { Fabricate(:status) } + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted, approval_uri: "https://#{Rails.configuration.x.web_domain}/approvals/1234") } + + it 'returns expected attributes' do + expect(subject.deep_symbolize_keys) + .to include( + actor: eq(ActivityPub::TagManager.instance.uri_for(status.account)), + object: quote.approval_uri, + type: 'Delete' + ) + end + end +end diff --git a/spec/services/revoke_quote_service_spec.rb b/spec/services/revoke_quote_service_spec.rb new file mode 100644 index 00000000000..282a589162c --- /dev/null +++ b/spec/services/revoke_quote_service_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe RevokeQuoteService do + subject { described_class.new } + + let!(:alice) { Fabricate(:account) } + let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + + let(:status) { Fabricate(:status, account: alice) } + + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted, approval_uri: "https://#{Rails.configuration.x.web_domain}/approvals/1234") } + + before do + hank.follow!(alice) + end + + context 'with an accepted quote' do + it 'revokes the quote and sends a Delete activity' do + expect { described_class.new.call(quote) } + .to change { quote.reload.state }.from('accepted').to('revoked') + .and enqueue_sidekiq_job(ActivityPub::DeliveryWorker).with(/Delete/, alice.id, hank.inbox_url) + end + end +end From f7388af72159e9412d9bcbfd9f69f9a422ca8bcc Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 31 Jul 2025 14:34:21 +0200 Subject: [PATCH 186/660] Fix `Chewy::UndefinedUpdateStrategy` in `dev:populate_sample_data` task when Elasticsearch is enabled (#35615) --- lib/tasks/dev.rake | 804 +++++++++++++++++++++++---------------------- 1 file changed, 403 insertions(+), 401 deletions(-) diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index f76d1891611..921ffba5912 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -3,420 +3,422 @@ namespace :dev do desc 'Populate database with test data. Can be run multiple times. Should not be run in production environments' task populate_sample_data: :environment do - # Create a valid account to showcase multiple post types - showcase_account = Account.create_with(username: 'showcase_account').find_or_create_by!(id: 10_000_000) - showcase_user = User.create_with( - account_id: showcase_account.id, - agreement: true, - password: SecureRandom.hex, - email: ENV.fetch('TEST_DATA_SHOWCASE_EMAIL', 'showcase_account@joinmastodon.org'), - confirmed_at: Time.now.utc, - approved: true, - bypass_registration_checks: true - ).find_or_create_by!(id: 10_000_000) - showcase_user.mark_email_as_confirmed! - showcase_user.approve! + Chewy.strategy(:mastodon) do + # Create a valid account to showcase multiple post types + showcase_account = Account.create_with(username: 'showcase_account').find_or_create_by!(id: 10_000_000) + showcase_user = User.create_with( + account_id: showcase_account.id, + agreement: true, + password: SecureRandom.hex, + email: ENV.fetch('TEST_DATA_SHOWCASE_EMAIL', 'showcase_account@joinmastodon.org'), + confirmed_at: Time.now.utc, + approved: true, + bypass_registration_checks: true + ).find_or_create_by!(id: 10_000_000) + showcase_user.mark_email_as_confirmed! + showcase_user.approve! - french_post = Status.create_with( - text: 'Ceci est un sondage public écrit en Français', - language: 'fr', - account: showcase_account, - visibility: :public, - poll_attributes: { - voters_count: 0, + french_post = Status.create_with( + text: 'Ceci est un sondage public écrit en Français', + language: 'fr', account: showcase_account, - expires_at: 1.day.from_now, - options: ['ceci est un choix', 'ceci est un autre choix'], - multiple: false, - } - ).find_or_create_by!(id: 10_000_000) + visibility: :public, + poll_attributes: { + voters_count: 0, + account: showcase_account, + expires_at: 1.day.from_now, + options: ['ceci est un choix', 'ceci est un autre choix'], + multiple: false, + } + ).find_or_create_by!(id: 10_000_000) - private_mentionless = Status.create_with( - text: 'This is a private message written in English', - language: 'en', - account: showcase_account, - visibility: :private - ).find_or_create_by!(id: 10_000_001) - - public_self_reply_with_cw = Status.create_with( - text: 'This is a public self-reply written in English; it has a CW and a multi-choice poll', - spoiler_text: 'poll (CW example)', - language: 'en', - account: showcase_account, - visibility: :public, - thread: french_post, - poll_attributes: { - voters_count: 0, + private_mentionless = Status.create_with( + text: 'This is a private message written in English', + language: 'en', account: showcase_account, - expires_at: 1.day.from_now, - options: ['this is a choice', 'this is another choice', 'you can chose any number of them'], - multiple: true, - } - ).find_or_create_by!(id: 10_000_002) - ProcessHashtagsService.new.call(public_self_reply_with_cw) + visibility: :private + ).find_or_create_by!(id: 10_000_001) - unlisted_self_reply_with_cw_tag_mention = Status.create_with( - text: 'This is an unlisted (Quiet Public) self-reply written in #English; it has a CW, mentions @showcase_account, and uses an emoji 🦣', - spoiler_text: 'CW example', - language: 'en', - account: showcase_account, - visibility: :unlisted, - thread: public_self_reply_with_cw - ).find_or_create_by!(id: 10_000_003) - Mention.find_or_create_by!(status: unlisted_self_reply_with_cw_tag_mention, account: showcase_account) - ProcessHashtagsService.new.call(unlisted_self_reply_with_cw_tag_mention) + public_self_reply_with_cw = Status.create_with( + text: 'This is a public self-reply written in English; it has a CW and a multi-choice poll', + spoiler_text: 'poll (CW example)', + language: 'en', + account: showcase_account, + visibility: :public, + thread: french_post, + poll_attributes: { + voters_count: 0, + account: showcase_account, + expires_at: 1.day.from_now, + options: ['this is a choice', 'this is another choice', 'you can chose any number of them'], + multiple: true, + } + ).find_or_create_by!(id: 10_000_002) + ProcessHashtagsService.new.call(public_self_reply_with_cw) - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/600x400.png'), - description: 'Mastodon logo' - ).find_or_create_by!(id: 10_000_000) - status_with_media = Status.create_with( - text: "This is a public status with a picture and tags. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_004) - media_attachment.update(status_id: status_with_media.id) - ProcessHashtagsService.new.call(status_with_media) + unlisted_self_reply_with_cw_tag_mention = Status.create_with( + text: 'This is an unlisted (Quiet Public) self-reply written in #English; it has a CW, mentions @showcase_account, and uses an emoji 🦣', + spoiler_text: 'CW example', + language: 'en', + account: showcase_account, + visibility: :unlisted, + thread: public_self_reply_with_cw + ).find_or_create_by!(id: 10_000_003) + Mention.find_or_create_by!(status: unlisted_self_reply_with_cw_tag_mention, account: showcase_account) + ProcessHashtagsService.new.call(unlisted_self_reply_with_cw_tag_mention) - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/600x400.png'), - description: 'Mastodon logo' - ).find_or_create_by!(id: 10_000_001) - status_with_sensitive_media = Status.create_with( - text: "This is the same public status with a picture and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public, - sensitive: true, - thread: status_with_media - ).find_or_create_by!(id: 10_000_005) - media_attachment.update(status_id: status_with_sensitive_media.id) - ProcessHashtagsService.new.call(status_with_sensitive_media) - - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/600x400.png'), - description: 'Mastodon logo' - ).find_or_create_by!(id: 10_000_002) - status_with_cw_media = Status.create_with( - text: "This is the same public status with a picture and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", - spoiler_text: 'Mastodon logo', - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public, - sensitive: true, - thread: status_with_sensitive_media - ).find_or_create_by!(id: 10_000_006) - media_attachment.update(status_id: status_with_cw_media.id) - ProcessHashtagsService.new.call(status_with_cw_media) - - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/boop.ogg'), - description: 'Mastodon boop' - ).find_or_create_by!(id: 10_000_003) - status_with_audio = Status.create_with( - text: "This is the same public status with an audio file and tags. The attached picture has an alt text\n\n#Mastodon #English #Test", - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public, - thread: status_with_cw_media - ).find_or_create_by!(id: 10_000_007) - media_attachment.update(status_id: status_with_audio.id) - ProcessHashtagsService.new.call(status_with_audio) - - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/boop.ogg'), - description: 'Mastodon boop' - ).find_or_create_by!(id: 10_000_004) - status_with_sensitive_audio = Status.create_with( - text: "This is the same public status with an audio file and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #English #Test", - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public, - sensitive: true, - thread: status_with_audio - ).find_or_create_by!(id: 10_000_008) - media_attachment.update(status_id: status_with_sensitive_audio.id) - ProcessHashtagsService.new.call(status_with_sensitive_audio) - - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/boop.ogg'), - description: 'Mastodon boop' - ).find_or_create_by!(id: 10_000_005) - status_with_cw_audio = Status.create_with( - text: "This is the same public status with an audio file and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #English #Test", - spoiler_text: 'Mastodon boop', - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public, - sensitive: true, - thread: status_with_sensitive_audio - ).find_or_create_by!(id: 10_000_009) - media_attachment.update(status_id: status_with_cw_audio.id) - ProcessHashtagsService.new.call(status_with_cw_audio) - - media_attachments = [ - MediaAttachment.create_with( + media_attachment = MediaAttachment.create_with( account: showcase_account, file: File.open('spec/fixtures/files/600x400.png'), description: 'Mastodon logo' - ).find_or_create_by!(id: 10_000_006), - MediaAttachment.create_with( + ).find_or_create_by!(id: 10_000_000) + status_with_media = Status.create_with( + text: "This is a public status with a picture and tags. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_004) + media_attachment.update(status_id: status_with_media.id) + ProcessHashtagsService.new.call(status_with_media) + + media_attachment = MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/600x400.png'), + description: 'Mastodon logo' + ).find_or_create_by!(id: 10_000_001) + status_with_sensitive_media = Status.create_with( + text: "This is the same public status with a picture and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public, + sensitive: true, + thread: status_with_media + ).find_or_create_by!(id: 10_000_005) + media_attachment.update(status_id: status_with_sensitive_media.id) + ProcessHashtagsService.new.call(status_with_sensitive_media) + + media_attachment = MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/600x400.png'), + description: 'Mastodon logo' + ).find_or_create_by!(id: 10_000_002) + status_with_cw_media = Status.create_with( + text: "This is the same public status with a picture and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #Logo #English #Test", + spoiler_text: 'Mastodon logo', + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public, + sensitive: true, + thread: status_with_sensitive_media + ).find_or_create_by!(id: 10_000_006) + media_attachment.update(status_id: status_with_cw_media.id) + ProcessHashtagsService.new.call(status_with_cw_media) + + media_attachment = MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/boop.ogg'), + description: 'Mastodon boop' + ).find_or_create_by!(id: 10_000_003) + status_with_audio = Status.create_with( + text: "This is the same public status with an audio file and tags. The attached picture has an alt text\n\n#Mastodon #English #Test", + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public, + thread: status_with_cw_media + ).find_or_create_by!(id: 10_000_007) + media_attachment.update(status_id: status_with_audio.id) + ProcessHashtagsService.new.call(status_with_audio) + + media_attachment = MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/boop.ogg'), + description: 'Mastodon boop' + ).find_or_create_by!(id: 10_000_004) + status_with_sensitive_audio = Status.create_with( + text: "This is the same public status with an audio file and tags, but it is marked as sensitive. The attached picture has an alt text\n\n#Mastodon #English #Test", + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public, + sensitive: true, + thread: status_with_audio + ).find_or_create_by!(id: 10_000_008) + media_attachment.update(status_id: status_with_sensitive_audio.id) + ProcessHashtagsService.new.call(status_with_sensitive_audio) + + media_attachment = MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/boop.ogg'), + description: 'Mastodon boop' + ).find_or_create_by!(id: 10_000_005) + status_with_cw_audio = Status.create_with( + text: "This is the same public status with an audio file and tags, but it is behind a CW. The attached picture has an alt text\n\n#Mastodon #English #Test", + spoiler_text: 'Mastodon boop', + ordered_media_attachment_ids: [media_attachment.id], + account: showcase_account, + visibility: :public, + sensitive: true, + thread: status_with_sensitive_audio + ).find_or_create_by!(id: 10_000_009) + media_attachment.update(status_id: status_with_cw_audio.id) + ProcessHashtagsService.new.call(status_with_cw_audio) + + media_attachments = [ + MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/600x400.png'), + description: 'Mastodon logo' + ).find_or_create_by!(id: 10_000_006), + MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/attachment.jpg') + ).find_or_create_by!(id: 10_000_007), + MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/avatar-high.gif'), + description: 'Walking cartoon cat' + ).find_or_create_by!(id: 10_000_008), + MediaAttachment.create_with( + account: showcase_account, + file: File.open('spec/fixtures/files/text.png'), + description: 'Text saying “Hello Mastodon”' + ).find_or_create_by!(id: 10_000_009), + ] + status_with_multiple_attachments = Status.create_with( + text: "This is a post with multiple attachments, not all of which have a description\n\n#Mastodon #English #Test", + spoiler_text: 'multiple attachments', + ordered_media_attachment_ids: media_attachments.pluck(:id), + account: showcase_account, + visibility: :public, + sensitive: true, + thread: status_with_cw_audio + ).find_or_create_by!(id: 10_000_010) + media_attachments.each { |attachment| attachment.update!(status_id: status_with_multiple_attachments.id) } + ProcessHashtagsService.new.call(status_with_multiple_attachments) + + remote_account = Account.create_with( + username: 'fake.example', + domain: 'example.org', + uri: 'https://example.org/foo/bar', + url: 'https://example.org/foo/bar', + locked: true + ).find_or_create_by!(id: 10_000_001) + + remote_formatted_post = Status.create_with( + text: <<~HTML, +

This is a post with a variety of HTML in it

+

For instance, this text is bold and this one as well, while this text is stricken through and this one as well.

+
+

This thing, here, is a block quote
with some bold as well

+
    +
  • a list item
  • +
  • + and another with +
      +
    • nested
    • +
    • items!
    • +
    +
  • +
+
+
// And this is some code
+          // with two lines of comments
+          
+

And this is inline code

+

Finally, please observe this Ruby element: 明日 (Ashita)

+ HTML + account: remote_account, + uri: 'https://example.org/foo/bar/baz', + url: 'https://example.org/foo/bar/baz' + ).find_or_create_by!(id: 10_000_011) + Status.create_with(account: showcase_account, reblog: remote_formatted_post).find_or_create_by!(id: 10_000_012) + + unattached_quote_post = Status.create_with( + text: 'This is a quote of a post that does not exist', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_013) + Quote.create_with( + status: unattached_quote_post, + quoted_status: nil + ).find_or_create_by!(id: 10_000_000) + + self_quote = Status.create_with( + text: 'This is a quote of a public self-post', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_014) + Quote.create_with( + status: self_quote, + quoted_status: status_with_media, + state: :accepted + ).find_or_create_by!(id: 10_000_001) + + nested_self_quote = Status.create_with( + text: 'This is a quote of a public self-post which itself is a self-quote', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_015) + Quote.create_with( + status: nested_self_quote, + quoted_status: self_quote, + state: :accepted + ).find_or_create_by!(id: 10_000_002) + + recursive_self_quote = Status.create_with( + text: 'This is a recursive self-quote; no real reason for it to exist, but just to make sure we handle them gracefuly', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_016) + Quote.create_with( + status: recursive_self_quote, + quoted_status: recursive_self_quote, + state: :accepted + ).find_or_create_by!(id: 10_000_003) + + self_private_quote = Status.create_with( + text: 'This is a public post of a private self-post: the quoted post should not be visible to non-followers', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_017) + Quote.create_with( + status: self_private_quote, + quoted_status: private_mentionless, + state: :accepted + ).find_or_create_by!(id: 10_000_004) + + uncwed_quote_cwed = Status.create_with( + text: 'This is a quote without CW of a quoted post that has a CW', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_018) + Quote.create_with( + status: uncwed_quote_cwed, + quoted_status: public_self_reply_with_cw, + state: :accepted + ).find_or_create_by!(id: 10_000_005) + + cwed_quote_cwed = Status.create_with( + text: 'This is a quote with a CW of a quoted post that itself has a CW', + spoiler_text: 'Quote post with a CW', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_019) + Quote.create_with( + status: cwed_quote_cwed, + quoted_status: public_self_reply_with_cw, + state: :accepted + ).find_or_create_by!(id: 10_000_006) + + pending_quote_post = Status.create_with( + text: 'This quote post is pending', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_020) + Quote.create_with( + status: pending_quote_post, + quoted_status: remote_formatted_post, + activity_uri: 'https://foo/bar', + state: :pending + ).find_or_create_by!(id: 10_000_007) + + rejected_quote_post = Status.create_with( + text: 'This quote post is rejected', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_021) + Quote.create_with( + status: rejected_quote_post, + quoted_status: remote_formatted_post, + activity_uri: 'https://foo/foo', + state: :rejected + ).find_or_create_by!(id: 10_000_008) + + revoked_quote_post = Status.create_with( + text: 'This quote post is revoked', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_022) + Quote.create_with( + status: revoked_quote_post, + quoted_status: remote_formatted_post, + activity_uri: 'https://foo/baz', + state: :revoked + ).find_or_create_by!(id: 10_000_009) + + StatusPin.create_with(account: showcase_account, status: public_self_reply_with_cw).find_or_create_by!(id: 10_000_000) + StatusPin.create_with(account: showcase_account, status: private_mentionless).find_or_create_by!(id: 10_000_001) + + showcase_account.update!( + display_name: 'Mastodon test/showcase account', + note: 'Test account to showcase many Mastodon features. Most of its posts are public, but some are private!' + ) + + remote_quote = Status.create_with( + text: <<~HTML, +

This is a self-quote of a remote formatted post

+

RE: https://example.org/foo/bar/baz

+ HTML + account: remote_account, + uri: 'https://example.org/foo/bar/quote', + url: 'https://example.org/foo/bar/quote' + ).find_or_create_by!(id: 10_000_023) + Quote.create_with( + status: remote_quote, + quoted_status: remote_formatted_post, + state: :accepted + ).find_or_create_by!(id: 10_000_010) + Status.create_with( + account: showcase_account, + reblog: remote_quote + ).find_or_create_by!(id: 10_000_024) + + media_attachment = MediaAttachment.create_with( account: showcase_account, file: File.open('spec/fixtures/files/attachment.jpg') - ).find_or_create_by!(id: 10_000_007), - MediaAttachment.create_with( + ).find_or_create_by!(id: 10_000_010) + quote_post_with_media = Status.create_with( + text: "This is a status with a picture and tags which also quotes a status with a picture.\n\n#Mastodon #Test", + ordered_media_attachment_ids: [media_attachment.id], account: showcase_account, - file: File.open('spec/fixtures/files/avatar-high.gif'), - description: 'Walking cartoon cat' - ).find_or_create_by!(id: 10_000_008), - MediaAttachment.create_with( + visibility: :public + ).find_or_create_by!(id: 10_000_025) + media_attachment.update(status_id: quote_post_with_media.id) + ProcessHashtagsService.new.call(quote_post_with_media) + Quote.create_with( + status: quote_post_with_media, + quoted_status: status_with_media, + state: :accepted + ).find_or_create_by!(id: 10_000_011) + + showcase_sidekick_account = Account.create_with(username: 'showcase_sidekick').find_or_create_by!(id: 10_000_002) + sidekick_user = User.create_with( + account_id: showcase_sidekick_account.id, + agreement: true, + password: SecureRandom.hex, + email: ENV.fetch('TEST_DATA_SHOWCASE_SIDEKICK_EMAIL', 'showcase_sidekick@joinmastodon.org'), + confirmed_at: Time.now.utc, + approved: true, + bypass_registration_checks: true + ).find_or_create_by!(id: 10_000_001) + sidekick_user.mark_email_as_confirmed! + sidekick_user.approve! + + sidekick_post = Status.create_with( + text: 'This post only exists to be quoted.', + account: showcase_sidekick_account, + visibility: :public + ).find_or_create_by!(id: 10_000_026) + sidekick_quote_post = Status.create_with( + text: 'This is a quote of a different user.', account: showcase_account, - file: File.open('spec/fixtures/files/text.png'), - description: 'Text saying “Hello Mastodon”' - ).find_or_create_by!(id: 10_000_009), - ] - status_with_multiple_attachments = Status.create_with( - text: "This is a post with multiple attachments, not all of which have a description\n\n#Mastodon #English #Test", - spoiler_text: 'multiple attachments', - ordered_media_attachment_ids: media_attachments.pluck(:id), - account: showcase_account, - visibility: :public, - sensitive: true, - thread: status_with_cw_audio - ).find_or_create_by!(id: 10_000_010) - media_attachments.each { |attachment| attachment.update!(status_id: status_with_multiple_attachments.id) } - ProcessHashtagsService.new.call(status_with_multiple_attachments) - - remote_account = Account.create_with( - username: 'fake.example', - domain: 'example.org', - uri: 'https://example.org/foo/bar', - url: 'https://example.org/foo/bar', - locked: true - ).find_or_create_by!(id: 10_000_001) - - remote_formatted_post = Status.create_with( - text: <<~HTML, -

This is a post with a variety of HTML in it

-

For instance, this text is bold and this one as well, while this text is stricken through and this one as well.

-
-

This thing, here, is a block quote
with some bold as well

-
    -
  • a list item
  • -
  • - and another with -
      -
    • nested
    • -
    • items!
    • -
    -
  • -
-
-
// And this is some code
-        // with two lines of comments
-        
-

And this is inline code

-

Finally, please observe this Ruby element: 明日 (Ashita)

- HTML - account: remote_account, - uri: 'https://example.org/foo/bar/baz', - url: 'https://example.org/foo/bar/baz' - ).find_or_create_by!(id: 10_000_011) - Status.create_with(account: showcase_account, reblog: remote_formatted_post).find_or_create_by!(id: 10_000_012) - - unattached_quote_post = Status.create_with( - text: 'This is a quote of a post that does not exist', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_013) - Quote.create_with( - status: unattached_quote_post, - quoted_status: nil - ).find_or_create_by!(id: 10_000_000) - - self_quote = Status.create_with( - text: 'This is a quote of a public self-post', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_014) - Quote.create_with( - status: self_quote, - quoted_status: status_with_media, - state: :accepted - ).find_or_create_by!(id: 10_000_001) - - nested_self_quote = Status.create_with( - text: 'This is a quote of a public self-post which itself is a self-quote', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_015) - Quote.create_with( - status: nested_self_quote, - quoted_status: self_quote, - state: :accepted - ).find_or_create_by!(id: 10_000_002) - - recursive_self_quote = Status.create_with( - text: 'This is a recursive self-quote; no real reason for it to exist, but just to make sure we handle them gracefuly', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_016) - Quote.create_with( - status: recursive_self_quote, - quoted_status: recursive_self_quote, - state: :accepted - ).find_or_create_by!(id: 10_000_003) - - self_private_quote = Status.create_with( - text: 'This is a public post of a private self-post: the quoted post should not be visible to non-followers', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_017) - Quote.create_with( - status: self_private_quote, - quoted_status: private_mentionless, - state: :accepted - ).find_or_create_by!(id: 10_000_004) - - uncwed_quote_cwed = Status.create_with( - text: 'This is a quote without CW of a quoted post that has a CW', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_018) - Quote.create_with( - status: uncwed_quote_cwed, - quoted_status: public_self_reply_with_cw, - state: :accepted - ).find_or_create_by!(id: 10_000_005) - - cwed_quote_cwed = Status.create_with( - text: 'This is a quote with a CW of a quoted post that itself has a CW', - spoiler_text: 'Quote post with a CW', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_019) - Quote.create_with( - status: cwed_quote_cwed, - quoted_status: public_self_reply_with_cw, - state: :accepted - ).find_or_create_by!(id: 10_000_006) - - pending_quote_post = Status.create_with( - text: 'This quote post is pending', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_020) - Quote.create_with( - status: pending_quote_post, - quoted_status: remote_formatted_post, - activity_uri: 'https://foo/bar', - state: :pending - ).find_or_create_by!(id: 10_000_007) - - rejected_quote_post = Status.create_with( - text: 'This quote post is rejected', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_021) - Quote.create_with( - status: rejected_quote_post, - quoted_status: remote_formatted_post, - activity_uri: 'https://foo/foo', - state: :rejected - ).find_or_create_by!(id: 10_000_008) - - revoked_quote_post = Status.create_with( - text: 'This quote post is revoked', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_022) - Quote.create_with( - status: revoked_quote_post, - quoted_status: remote_formatted_post, - activity_uri: 'https://foo/baz', - state: :revoked - ).find_or_create_by!(id: 10_000_009) - - StatusPin.create_with(account: showcase_account, status: public_self_reply_with_cw).find_or_create_by!(id: 10_000_000) - StatusPin.create_with(account: showcase_account, status: private_mentionless).find_or_create_by!(id: 10_000_001) - - showcase_account.update!( - display_name: 'Mastodon test/showcase account', - note: 'Test account to showcase many Mastodon features. Most of its posts are public, but some are private!' - ) - - remote_quote = Status.create_with( - text: <<~HTML, -

This is a self-quote of a remote formatted post

-

RE: https://example.org/foo/bar/baz

- HTML - account: remote_account, - uri: 'https://example.org/foo/bar/quote', - url: 'https://example.org/foo/bar/quote' - ).find_or_create_by!(id: 10_000_023) - Quote.create_with( - status: remote_quote, - quoted_status: remote_formatted_post, - state: :accepted - ).find_or_create_by!(id: 10_000_010) - Status.create_with( - account: showcase_account, - reblog: remote_quote - ).find_or_create_by!(id: 10_000_024) - - media_attachment = MediaAttachment.create_with( - account: showcase_account, - file: File.open('spec/fixtures/files/attachment.jpg') - ).find_or_create_by!(id: 10_000_010) - quote_post_with_media = Status.create_with( - text: "This is a status with a picture and tags which also quotes a status with a picture.\n\n#Mastodon #Test", - ordered_media_attachment_ids: [media_attachment.id], - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_025) - media_attachment.update(status_id: quote_post_with_media.id) - ProcessHashtagsService.new.call(quote_post_with_media) - Quote.create_with( - status: quote_post_with_media, - quoted_status: status_with_media, - state: :accepted - ).find_or_create_by!(id: 10_000_011) - - showcase_sidekick_account = Account.create_with(username: 'showcase_sidekick').find_or_create_by!(id: 10_000_002) - sidekick_user = User.create_with( - account_id: showcase_sidekick_account.id, - agreement: true, - password: SecureRandom.hex, - email: ENV.fetch('TEST_DATA_SHOWCASE_SIDEKICK_EMAIL', 'showcase_sidekick@joinmastodon.org'), - confirmed_at: Time.now.utc, - approved: true, - bypass_registration_checks: true - ).find_or_create_by!(id: 10_000_001) - sidekick_user.mark_email_as_confirmed! - sidekick_user.approve! - - sidekick_post = Status.create_with( - text: 'This post only exists to be quoted.', - account: showcase_sidekick_account, - visibility: :public - ).find_or_create_by!(id: 10_000_026) - sidekick_quote_post = Status.create_with( - text: 'This is a quote of a different user.', - account: showcase_account, - visibility: :public - ).find_or_create_by!(id: 10_000_027) - Quote.create_with( - status: sidekick_quote_post, - quoted_status: sidekick_post, - activity_uri: 'https://foo/cross-account-quote', - state: :accepted - ).find_or_create_by!(id: 10_000_012) + visibility: :public + ).find_or_create_by!(id: 10_000_027) + Quote.create_with( + status: sidekick_quote_post, + quoted_status: sidekick_post, + activity_uri: 'https://foo/cross-account-quote', + state: :accepted + ).find_or_create_by!(id: 10_000_012) + end end end From 8452ec6f3bb463f9a93503d29422de9f032c8ff2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:35:22 +0000 Subject: [PATCH 187/660] Update dependency rubocop to v1.79.1 (#35613) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9123197e5a3..08f5108915e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -765,7 +765,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.4) - rubocop (1.79.0) + rubocop (1.79.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -775,7 +775,6 @@ GEM regexp_parser (>= 2.9.3, < 3.0) rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) - tsort (>= 0.2.0) unicode-display_width (>= 2.4.0, < 4.0) rubocop-ast (1.46.0) parser (>= 3.3.7.2) @@ -881,7 +880,6 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) - tsort (0.2.0) tty-color (0.6.0) tty-cursor (0.7.1) tty-prompt (0.23.1) From b81670776fd757341f5ec9060b6341ec2b23af63 Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 31 Jul 2025 15:33:34 +0200 Subject: [PATCH 188/660] Modern Emoji: Use local storage to opt-in (#35605) --- app/javascript/mastodon/utils/environment.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index 457da5c7367..c5fe46bc931 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -19,5 +19,12 @@ export function isFeatureEnabled(feature: Features) { } export function isModernEmojiEnabled() { - return isFeatureEnabled('modern_emojis') && isDevelopment(); + try { + return ( + isFeatureEnabled('modern_emojis') && + localStorage.getItem('experiments')?.split(',').includes('modern_emojis') + ); + } catch { + return false; + } } From 19db4cb7c133c375a888833f5d702d39411b49c3 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 31 Jul 2025 15:39:26 +0200 Subject: [PATCH 189/660] Add example of quote post with a preview card to development sample data (#35616) --- lib/tasks/dev.rake | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index 921ffba5912..2aad68d53d8 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -419,6 +419,23 @@ namespace :dev do activity_uri: 'https://foo/cross-account-quote', state: :accepted ).find_or_create_by!(id: 10_000_012) + + quoted = Status.create_with( + text: 'This should have a preview card: https://joinmastodon.org', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_028) + LinkCrawlWorker.perform_async(10_000_028) + quoting = Status.create_with( + text: 'This should quote a post with a preview card', + account: showcase_account, + visibility: :public + ).find_or_create_by!(id: 10_000_029) + Quote.create_with( + status: quoting, + quoted_status: quoted, + state: :accepted + ).find_or_create_by!(id: 10_000_013) end end end From 0e249cba4b4d010c56bb5a1ee7e6d9a9d5929e59 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 31 Jul 2025 16:23:36 +0200 Subject: [PATCH 190/660] Revoke quote posts when those get deleted (#35614) --- app/services/activitypub/process_status_update_service.rb | 2 ++ app/services/remove_status_service.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 9c96c51851f..064ccf0f337 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -298,6 +298,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService if @status.quote.present? # If the quoted post has changed, discard the old object and create a new one if @status.quote.quoted_status.present? && ActivityPub::TagManager.instance.uri_for(@status.quote.quoted_status) != quote_uri + # Revoke the quote while we get a chance… maybe this should be a `before_destroy` hook? + RevokeQuoteService.new.call(@status.quote) if @status.quote.quoted_account&.local? && @status.quote.accepted? @status.quote.destroy quote = Quote.create(status: @status, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?) @quote_changed = true diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 2adb8c1edbe..f61cb632b23 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -47,6 +47,9 @@ class RemoveStatusService < BaseService remove_media end + # Revoke the quote while we get a chance… maybe this should be a `before_destroy` hook? + RevokeQuoteService.new.call(@status.quote) if @status.quote&.quoted_account&.local? && @status.quote&.accepted? + @status.destroy! if permanently? end end From 6bca52453af3a4e76063e28e797f5e5b45ba74aa Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 31 Jul 2025 19:30:14 +0200 Subject: [PATCH 191/660] Emoji Rendering Efficiency (#35568) --- .../mastodon/features/emoji/constants.ts | 11 + .../mastodon/features/emoji/database.test.ts | 139 ++++++++++++ .../mastodon/features/emoji/database.ts | 166 ++++++++++---- .../mastodon/features/emoji/emoji_html.tsx | 84 ++----- .../mastodon/features/emoji/emoji_text.tsx | 45 ---- .../mastodon/features/emoji/hooks.ts | 65 +++++- .../mastodon/features/emoji/index.ts | 32 ++- .../mastodon/features/emoji/loader.ts | 10 +- .../mastodon/features/emoji/render.test.ts | 214 +++++++++++++----- .../mastodon/features/emoji/render.ts | 198 ++++++++++------ .../mastodon/features/emoji/types.ts | 18 +- .../mastodon/features/emoji/utils.test.ts | 38 +++- .../mastodon/features/emoji/utils.ts | 32 ++- .../mastodon/features/emoji/worker.ts | 15 +- app/javascript/mastodon/main.tsx | 4 +- .../mastodon/utils/__tests__/cache.test.ts | 78 +++++++ app/javascript/mastodon/utils/cache.ts | 60 +++++ .../{performance.js => utils/performance.ts} | 6 +- app/javascript/testing/factories.ts | 27 +++ eslint.config.mjs | 6 + package.json | 4 +- vitest.config.mts | 1 + yarn.lock | 34 ++- 23 files changed, 954 insertions(+), 333 deletions(-) create mode 100644 app/javascript/mastodon/features/emoji/database.test.ts delete mode 100644 app/javascript/mastodon/features/emoji/emoji_text.tsx create mode 100644 app/javascript/mastodon/utils/__tests__/cache.test.ts create mode 100644 app/javascript/mastodon/utils/cache.ts rename app/javascript/mastodon/{performance.js => utils/performance.ts} (70%) diff --git a/app/javascript/mastodon/features/emoji/constants.ts b/app/javascript/mastodon/features/emoji/constants.ts index 09022371b22..a5ec9e6e2b4 100644 --- a/app/javascript/mastodon/features/emoji/constants.ts +++ b/app/javascript/mastodon/features/emoji/constants.ts @@ -15,6 +15,17 @@ export const SKIN_TONE_CODES = [ 0x1f3ff, // Dark skin tone ] as const; +// TODO: Test and create fallback for browsers that do not handle the /v flag. +export const UNICODE_EMOJI_REGEX = /\p{RGI_Emoji}/v; +// See: https://www.unicode.org/reports/tr51/#valid-emoji-tag-sequences +export const UNICODE_FLAG_EMOJI_REGEX = + /\p{RGI_Emoji_Flag_Sequence}|\p{RGI_Emoji_Tag_Sequence}/v; +export const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; +export const ANY_EMOJI_REGEX = new RegExp( + `(${UNICODE_EMOJI_REGEX.source}|${CUSTOM_EMOJI_REGEX.source})`, + 'gv', +); + // Emoji rendering modes. A mode is what we are using to render emojis, a style is what the user has selected. export const EMOJI_MODE_NATIVE = 'native'; export const EMOJI_MODE_NATIVE_WITH_FLAGS = 'native-flags'; diff --git a/app/javascript/mastodon/features/emoji/database.test.ts b/app/javascript/mastodon/features/emoji/database.test.ts new file mode 100644 index 00000000000..0689fd7c542 --- /dev/null +++ b/app/javascript/mastodon/features/emoji/database.test.ts @@ -0,0 +1,139 @@ +import { IDBFactory } from 'fake-indexeddb'; + +import { unicodeEmojiFactory } from '@/testing/factories'; + +import { + putEmojiData, + loadEmojiByHexcode, + searchEmojisByHexcodes, + searchEmojisByTag, + testClear, + testGet, +} from './database'; + +describe('emoji database', () => { + afterEach(() => { + testClear(); + indexedDB = new IDBFactory(); + }); + describe('putEmojiData', () => { + test('adds to loaded locales', async () => { + const { loadedLocales } = await testGet(); + expect(loadedLocales).toHaveLength(0); + await putEmojiData([], 'en'); + expect(loadedLocales).toContain('en'); + }); + + test('loads emoji into indexedDB', async () => { + await putEmojiData([unicodeEmojiFactory()], 'en'); + const { db } = await testGet(); + await expect(db.get('en', 'test')).resolves.toEqual( + unicodeEmojiFactory(), + ); + }); + }); + + describe('loadEmojiByHexcode', () => { + test('throws if the locale is not loaded', async () => { + await expect(loadEmojiByHexcode('en', 'test')).rejects.toThrowError( + 'Locale en', + ); + }); + + test('retrieves the emoji', async () => { + await putEmojiData([unicodeEmojiFactory()], 'en'); + await expect(loadEmojiByHexcode('test', 'en')).resolves.toEqual( + unicodeEmojiFactory(), + ); + }); + + test('returns undefined if not found', async () => { + await putEmojiData([], 'en'); + await expect(loadEmojiByHexcode('test', 'en')).resolves.toBeUndefined(); + }); + }); + + describe('searchEmojisByHexcodes', () => { + const data = [ + unicodeEmojiFactory({ hexcode: 'not a number' }), + unicodeEmojiFactory({ hexcode: '1' }), + unicodeEmojiFactory({ hexcode: '2' }), + unicodeEmojiFactory({ hexcode: '3' }), + unicodeEmojiFactory({ hexcode: 'another not a number' }), + ]; + beforeEach(async () => { + await putEmojiData(data, 'en'); + }); + test('finds emoji in consecutive range', async () => { + const actual = await searchEmojisByHexcodes(['1', '2', '3'], 'en'); + expect(actual).toHaveLength(3); + }); + + test('finds emoji in split range', async () => { + const actual = await searchEmojisByHexcodes(['1', '3'], 'en'); + expect(actual).toHaveLength(2); + expect(actual).toContainEqual(data.at(1)); + expect(actual).toContainEqual(data.at(3)); + }); + + test('finds emoji with non-numeric range', async () => { + const actual = await searchEmojisByHexcodes( + ['3', 'not a number', '1'], + 'en', + ); + expect(actual).toHaveLength(3); + expect(actual).toContainEqual(data.at(0)); + expect(actual).toContainEqual(data.at(1)); + expect(actual).toContainEqual(data.at(3)); + }); + + test('not found emoji are not returned', async () => { + const actual = await searchEmojisByHexcodes(['not found'], 'en'); + expect(actual).toHaveLength(0); + }); + + test('only found emojis are returned', async () => { + const actual = await searchEmojisByHexcodes( + ['another not a number', 'not found'], + 'en', + ); + expect(actual).toHaveLength(1); + expect(actual).toContainEqual(data.at(4)); + }); + }); + + describe('searchEmojisByTag', () => { + const data = [ + unicodeEmojiFactory({ hexcode: 'test1', tags: ['test 1'] }), + unicodeEmojiFactory({ + hexcode: 'test2', + tags: ['test 2', 'something else'], + }), + unicodeEmojiFactory({ hexcode: 'test3', tags: ['completely different'] }), + ]; + beforeEach(async () => { + await putEmojiData(data, 'en'); + }); + test('finds emojis with tag', async () => { + const actual = await searchEmojisByTag('test 1', 'en'); + expect(actual).toHaveLength(1); + expect(actual).toContainEqual(data.at(0)); + }); + + test('finds emojis starting with tag', async () => { + const actual = await searchEmojisByTag('test', 'en'); + expect(actual).toHaveLength(2); + expect(actual).not.toContainEqual(data.at(2)); + }); + + test('does not find emojis ending with tag', async () => { + const actual = await searchEmojisByTag('else', 'en'); + expect(actual).toHaveLength(0); + }); + + test('finds nothing with invalid tag', async () => { + const actual = await searchEmojisByTag('not found', 'en'); + expect(actual).toHaveLength(0); + }); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/database.ts b/app/javascript/mastodon/features/emoji/database.ts index 0b8ddd34fbe..0e8ada1d0e0 100644 --- a/app/javascript/mastodon/features/emoji/database.ts +++ b/app/javascript/mastodon/features/emoji/database.ts @@ -9,6 +9,7 @@ import type { UnicodeEmojiData, LocaleOrCustom, } from './types'; +import { emojiLogger } from './utils'; interface EmojiDB extends LocaleTables, DBSchema { custom: { @@ -36,40 +37,63 @@ interface LocaleTable { } type LocaleTables = Record; +type Database = IDBPDatabase; + const SCHEMA_VERSION = 1; -let db: IDBPDatabase | null = null; +const loadedLocales = new Set(); -async function loadDB() { - if (db) { - return db; - } - db = await openDB('mastodon-emoji', SCHEMA_VERSION, { - upgrade(database) { - const customTable = database.createObjectStore('custom', { - keyPath: 'shortcode', - autoIncrement: false, - }); - customTable.createIndex('category', 'category'); +const log = emojiLogger('database'); - database.createObjectStore('etags'); +// Loads the database in a way that ensures it's only loaded once. +const loadDB = (() => { + let dbPromise: Promise | null = null; - for (const locale of SUPPORTED_LOCALES) { - const localeTable = database.createObjectStore(locale, { - keyPath: 'hexcode', + // Actually load the DB. + async function initDB() { + const db = await openDB('mastodon-emoji', SCHEMA_VERSION, { + upgrade(database) { + const customTable = database.createObjectStore('custom', { + keyPath: 'shortcode', autoIncrement: false, }); - localeTable.createIndex('group', 'group'); - localeTable.createIndex('label', 'label'); - localeTable.createIndex('order', 'order'); - localeTable.createIndex('tags', 'tags', { multiEntry: true }); - } - }, - }); - return db; -} + customTable.createIndex('category', 'category'); + + database.createObjectStore('etags'); + + for (const locale of SUPPORTED_LOCALES) { + const localeTable = database.createObjectStore(locale, { + keyPath: 'hexcode', + autoIncrement: false, + }); + localeTable.createIndex('group', 'group'); + localeTable.createIndex('label', 'label'); + localeTable.createIndex('order', 'order'); + localeTable.createIndex('tags', 'tags', { multiEntry: true }); + } + }, + }); + await syncLocales(db); + return db; + } + + // Loads the database, or returns the existing promise if it hasn't resolved yet. + const loadPromise = async (): Promise => { + if (dbPromise) { + return dbPromise; + } + dbPromise = initDB(); + return dbPromise; + }; + // Special way to reset the database, used for unit testing. + loadPromise.reset = () => { + dbPromise = null; + }; + return loadPromise; +})(); export async function putEmojiData(emojis: UnicodeEmojiData[], locale: Locale) { + loadedLocales.add(locale); const db = await loadDB(); const trx = db.transaction(locale, 'readwrite'); await Promise.all(emojis.map((emoji) => trx.store.put(emoji))); @@ -86,15 +110,15 @@ export async function putCustomEmojiData(emojis: CustomEmojiData[]) { export async function putLatestEtag(etag: string, localeString: string) { const locale = toSupportedLocaleOrCustom(localeString); const db = await loadDB(); - return db.put('etags', etag, locale); + await db.put('etags', etag, locale); } -export async function searchEmojiByHexcode( +export async function loadEmojiByHexcode( hexcode: string, localeString: string, ) { - const locale = toSupportedLocale(localeString); const db = await loadDB(); + const locale = toLoadedLocale(localeString); return db.get(locale, hexcode); } @@ -102,45 +126,39 @@ export async function searchEmojisByHexcodes( hexcodes: string[], localeString: string, ) { - const locale = toSupportedLocale(localeString); const db = await loadDB(); - return db.getAll( + const locale = toLoadedLocale(localeString); + const sortedCodes = hexcodes.toSorted(); + const results = await db.getAll( locale, - IDBKeyRange.bound(hexcodes[0], hexcodes[hexcodes.length - 1]), + IDBKeyRange.bound(sortedCodes.at(0), sortedCodes.at(-1)), ); + return results.filter((emoji) => hexcodes.includes(emoji.hexcode)); } -export async function searchEmojiByTag(tag: string, localeString: string) { - const locale = toSupportedLocale(localeString); - const range = IDBKeyRange.only(tag.toLowerCase()); +export async function searchEmojisByTag(tag: string, localeString: string) { const db = await loadDB(); + const locale = toLoadedLocale(localeString); + const range = IDBKeyRange.bound( + tag.toLowerCase(), + `${tag.toLowerCase()}\uffff`, + ); return db.getAllFromIndex(locale, 'tags', range); } -export async function searchCustomEmojiByShortcode(shortcode: string) { +export async function loadCustomEmojiByShortcode(shortcode: string) { const db = await loadDB(); return db.get('custom', shortcode); } export async function searchCustomEmojisByShortcodes(shortcodes: string[]) { const db = await loadDB(); - return db.getAll( + const sortedCodes = shortcodes.toSorted(); + const results = await db.getAll( 'custom', - IDBKeyRange.bound(shortcodes[0], shortcodes[shortcodes.length - 1]), + IDBKeyRange.bound(sortedCodes.at(0), sortedCodes.at(-1)), ); -} - -export async function findMissingLocales(localeStrings: string[]) { - const locales = new Set(localeStrings.map(toSupportedLocale)); - const missingLocales: Locale[] = []; - const db = await loadDB(); - for (const locale of locales) { - const rowCount = await db.count(locale); - if (!rowCount) { - missingLocales.push(locale); - } - } - return missingLocales; + return results.filter((emoji) => shortcodes.includes(emoji.shortcode)); } export async function loadLatestEtag(localeString: string) { @@ -153,3 +171,51 @@ export async function loadLatestEtag(localeString: string) { const etag = await db.get('etags', locale); return etag ?? null; } + +// Private functions + +async function syncLocales(db: Database) { + const locales = await Promise.all( + SUPPORTED_LOCALES.map( + async (locale) => + [locale, await hasLocale(locale, db)] satisfies [Locale, boolean], + ), + ); + for (const [locale, loaded] of locales) { + if (loaded) { + loadedLocales.add(locale); + } else { + loadedLocales.delete(locale); + } + } + log('Loaded %d locales: %o', loadedLocales.size, loadedLocales); +} + +function toLoadedLocale(localeString: string) { + const locale = toSupportedLocale(localeString); + if (localeString !== locale) { + log(`Locale ${locale} is different from provided ${localeString}`); + } + if (!loadedLocales.has(locale)) { + throw new Error(`Locale ${locale} is not loaded in emoji database`); + } + return locale; +} + +async function hasLocale(locale: Locale, db: Database): Promise { + if (loadedLocales.has(locale)) { + return true; + } + const rowCount = await db.count(locale); + return !!rowCount; +} + +// Testing helpers +export async function testGet() { + const db = await loadDB(); + return { db, loadedLocales }; +} +export function testClear() { + loadedLocales.clear(); + loadDB.reset(); +} diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx index 85628e6723d..fdda62a3e61 100644 --- a/app/javascript/mastodon/features/emoji/emoji_html.tsx +++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx @@ -1,81 +1,31 @@ -import type { HTMLAttributes } from 'react'; -import { useEffect, useMemo, useState } from 'react'; +import type { ComponentPropsWithoutRef, ElementType } from 'react'; -import type { List as ImmutableList } from 'immutable'; -import { isList } from 'immutable'; +import { useEmojify } from './hooks'; +import type { CustomEmojiMapArg } from './types'; -import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; -import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; -import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; - -import { useEmojiAppState } from './hooks'; -import { emojifyElement } from './render'; -import type { ExtraCustomEmojiMap } from './types'; - -type EmojiHTMLProps = Omit< - HTMLAttributes, +type EmojiHTMLProps = Omit< + ComponentPropsWithoutRef, 'dangerouslySetInnerHTML' > & { htmlString: string; - extraEmojis?: ExtraCustomEmojiMap | ImmutableList; + extraEmojis?: CustomEmojiMapArg; + as?: Element; }; -export const EmojiHTML: React.FC = ({ - htmlString, +export const EmojiHTML = ({ extraEmojis, + htmlString, + as: asElement, // Rename for syntax highlighting ...props -}) => { - if (isModernEmojiEnabled()) { - return ( - - ); - } - return
; -}; +}: EmojiHTMLProps) => { + const Wrapper = asElement ?? 'div'; + const emojifiedHtml = useEmojify(htmlString, extraEmojis); -const ModernEmojiHTML: React.FC = ({ - extraEmojis: rawEmojis, - htmlString: text, - ...props -}) => { - const appState = useEmojiAppState(); - const [innerHTML, setInnerHTML] = useState(''); - - const extraEmojis: ExtraCustomEmojiMap = useMemo(() => { - if (!rawEmojis) { - return {}; - } - if (isList(rawEmojis)) { - return ( - rawEmojis.toJS() as ApiCustomEmojiJSON[] - ).reduce( - (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), - {}, - ); - } - return rawEmojis; - }, [rawEmojis]); - - useEffect(() => { - if (!text) { - return; - } - const cb = async () => { - const div = document.createElement('div'); - div.innerHTML = text; - const ele = await emojifyElement(div, appState, extraEmojis); - setInnerHTML(ele.innerHTML); - }; - void cb(); - }, [text, appState, extraEmojis]); - - if (!innerHTML) { + if (emojifiedHtml === null) { return null; } - return
; + return ( + + ); }; diff --git a/app/javascript/mastodon/features/emoji/emoji_text.tsx b/app/javascript/mastodon/features/emoji/emoji_text.tsx deleted file mode 100644 index 253371391a4..00000000000 --- a/app/javascript/mastodon/features/emoji/emoji_text.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { useEmojiAppState } from './hooks'; -import { emojifyText } from './render'; - -interface EmojiTextProps { - text: string; -} - -export const EmojiText: React.FC = ({ text }) => { - const appState = useEmojiAppState(); - const [rendered, setRendered] = useState<(string | HTMLImageElement)[]>([]); - - useEffect(() => { - const cb = async () => { - const rendered = await emojifyText(text, appState); - setRendered(rendered ?? []); - }; - void cb(); - }, [text, appState]); - - if (rendered.length === 0) { - return null; - } - - return ( - <> - {rendered.map((fragment, index) => { - if (typeof fragment === 'string') { - return {fragment}; - } - return ( - {fragment.alt} - ); - })} - - ); -}; diff --git a/app/javascript/mastodon/features/emoji/hooks.ts b/app/javascript/mastodon/features/emoji/hooks.ts index fd38129a19b..47af37b3731 100644 --- a/app/javascript/mastodon/features/emoji/hooks.ts +++ b/app/javascript/mastodon/features/emoji/hooks.ts @@ -1,8 +1,64 @@ +import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; + +import { isList } from 'immutable'; + +import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; import { useAppSelector } from '@/mastodon/store'; +import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; import { toSupportedLocale } from './locale'; import { determineEmojiMode } from './mode'; -import type { EmojiAppState } from './types'; +import { emojifyElement } from './render'; +import type { + CustomEmojiMapArg, + EmojiAppState, + ExtraCustomEmojiMap, +} from './types'; +import { stringHasAnyEmoji } from './utils'; + +export function useEmojify(text: string, extraEmojis?: CustomEmojiMapArg) { + const [emojifiedText, setEmojifiedText] = useState(null); + + const appState = useEmojiAppState(); + const extra: ExtraCustomEmojiMap = useMemo(() => { + if (!extraEmojis) { + return {}; + } + if (isList(extraEmojis)) { + return ( + extraEmojis.toJS() as ApiCustomEmojiJSON[] + ).reduce( + (acc, emoji) => ({ ...acc, [emoji.shortcode]: emoji }), + {}, + ); + } + return extraEmojis; + }, [extraEmojis]); + + const emojify = useCallback( + async (input: string) => { + const wrapper = document.createElement('div'); + wrapper.innerHTML = input; + const result = await emojifyElement(wrapper, appState, extra); + if (result) { + setEmojifiedText(result.innerHTML); + } else { + setEmojifiedText(input); + } + }, + [appState, extra], + ); + useLayoutEffect(() => { + if (isModernEmojiEnabled() && !!text.trim() && stringHasAnyEmoji(text)) { + void emojify(text); + } else { + // If no emoji or we don't want to render, fall back. + setEmojifiedText(text); + } + }, [emojify, text]); + + return emojifiedText; +} export function useEmojiAppState(): EmojiAppState { const locale = useAppSelector((state) => @@ -12,5 +68,10 @@ export function useEmojiAppState(): EmojiAppState { determineEmojiMode(state.meta.get('emoji_style') as string), ); - return { currentLocale: locale, locales: [locale], mode }; + return { + currentLocale: locale, + locales: [locale], + mode, + darkTheme: document.body.classList.contains('theme-default'), + }; } diff --git a/app/javascript/mastodon/features/emoji/index.ts b/app/javascript/mastodon/features/emoji/index.ts index 541cea9aa99..99c16fe361c 100644 --- a/app/javascript/mastodon/features/emoji/index.ts +++ b/app/javascript/mastodon/features/emoji/index.ts @@ -2,12 +2,16 @@ import initialState from '@/mastodon/initial_state'; import { loadWorker } from '@/mastodon/utils/workers'; import { toSupportedLocale } from './locale'; +import { emojiLogger } from './utils'; const userLocale = toSupportedLocale(initialState?.meta.locale ?? 'en'); let worker: Worker | null = null; -export async function initializeEmoji() { +const log = emojiLogger('index'); + +export function initializeEmoji() { + log('initializing emojis'); if (!worker && 'Worker' in window) { try { worker = loadWorker(new URL('./worker', import.meta.url), { @@ -21,9 +25,16 @@ export async function initializeEmoji() { if (worker) { // Assign worker to const to make TS happy inside the event listener. const thisWorker = worker; + const timeoutId = setTimeout(() => { + log('worker is not ready after timeout'); + worker = null; + void fallbackLoad(); + }, 500); thisWorker.addEventListener('message', (event: MessageEvent) => { const { data: message } = event; if (message === 'ready') { + log('worker ready, loading data'); + clearTimeout(timeoutId); thisWorker.postMessage('custom'); void loadEmojiLocale(userLocale); // Load English locale as well, because people are still used to @@ -31,15 +42,22 @@ export async function initializeEmoji() { if (userLocale !== 'en') { void loadEmojiLocale('en'); } + } else { + log('got worker message: %s', message); } }); } else { - const { importCustomEmojiData } = await import('./loader'); - await importCustomEmojiData(); - await loadEmojiLocale(userLocale); - if (userLocale !== 'en') { - await loadEmojiLocale('en'); - } + void fallbackLoad(); + } +} + +async function fallbackLoad() { + log('falling back to main thread for loading'); + const { importCustomEmojiData } = await import('./loader'); + await importCustomEmojiData(); + await loadEmojiLocale(userLocale); + if (userLocale !== 'en') { + await loadEmojiLocale('en'); } } diff --git a/app/javascript/mastodon/features/emoji/loader.ts b/app/javascript/mastodon/features/emoji/loader.ts index 454b8383f07..72f57b6f6c0 100644 --- a/app/javascript/mastodon/features/emoji/loader.ts +++ b/app/javascript/mastodon/features/emoji/loader.ts @@ -2,7 +2,6 @@ import { flattenEmojiData } from 'emojibase'; import type { CompactEmoji, FlatCompactEmoji } from 'emojibase'; import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; -import { isDevelopment } from '@/mastodon/utils/environment'; import { putEmojiData, @@ -12,6 +11,9 @@ import { } from './database'; import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale'; import type { LocaleOrCustom } from './types'; +import { emojiLogger } from './utils'; + +const log = emojiLogger('loader'); export async function importEmojiData(localeString: string) { const locale = toSupportedLocale(localeString); @@ -20,6 +22,7 @@ export async function importEmojiData(localeString: string) { return; } const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis); + log('loaded %d for %s locale', flattenedEmojis.length, locale); await putEmojiData(flattenedEmojis, locale); } @@ -28,6 +31,7 @@ export async function importCustomEmojiData() { if (!emojis) { return; } + log('loaded %d custom emojis', emojis.length); await putCustomEmojiData(emojis); } @@ -41,7 +45,9 @@ async function fetchAndCheckEtag( if (locale === 'custom') { url.pathname = '/api/v1/custom_emojis'; } else { - url.pathname = `/packs${isDevelopment() ? '-dev' : ''}/emoji/${locale}.json`; + // This doesn't use isDevelopment() as that module loads initial state + // which breaks workers, as they cannot access the DOM. + url.pathname = `/packs${import.meta.env.DEV ? '-dev' : ''}/emoji/${locale}.json`; } const oldEtag = await loadLatestEtag(locale); diff --git a/app/javascript/mastodon/features/emoji/render.test.ts b/app/javascript/mastodon/features/emoji/render.test.ts index 23f85c36b3e..e9609e15dc5 100644 --- a/app/javascript/mastodon/features/emoji/render.test.ts +++ b/app/javascript/mastodon/features/emoji/render.test.ts @@ -1,94 +1,184 @@ +import { customEmojiFactory, unicodeEmojiFactory } from '@/testing/factories'; + import { EMOJI_MODE_NATIVE, EMOJI_MODE_NATIVE_WITH_FLAGS, EMOJI_MODE_TWEMOJI, } from './constants'; -import { emojifyElement, tokenizeText } from './render'; -import type { CustomEmojiData, UnicodeEmojiData } from './types'; +import * as db from './database'; +import { + emojifyElement, + emojifyText, + testCacheClear, + tokenizeText, +} from './render'; +import type { EmojiAppState, ExtraCustomEmojiMap } from './types'; -vitest.mock('./database', () => ({ - searchCustomEmojisByShortcodes: vitest.fn( - () => - [ - { - shortcode: 'custom', - static_url: 'emoji/static', - url: 'emoji/custom', - category: 'test', - visible_in_picker: true, - }, - ] satisfies CustomEmojiData[], - ), - searchEmojisByHexcodes: vitest.fn( - () => - [ - { +function mockDatabase() { + return { + searchCustomEmojisByShortcodes: vi + .spyOn(db, 'searchCustomEmojisByShortcodes') + .mockResolvedValue([customEmojiFactory()]), + searchEmojisByHexcodes: vi + .spyOn(db, 'searchEmojisByHexcodes') + .mockResolvedValue([ + unicodeEmojiFactory({ hexcode: '1F60A', - group: 0, label: 'smiling face with smiling eyes', - order: 0, - tags: ['smile', 'happy'], unicode: '😊', - }, - { + }), + unicodeEmojiFactory({ hexcode: '1F1EA-1F1FA', - group: 0, label: 'flag-eu', - order: 0, - tags: ['flag', 'european union'], unicode: '🇪🇺', - }, - ] satisfies UnicodeEmojiData[], - ), - findMissingLocales: vitest.fn(() => []), -})); + }), + ]), + }; +} + +const expectedSmileImage = + '😊'; +const expectedFlagImage = + '🇪🇺'; +const expectedCustomEmojiImage = + ':custom:'; +const expectedRemoteCustomEmojiImage = + ':remote:'; + +const mockExtraCustom: ExtraCustomEmojiMap = { + remote: { + shortcode: 'remote', + static_url: 'remote.social/static', + url: 'remote.social/custom', + }, +}; + +function testAppState(state: Partial = {}) { + return { + locales: ['en'], + mode: EMOJI_MODE_TWEMOJI, + currentLocale: 'en', + darkTheme: false, + ...state, + } satisfies EmojiAppState; +} describe('emojifyElement', () => { - const testElement = document.createElement('div'); - testElement.innerHTML = '

Hello 😊🇪🇺!

:custom:

'; - - const expectedSmileImage = - '😊'; - const expectedFlagImage = - '🇪🇺'; - const expectedCustomEmojiImage = - ':custom:'; - - function cloneTestElement() { - return testElement.cloneNode(true) as HTMLElement; + function testElement(text = '

Hello 😊🇪🇺!

:custom:

') { + const testElement = document.createElement('div'); + testElement.innerHTML = text; + return testElement; } + afterEach(() => { + testCacheClear(); + vi.restoreAllMocks(); + }); + + test('caches element rendering results', async () => { + const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = + mockDatabase(); + await emojifyElement(testElement(), testAppState()); + await emojifyElement(testElement(), testAppState()); + await emojifyElement(testElement(), testAppState()); + expect(searchEmojisByHexcodes).toHaveBeenCalledExactlyOnceWith( + ['1F1EA-1F1FA', '1F60A'], + 'en', + ); + expect(searchCustomEmojisByShortcodes).toHaveBeenCalledExactlyOnceWith([ + 'custom', + ]); + }); + test('emojifies custom emoji in native mode', async () => { - const emojifiedElement = await emojifyElement(cloneTestElement(), { - locales: ['en'], - mode: EMOJI_MODE_NATIVE, - currentLocale: 'en', - }); - expect(emojifiedElement.innerHTML).toBe( + const { searchEmojisByHexcodes } = mockDatabase(); + const actual = await emojifyElement( + testElement(), + testAppState({ mode: EMOJI_MODE_NATIVE }), + ); + assert(actual); + expect(actual.innerHTML).toBe( `

Hello 😊🇪🇺!

${expectedCustomEmojiImage}

`, ); + expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); }); test('emojifies flag emoji in native-with-flags mode', async () => { - const emojifiedElement = await emojifyElement(cloneTestElement(), { - locales: ['en'], - mode: EMOJI_MODE_NATIVE_WITH_FLAGS, - currentLocale: 'en', - }); - expect(emojifiedElement.innerHTML).toBe( + const { searchEmojisByHexcodes } = mockDatabase(); + const actual = await emojifyElement( + testElement(), + testAppState({ mode: EMOJI_MODE_NATIVE_WITH_FLAGS }), + ); + assert(actual); + expect(actual.innerHTML).toBe( `

Hello 😊${expectedFlagImage}!

${expectedCustomEmojiImage}

`, ); + expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); }); test('emojifies everything in twemoji mode', async () => { - const emojifiedElement = await emojifyElement(cloneTestElement(), { - locales: ['en'], - mode: EMOJI_MODE_TWEMOJI, - currentLocale: 'en', - }); - expect(emojifiedElement.innerHTML).toBe( + const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = + mockDatabase(); + const actual = await emojifyElement(testElement(), testAppState()); + assert(actual); + expect(actual.innerHTML).toBe( `

Hello ${expectedSmileImage}${expectedFlagImage}!

${expectedCustomEmojiImage}

`, ); + expect(searchEmojisByHexcodes).toHaveBeenCalledOnce(); + expect(searchCustomEmojisByShortcodes).toHaveBeenCalledOnce(); + }); + + test('emojifies with provided custom emoji', async () => { + const { searchCustomEmojisByShortcodes, searchEmojisByHexcodes } = + mockDatabase(); + const actual = await emojifyElement( + testElement('

hi :remote:

'), + testAppState(), + mockExtraCustom, + ); + assert(actual); + expect(actual.innerHTML).toBe( + `

hi ${expectedRemoteCustomEmojiImage}

`, + ); + expect(searchEmojisByHexcodes).not.toHaveBeenCalled(); + expect(searchCustomEmojisByShortcodes).not.toHaveBeenCalled(); + }); + + test('returns null when no emoji are found', async () => { + mockDatabase(); + const actual = await emojifyElement( + testElement('

here is just text :)

'), + testAppState(), + ); + expect(actual).toBeNull(); + }); +}); + +describe('emojifyText', () => { + test('returns original input when no emoji are in string', async () => { + const actual = await emojifyText('nothing here', testAppState()); + expect(actual).toBe('nothing here'); + }); + + test('renders Unicode emojis to twemojis', async () => { + mockDatabase(); + const actual = await emojifyText('Hello 😊🇪🇺!', testAppState()); + expect(actual).toBe(`Hello ${expectedSmileImage}${expectedFlagImage}!`); + }); + + test('renders custom emojis', async () => { + mockDatabase(); + const actual = await emojifyText('Hello :custom:!', testAppState()); + expect(actual).toBe(`Hello ${expectedCustomEmojiImage}!`); + }); + + test('renders provided extra emojis', async () => { + const actual = await emojifyText( + 'remote emoji :remote:', + testAppState(), + mockExtraCustom, + ); + expect(actual).toBe(`remote emoji ${expectedRemoteCustomEmojiImage}`); }); }); diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts index 6ef9492147c..6486e65a709 100644 --- a/app/javascript/mastodon/features/emoji/render.ts +++ b/app/javascript/mastodon/features/emoji/render.ts @@ -1,8 +1,7 @@ -import type { Locale } from 'emojibase'; -import EMOJI_REGEX from 'emojibase-regex/emoji-loose'; - import { autoPlayGif } from '@/mastodon/initial_state'; +import { createLimitedCache } from '@/mastodon/utils/cache'; import { assetHost } from '@/mastodon/utils/config'; +import * as perf from '@/mastodon/utils/performance'; import { EMOJI_MODE_NATIVE, @@ -10,13 +9,12 @@ import { EMOJI_TYPE_UNICODE, EMOJI_TYPE_CUSTOM, EMOJI_STATE_MISSING, + ANY_EMOJI_REGEX, } from './constants'; import { - findMissingLocales, searchCustomEmojisByShortcodes, searchEmojisByHexcodes, } from './database'; -import { loadEmojiLocale } from './index'; import { emojiToUnicodeHex, twemojiHasBorder, @@ -34,18 +32,33 @@ import type { LocaleOrCustom, UnicodeEmojiToken, } from './types'; -import { stringHasUnicodeFlags } from './utils'; +import { emojiLogger, stringHasAnyEmoji, stringHasUnicodeFlags } from './utils'; -const localeCacheMap = new Map([ - [EMOJI_TYPE_CUSTOM, new Map()], -]); +const log = emojiLogger('render'); -// Emojifies an element. This modifies the element in place, replacing text nodes with emojified versions. +/** + * Emojifies an element. This modifies the element in place, replacing text nodes with emojified versions. + */ export async function emojifyElement( element: Element, appState: EmojiAppState, extraEmojis: ExtraCustomEmojiMap = {}, -): Promise { +): Promise { + const cacheKey = createCacheKey(element, appState, extraEmojis); + const cached = getCached(cacheKey); + if (cached !== undefined) { + log('Cache hit on %s', element.outerHTML); + if (cached === null) { + return null; + } + element.innerHTML = cached; + return element; + } + if (!stringHasAnyEmoji(element.innerHTML)) { + updateCache(cacheKey, null); + return null; + } + perf.start('emojifyElement()'); const queue: (HTMLElement | Text)[] = [element]; while (queue.length > 0) { const current = queue.shift(); @@ -61,7 +74,7 @@ export async function emojifyElement( current.textContent && (current instanceof Text || !current.hasChildNodes()) ) { - const renderedContent = await emojifyText( + const renderedContent = await textToElementArray( current.textContent, appState, extraEmojis, @@ -70,7 +83,7 @@ export async function emojifyElement( if (!(current instanceof Text)) { current.textContent = null; // Clear the text content if it's not a Text node. } - current.replaceWith(renderedToHTMLFragment(renderedContent)); + current.replaceWith(renderedToHTML(renderedContent)); } continue; } @@ -81,6 +94,8 @@ export async function emojifyElement( } } } + updateCache(cacheKey, element.innerHTML); + perf.stop('emojifyElement()'); return element; } @@ -88,7 +103,54 @@ export async function emojifyText( text: string, appState: EmojiAppState, extraEmojis: ExtraCustomEmojiMap = {}, +): Promise { + const cacheKey = createCacheKey(text, appState, extraEmojis); + const cached = getCached(cacheKey); + if (cached !== undefined) { + log('Cache hit on %s', text); + return cached ?? text; + } + if (!stringHasAnyEmoji(text)) { + updateCache(cacheKey, null); + return text; + } + const eleArray = await textToElementArray(text, appState, extraEmojis); + if (!eleArray) { + updateCache(cacheKey, null); + return text; + } + const rendered = renderedToHTML(eleArray, document.createElement('div')); + updateCache(cacheKey, rendered.innerHTML); + return rendered.innerHTML; +} + +// Private functions + +const { + set: updateCache, + get: getCached, + clear: cacheClear, +} = createLimitedCache({ log: log.extend('cache') }); + +function createCacheKey( + input: HTMLElement | string, + appState: EmojiAppState, + extraEmojis: ExtraCustomEmojiMap, ) { + return JSON.stringify([ + input instanceof HTMLElement ? input.outerHTML : input, + appState, + extraEmojis, + ]); +} + +type EmojifiedTextArray = (string | HTMLImageElement)[]; + +async function textToElementArray( + text: string, + appState: EmojiAppState, + extraEmojis: ExtraCustomEmojiMap = {}, +): Promise { // Exit if no text to convert. if (!text.trim()) { return null; @@ -102,10 +164,9 @@ export async function emojifyText( } // Get all emoji from the state map, loading any missing ones. - await ensureLocalesAreLoaded(appState.locales); - await loadMissingEmojiIntoCache(tokens, appState.locales); + await loadMissingEmojiIntoCache(tokens, appState, extraEmojis); - const renderedFragments: (string | HTMLImageElement)[] = []; + const renderedFragments: EmojifiedTextArray = []; for (const token of tokens) { if (typeof token !== 'string' && shouldRenderImage(token, appState.mode)) { let state: EmojiState | undefined; @@ -125,7 +186,7 @@ export async function emojifyText( // If the state is valid, create an image element. Otherwise, just append as text. if (state && typeof state !== 'string') { - const image = stateToImage(state); + const image = stateToImage(state, appState); renderedFragments.push(image); continue; } @@ -137,21 +198,6 @@ export async function emojifyText( return renderedFragments; } -// Private functions - -async function ensureLocalesAreLoaded(locales: Locale[]) { - const missingLocales = await findMissingLocales(locales); - for (const locale of missingLocales) { - await loadEmojiLocale(locale); - } -} - -const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; -const TOKENIZE_REGEX = new RegExp( - `(${EMOJI_REGEX.source}|${CUSTOM_EMOJI_REGEX.source})`, - 'g', -); - type TokenizedText = (string | EmojiToken)[]; export function tokenizeText(text: string): TokenizedText { @@ -161,7 +207,7 @@ export function tokenizeText(text: string): TokenizedText { const tokens = []; let lastIndex = 0; - for (const match of text.matchAll(TOKENIZE_REGEX)) { + for (const match of text.matchAll(ANY_EMOJI_REGEX)) { if (match.index > lastIndex) { tokens.push(text.slice(lastIndex, match.index)); } @@ -189,8 +235,18 @@ export function tokenizeText(text: string): TokenizedText { return tokens; } +const localeCacheMap = new Map([ + [ + EMOJI_TYPE_CUSTOM, + createLimitedCache({ log: log.extend('custom') }), + ], +]); + function cacheForLocale(locale: LocaleOrCustom): EmojiStateMap { - return localeCacheMap.get(locale) ?? (new Map() as EmojiStateMap); + return ( + localeCacheMap.get(locale) ?? + createLimitedCache({ log: log.extend(locale) }) + ); } function emojiForLocale( @@ -203,7 +259,8 @@ function emojiForLocale( async function loadMissingEmojiIntoCache( tokens: TokenizedText, - locales: Locale[], + { mode, currentLocale }: EmojiAppState, + extraEmojis: ExtraCustomEmojiMap, ) { const missingUnicodeEmoji = new Set(); const missingCustomEmoji = new Set(); @@ -217,42 +274,41 @@ async function loadMissingEmojiIntoCache( // If this is a custom emoji, check it separately. if (token.type === EMOJI_TYPE_CUSTOM) { const code = token.code; + if (code in extraEmojis) { + continue; // We don't care about extra emoji. + } const emojiState = emojiForLocale(code, EMOJI_TYPE_CUSTOM); if (!emojiState) { missingCustomEmoji.add(code); } // Otherwise this is a unicode emoji, so check it against all locales. - } else { + } else if (shouldRenderImage(token, mode)) { const code = emojiToUnicodeHex(token.code); if (missingUnicodeEmoji.has(code)) { continue; // Already marked as missing. } - for (const locale of locales) { - const emojiState = emojiForLocale(code, locale); - if (!emojiState) { - // If it's missing in one locale, we consider it missing for all. - missingUnicodeEmoji.add(code); - } + const emojiState = emojiForLocale(code, currentLocale); + if (!emojiState) { + // If it's missing in one locale, we consider it missing for all. + missingUnicodeEmoji.add(code); } } } if (missingUnicodeEmoji.size > 0) { const missingEmojis = Array.from(missingUnicodeEmoji).toSorted(); - for (const locale of locales) { - const emojis = await searchEmojisByHexcodes(missingEmojis, locale); - const cache = cacheForLocale(locale); - for (const emoji of emojis) { - cache.set(emoji.hexcode, { type: EMOJI_TYPE_UNICODE, data: emoji }); - } - const notFoundEmojis = missingEmojis.filter((code) => - emojis.every((emoji) => emoji.hexcode !== code), - ); - for (const code of notFoundEmojis) { - cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. - } - localeCacheMap.set(locale, cache); + const emojis = await searchEmojisByHexcodes(missingEmojis, currentLocale); + const cache = cacheForLocale(currentLocale); + for (const emoji of emojis) { + cache.set(emoji.hexcode, { type: EMOJI_TYPE_UNICODE, data: emoji }); } + const notFoundEmojis = missingEmojis.filter((code) => + emojis.every((emoji) => emoji.hexcode !== code), + ); + for (const code of notFoundEmojis) { + cache.set(code, EMOJI_STATE_MISSING); // Mark as missing if not found, as it's probably not a valid emoji. + } + localeCacheMap.set(currentLocale, cache); } if (missingCustomEmoji.size > 0) { @@ -288,22 +344,24 @@ function shouldRenderImage(token: EmojiToken, mode: EmojiMode): boolean { return true; } -function stateToImage(state: EmojiLoadedState) { +function stateToImage(state: EmojiLoadedState, appState: EmojiAppState) { const image = document.createElement('img'); image.draggable = false; image.classList.add('emojione'); if (state.type === EMOJI_TYPE_UNICODE) { const emojiInfo = twemojiHasBorder(unicodeToTwemojiHex(state.data.hexcode)); - if (emojiInfo.hasLightBorder) { - image.dataset.lightCode = `${emojiInfo.hexCode}_BORDER`; - } else if (emojiInfo.hasDarkBorder) { - image.dataset.darkCode = `${emojiInfo.hexCode}_BORDER`; + let fileName = emojiInfo.hexCode; + if ( + (appState.darkTheme && emojiInfo.hasDarkBorder) || + (!appState.darkTheme && emojiInfo.hasLightBorder) + ) { + fileName = `${emojiInfo.hexCode}_border`; } image.alt = state.data.unicode; image.title = state.data.label; - image.src = `${assetHost}/emoji/${emojiInfo.hexCode}.svg`; + image.src = `${assetHost}/emoji/${fileName}.svg`; } else { // Custom emoji const shortCode = `:${state.data.shortcode}:`; @@ -318,8 +376,16 @@ function stateToImage(state: EmojiLoadedState) { return image; } -function renderedToHTMLFragment(renderedArray: (string | HTMLImageElement)[]) { - const fragment = document.createDocumentFragment(); +function renderedToHTML(renderedArray: EmojifiedTextArray): DocumentFragment; +function renderedToHTML( + renderedArray: EmojifiedTextArray, + parent: ParentType, +): ParentType; +function renderedToHTML( + renderedArray: EmojifiedTextArray, + parent: ParentNode | null = null, +) { + const fragment = parent ?? document.createDocumentFragment(); for (const fragmentItem of renderedArray) { if (typeof fragmentItem === 'string') { fragment.appendChild(document.createTextNode(fragmentItem)); @@ -329,3 +395,9 @@ function renderedToHTMLFragment(renderedArray: (string | HTMLImageElement)[]) { } return fragment; } + +// Testing helpers +export const testCacheClear = () => { + cacheClear(); + localeCacheMap.clear(); +}; diff --git a/app/javascript/mastodon/features/emoji/types.ts b/app/javascript/mastodon/features/emoji/types.ts index f5932ed97fd..85bbe6d1a56 100644 --- a/app/javascript/mastodon/features/emoji/types.ts +++ b/app/javascript/mastodon/features/emoji/types.ts @@ -1,6 +1,10 @@ +import type { List as ImmutableList } from 'immutable'; + import type { FlatCompactEmoji, Locale } from 'emojibase'; import type { ApiCustomEmojiJSON } from '@/mastodon/api_types/custom_emoji'; +import type { CustomEmoji } from '@/mastodon/models/custom_emoji'; +import type { LimitedCache } from '@/mastodon/utils/cache'; import type { EMOJI_MODE_NATIVE, @@ -22,6 +26,7 @@ export interface EmojiAppState { locales: Locale[]; currentLocale: Locale; mode: EmojiMode; + darkTheme: boolean; } export interface UnicodeEmojiToken { @@ -45,7 +50,7 @@ export interface EmojiStateUnicode { } export interface EmojiStateCustom { type: typeof EMOJI_TYPE_CUSTOM; - data: CustomEmojiData; + data: CustomEmojiRenderFields; } export type EmojiState = | EmojiStateMissing @@ -53,9 +58,16 @@ export type EmojiState = | EmojiStateCustom; export type EmojiLoadedState = EmojiStateUnicode | EmojiStateCustom; -export type EmojiStateMap = Map; +export type EmojiStateMap = LimitedCache; -export type ExtraCustomEmojiMap = Record; +export type CustomEmojiMapArg = + | ExtraCustomEmojiMap + | ImmutableList; +export type CustomEmojiRenderFields = Pick< + CustomEmojiData, + 'shortcode' | 'static_url' | 'url' +>; +export type ExtraCustomEmojiMap = Record; export interface TwemojiBorderInfo { hexCode: string; diff --git a/app/javascript/mastodon/features/emoji/utils.test.ts b/app/javascript/mastodon/features/emoji/utils.test.ts index 75cac8c5b4c..b9062294c47 100644 --- a/app/javascript/mastodon/features/emoji/utils.test.ts +++ b/app/javascript/mastodon/features/emoji/utils.test.ts @@ -1,8 +1,14 @@ -import { stringHasUnicodeEmoji, stringHasUnicodeFlags } from './utils'; +import { + stringHasAnyEmoji, + stringHasCustomEmoji, + stringHasUnicodeEmoji, + stringHasUnicodeFlags, +} from './utils'; -describe('stringHasEmoji', () => { +describe('stringHasUnicodeEmoji', () => { test.concurrent.for([ ['only text', false], + ['text with non-emoji symbols ™©', false], ['text with emoji 😀', true], ['multiple emojis 😀😃😄', true], ['emoji with skin tone 👍🏽', true], @@ -19,14 +25,14 @@ describe('stringHasEmoji', () => { ['emoji with enclosing keycap #️⃣', true], ['emoji with no visible glyph \u200D', false], ] as const)( - 'stringHasEmoji has emojis in "%s": %o', + 'stringHasUnicodeEmoji has emojis in "%s": %o', ([text, expected], { expect }) => { expect(stringHasUnicodeEmoji(text)).toBe(expected); }, ); }); -describe('stringHasFlags', () => { +describe('stringHasUnicodeFlags', () => { test.concurrent.for([ ['EU 🇪🇺', true], ['Germany 🇩🇪', true], @@ -45,3 +51,27 @@ describe('stringHasFlags', () => { }, ); }); + +describe('stringHasCustomEmoji', () => { + test('string with custom emoji returns true', () => { + expect(stringHasCustomEmoji(':custom: :test:')).toBeTruthy(); + }); + test('string without custom emoji returns false', () => { + expect(stringHasCustomEmoji('🏳️‍🌈 :🏳️‍🌈: text ™')).toBeFalsy(); + }); +}); + +describe('stringHasAnyEmoji', () => { + test('string without any emoji or characters', () => { + expect(stringHasAnyEmoji('normal text. 12356?!')).toBeFalsy(); + }); + test('string with non-emoji characters', () => { + expect(stringHasAnyEmoji('™©')).toBeFalsy(); + }); + test('has unicode emoji', () => { + expect(stringHasAnyEmoji('🏳️‍🌈🔥🇸🇹 👩‍🔬')).toBeTruthy(); + }); + test('has custom emoji', () => { + expect(stringHasAnyEmoji(':test: :custom:')).toBeTruthy(); + }); +}); diff --git a/app/javascript/mastodon/features/emoji/utils.ts b/app/javascript/mastodon/features/emoji/utils.ts index d00accea8c5..89f8d926466 100644 --- a/app/javascript/mastodon/features/emoji/utils.ts +++ b/app/javascript/mastodon/features/emoji/utils.ts @@ -1,13 +1,27 @@ -import EMOJI_REGEX from 'emojibase-regex/emoji-loose'; +import debug from 'debug'; -export function stringHasUnicodeEmoji(text: string): boolean { - return EMOJI_REGEX.test(text); +import { + CUSTOM_EMOJI_REGEX, + UNICODE_EMOJI_REGEX, + UNICODE_FLAG_EMOJI_REGEX, +} from './constants'; + +export function emojiLogger(segment: string) { + return debug(`emojis:${segment}`); } -// From https://github.com/talkjs/country-flag-emoji-polyfill/blob/master/src/index.ts#L49-L50 -const EMOJIS_FLAGS_REGEX = - /[\u{1F1E6}-\u{1F1FF}|\u{E0062}-\u{E0063}|\u{E0065}|\u{E0067}|\u{E006C}|\u{E006E}|\u{E0073}-\u{E0074}|\u{E0077}|\u{E007F}]+/u; - -export function stringHasUnicodeFlags(text: string): boolean { - return EMOJIS_FLAGS_REGEX.test(text); +export function stringHasUnicodeEmoji(input: string): boolean { + return UNICODE_EMOJI_REGEX.test(input); +} + +export function stringHasUnicodeFlags(input: string): boolean { + return UNICODE_FLAG_EMOJI_REGEX.test(input); +} + +export function stringHasCustomEmoji(input: string) { + return CUSTOM_EMOJI_REGEX.test(input); +} + +export function stringHasAnyEmoji(input: string) { + return stringHasUnicodeEmoji(input) || stringHasCustomEmoji(input); } diff --git a/app/javascript/mastodon/features/emoji/worker.ts b/app/javascript/mastodon/features/emoji/worker.ts index 1c48a077730..6fb7d36e936 100644 --- a/app/javascript/mastodon/features/emoji/worker.ts +++ b/app/javascript/mastodon/features/emoji/worker.ts @@ -5,9 +5,14 @@ self.postMessage('ready'); // After the worker is ready, notify the main thread function handleMessage(event: MessageEvent) { const { data: locale } = event; - if (locale !== 'custom') { - void importEmojiData(locale); - } else { - void importCustomEmojiData(); - } + void loadData(locale); +} + +async function loadData(locale: string) { + if (locale !== 'custom') { + await importEmojiData(locale); + } else { + await importCustomEmojiData(); + } + self.postMessage(`loaded ${locale}`); } diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index dcc71bdb843..456cc21c318 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -2,10 +2,10 @@ import { createRoot } from 'react-dom/client'; import { Globals } from '@react-spring/web'; +import * as perf from '@/mastodon/utils/performance'; import { setupBrowserNotifications } from 'mastodon/actions/notifications'; import Mastodon from 'mastodon/containers/mastodon'; import { me, reduceMotion } from 'mastodon/initial_state'; -import * as perf from 'mastodon/performance'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; @@ -35,7 +35,7 @@ function main() { if (isModernEmojiEnabled()) { const { initializeEmoji } = await import('@/mastodon/features/emoji'); - await initializeEmoji(); + initializeEmoji(); } const root = createRoot(mountNode); diff --git a/app/javascript/mastodon/utils/__tests__/cache.test.ts b/app/javascript/mastodon/utils/__tests__/cache.test.ts new file mode 100644 index 00000000000..340a51fdb4b --- /dev/null +++ b/app/javascript/mastodon/utils/__tests__/cache.test.ts @@ -0,0 +1,78 @@ +import { createLimitedCache } from '../cache'; + +describe('createCache', () => { + test('returns expected methods', () => { + const actual = createLimitedCache(); + expect(actual).toBeTypeOf('object'); + expect(actual).toHaveProperty('get'); + expect(actual).toHaveProperty('has'); + expect(actual).toHaveProperty('delete'); + expect(actual).toHaveProperty('set'); + }); + + test('caches values provided to it', () => { + const cache = createLimitedCache(); + cache.set('test', 'result'); + expect(cache.get('test')).toBe('result'); + }); + + test('has returns expected values', () => { + const cache = createLimitedCache(); + cache.set('test', 'result'); + expect(cache.has('test')).toBeTruthy(); + expect(cache.has('not found')).toBeFalsy(); + }); + + test('updates a value if keys are the same', () => { + const cache = createLimitedCache(); + cache.set('test1', 1); + cache.set('test1', 2); + expect(cache.get('test1')).toBe(2); + }); + + test('delete removes an item', () => { + const cache = createLimitedCache(); + cache.set('test', 'result'); + expect(cache.has('test')).toBeTruthy(); + cache.delete('test'); + expect(cache.has('test')).toBeFalsy(); + expect(cache.get('test')).toBeUndefined(); + }); + + test('removes oldest item cached if it exceeds a set size', () => { + const cache = createLimitedCache({ maxSize: 1 }); + cache.set('test1', 1); + cache.set('test2', 2); + expect(cache.get('test1')).toBeUndefined(); + expect(cache.get('test2')).toBe(2); + }); + + test('retrieving a value bumps up last access', () => { + const cache = createLimitedCache({ maxSize: 2 }); + cache.set('test1', 1); + cache.set('test2', 2); + expect(cache.get('test1')).toBe(1); + cache.set('test3', 3); + expect(cache.get('test1')).toBe(1); + expect(cache.get('test2')).toBeUndefined(); + expect(cache.get('test3')).toBe(3); + }); + + test('logs when cache is added to and removed', () => { + const log = vi.fn(); + const cache = createLimitedCache({ maxSize: 1, log }); + cache.set('test1', 1); + expect(log).toHaveBeenLastCalledWith( + 'Added %s to cache, now size %d', + 'test1', + 1, + ); + cache.set('test2', 1); + expect(log).toHaveBeenLastCalledWith( + 'Added %s and deleted %s from cache, now size %d', + 'test2', + 'test1', + 1, + ); + }); +}); diff --git a/app/javascript/mastodon/utils/cache.ts b/app/javascript/mastodon/utils/cache.ts new file mode 100644 index 00000000000..2e3d21bfed4 --- /dev/null +++ b/app/javascript/mastodon/utils/cache.ts @@ -0,0 +1,60 @@ +export interface LimitedCache { + has: (key: CacheKey) => boolean; + get: (key: CacheKey) => CacheValue | undefined; + delete: (key: CacheKey) => void; + set: (key: CacheKey, value: CacheValue) => void; + clear: () => void; +} + +interface LimitedCacheArguments { + maxSize?: number; + log?: (...args: unknown[]) => void; +} + +export function createLimitedCache({ + maxSize = 100, + log = () => null, +}: LimitedCacheArguments = {}): LimitedCache { + const cacheMap = new Map(); + const cacheKeys = new Set(); + + function touchKey(key: CacheKey) { + if (cacheKeys.has(key)) { + cacheKeys.delete(key); + } + cacheKeys.add(key); + } + + return { + has: (key) => cacheMap.has(key), + get: (key) => { + if (cacheMap.has(key)) { + touchKey(key); + } + return cacheMap.get(key); + }, + delete: (key) => cacheMap.delete(key) && cacheKeys.delete(key), + set: (key, value) => { + cacheMap.set(key, value); + touchKey(key); + + const lastKey = cacheKeys.values().toArray().shift(); + if (cacheMap.size > maxSize && lastKey) { + cacheMap.delete(lastKey); + cacheKeys.delete(lastKey); + log( + 'Added %s and deleted %s from cache, now size %d', + key, + lastKey, + cacheMap.size, + ); + } else { + log('Added %s to cache, now size %d', key, cacheMap.size); + } + }, + clear: () => { + cacheMap.clear(); + cacheKeys.clear(); + }, + }; +} diff --git a/app/javascript/mastodon/performance.js b/app/javascript/mastodon/utils/performance.ts similarity index 70% rename from app/javascript/mastodon/performance.js rename to app/javascript/mastodon/utils/performance.ts index 1b2092cfc4c..e503e1ef587 100644 --- a/app/javascript/mastodon/performance.js +++ b/app/javascript/mastodon/utils/performance.ts @@ -4,15 +4,15 @@ import * as marky from 'marky'; -import { isDevelopment } from './utils/environment'; +import { isDevelopment } from './environment'; -export function start(name) { +export function start(name: string) { if (isDevelopment()) { marky.mark(name); } } -export function stop(name) { +export function stop(name: string) { if (isDevelopment()) { marky.stop(name); } diff --git a/app/javascript/testing/factories.ts b/app/javascript/testing/factories.ts index 5b2fbfe594e..cd5f72a06f0 100644 --- a/app/javascript/testing/factories.ts +++ b/app/javascript/testing/factories.ts @@ -1,4 +1,8 @@ import type { ApiRelationshipJSON } from '@/mastodon/api_types/relationships'; +import type { + CustomEmojiData, + UnicodeEmojiData, +} from '@/mastodon/features/emoji/types'; import { createAccountFromServerJSON } from '@/mastodon/models/account'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; @@ -68,3 +72,26 @@ export const relationshipsFactory: FactoryFunction = ({ showing_reblogs: true, ...data, }); + +export function unicodeEmojiFactory( + data: Partial = {}, +): UnicodeEmojiData { + return { + hexcode: 'test', + label: 'Test', + unicode: '🧪', + ...data, + }; +} + +export function customEmojiFactory( + data: Partial = {}, +): CustomEmojiData { + return { + shortcode: 'custom', + static_url: 'emoji/custom/static', + url: 'emoji/custom', + visible_in_picker: true, + ...data, + }; +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 3d00a4adce9..43aabc51100 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -406,6 +406,12 @@ export default tseslint.config([ globals: globals.vitest, }, }, + { + files: ['**/*.test.*'], + rules: { + 'no-global-assign': 'off', + }, + }, { files: ['**/*.stories.ts', '**/*.stories.tsx', '.storybook/*'], rules: { diff --git a/package.json b/package.json index efe5b45fefb..736f29fb814 100644 --- a/package.json +++ b/package.json @@ -64,11 +64,11 @@ "color-blend": "^4.0.0", "core-js": "^3.30.2", "cross-env": "^10.0.0", + "debug": "^4.4.1", "detect-passive-events": "^2.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", "emojibase-data": "^16.0.3", - "emojibase-regex": "^16.0.0", "escape-html": "^1.0.3", "fast-glob": "^3.3.3", "fuzzysort": "^3.0.0", @@ -137,6 +137,7 @@ "@storybook/react-vite": "^9.0.4", "@testing-library/dom": "^10.2.0", "@testing-library/react": "^16.0.0", + "@types/debug": "^4", "@types/emoji-mart": "3.0.14", "@types/escape-html": "^1.0.2", "@types/hoist-non-react-statics": "^3.3.1", @@ -174,6 +175,7 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-storybook": "^9.0.4", + "fake-indexeddb": "^6.0.1", "globals": "^16.0.0", "husky": "^9.0.11", "lint-staged": "^16.0.0", diff --git a/vitest.config.mts b/vitest.config.mts index 7df462ed6db..b129c293f4c 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -49,6 +49,7 @@ const legacyTests: TestProjectInlineConfiguration = { 'tmp/**', ], globals: true, + setupFiles: ['fake-indexeddb/auto'], }, }; diff --git a/yarn.lock b/yarn.lock index 9cb29176d8b..377f2999c85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2632,6 +2632,7 @@ __metadata: "@storybook/react-vite": "npm:^9.0.4" "@testing-library/dom": "npm:^10.2.0" "@testing-library/react": "npm:^16.0.0" + "@types/debug": "npm:^4" "@types/emoji-mart": "npm:3.0.14" "@types/escape-html": "npm:^1.0.2" "@types/hoist-non-react-statics": "npm:^3.3.1" @@ -2673,11 +2674,11 @@ __metadata: color-blend: "npm:^4.0.0" core-js: "npm:^3.30.2" cross-env: "npm:^10.0.0" + debug: "npm:^4.4.1" detect-passive-events: "npm:^2.0.3" emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" emojibase-data: "npm:^16.0.3" - emojibase-regex: "npm:^16.0.0" escape-html: "npm:^1.0.3" eslint: "npm:^9.23.0" eslint-import-resolver-typescript: "npm:^4.2.5" @@ -2689,6 +2690,7 @@ __metadata: eslint-plugin-react: "npm:^7.37.4" eslint-plugin-react-hooks: "npm:^5.2.0" eslint-plugin-storybook: "npm:^9.0.4" + fake-indexeddb: "npm:^6.0.1" fast-glob: "npm:^3.3.3" fuzzysort: "npm:^3.0.0" globals: "npm:^16.0.0" @@ -3931,6 +3933,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + "@types/deep-eql@npm:*": version: 4.0.2 resolution: "@types/deep-eql@npm:4.0.2" @@ -4112,6 +4123,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:^22.0.0": version: 22.13.14 resolution: "@types/node@npm:22.13.14" @@ -6599,13 +6617,6 @@ __metadata: languageName: node linkType: hard -"emojibase-regex@npm:^16.0.0": - version: 16.0.0 - resolution: "emojibase-regex@npm:16.0.0" - checksum: 10c0/8ee5ff798e51caa581434b1cb2f9737e50195093c4efa1739df21a50a5496f80517924787d865e8cf7d6a0b4c90dbedc04bdc506dcbcc582e14cdf0bb47af0f0 - languageName: node - linkType: hard - "emojibase@npm:^16.0.0": version: 16.0.0 resolution: "emojibase@npm:16.0.0" @@ -7370,6 +7381,13 @@ __metadata: languageName: node linkType: hard +"fake-indexeddb@npm:^6.0.1": + version: 6.0.1 + resolution: "fake-indexeddb@npm:6.0.1" + checksum: 10c0/60f4ccdfd5ecb37bb98019056c688366847840cce7146e0005c5ca54823238455403b0a8803b898a11cf80f6147b1bb553457c6af427a644a6e64566cdbe42ec + languageName: node + linkType: hard + "fast-copy@npm:^3.0.2": version: 3.0.2 resolution: "fast-copy@npm:3.0.2" From 1d86df685b3afeb47e845c1212d942dad8c8fd4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:34:05 +0200 Subject: [PATCH 192/660] Update dependency puma to v6.6.1 (#35620) 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 08f5108915e..73c2abda1cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -635,7 +635,7 @@ GEM date stringio public_suffix (6.0.2) - puma (6.6.0) + puma (6.6.1) nio4r (~> 2.0) pundit (2.5.0) activesupport (>= 3.0.0) From d09f866daa1c5c45f076a2a0a010cccc7a402912 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 09:57:38 +0200 Subject: [PATCH 193/660] New Crowdin Translations (automated) (#35622) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 7 ++ app/javascript/mastodon/locales/es-MX.json | 7 ++ app/javascript/mastodon/locales/es.json | 7 ++ app/javascript/mastodon/locales/et.json | 7 ++ app/javascript/mastodon/locales/fi.json | 4 +- app/javascript/mastodon/locales/ga.json | 7 ++ app/javascript/mastodon/locales/he.json | 7 ++ app/javascript/mastodon/locales/is.json | 7 ++ app/javascript/mastodon/locales/nan.json | 4 ++ app/javascript/mastodon/locales/nl.json | 7 ++ config/locales/ar.yml | 2 - config/locales/be.yml | 2 - config/locales/bg.yml | 2 - config/locales/ca.yml | 4 +- config/locales/cs.yml | 29 ++++++++- config/locales/cy.yml | 2 - config/locales/da.yml | 4 +- config/locales/de.yml | 4 +- config/locales/el.yml | 2 - config/locales/en-GB.yml | 2 - config/locales/eo.yml | 2 - config/locales/es-AR.yml | 4 +- config/locales/es-MX.yml | 4 +- config/locales/es.yml | 4 +- config/locales/et.yml | 2 - config/locales/eu.yml | 2 - config/locales/fa.yml | 2 - config/locales/fi.yml | 4 +- config/locales/fo.yml | 2 - config/locales/fr-CA.yml | 2 - config/locales/fr.yml | 2 - config/locales/fy.yml | 2 - config/locales/ga.yml | 29 ++++++++- config/locales/gd.yml | 2 - config/locales/gl.yml | 4 +- config/locales/he.yml | 4 +- config/locales/hu.yml | 2 - config/locales/ia.yml | 2 - config/locales/is.yml | 4 +- config/locales/it.yml | 2 - config/locales/ja.yml | 2 - config/locales/ko.yml | 2 - config/locales/lv.yml | 2 - config/locales/nan.yml | 3 + config/locales/nl.yml | 6 +- config/locales/nn.yml | 2 - config/locales/pt-BR.yml | 2 - config/locales/pt-PT.yml | 2 - config/locales/ru.yml | 76 +++++++++++----------- config/locales/simple_form.ar.yml | 1 - config/locales/simple_form.bg.yml | 1 - config/locales/simple_form.ca.yml | 1 - config/locales/simple_form.cs.yml | 10 ++- config/locales/simple_form.cy.yml | 1 - config/locales/simple_form.da.yml | 2 +- config/locales/simple_form.de.yml | 2 +- config/locales/simple_form.el.yml | 1 - config/locales/simple_form.en-GB.yml | 1 - config/locales/simple_form.es-AR.yml | 2 +- config/locales/simple_form.es-MX.yml | 2 +- config/locales/simple_form.es.yml | 2 +- config/locales/simple_form.eu.yml | 1 - config/locales/simple_form.fa.yml | 1 - config/locales/simple_form.fi.yml | 2 +- config/locales/simple_form.fo.yml | 1 - config/locales/simple_form.fy.yml | 1 - config/locales/simple_form.ga.yml | 10 ++- config/locales/simple_form.gd.yml | 1 - config/locales/simple_form.gl.yml | 2 +- config/locales/simple_form.he.yml | 2 +- config/locales/simple_form.hu.yml | 1 - config/locales/simple_form.ia.yml | 1 - config/locales/simple_form.is.yml | 2 +- config/locales/simple_form.it.yml | 1 - config/locales/simple_form.ja.yml | 1 - config/locales/simple_form.ko.yml | 1 - config/locales/simple_form.lv.yml | 1 - config/locales/simple_form.nl.yml | 4 +- config/locales/simple_form.nn.yml | 1 - config/locales/simple_form.pt-BR.yml | 1 - config/locales/simple_form.pt-PT.yml | 1 - config/locales/simple_form.ru.yml | 1 - config/locales/simple_form.si.yml | 1 - config/locales/simple_form.sq.yml | 1 - config/locales/simple_form.sv.yml | 1 - config/locales/simple_form.tr.yml | 1 - config/locales/simple_form.vi.yml | 2 +- config/locales/simple_form.zh-CN.yml | 1 - config/locales/simple_form.zh-TW.yml | 2 +- config/locales/sq.yml | 2 - config/locales/sv.yml | 2 - config/locales/tr.yml | 2 - config/locales/vi.yml | 4 +- config/locales/zh-CN.yml | 2 - config/locales/zh-TW.yml | 4 +- 95 files changed, 215 insertions(+), 168 deletions(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 40273be1bfa..17fb3eb2b84 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "k přeložení příspěvku", "keyboard_shortcuts.unfocus": "Zrušit zaměření na nový příspěvek/hledání", "keyboard_shortcuts.up": "Posunout v seznamu nahoru", + "learn_more_link.got_it": "Rozumím", + "learn_more_link.learn_more": "Zjistit více", "lightbox.close": "Zavřít", "lightbox.next": "Další", "lightbox.previous": "Předchozí", @@ -873,6 +875,11 @@ "status.open": "Rozbalit tento příspěvek", "status.pin": "Připnout na profil", "status.quote_error.filtered": "Skryté kvůli jednomu z vašich filtrů", + "status.quote_error.not_available": "Příspěvek není dostupný", + "status.quote_error.pending_approval": "Příspěvek čeká na schválení", + "status.quote_error.pending_approval_popout.body": "Zobrazení citátů sdílených napříč Fediversem může chvíli trvat, protože různé servery používají různé protokoly.", + "status.quote_error.pending_approval_popout.title": "Příspěvek čeká na schválení? Buďte klidní", + "status.quote_post_author": "Citovali příspěvek od @{name}", "status.read_more": "Číst více", "status.reblog": "Boostnout", "status.reblog_private": "Boostnout s původní viditelností", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 55d327b4977..834c1f33bf2 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "para traducir una publicación", "keyboard_shortcuts.unfocus": "Desenfocar área de redacción/búsqueda", "keyboard_shortcuts.up": "Ascender en la lista", + "learn_more_link.got_it": "Entendido", + "learn_more_link.learn_more": "Más información", "lightbox.close": "Cerrar", "lightbox.next": "Siguiente", "lightbox.previous": "Anterior", @@ -873,6 +875,11 @@ "status.open": "Expandir estado", "status.pin": "Fijar", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", + "status.quote_error.not_available": "Publicación no disponible", + "status.quote_error.pending_approval": "Publicación pendiente", + "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que los diferentes servidores tienen diferentes protocolos.", + "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", + "status.quote_post_author": "Ha citado una publicación de @{name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Implusar a la audiencia original", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 5732da2735e..475f4ca7068 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "para traducir una publicación", "keyboard_shortcuts.unfocus": "Quitar el foco de la caja de redacción/búsqueda", "keyboard_shortcuts.up": "Moverse hacia arriba en la lista", + "learn_more_link.got_it": "Entendido", + "learn_more_link.learn_more": "Más información", "lightbox.close": "Cerrar", "lightbox.next": "Siguiente", "lightbox.previous": "Anterior", @@ -873,6 +875,11 @@ "status.open": "Expandir publicación", "status.pin": "Fijar", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", + "status.quote_error.not_available": "Publicación no disponible", + "status.quote_error.pending_approval": "Publicación pendiente", + "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que los diferentes servidores tienen diferentes protocolos.", + "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", + "status.quote_post_author": "Ha citado una publicación de @{name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Impulsar a la audiencia original", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 6d9946985d1..e5d717d2270 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "postituse tõlkimiseks", "keyboard_shortcuts.unfocus": "Fookus tekstialalt/otsingult ära", "keyboard_shortcuts.up": "Liigu loetelus üles", + "learn_more_link.got_it": "Sain aru", + "learn_more_link.learn_more": "Lisateave", "lightbox.close": "Sulge", "lightbox.next": "Järgmine", "lightbox.previous": "Eelmine", @@ -873,6 +875,11 @@ "status.open": "Laienda postitus", "status.pin": "Kinnita profiilile", "status.quote_error.filtered": "Peidetud mõne kasutatud filtri tõttu", + "status.quote_error.not_available": "Postitus pole saadaval", + "status.quote_error.pending_approval": "Postitus on ootel", + "status.quote_error.pending_approval_popout.body": "Kuna erinevates serverites on erinevad reeglid, siis üle Födiversumi jagatud tsitaatide kuvamine võib võtta aega.", + "status.quote_error.pending_approval_popout.title": "Tsiteerimine on ootel? Palun jää rahulikuks", + "status.quote_post_author": "Tsiteeris kasutaja @{name} postitust", "status.read_more": "Loe veel", "status.reblog": "Jaga", "status.reblog_private": "Jaga algse nähtavusega", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index b9898a8c9e9..6efe46693d4 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -311,7 +311,7 @@ "empty_column.account_featured_other.unknown": "Tämä tili ei suosittele vielä mitään.", "empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä", "empty_column.account_suspended": "Tili jäädytetty", - "empty_column.account_timeline": "Ei viestejä täällä.", + "empty_column.account_timeline": "Ei julkaisuja täällä!", "empty_column.account_unavailable": "Profiilia ei ole saatavilla", "empty_column.blocks": "Et ole vielä estänyt käyttäjiä.", "empty_column.bookmarked_statuses": "Et ole vielä lisännyt julkaisuja kirjanmerkkeihisi. Kun lisäät yhden, se näkyy tässä.", @@ -756,7 +756,7 @@ "reply_indicator.cancel": "Peruuta", "reply_indicator.poll": "Äänestys", "report.block": "Estä", - "report.block_explanation": "Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkee, että olet estänyt hänet.", + "report.block_explanation": "Et näe hänen julkaisujaan. Hän voi nähdä julkaisujasi eikä seurata sinua. Hän näkee, että olet estänyt hänet.", "report.categories.legal": "Lakiseikat", "report.categories.other": "Muu", "report.categories.spam": "Roskaposti", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index a710043a887..afdc15e99d1 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "post a aistriú", "keyboard_shortcuts.unfocus": "Unfocus cum textarea/search", "keyboard_shortcuts.up": "Bog suas ar an liosta", + "learn_more_link.got_it": "Tuigim é", + "learn_more_link.learn_more": "Foghlaim níos mó", "lightbox.close": "Dún", "lightbox.next": "An céad eile", "lightbox.previous": "Roimhe seo", @@ -873,6 +875,11 @@ "status.open": "Leathnaigh an post seo", "status.pin": "Pionnáil ar do phróifíl", "status.quote_error.filtered": "I bhfolach mar gheall ar cheann de do scagairí", + "status.quote_error.not_available": "Níl an postáil ar fáil", + "status.quote_error.pending_approval": "Post ar feitheamh", + "status.quote_error.pending_approval_popout.body": "D’fhéadfadh sé go dtógfadh sé tamall le Sleachta a roinntear ar fud Fediverse a thaispeáint, toisc go mbíonn prótacail éagsúla ag freastalaithe éagsúla.", + "status.quote_error.pending_approval_popout.title": "Ag fanacht le luachan? Fan socair", + "status.quote_post_author": "Luaigh mé post le @{name}", "status.read_more": "Léan a thuilleadh", "status.reblog": "Treisiú", "status.reblog_private": "Mol le léargas bunúsach", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e185490de93..e9a755fab72 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "לתרגם הודעה", "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש", "keyboard_shortcuts.up": "לנוע במעלה הרשימה", + "learn_more_link.got_it": "הבנתי", + "learn_more_link.learn_more": "למידע נוסף", "lightbox.close": "סגירה", "lightbox.next": "הבא", "lightbox.previous": "הקודם", @@ -873,6 +875,11 @@ "status.open": "הרחבת הודעה זו", "status.pin": "הצמדה לפרופיל שלי", "status.quote_error.filtered": "מוסתר בהתאם לסננים שלך", + "status.quote_error.not_available": "ההודעה לא זמינה", + "status.quote_error.pending_approval": "ההודעה בהמתנה לאישור", + "status.quote_error.pending_approval_popout.body": "ציטוטים ששותפו בפדיוורס עשויים להתפרסם אחרי עיכוב קל, כיוון ששרתים שונים משתמשים בפרוטוקולים שונים.", + "status.quote_error.pending_approval_popout.title": "ההודעה בהמתנה? המתינו ברוגע", + "status.quote_post_author": "ההודעה צוטטה על ידי @{name}", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", "status.reblog_private": "להדהד ברמת הנראות המקורית", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 96379263314..54dcc70a093 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "að þýða færslu", "keyboard_shortcuts.unfocus": "Taka virkni úr textainnsetningarreit eða leit", "keyboard_shortcuts.up": "Fara ofar í listanum", + "learn_more_link.got_it": "Náði því", + "learn_more_link.learn_more": "Kanna nánar", "lightbox.close": "Loka", "lightbox.next": "Næsta", "lightbox.previous": "Fyrra", @@ -873,6 +875,11 @@ "status.open": "Opna þessa færslu", "status.pin": "Festa á notandasnið", "status.quote_error.filtered": "Falið vegna einnar síu sem er virk", + "status.quote_error.not_available": "Færsla ekki tiltæk", + "status.quote_error.pending_approval": "Færsla í bið", + "status.quote_error.pending_approval_popout.body": "Tilvitnanir sem deilt er út um samfélagsnetið geta þurft nokkurn tíma áður en þær birtast, því mismunandi netþjónar geta haft mismunandi samskiptareglur.", + "status.quote_error.pending_approval_popout.title": "Færsla í bið? Verum róleg", + "status.quote_post_author": "Vitnaði í færslu frá @{name}", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", "status.reblog_private": "Endurbirta til upphaflegra lesenda", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index f6ca3c4c31a..a5611c6c9de 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "kā PO文翻譯", "keyboard_shortcuts.unfocus": "離開輸入框仔/tshiau-tshuē格仔", "keyboard_shortcuts.up": "佇列單內kā suá khah面頂", + "learn_more_link.got_it": "知矣", + "learn_more_link.learn_more": "看詳細", "lightbox.close": "關", "lightbox.next": "下tsi̍t ê", "lightbox.previous": "頂tsi̍t ê", @@ -872,6 +874,8 @@ "status.mute_conversation": "Kā對話消音", "status.open": "Kā PO文展開", "status.quote_error.filtered": "Lí所設定ê過濾器kā tse khàm起來", + "status.quote_error.not_available": "鋪文bē當看", + "status.quote_error.pending_approval": "鋪文當咧送", "status.read_more": "讀詳細", "status.reblog": "轉送", "status.reblog_private": "照原PO ê通看見ê範圍轉送", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 54d171d38d5..54a1eac1ba8 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "om een bericht te vertalen", "keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen", "keyboard_shortcuts.up": "Naar boven in de lijst bewegen", + "learn_more_link.got_it": "Begrepen", + "learn_more_link.learn_more": "Meer informatie", "lightbox.close": "Sluiten", "lightbox.next": "Volgende", "lightbox.previous": "Vorige", @@ -873,6 +875,11 @@ "status.open": "Volledig bericht tonen", "status.pin": "Aan profielpagina vastmaken", "status.quote_error.filtered": "Verborgen door een van je filters", + "status.quote_error.not_available": "Bericht niet beschikbaar", + "status.quote_error.pending_approval": "Bericht in afwachting", + "status.quote_error.pending_approval_popout.body": "Het kan even duren voordat citaten die in de Fediverse gedeeld worden, worden weergegeven. Omdat verschillende servers niet allemaal hetzelfde protocol gebruiken.", + "status.quote_error.pending_approval_popout.title": "Even geduld wanneer het citaat nog moet worden goedgekeurd.", + "status.quote_post_author": "Citeerde een bericht van @{name}", "status.read_more": "Meer lezen", "status.reblog": "Boosten", "status.reblog_private": "Boost naar oorspronkelijke ontvangers", diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 2fe619a60c7..026972b2256 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -2047,8 +2047,6 @@ ar: ownership: لا يمكن تثبيت منشور نشره شخص آخر reblog: لا يمكن تثبيت إعادة نشر quote_policies: - followers: المتابعين والمستخدمين المذكورين - nobody: المستخدمين المذكورين فقط public: الجميع title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/be.yml b/config/locales/be.yml index 601a81b6487..2855230b156 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1842,8 +1842,6 @@ be: ownership: Немагчыма замацаваць чужы допіс reblog: Немагчыма замацаваць пашырэнне quote_policies: - followers: Падпісчыкі і згаданыя карыстальнікі - nobody: Толькі згаданыя карыстальнікі public: Усе title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/bg.yml b/config/locales/bg.yml index cbb0b682fd3..077a729b919 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1857,8 +1857,6 @@ bg: ownership: Публикация на някого другиго не може да бъде закачена reblog: Раздуване не може да бъде закачано quote_policies: - followers: Последователи и споменати потребители - nobody: Само споменатите потребители public: Всеки title: "%{name}: „%{quote}“" visibilities: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index a3e62d7d721..2057e4cc3d5 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1878,8 +1878,8 @@ ca: ownership: No es pot fixar el tut d'algú altre reblog: No es pot fixar un impuls quote_policies: - followers: Seguidors i usuaris mencionats - nobody: Només usuaris mencionats + followers: Només els vostres seguidors + nobody: Ningú public: Tothom title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 7df98b90bbf..e4ebcd744c7 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -196,6 +196,7 @@ cs: create_relay: Vytvořit relay create_unavailable_domain: Vytvořit nedostupnou doménu create_user_role: Vytvořit roli + create_username_block: Vytvořit pravidlo pro uživatelská jména demote_user: Snížit roli uživatele destroy_announcement: Odstranit oznámení destroy_canonical_email_block: Odblokovat email @@ -209,6 +210,7 @@ cs: destroy_status: Odstranit Příspěvek destroy_unavailable_domain: Smazat nedostupnou doménu destroy_user_role: Zničit roli + destroy_username_block: Odstranit pravidlo pro uživatelská jména disable_2fa_user: Vypnout 2FA disable_custom_emoji: Zakázat vlastní emoji disable_relay: Deaktivovat relay @@ -243,6 +245,7 @@ cs: update_report: Upravit hlášení update_status: Aktualizovat Příspěvek update_user_role: Aktualizovat roli + update_username_block: Aktualizovat pravidlo pro uživatelská jména actions: approve_appeal_html: Uživatel %{name} schválil odvolání proti rozhodnutí moderátora %{target} approve_user_html: "%{name} schválil registraci od %{target}" @@ -261,6 +264,7 @@ cs: create_relay_html: "%{name} vytvořil*a relay %{target}" create_unavailable_domain_html: "%{name} zastavil doručování na doménu %{target}" create_user_role_html: "%{name} vytvořil %{target} roli" + create_username_block_html: "%{name} přidali pravidlo pro uživatelská jména obsahující %{target}" demote_user_html: Uživatel %{name} degradoval uživatele %{target} destroy_announcement_html: Uživatel %{name} odstranil oznámení %{target} destroy_canonical_email_block_html: "%{name} odblokoval*a e-mail s hashem %{target}" @@ -274,6 +278,7 @@ cs: destroy_status_html: Uživatel %{name} odstranil příspěvek uživatele %{target} destroy_unavailable_domain_html: "%{name} obnovil doručování na doménu %{target}" destroy_user_role_html: "%{name} odstranil %{target} roli" + destroy_username_block_html: "%{name} odstranili pravidlo pro uživatelská jména obsahující %{target}" disable_2fa_user_html: Uživatel %{name} vypnul dvoufázové ověřování pro uživatele %{target} disable_custom_emoji_html: Uživatel %{name} zakázal emoji %{target} disable_relay_html: "%{name} deaktivoval*a relay %{target}" @@ -308,6 +313,7 @@ cs: update_report_html: "%{name} aktualizoval hlášení %{target}" update_status_html: Uživatel %{name} aktualizoval příspěvek uživatele %{target} update_user_role_html: "%{name} změnil %{target} roli" + update_username_block_html: "%{name} aktualizovali pravidlo pro uživatelská jména obsahující %{target}" deleted_account: smazaný účet empty: Nebyly nalezeny žádné záznamy. filter_by_action: Filtrovat podle akce @@ -1121,6 +1127,25 @@ cs: other: Použit %{count} lidmi za poslední týden title: Doporučení & Trendy trending: Populární + username_blocks: + add_new: Přidat + block_registrations: Blokovat registrace + comparison: + contains: Obsahuje + equals: Rovná se + contains_html: Obsahuje %{string} + created_msg: Vytvořeno pravidlo pro uživatelská jména + delete: Smazat + edit: + title: Upravit pravidlo pro uživatelská jména + matches_exactly_html: Rovná se %{string} + new: + create: Vytvořit pravidlo + title: Vytvořit nové pravidlo pro uživatelská jména + no_username_block_selected: Nebyla změněna žádná pravidla pro uživatelská jména, protože žádná nebyla vybrána + not_permitted: Není povoleno + title: Pravidla uživatelských jmen + updated_msg: Pravidlo pro jména uživatelů bylo úspěšně aktualizováno warning_presets: add_new: Přidat nové delete: Smazat @@ -1966,8 +1991,8 @@ cs: ownership: Nelze připnout příspěvek někoho jiného reblog: Boosty nelze připnout quote_policies: - followers: Sledující a zmínění uživatelé - nobody: Pouze zmínění uživatelé + followers: Pouze vaši sledující + nobody: Nikdo public: Všichni title: "%{name}: „%{quote}“" visibilities: diff --git a/config/locales/cy.yml b/config/locales/cy.yml index ab45a600128..e8990d34820 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -2051,8 +2051,6 @@ cy: ownership: Nid oes modd pinio postiad rhywun arall reblog: Nid oes modd pinio hwb quote_policies: - followers: Dilynwyr a defnyddwyr wedi'u crybwyll - nobody: Dim ond defnyddwyr wedi'u crybwyll public: Pawb title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/da.yml b/config/locales/da.yml index 6f46fe6d5c6..67e5a716227 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1905,8 +1905,8 @@ da: ownership: Andres indlæg kan ikke fastgøres reblog: En fremhævelse kan ikke fastgøres quote_policies: - followers: Følgere og nævnte brugere - nobody: Kun nævnte brugere + followers: Kun dine følgere + nobody: Ingen public: Alle title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/de.yml b/config/locales/de.yml index 7b8594fcc5b..adef04dfa85 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1905,8 +1905,8 @@ de: ownership: Du kannst nur eigene Beiträge anheften reblog: Du kannst keine geteilten Beiträge anheften quote_policies: - followers: Follower und erwähnte Profile - nobody: Nur erwähnte Profile + followers: Nur meine Follower + nobody: Niemand public: Alle title: "%{name}: „%{quote}“" visibilities: diff --git a/config/locales/el.yml b/config/locales/el.yml index 00ef7fdd56f..73e46ee13c0 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1905,8 +1905,6 @@ el: ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου reblog: Οι ενισχύσεις δεν καρφιτσώνονται quote_policies: - followers: Ακόλουθοι και αναφερόμενοι χρήστες - nobody: Μόνο αναφερόμενοι χρήστες public: Όλοι title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index a65ee021318..564900027c1 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1879,8 +1879,6 @@ en-GB: ownership: Someone else's post cannot be pinned reblog: A boost cannot be pinned quote_policies: - followers: Followers and mentioned users - nobody: Only mentioned users public: Everyone title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 69c6c361bf2..f3e0b8186c6 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -1860,8 +1860,6 @@ eo: ownership: Mesaĝo de iu alia ne povas esti alpinglita reblog: Diskonigo ne povas esti alpinglita quote_policies: - followers: Sekvantoj kaj menciitaj uzantoj - nobody: Nur menciitaj uzantoj public: Ĉiuj title: "%{name}: “%{quote}”" visibilities: diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index c18172fccaa..f01db94590c 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1905,8 +1905,8 @@ es-AR: ownership: No se puede fijar el mensaje de otra cuenta reblog: No se puede fijar una adhesión quote_policies: - followers: Seguidores y usuarios mencionados - nobody: Solo usuarios mencionados + followers: Solo tus seguidores + nobody: Nadie public: Todos title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index c5309f22660..e7a6e3df629 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1905,8 +1905,8 @@ es-MX: ownership: La publicación de alguien más no puede fijarse reblog: No se puede fijar una publicación impulsada quote_policies: - followers: Seguidores y usuarios mencionados - nobody: Solo usuarios mencionados + followers: Solo tus seguidores + nobody: Nadie public: Cualquiera title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/es.yml b/config/locales/es.yml index 0481e473874..6c076d0b51d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1905,8 +1905,8 @@ es: ownership: La publicación de otra persona no puede fijarse reblog: Una publicación impulsada no puede fijarse quote_policies: - followers: Seguidores y usuarios mencionados - nobody: Solo usuarios mencionados + followers: Solo tus seguidores + nobody: Nadie public: Cualquiera title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/et.yml b/config/locales/et.yml index 2e9a7fbf74f..e9935cc72b5 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1890,8 +1890,6 @@ et: ownership: Kellegi teise postitust ei saa kinnitada reblog: Jagamist ei saa kinnitada quote_policies: - followers: Jälgijad ja mainitud kasutajad - nobody: Vaid mainitud kasutajad public: Kõik title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 95c163c3818..b777b05eb95 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1742,8 +1742,6 @@ eu: ownership: Ezin duzu beste norbaiten bidalketa bat finkatu reblog: Bultzada bat ezin da finkatu quote_policies: - followers: Jarraitzaileak eta aipatutako erabiltzaileak - nobody: Aipatutako erabiltzaileak soilik public: Guztiak title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/fa.yml b/config/locales/fa.yml index b9d87d600f2..64a29da0953 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -1880,8 +1880,6 @@ fa: ownership: نوشته‌های دیگران را نمی‌توان ثابت کرد reblog: تقویت نمی‌تواند سنجاق شود quote_policies: - followers: پی‌گیران و کاربران اشاره شده - nobody: فقط کاربران اشاره شده public: هرکسی title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/fi.yml b/config/locales/fi.yml index dc4a98c49bf..6d7773a1f64 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1903,8 +1903,8 @@ fi: ownership: Muiden julkaisuja ei voi kiinnittää reblog: Tehostusta ei voi kiinnittää quote_policies: - followers: Seuraajat ja mainitut käyttäjät - nobody: Vain mainitut käyttäjät + followers: Vain seuraajasi + nobody: Ei kukaan public: Kaikki title: "%{name}: ”%{quote}”" visibilities: diff --git a/config/locales/fo.yml b/config/locales/fo.yml index a63b39b5139..2dd500d694d 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1905,8 +1905,6 @@ fo: ownership: Postar hjá øðrum kunnu ikki festast reblog: Ein stimbran kann ikki festast quote_policies: - followers: Fylgjarar og nevndir brúkarar - nobody: Bara nevndir brúkarar public: Øll title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index a866debe453..38de6ee8612 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1852,8 +1852,6 @@ fr-CA: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé - quote_policies: - followers: Abonné·e·s et utilisateur·trice·s mentionné·e·s title: "%{name} : « %{quote} »" visibilities: direct: Direct diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c171a9ed731..da5224fcf5e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1852,8 +1852,6 @@ fr: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé - quote_policies: - followers: Abonné·e·s et utilisateur·trice·s mentionné·e·s title: "%{name} : « %{quote} »" visibilities: direct: Direct diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 05b5b2fa39f..db7816996a0 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1905,8 +1905,6 @@ fy: ownership: In berjocht fan in oar kin net fêstmakke wurde reblog: In boost kin net fêstset wurde quote_policies: - followers: Folgers en fermelde brûkers - nobody: Allinnich fermelde brûkers public: Elkenien title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 8fff68b3d97..5e7da40a585 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -199,6 +199,7 @@ ga: create_relay: Cruthaigh Leaschraolacháin create_unavailable_domain: Cruthaigh Fearann ​​Gan Fáil create_user_role: Cruthaigh Ról + create_username_block: Cruthaigh Riail Ainm Úsáideora demote_user: Ísligh úsáideoir destroy_announcement: Scrios Fógra destroy_canonical_email_block: Scrios Bloc Ríomhphoist @@ -212,6 +213,7 @@ ga: destroy_status: Scrios Postáil destroy_unavailable_domain: Scrios Fearann ​​Gan Fáil destroy_user_role: Scrios ról + destroy_username_block: Scrios an Riail Ainm Úsáideora disable_2fa_user: Díchumasaigh 2FA disable_custom_emoji: Díchumasaigh Emoji Saincheaptha disable_relay: Díchumasaigh Leaschraolacháin @@ -246,6 +248,7 @@ ga: update_report: Tuairisc Nuashonraithe update_status: Nuashonraigh Postáil update_user_role: Nuashonraigh Ról + update_username_block: Nuashonraigh an Riail Ainm Úsáideora actions: approve_appeal_html: Cheadaigh %{name} achomharc ar chinneadh modhnóireachta ó %{target} approve_user_html: Cheadaigh %{name} clárú ó %{target} @@ -264,6 +267,7 @@ ga: create_relay_html: Chruthaigh %{name} athsheoladh %{target} create_unavailable_domain_html: Chuir %{name} deireadh leis an seachadadh chuig fearann ​​%{target} create_user_role_html: Chruthaigh %{name} %{target} ról + create_username_block_html: Chuir %{name} riail leis d'ainmneacha úsáideora ina bhfuil %{target} 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}" @@ -277,6 +281,7 @@ ga: destroy_status_html: Bhain %{name} postáil le %{target} destroy_unavailable_domain_html: D'athchrom %{name} ar an seachadadh chuig fearann ​​%{target} destroy_user_role_html: Scrios %{name} ról %{target} + destroy_username_block_html: Bhain %{name} riail as ainmneacha úsáideora ina bhfuil %{target} disable_2fa_user_html: Dhíchumasaigh %{name} riachtanas dhá fhachtóir don úsáideoir %{target} disable_custom_emoji_html: Dhíchumasaigh %{name} emoji %{target} disable_relay_html: Dhíchumasaigh %{name} an athsheoladh %{target} @@ -311,6 +316,7 @@ ga: update_report_html: "%{name} tuairisc nuashonraithe %{target}" update_status_html: "%{name} postáil nuashonraithe faoi %{target}" update_user_role_html: D'athraigh %{name} ról %{target} + update_username_block_html: Nuashonraíodh riail %{name} le haghaidh ainmneacha úsáideora ina bhfuil %{target} deleted_account: cuntas scriosta empty: Níor aimsíodh aon logaí. filter_by_action: Scag de réir gnímh @@ -1139,6 +1145,25 @@ ga: two: Úsáidte ag %{count} duine le seachtain anuas title: Moltaí & Treochtaí trending: Treocht + username_blocks: + add_new: Cuir nua leis + block_registrations: Clárúcháin blocála + comparison: + contains: Ina bhfuil + equals: Cothrom le + contains_html: Tá %{string} ann + created_msg: Cruthaíodh riail ainm úsáideora go rathúil + delete: Scrios + edit: + title: Cuir riail ainm úsáideora in eagar + matches_exactly_html: Cothrom le %{string} + new: + create: Cruthaigh riail + title: Cruthaigh riail ainm úsáideora nua + no_username_block_selected: Níor athraíodh aon rialacha ainm úsáideora mar nár roghnaíodh aon cheann acu + not_permitted: Ní cheadaítear + title: Rialacha ainm úsáideora + updated_msg: Nuashonraíodh an riail ainm úsáideora go rathúil warning_presets: add_new: Cuir nua leis delete: Scrios @@ -2009,8 +2034,8 @@ ga: ownership: Ní féidir postáil duine éigin eile a phionnáil reblog: Ní féidir treisiú a phinnáil quote_policies: - followers: Leantóirí agus úsáideoirí luaite - nobody: Úsáideoirí luaite amháin + followers: Do leanúna amháin + nobody: Aon duine public: Gach duine title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/gd.yml b/config/locales/gd.yml index f7791fe3544..fec5df06981 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -1961,8 +1961,6 @@ gd: ownership: Chan urrainn dhut post càich a phrìneachadh reblog: Chan urrainn dhut brosnachadh a phrìneachadh quote_policies: - followers: Luchd-leantainn ’s cleachdaichean le iomradh orra - nobody: Cleachdaichean le iomradh orra a-mhàin public: A h-uile duine title: "%{name}: “%{quote}”" visibilities: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ceace60dbc8..4a2f593d09e 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1905,8 +1905,8 @@ gl: ownership: Non podes fixar a publicación doutra usuaria reblog: Non se poden fixar as mensaxes promovidas quote_policies: - followers: Seguidoras e usuarias mencionadas - nobody: Só usuarias mencionadas + followers: Só para seguidoras + nobody: Ninguén public: Calquera title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/he.yml b/config/locales/he.yml index 737d7464b26..53f42bfa3bc 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1991,8 +1991,8 @@ he: ownership: הודעות של אחרים לא יכולות להיות מוצמדות reblog: אין אפשרות להצמיד הדהודים quote_policies: - followers: עוקבים ומאוזכרים - nobody: רק מאוזכרים ומאוזכרות + followers: לעוקביך בלבד + nobody: אף אחד public: כולם title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/hu.yml b/config/locales/hu.yml index d088cb3b6e8..4e8f9e80513 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1905,8 +1905,6 @@ hu: ownership: Nem tűzheted ki valaki más bejegyzését reblog: Megtolt bejegyzést nem tudsz kitűzni quote_policies: - followers: Követők és említett felhasználók - nobody: Csak említett felhasználók public: Mindenki title: "%{name}: „%{quote}”" visibilities: diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 35dd56aad1d..f2b00a0e555 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -1832,8 +1832,6 @@ ia: ownership: Le message de alcuno altere non pote esser appunctate reblog: Un impulso non pote esser affixate quote_policies: - followers: Sequitores e usatores mentionate - nobody: Solmente usatores mentionate public: Omnes title: "%{name}: “%{quote}”" visibilities: diff --git a/config/locales/is.yml b/config/locales/is.yml index 11c8a7f568b..5feccdc9bd6 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1909,8 +1909,8 @@ is: ownership: Færslur frá einhverjum öðrum er ekki hægt að festa reblog: Ekki er hægt að festa endurbirtingu quote_policies: - followers: Fylgjendur og notendur sem minnst er á - nobody: Einungis notendur sem minnst er á + followers: Einungis þeir sem fylgjast með þér + nobody: Enginn public: Allir title: "%{name}: „%{quote}‟" visibilities: diff --git a/config/locales/it.yml b/config/locales/it.yml index c9d04bc33d5..cef3224f63a 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1907,8 +1907,6 @@ it: ownership: Non puoi fissare in cima un post di qualcun altro reblog: Un toot condiviso non può essere fissato in cima quote_policies: - followers: Seguaci e utenti menzionati - nobody: Solo gli utenti menzionati public: Tutti title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index f3c4d3089ac..43433b7934c 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1825,8 +1825,6 @@ ja: ownership: 他人の投稿を固定することはできません reblog: ブーストを固定することはできません quote_policies: - followers: フォロワーとメンションされたユーザー - nobody: メンションされたユーザーのみ public: 全員 title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index a3ae182a5c4..52825685013 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1844,8 +1844,6 @@ ko: ownership: 다른 사람의 게시물은 고정될 수 없습니다 reblog: 부스트는 고정될 수 없습니다 quote_policies: - followers: 팔로워와 멘션된 사람들만 - nobody: 멘션된 사람들만 public: 모두 title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/lv.yml b/config/locales/lv.yml index f7db52a40f0..da04f36494c 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1905,8 +1905,6 @@ lv: ownership: Kāda cita ierakstu nevar piespraust reblog: Pastiprinātu ierakstu nevar piespraust quote_policies: - followers: Sekotāji un pieminētie lietotāji - nobody: Tikai pieminētie lietotāji public: Visi title: "%{name}: “%{quote}”" visibilities: diff --git a/config/locales/nan.yml b/config/locales/nan.yml index 007ca847a81..f6c4f0dc383 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -187,6 +187,7 @@ nan: create_relay: 建立中繼 create_unavailable_domain: 建立bē當用ê域名 create_user_role: 建立角色 + create_username_block: 新造使用者號名規則 demote_user: Kā用者降級 destroy_announcement: Thâi掉公告 destroy_canonical_email_block: Thâi掉電子phue ê封鎖 @@ -200,6 +201,7 @@ nan: destroy_status: Thâi掉PO文 destroy_unavailable_domain: Thâi掉bē當用ê域名 destroy_user_role: Thâi掉角色 + destroy_username_block: 共使用者號名規則刣掉 disable_2fa_user: 停止用雙因素認證 disable_custom_emoji: 停止用自訂ê Emoji disable_relay: 停止用中繼 @@ -234,6 +236,7 @@ nan: update_report: 更新檢舉 update_status: 更新PO文 update_user_role: 更新角色 + update_username_block: 更新使用者號名規則 actions: approve_appeal_html: "%{name} 允准 %{target} 所寫ê tuì審核決定ê投訴" approve_user_html: "%{name} 允准 %{target} ê 註冊" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 8e9cbaabbd7..85fe706c44c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1092,7 +1092,7 @@ nl: title: Aanbevelingen & trends trending: Trending username_blocks: - add_new: Nieuw toevoegen + add_new: Nieuwe toevoegen block_registrations: Registraties blokkeren comparison: contains: Bevat @@ -1905,8 +1905,8 @@ nl: ownership: Een bericht van iemand anders kan niet worden vastgemaakt reblog: Een boost kan niet worden vastgezet quote_policies: - followers: Volgers en vermelde gebruikers - nobody: Alleen vermelde gebruikers + followers: Alleen jouw volgers + nobody: Niemand public: Iedereen title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 7e3b453371f..14c26ea74a0 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1875,8 +1875,6 @@ nn: ownership: Du kan ikkje festa andre sine tut reblog: Ei framheving kan ikkje festast quote_policies: - followers: Tilhengjarar og nemnde folk - nobody: Berre nemnde folk public: Alle title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 1e630275ce8..a4d5184f37e 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -1870,8 +1870,6 @@ pt-BR: ownership: As publicações dos outros não podem ser fixadas reblog: Um impulso não pode ser fixado quote_policies: - followers: Seguidores e usuários mencionados - nobody: Apenas usuários mencionados public: Todos title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 1e988b5baed..e8c357ee0fa 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1880,8 +1880,6 @@ pt-PT: ownership: Não podem ser fixadas publicações de outras pessoas reblog: Não é possível fixar um impulso quote_policies: - followers: Seguidores e utilizadores mencionados - nobody: Apenas utilizadores mencionados public: Todos title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b04e99f5fed..c825cac33dd 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1776,40 +1776,40 @@ ru: prompt: Переходите по ссылке только в том случае, если доверяете сайту, на который она ведёт. title: Вы покидаете %{instance}. relationships: - activity: Активность учётной записи - confirm_follow_selected_followers: Вы уверены, что хотите подписаться на выбранных подписчиков? - confirm_remove_selected_followers: Вы уверены, что хотите удалить выбранных подписчиков? - confirm_remove_selected_follows: Вы уверены, что хотите удалить выбранные подписки? - dormant: Заброшенная + activity: Фильтр по активности + confirm_follow_selected_followers: Вы уверены, что хотите взаимно подписаться на выбранных пользователей? + confirm_remove_selected_followers: Вы уверены, что хотите убрать выбранных пользователей из подписчиков? + confirm_remove_selected_follows: Вы уверены, что хотите отписаться от выбранных пользователей? + dormant: Неактивные follow_failure: Не удалось подписаться за некоторыми из выбранных аккаунтов. - follow_selected_followers: Подписаться на выбранных подписчиков + follow_selected_followers: Подписаться в ответ followers: Подписчики following: Подписки invited: Приглашённые - last_active: По последней активности - most_recent: По недавности - moved: Мигрировавшая + last_active: Сначала активные + most_recent: Сначала новые + moved: Перенаправленные mutual: Взаимные подписки - primary: Основная - relationship: Связь - remove_selected_domains: Удалить всех подписчиков для выбранных доменов - remove_selected_followers: Удалить выбранных подписчиков + primary: Действующие + relationship: Вид отношений + remove_selected_domains: Убрать всех подписчиков с того же сервера + remove_selected_followers: Убрать из подписчиков remove_selected_follows: Отписаться от выбранных пользователей - status: Статус учётной записи + status: Фильтр по состоянию учётной записи remote_follow: missing_resource: Поиск требуемого перенаправления URL для Вашей учётной записи завершился неудачей reports: errors: - invalid_rules: не ссылается на действительные правила + invalid_rules: должны соответствовать правилам сервера rss: content_warning: 'Предупреждение о содержании:' descriptions: account: Публичные посты @%{acct} - tag: 'Публичные посты отмеченные хэштегом #%{hashtag}' + tag: 'Публичные посты с хештегом #%{hashtag}' scheduled_statuses: - over_daily_limit: Вы превысили лимит в %{limit} запланированных постов на указанный день - over_total_limit: Вы превысили лимит на %{limit} запланированных постов - too_soon: дата публикации должна быть в будущем + over_daily_limit: За сутки можно создать не более %{limit} отложенных постов + over_total_limit: Всего можно создать не более %{limit} отложенных постов + too_soon: задано слишком рано self_destruct: lead_html: К сожалению, %{domain} закрывается навсегда. Если вас учётная запись находиться здесь вы не сможете продолжить использовать его, но вы можете запросить резервную копию ваших данных. title: Этот сервер закрывается @@ -1818,13 +1818,13 @@ ru: browser: Браузер browsers: alipay: Alipay - blackberry: Blackberry + blackberry: BlackBerry chrome: Chrome edge: Microsoft Edge electron: Electron firefox: Firefox generic: Неизвестный браузер - huawei_browser: Huawei Browser + huawei_browser: Браузер Huawei ie: Internet Explorer micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser @@ -1836,10 +1836,10 @@ ru: uc_browser: UC Browser unknown_browser: Неизвестный браузер weibo: Weibo - current_session: Текущая сессия + current_session: Текущий сеанс date: Дата - description: "%{browser} на %{platform}" - explanation: Здесь отображаются все браузеры, с которых выполнен вход в вашу учётную запись. Авторизованные приложения находятся в секции «Приложения». + description: "%{platform}, %{browser}" + explanation: Здесь перечислены все устройства, на которых вы используете свою учётную запись Mastodon. Также вы можете ip: IP platforms: adobe_air: Adobe Air @@ -1848,46 +1848,46 @@ ru: chrome_os: ChromeOS firefox_os: Firefox OS ios: iOS - kai_os: OS Кай + kai_os: KaiOS linux: Linux - mac: Mac + mac: macOS unknown_platform: Неизвестная платформа windows: Windows windows_mobile: Windows Mobile windows_phone: Windows Phone revoke: Завершить - revoke_success: Сессия завершена - title: Сессии - view_authentication_history: Посмотреть историю входов с учётной записью + revoke_success: Сеанс завершён + title: Сеансы + view_authentication_history: просмотреть историю входов в вашу учётную запись settings: account: Учётная запись - account_settings: Управление учётной записью - aliases: Псевдонимы учётной записи + account_settings: Настройки учётной записи + aliases: Связанные учётные записи appearance: Внешний вид authorized_apps: Приложения back: Назад в Mastodon delete: Удаление учётной записи development: Разработчикам - edit_profile: Изменить профиль + edit_profile: " Данные профиля" export: Экспорт - featured_tags: Избранные хэштеги + featured_tags: Рекомендации хештегов import: Импорт import_and_export: Импорт и экспорт - migrate: Миграция учётной записи - notifications: E-mail уведомление + migrate: Настройка перенаправления + notifications: Уведомления по эл. почте preferences: Настройки profile: Профиль relationships: Подписки и подписчики severed_relationships: Разорванные отношения - statuses_cleanup: Авто-удаление постов + statuses_cleanup: Автоудаление постов strikes: Замечания модерации two_factor_authentication: Подтверждение входа webauthn_authentication: Электронные ключи severed_relationships: download: Скачать (%{count}) event_type: - account_suspension: Приостановка аккаунта (%{target_name}) - domain_block: Приостановка сервера (%{target_name}) + account_suspension: Пользователь был заблокирован модераторами (%{target_name}) + domain_block: Сервер был заблокирован модераторами (%{target_name}) user_domain_block: Вы заблокировали %{target_name} lost_followers: Потерянные подписчики lost_follows: Потерянные подписки diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 1bb2128f19b..ba1f3c12189 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -56,7 +56,6 @@ ar: scopes: ما هي المجالات المسموح بها في التطبيق ؟ إن قمت باختيار أعلى المجالات فيمكنك الاستغناء عن الخَيار اليدوي. setting_aggregate_reblogs: لا تقم بعرض المشارَكات الجديدة لمنشورات قد قُمتَ بمشاركتها سابقا (هذا الإجراء يعني المشاركات الجديدة فقط التي تلقيتَها) setting_always_send_emails: عادة لن تُرسَل إليك إشعارات البريد الإلكتروني عندما تكون نشطًا على ماستدون - setting_default_quote_policy: يسمح بالاقتباس دائما للمستخدمين المذكورين. هذا الإعداد سوف يكون نافذ المفعول فقط للمشاركات التي تم إنشاؤها مع إصدار ماستدون القادم، ولكن يمكنك تحديد تفضيلاتك للإعداد لذلك setting_default_sensitive: تُخفى الوسائط الحساسة تلقائيا ويمكن اظهارها عن طريق النقر عليها setting_display_media_default: إخفاء الوسائط المُعيَّنة كحساسة setting_display_media_hide_all: إخفاء كافة الوسائط دائمًا diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml index e43970dba98..928b17ed1d1 100644 --- a/config/locales/simple_form.bg.yml +++ b/config/locales/simple_form.bg.yml @@ -56,7 +56,6 @@ bg: scopes: Указва до кои API има достъп приложението. Ако изберете диапазон от най-високо ниво, няма нужда да избирате индивидуални. setting_aggregate_reblogs: Без показване на нови подсилвания за публикации, които са неотдавна подсилени (засяга само новополучени подсилвания) setting_always_send_emails: Обикновено известията по имейл няма да са изпратени при дейна употреба на Mastodon - setting_default_quote_policy: Споменатите потребители винаги им е позволено да цитират. Тази настройка ще се отрази за публикациите, създадени със следващата версия на Mastodon, но може да изберете предпочитанията си в подготовката setting_default_sensitive: Деликатната мултимедия е скрита по подразбиране и може да се разкрие с едно щракване setting_display_media_default: Скриване на мултимедия отбелязана като деликатна setting_display_media_hide_all: Винаги скриване на мултимедията diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 7ad6a71dd1e..64958711dab 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -56,7 +56,6 @@ ca: scopes: API permeses per a accedir a l'aplicació. Si selecciones un àmbit de nivell superior, no cal que en seleccionis un d'individual. setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts) setting_always_send_emails: Normalment, no s'enviarà cap notificació per correu electrònic mentre facis servir Mastodon - setting_default_quote_policy: Els usuaris mencionats sempre poden citar. Aquesta configuració només tindrà efecte en les publicacions creades amb la següent versió de Mastodon, però podeu seleccionar-ho en preparació setting_default_sensitive: El contingut sensible està ocult per defecte i es pot mostrar fent-hi clic setting_display_media_default: Amaga el contingut gràfic marcat com a sensible setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 9ee28a9b9f1..5ca0de9dc82 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -56,7 +56,7 @@ cs: scopes: Která API bude aplikace moct používat. Pokud vyberete rozsah nejvyššího stupně, nebudete je muset vybírat jednotlivě. setting_aggregate_reblogs: Nezobrazovat nové boosty pro příspěvky, které byly nedávno boostnuty (ovlivňuje pouze nově přijaté boosty) setting_always_send_emails: Jinak nebudou e-mailové notifikace posílány, když Mastodon aktivně používáte - setting_default_quote_policy: Zmínení uživatelé mají vždy povoleno citovat. Toto nastavení se projeví pouze u příspěvků vytvořených s další verzí Mastodonu, ale můžete si vybrat vaše předvolby v předstihu + setting_default_quote_policy: Toto nastavení se projeví pouze u příspěvků vytvořených s další verzí Mastodon, ale svou předvolbu si můžete vybrat předem. setting_default_sensitive: Citlivá média jsou ve výchozím stavu skryta a mohou být zobrazena kliknutím setting_display_media_default: Skrývat média označená jako citlivá setting_display_media_hide_all: Vždy skrývat média @@ -162,6 +162,10 @@ cs: name: Veřejný název role, pokud má být role zobrazena jako odznak permissions_as_keys: Uživatelé s touto rolí budou moci... position: Vyšší role rozhoduje o řešení konfliktů v určitých situacích. Některé akce lze provádět pouze na rolích s nižší prioritou + username_block: + allow_with_approval: Namísto toho, aby se zabránilo registraci, bude vyžadováno vaše schválení + comparison: Mějte prosím na paměti 'Scunthorpe problém' při blokování částečných shod + username: Bude součástí shod bez ohledu na kapitalizace a běžné homoglyfy jako "4" pro "a" nebo "3" pro "e" webhook: events: Zvolte odesílané události template: Sestavte si vlastní JSON payload pomocí interpolace proměnných. Pro výchozí JSON ponechte prázdné. @@ -373,6 +377,10 @@ cs: name: Název permissions_as_keys: Oprávnění position: Priorita + username_block: + allow_with_approval: Povolit registrace se schválením + comparison: Srovnávací metoda + username: Na základě slov webhook: events: Zapnuté události template: Šablona payloadu diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index a1390a3cc62..260d7530005 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -56,7 +56,6 @@ cy: scopes: Pa APIs y bydd y rhaglen yn cael mynediad iddynt. Os dewiswch gwmpas lefel uchaf, nid oes angen i chi ddewis rhai unigol. setting_aggregate_reblogs: Peidiwch â dangos hybiau newydd ar bostiadau sydd wedi cael eu hybu'n ddiweddar (dim ond yn effeithio ar hybiau newydd ei dderbyn) setting_always_send_emails: Fel arfer ni fydd hysbysiadau e-bost yn cael eu hanfon pan fyddwch chi wrthi'n defnyddio Mastodon - setting_default_quote_policy: Mae defnyddwyr sy'n cael eu crybwyll yn cael dyfynnu bob amser. Dim ond ar gyfer postiadau a grëwyd gyda'r fersiwn nesaf o Mastodon y bydd y gosodiad hwn yn dod i rym, ond gallwch ddewis eich dewis wrth baratoi setting_default_sensitive: Mae cyfryngau sensitif wedi'u cuddio yn rhagosodedig a gellir eu datgelu trwy glicio setting_display_media_default: Cuddio cyfryngau wedi eu marcio'n sensitif setting_display_media_hide_all: Cuddio cyfryngau bob tro diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 72f98477b71..98412270e98 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -56,7 +56,7 @@ da: scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige. setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser) setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon - setting_default_quote_policy: Nævnte brugere har altid lov til at citere. Denne indstilling vil kun træde i kraft for indlæg oprettet med den næste Mastodon-version, men du kan som forberedelse vælge din præference + setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg, der oprettes med den næste Mastodon-version, men du kan vælge din præference som forberedelse. setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_hide_all: Skjul altid medier diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index a6b1bc4f9a3..998befb1113 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -56,7 +56,7 @@ de: scopes: Welche Schnittstellen der Applikation erlaubt sind. Wenn du einen Top-Level-Scope auswählst, dann musst du nicht jeden einzelnen darunter auswählen. setting_aggregate_reblogs: Beiträge, die erst kürzlich geteilt wurden, werden nicht noch einmal angezeigt (betrifft nur zukünftig geteilte Beiträge) setting_always_send_emails: Normalerweise werden Benachrichtigungen nicht per E-Mail versendet, wenn du gerade auf Mastodon aktiv bist - setting_default_quote_policy: Erwähnte Profile dürfen immer zitieren. Diese Einstellung gilt nur für Beiträge, die mit der zukünftigen Mastodon-Version erstellt wurden. Als Vorbereitung darauf kannst du bereits jetzt die Einstellung vornehmen + setting_default_quote_policy: Diese Einstellung gilt nur für Beiträge, die mit der zukünftigen Mastodon-Version erstellt wurden. Als Vorbereitung darauf kannst du bereits jetzt die Einstellung vornehmen. setting_default_sensitive: Medien, die mit einer Inhaltswarnung versehen worden sind, werden – je nach Einstellung – erst nach einem zusätzlichen Klick angezeigt setting_display_media_default: Medien mit Inhaltswarnung ausblenden setting_display_media_hide_all: Medien immer ausblenden diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 47dc24583ef..717ed59b1da 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -56,7 +56,6 @@ el: scopes: Ποια API θα επιτρέπεται στην εφαρμογή να χρησιμοποιήσεις. Αν επιλέξεις κάποιο υψηλό εύρος εφαρμογής, δε χρειάζεται να επιλέξεις και το καθένα ξεχωριστά. setting_aggregate_reblogs: Απόκρυψη των νέων αναρτήσεων για τις αναρτήσεις που έχουν ενισχυθεί πρόσφατα (επηρεάζει μόνο τις νέες ενισχύσεις) setting_always_send_emails: Κανονικά οι ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου δεν θα αποστέλλονται όταν χρησιμοποιείτε ενεργά το Mastodon - setting_default_quote_policy: Οι αναφερόμενοι χρήστες επιτρέπεται πάντα να παραθέτουν. Αυτή η ρύθμιση θα τεθεί σε ισχύ μόνο για αναρτήσεις που δημιουργήθηκαν με την επόμενη έκδοση Mastodon, αλλά μπορείς να επιλέξετε την προτίμησή σου κατά την προετοιμασία setting_default_sensitive: Τα ευαίσθητα πολυμέσα είναι κρυμμένα και εμφανίζονται με ένα κλικ setting_display_media_default: Απόκρυψη ευαίσθητων πολυμέσων setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων diff --git a/config/locales/simple_form.en-GB.yml b/config/locales/simple_form.en-GB.yml index c61acd3248b..494f0c7b2a6 100644 --- a/config/locales/simple_form.en-GB.yml +++ b/config/locales/simple_form.en-GB.yml @@ -56,7 +56,6 @@ en-GB: scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon - setting_default_quote_policy: Mentioned users are always allowed to quote. This setting will only take effect for posts created with the next Mastodon version, but you can select your preference in preparation setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_display_media_default: Hide media marked as sensitive setting_display_media_hide_all: Always hide media diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index b1b89124d80..0b7dde5cb8c 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -56,7 +56,7 @@ es-AR: scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionás el alcance de nivel más alto, no necesitás seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevas adhesiones de los mensajes que fueron recientemente adheridos (sólo afecta a las adhesiones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente - setting_default_quote_policy: Los usuarios mencionados siempre pueden citar. Este ajuste solo afecta a las publicaciones creadas con la próxima versión de Mastodon, pero podés seleccionar tus preferencias con antelación. + setting_default_quote_policy: Este ajuste solo tendrá efecto en mensajes creados usando la próxima versión mayor de Mastodon, pero podés configurarlo con anticipación. setting_default_sensitive: El contenido de medios sensibles está oculto predeterminadamente y puede ser mostrado con un clic setting_display_media_default: Ocultar medios marcados como sensibles setting_display_media_hide_all: Siempre ocultar todos los medios diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index dd57c522a11..0aee8fab9d8 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -56,7 +56,7 @@ es-MX: scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente - setting_default_quote_policy: Los usuarios mencionados siempre pueden citar. Esta configuración solo se aplicará a las publicaciones creadas con la próxima versión de Mastodon, pero puedes seleccionar tus preferencias anticipadamente + setting_default_quote_policy: Este ajuste solo tendrá efecto en publicaciones creadas con la próxima versión de Mastodon, pero puedes configurarlo previamente. setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un clic setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index 6bef4ba62d9..ce2a74c3e10 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -56,7 +56,7 @@ es: scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a los impulsos recibidos recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente - setting_default_quote_policy: Los usuarios mencionados siempre pueden citar. Este ajuste solo afecta a las publicaciones creadas con la próxima versión de Mastodon, pero puedes seleccionar tus preferencias anticipadamente + setting_default_quote_policy: Este ajuste solo tendrá efecto en publicaciones creadas con la próxima versión de Mastodon, pero puedes configurarlo previamente. setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml index 239591e05f9..be04b7b0267 100644 --- a/config/locales/simple_form.eu.yml +++ b/config/locales/simple_form.eu.yml @@ -56,7 +56,6 @@ eu: scopes: Zeintzuk API atzitu ditzakeen aplikazioak. Goi mailako arloa aukeratzen baduzu, ez dituzu azpikoak aukeratu behar. setting_aggregate_reblogs: Ez erakutsi bultzada berriak berriki bultzada jaso duten tootentzat (berriki jasotako bultzadei eragiten die bakarrik) setting_always_send_emails: Normalean eposta jakinarazpenak ez dira bidaliko Mastodon aktiboki erabiltzen ari zaren bitartean - setting_default_quote_policy: Aipaturiko erabiltzaileek beti dute aipatzeko baimena. Ezarpen honek Mastodon-en hurrengo bertsioarekin sortutako argitalpenetan bakarrik izango du eragina, baina prestatzean lehentasuna hauta dezakezu setting_default_sensitive: Multimedia hunkigarria lehenetsita ezkutatzen da, eta sakatuz ikusi daiteke setting_display_media_default: Ezkutatu hunkigarri gisa markatutako multimedia setting_display_media_hide_all: Ezkutatu multimedia guztia beti diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index bc7c4703da0..e96f7d43db5 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -56,7 +56,6 @@ fa: scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید. setting_aggregate_reblogs: برای تقویت‌هایی که به تازگی برایتان نمایش داده شده‌اند، تقویت‌های بیشتر را نمایش نده (فقط روی تقویت‌های اخیر تأثیر می‌گذارد) setting_always_send_emails: در حالت عادی آگاهی‌های رایانامه‌ای هنگامی که فعّالانه از ماستودون استفاده می‌کنید فرستاده نمی‌شوند - setting_default_quote_policy: کاربران اشاره شده همواره مجاز به نقلند. این تنظیمات تنها روی فرسته‌های ایجاد شده با نگارش بعدی ماستودون موثّر است، ولی می‌توانید ترجیحاتتان را پیشاپیش بگزینید setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 7d6c97d8deb..5edb75cdb90 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -56,7 +56,7 @@ fi: scopes: Mihin ohjelmointirajapintoihin sovelluksella on pääsy. Jos valitset ylätason käyttöoikeuden, sinun ei tarvitse valita yksittäisiä. setting_aggregate_reblogs: Älä näytä uusia tehostuksia julkaisuille, joita on äskettäin tehostettu (koskee vain juuri vastaanotettuja tehostuksia) setting_always_send_emails: Yleensä sähköposti-ilmoituksia ei lähetetä, kun käytät Mastodonia aktiivisesti - setting_default_quote_policy: Mainitut käyttäjät saavat aina lainata. Tämä asetus koskee vain julkaisuja, jotka on luotu seuraavalla Mastodon-versiolla, mutta voit valita asetuksesi valmistautuaksesi + setting_default_quote_policy: Tämä asetus tulee voimaan vain julkaisuissa, jotka on luotu seuraavalla Mastodon-versiolla, mutta voit valita asetuksesi jo etukäteen. setting_default_sensitive: Arkaluonteinen media piilotetaan oletusarvoisesti, ja se voidaan näyttää yhdellä napsautuksella setting_display_media_default: Piilota arkaluonteiseksi merkitty mediasisältö setting_display_media_hide_all: Piilota mediasisältö aina diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index acbd35b9874..b9176cb6399 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -56,7 +56,6 @@ fo: scopes: Hvørji API nýtsluskipanin fær atgongd til. Velur tú eitt vav á hægsta stigi, so er ikki neyðugt at velja tey einstøku. setting_aggregate_reblogs: Vís ikki nýggjar stimbranir fyri postar, sum nýliga eru stimbraðir (ávirkar einans stimbranir, ið eru móttiknar fyri kortum) setting_always_send_emails: Vanliga vera teldupostfráboðanir ikki sendar, tá tú virkin brúkar Mastodon - setting_default_quote_policy: Nevndir brúkarar hava altíð loyvi at sitera. Hendan stillingin verður bara virkin fyri postar, sum verða stovnaðir í næstu Mastodon útgávuni, men sum fyrireiking til tað, kanst tú velja tína stilling longu nú setting_default_sensitive: Viðkvæmar miðlafílur eru fjaldar og kunnu avdúkast við einum klikki setting_display_media_default: Fjal miðlafílur, sum eru merktar sum viðkvæmar setting_display_media_hide_all: Fjal altíð miðlafílur diff --git a/config/locales/simple_form.fy.yml b/config/locales/simple_form.fy.yml index 584da81f3d9..690522b8cf6 100644 --- a/config/locales/simple_form.fy.yml +++ b/config/locales/simple_form.fy.yml @@ -56,7 +56,6 @@ fy: scopes: Ta hokker API’s hat de tapassing tagong. Wannear’t jo in tastimming fan it boppeste nivo kieze, hoege jo gjin yndividuele tastimmingen mear te kiezen. setting_aggregate_reblogs: Gjin nije boosts toane foar berjochten dy’t resintlik noch boost binne (hat allinnich effekt op nij ûntfongen boosts) setting_always_send_emails: Normaliter wurde der gjin e-mailmeldingen ferstjoerd wannear’t jo aktyf Mastodon brûke - setting_default_quote_policy: It is foar brûkers dy’t fermeld wurde altyd tastien om te sitearjen. Dizze ynstelling is allinnich fan tapassing foar berjochten dy’t makke binne mei de folgjende Mastodon-ferzje, mar jo kinne jo foarkar no al ynstelle setting_default_sensitive: Gefoelige media wurdt standert ferstoppe en kin mei ien klik toand wurde setting_display_media_default: As gefoelich markearre media ferstopje setting_display_media_hide_all: Media altyd ferstopje diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index 59cd532fd99..0f8c8c3a703 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -56,7 +56,7 @@ ga: scopes: Cé na APIanna a mbeidh cead ag an bhfeidhmchlár rochtain a fháil orthu. Má roghnaíonn tú raon feidhme barrleibhéil, ní gá duit cinn aonair a roghnú. setting_aggregate_reblogs: Ná taispeáin treisithe nua do phoist a treisíodh le déanaí (ní dhéanann difear ach do threisithe nuafhaighte) setting_always_send_emails: Go hiondúil ní sheolfar fógraí ríomhphoist agus tú ag úsáid Mastodon go gníomhach - setting_default_quote_policy: Ceadaítear d’úsáideoirí luaite lua a dhéanamh i gcónaí. Ní bheidh an socrú seo i bhfeidhm ach amháin maidir le poist a cruthaíodh leis an gcéad leagan eile de Mastodon, ach is féidir leat do rogha féin a roghnú agus tú ag ullmhú + setting_default_quote_policy: Ní bheidh an socrú seo i bhfeidhm ach amháin maidir le poist a cruthaíodh leis an gcéad leagan eile de Mastodon, ach is féidir leat do rogha féin a roghnú agus tú ag ullmhú. setting_default_sensitive: Tá meáin íogair i bhfolach de réir réamhshocraithe agus is féidir iad a nochtadh le cliceáil setting_display_media_default: Folaigh meáin atá marcáilte mar íogair setting_display_media_hide_all: Folaigh meáin i gcónaí @@ -163,6 +163,10 @@ ga: name: Ainm poiblí an róil, má tá an ról socraithe le taispeáint mar shuaitheantas permissions_as_keys: Beidh rochtain ag úsáideoirí a bhfuil an ról seo acu ar... position: Cinneann ról níos airde réiteach coinbhleachta i gcásanna áirithe. Ní féidir gníomhartha áirithe a dhéanamh ach amháin ar róil a bhfuil tosaíocht níos ísle acu + username_block: + allow_with_approval: In ionad cosc iomlán a chur ar chlárú, beidh ort do cheadú a fháil chun clárúcháin a mheaitseáil + comparison: Tabhair aird ar Fhadhb Scunthorpe agus tú ag blocáil cluichí páirteacha + username: Déanfar é a mheaitseáil beag beann ar an gcásáil agus homaiglifí coitianta cosúil le "4" in ionad "a" nó "3" in ionad "e" webhook: events: Roghnaigh imeachtaí le seoladh template: Cum do phálasta JSON féin ag baint úsáide as idirshuíomh athróg. Fág bán le haghaidh JSON réamhshocraithe. @@ -374,6 +378,10 @@ ga: name: Ainm permissions_as_keys: Ceadanna position: Tosaíocht + username_block: + allow_with_approval: Ceadaigh clárúcháin le ceadú + comparison: Modh comparáide + username: Focal le meaitseáil webhook: events: Imeachtaí cumasaithe template: Teimpléad pá-ualach diff --git a/config/locales/simple_form.gd.yml b/config/locales/simple_form.gd.yml index dfcd2590d94..7cdc27750b4 100644 --- a/config/locales/simple_form.gd.yml +++ b/config/locales/simple_form.gd.yml @@ -56,7 +56,6 @@ gd: scopes: Na APIan a dh’fhaodas an aplacaid inntrigeadh. Ma thaghas tu sgòp air ìre as àirde, cha leig thu leas sgòpaichean fa leth a thaghadh. setting_aggregate_reblogs: Na seall brosnachaidhean ùra do phostaichean a chaidh a bhrosnachadh o chionn goirid (cha doir seo buaidh ach air brosnachaidhean ùra o seo a-mach) setting_always_send_emails: Mar as àbhaist, cha dèid brathan puist-d a chur nuair a a bhios tu ri Mastodon gu cunbhalach - setting_default_quote_policy: Faodaidh luchd-cleachdaidh le iomradh orra luaidh an-còmhnaidh. Cha bhi an roghainn seo ann sàs ach air postaichean a thèid a chruthachadh leis an ath-thionndadh de Mhastodon ach ’s urrainn dhut do roghainn a thaghadh airson ullachadh dha. setting_default_sensitive: Thèid meadhanan frionasach fhalach a ghnàth is gabhaidh an nochdadh le briogadh orra setting_display_media_default: Falaich meadhanan ris a bheil comharra gu bheil iad frionasach setting_display_media_hide_all: Falaich na meadhanan an-còmhnaidh diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 1e64cb94e64..18c259f22ce 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -56,7 +56,7 @@ gl: scopes: A que APIs terá acceso a aplicación. Se escolles un ámbito de alto nivel, non precisas seleccionar elementos individuais. setting_aggregate_reblogs: Non mostrar novas promocións de publicacións que foron promovidas recentemente (só afecta a promocións recén recibidas) setting_always_send_emails: Como norma xeral non che enviamos correos electrónicos se usas activamente Mastodon - setting_default_quote_policy: As usuarias mencionadas sempre teñen permiso para citar. Este axuste só ten efecto para publicacións creadas coa próxima versión de Mastodon, pero xa podes ir preparando o axuste. + setting_default_quote_policy: O axuste só afectará ás publicación creadas coa próxima versión de Mastodon, pero podes establecer o axuste con antelación. setting_default_sensitive: Medios sensibles marcados como ocultos por defecto e móstranse cun click setting_display_media_default: Ocultar medios marcados como sensibles setting_display_media_hide_all: Ocultar sempre os medios diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index d6ede9e3073..78c168f6fd5 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -56,7 +56,7 @@ he: scopes: לאיזה ממשק יורשה היישום לגשת. בבחירת תחום כללי, אין צורך לבחור ממשקים ספציפיים. setting_aggregate_reblogs: לא להראות הדהודים של הודעות שהודהדו לאחרונה (משפיע רק על הדהודים שהתקבלו לא מזמן) setting_always_send_emails: בדרך כלל התראות דוא"ל לא יישלחו בזמן שימוש פעיל במסטודון - setting_default_quote_policy: משתמשיםות מאוזכריםות תמיד חופשיים לצטט. הכיוונון הזה משפיע רק על פרסומים שישלחו בגרסאות מסטודון עתידיות, ניתן לבחור את העדפתך כהכנה לגרסא שתבוא + setting_default_quote_policy: הכיוונון הזה משפיע רק על פרסומים שישלחו בגרסאות מסטודון עתידיות, ניתן לבחור את העדפתך כהכנה לגרסא שתבוא. setting_default_sensitive: מדיה רגישה מוסתרת כברירת מחדל וניתן להציגה בקליק setting_display_media_default: הסתרת מדיה המסומנת כרגישה setting_display_media_hide_all: הסתר מדיה תמיד diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 79c163ebb7c..7e148d706c9 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -56,7 +56,6 @@ hu: scopes: Mely API-kat érheti el az alkalmazás. Ha felső szintű hatáskört választasz, nem kell egyesével kiválasztanod az alatta lévőeket. setting_aggregate_reblogs: Ne mutassunk megtolásokat olyan bejegyzésekhez, melyeket nemrég toltak meg (csak új megtolásokra lép életbe) setting_always_send_emails: Alapesetben nem küldünk e-mail-értesítéseket, ha aktívan használod a Mastodont - setting_default_quote_policy: A megemlített felhasználók mindig idézhetnek. A beállítás csak a Mastodon következő verziójával készült bejegyzésekre lesz hatással, de előre kiválaszthatod az előnyben részesített beállítást. setting_default_sensitive: A kényes médiatartalmat alapesetben elrejtjük, de egyetlen kattintással előhozható setting_display_media_default: Kényes tartalomnak jelölt média elrejtése setting_display_media_hide_all: Média elrejtése mindig diff --git a/config/locales/simple_form.ia.yml b/config/locales/simple_form.ia.yml index edcb43634a5..78af1c11922 100644 --- a/config/locales/simple_form.ia.yml +++ b/config/locales/simple_form.ia.yml @@ -56,7 +56,6 @@ ia: scopes: Le APIs al quales le application habera accesso. Si tu selige un ambito de nivello superior, non es necessari seliger ambitos individual. setting_aggregate_reblogs: Non monstrar nove impulsos pro messages que ha essite recentemente impulsate (affecta solmente le impulsos novemente recipite) setting_always_send_emails: Normalmente, le notificationes de e-mail non es inviate quando tu activemente usa Mastodon - setting_default_quote_policy: Le usatores mentionate sempre ha permission pro citar. Iste parametro solo habera effecto pro messages create con le proxime version de Mastodon, ma tu pote seliger tu preferentia anticipatemente setting_default_sensitive: Le medios sensibile es celate de ordinario e pote esser revelate con un clic setting_display_media_default: Celar le medios marcate como sensibile setting_display_media_hide_all: Sempre celar contento multimedial diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index 2a1895bfd76..49b3d6da35d 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -56,7 +56,7 @@ is: scopes: Að hvaða API-kerfisviðmótum forritið fær aðgang. Ef þú velur efsta-stigs svið, þarftu ekki að gefa einstakar heimildir. setting_aggregate_reblogs: Ekki sýna nýjar endurbirtingar á færslum sem hafa nýlega verið endurbirtar (hefur bara áhrif á ný-mótteknar endurbirtingar) setting_always_send_emails: Venjulega eru tilkynningar í tölvupósti ekki sendar þegar þú ert virk/ur í að nota Mastodon - setting_default_quote_policy: Notendur sem minnst er á geta alltaf gert tilvitnanir. Þessi stilling virkar einungis á færslur sem gerðar hafa verið í næstu útgáfu Mastodon, en þú getur samt valið þetta til að undirbúa þig + setting_default_quote_policy: Þessi stilling gildir einungis fyrir færslur sem útbúnar eru með næstu útgáfu Mastodon, en þú getur valið þetta fyrirfram til undirbúnings. setting_default_sensitive: Viðkvæmt myndefni er sjálfgefið falið og er hægt að birta með smelli setting_display_media_default: Fela myndefni sem merkt er viðkvæmt setting_display_media_hide_all: Alltaf fela allt myndefni diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index b07a1fc1dc3..0dc781c74e7 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -56,7 +56,6 @@ it: scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole. setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni) setting_always_send_emails: Normalmente le notifiche e-mail non vengono inviate quando si utilizza attivamente Mastodon - setting_default_quote_policy: Gli utenti menzionati sono sempre in grado di citare. Questa impostazione avrà effetto solo per i post che verranno creati con la prossima versione di Mastodon, ma puoi selezionare le tue preferenze in preparazione del rilascio della prossima versione setting_default_sensitive: Media con contenuti sensibili sono nascosti in modo predefinito e possono essere rivelati con un click setting_display_media_default: Nascondi media segnati come sensibili setting_display_media_hide_all: Nascondi sempre tutti i media diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 98cebfa6c97..a2fe712ed28 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -56,7 +56,6 @@ ja: scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。 setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響) setting_always_send_emails: 通常、Mastodon からメール通知は行われません。 - setting_default_quote_policy: メンションされたユーザーが常にその投稿を引用できるようになる。 この設定はMastodonの次のバージョンからしか効力を発揮しませんが、現時点で設定を選択しておくことができます setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります setting_display_media_default: 閲覧注意としてマークされたメディアは隠す setting_display_media_hide_all: メディアを常に隠す diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 5700f43d3d6..da8f90d189e 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -56,7 +56,6 @@ ko: scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다. setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다) setting_always_send_emails: 기본적으로 마스토돈을 활동적으로 사용하고 있을 때에는 이메일 알림이 보내지지 않습니다 - setting_default_quote_policy: 멘션된 사용자는 항상 인용할 수 있도록 허용됩니다. 이 설정은 다음 마스토돈 버전부터 효과가 적용되지만 미리 준비할 수 있도록 설정을 제공합니다 setting_default_sensitive: 민감한 미디어는 기본적으로 가려져 있으며 클릭해서 볼 수 있습니다 setting_display_media_default: 민감함으로 표시된 미디어 가리기 setting_display_media_hide_all: 모든 미디어를 가리기 diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index adc546b9adb..0349aa1809c 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -56,7 +56,6 @@ lv: scopes: Kuriem API lietotnei būs ļauts piekļūt. Ja atlasa augstākā līmeņa tvērumu, nav nepieciešamas atlasīt atsevišķus. setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus) setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon - setting_default_quote_policy: Pieminētajiem lietotājiem vienmēr ir atļauts citēt. Šis iestatījums stāsies spēkā tikai nākamo Mastodon versiju ierakstiem. Bet jūs tik un tā variet iestatīt savu izvēli, kamēr notiek ieviešana setting_default_sensitive: Pēc noklusējuma jūtīgi informācijas nesēji ir paslēpti, un tos var atklāt ar klikšķi setting_display_media_default: Paslēpt informācijas nesējus, kas atzīmēti kā jūtīgi setting_display_media_hide_all: Vienmēr slēpt multividi diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 53945af5313..1f0a4de9120 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -56,7 +56,7 @@ nl: 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_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_default_quote_policy: Het is voor gebruikers die vermeld worden altijd toegestaan om te citeren. Deze instelling is alleen van kracht voor berichten die gemaakt zijn met de volgende Mastodon-versie, maar je kunt je voorkeur nu alvast instellen + setting_default_quote_policy: Deze instelling is alleen van kracht voor berichten die gemaakt zijn met de volgende Mastodon-versie, maar je kunt je voorkeur nu alvast instellen. setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond setting_display_media_default: Als gevoelig gemarkeerde media verbergen setting_display_media_hide_all: Media altijd verbergen @@ -161,7 +161,7 @@ nl: permissions_as_keys: Gebruikers met deze rol hebben toegang tot... position: Een hogere rol beslist in bepaalde situaties over het oplossen van conflicten. Bepaalde acties kunnen alleen worden uitgevoerd op rollen met een lagere prioriteit username_block: - allow_with_approval: In plaats van inschrijving helemaal te voorkomen, zullen overeenkomstige inschrijvingen jouw goedkeuring vereisen + allow_with_approval: In plaats van dat het registreren helemaal wordt voorkomen, zullen overeenkomstige registraties jouw goedkeuring vereisen comparison: Houd rekening met het Scunthorpe-probleem wanneer je gedeeltelijke overeenkomsten blokkeert username: Wordt gezien als overeenkomst ongeacht de lettergrootte en gangbare lettervervangingen zoals "4" voor "a" of "3" voor "e" webhook: diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index d23ef70830c..12ae70ffa40 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -56,7 +56,6 @@ nn: scopes: API-ane som programmet vil få tilgjenge til. Ettersom du vel eit toppnivåomfang tarv du ikkje velja einskilde API-ar. setting_aggregate_reblogs: Ikkje vis nye framhevingar for tut som nyleg har vorte heva fram (Påverkar berre nylege framhevingar) setting_always_send_emails: Vanlegvis vil ikkje e-postvarsel bli sendt når du brukar Mastodon aktivt - setting_default_quote_policy: Dei nemnde folka får alltid lov å sitera. Denne innstillinga har berre verknad for innlegg som er laga med den neste utgåva av Mastodon, men du kan velja kva du vil ha i førebuingane setting_default_sensitive: Sensitive media vert gøymde som standard, og du syner dei ved å klikka på dei setting_display_media_default: Gøym media som er merka som sensitive setting_display_media_hide_all: Alltid skjul alt media diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index 39bce7c5943..9bcb70fc383 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -56,7 +56,6 @@ pt-BR: scopes: Quais APIs o aplicativo vai ter permissão de acessar. Se você selecionar uma autorização de alto nível, você não precisa selecionar individualmente os outros. setting_aggregate_reblogs: Não mostrar novos impulsos para publicações que já foram impulsionadas recentemente (afeta somente os impulsos mais recentes) setting_always_send_emails: Normalmente, as notificações por e-mail não serão enviadas enquanto você estiver usando ativamente o Mastodon - setting_default_quote_policy: Usuários mencionados sempre têm permissão para citar. Esta configuração só terá efeito para postagens criadas com a próxima versão do Mastodon, mas você pode selecionar sua preferência em preparação setting_default_sensitive: Mídia sensível está oculta por padrão e pode ser revelada com um clique setting_display_media_default: Sempre ocultar mídia sensível setting_display_media_hide_all: Sempre ocultar todas as mídias diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index 811b2ecd501..0e65d82fd83 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -56,7 +56,6 @@ pt-PT: scopes: Quais as API a que a aplicação terá permissão para aceder. Se selecionar um âmbito de nível superior, não precisa de selecionar âmbitos individuais. setting_aggregate_reblogs: Não mostrar os novos impulsos para publicações que tenham sido recentemente impulsionadas (apenas afeta os impulsos recentemente recebidos) setting_always_send_emails: Normalmente as notificações por e-mail não serão enviadas quando estiver a utilizar ativamente o Mastodon - setting_default_quote_policy: Os utilizadores mencionados têm sempre permissão para citar. Esta definição só terá efeito para publicações criadas com a próxima versão do Mastodon, mas pode selecionar a sua preferência em antecipação setting_default_sensitive: Os multimédia sensíveis são ocultados por predefinição e podem ser revelados com um clique/toque setting_display_media_default: Esconder multimédia marcada como sensível setting_display_media_hide_all: Esconder sempre toda a multimédia diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index cebd5d1281e..5c48d751b55 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -56,7 +56,6 @@ ru: scopes: Выберите, какие API приложение сможет использовать. Разрешения верхнего уровня имплицитно включают в себя все разрешения более низких уровней. setting_aggregate_reblogs: Не показывать новые продвижения постов, которые уже были недавно продвинуты (применяется только к будущим продвижениям) setting_always_send_emails: По умолчанию уведомления не доставляются по электронной почте, пока вы активно используете Mastodon - setting_default_quote_policy: Упомянутые пользователи всегда смогут вас цитировать. Эта настройка будет применена только к постам, созданным в следующей версии Mastodon, но вы можете заранее определить свои предпочтения setting_default_sensitive: Медиа деликатного характера скрыты по умолчанию и могут быть показаны по нажатию на них setting_display_media_default: Скрывать медиа деликатного характера setting_display_media_hide_all: Скрывать все медиа diff --git a/config/locales/simple_form.si.yml b/config/locales/simple_form.si.yml index f0a0dd02865..119e3d253db 100644 --- a/config/locales/simple_form.si.yml +++ b/config/locales/simple_form.si.yml @@ -56,7 +56,6 @@ si: scopes: යෙදුමට ප්‍රවේශ වීමට ඉඩ දෙන්නේ කුමන API වලටද. ඔබ ඉහළ මට්ටමේ විෂය පථයක් තෝරා ගන්නේ නම්, ඔබට තනි ඒවා තෝරා ගැනීමට අවශ්‍ය නොවේ. setting_aggregate_reblogs: මෑතකදී වැඩි කරන ලද පළ කිරීම් සඳහා නව වැඩි කිරීම් නොපෙන්වන්න (අලුතින් ලැබුණු වැඩි කිරීම් වලට පමණක් බලපායි) setting_always_send_emails: ඔබ නිතර මාස්ටඩන් භාවිතා කරන විට වි-තැපැල් දැනුම්දීම් නොලැබෙයි - setting_default_quote_policy: සඳහන් කළ පරිශීලකයින්ට සැමවිටම උපුටා දැක්වීමට අවසර ඇත. මෙම සැකසුම ඊළඟ Mastodon අනුවාදය සමඟ නිර්මාණය කරන ලද පළ කිරීම් සඳහා පමණක් ක්‍රියාත්මක වනු ඇත, නමුත් ඔබට සූදානම් වීමේදී ඔබේ මනාපය තෝරා ගත හැකිය. setting_default_sensitive: සංවේදී මාධ්‍ය පෙරනිමියෙන් සඟවා ඇති අතර ක්ලික් කිරීමකින් හෙළිදරව් කළ හැක setting_display_media_default: සංවේදී බව සලකුණු කළ මාධ්‍ය සඟවන්න setting_display_media_hide_all: සැමවිට මාධ්‍ය සඟවන්න diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index 5059fbd1e97..0672fe05a44 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -56,7 +56,6 @@ sq: scopes: Cilat API do të lejohen të përdorin aplikacioni. Nëse përzgjidhni një shkallë të epërme, nuk ju duhet të përzgjidhni individualet një nga një. setting_aggregate_reblogs: Mos shfaq përforcime të reja për mesazhe që janë përforcuar tani së fundi (prek vetëm përforcime të marra rishtas) setting_always_send_emails: Normalisht s’do të dërgohen njoftime, kur përdorni aktivisht Mastodon-in - setting_default_quote_policy: Përdoruesit e përmendur lejohen përherë të citojnë. Ky rregullim do të ketë efekt vetëm për postime të krijuar me versionin pasues të Mastodon-it, por mund të përzgjidhni paraprakisht parapëlqimin tuaj setting_default_sensitive: Media rezervat fshihet, si parazgjedhje, dhe mund të shfaqet me një klikim setting_display_media_default: Fshih media me shenjën rezervat setting_display_media_hide_all: Fshih përherë mediat diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index a5571e3d4db..96f00e48c25 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -56,7 +56,6 @@ sv: scopes: 'Vilka API: er applikationen kommer tillåtas åtkomst till. Om du väljer en omfattning på högstanivån behöver du inte välja individuella sådana.' setting_aggregate_reblogs: Visa inte nya boostar för inlägg som nyligen blivit boostade (påverkar endast nymottagna boostar) setting_always_send_emails: E-postnotiser kommer vanligtvis inte skickas när du aktivt använder Mastodon - setting_default_quote_policy: Nämnda användare får alltid citeras. Denna inställning kommer att träda i kraft för inlägg som skapats med nästa Mastodon-version, men förbereda dina inställningar för det redan nu setting_default_sensitive: Känslig media döljs som standard och kan visas med ett klick setting_display_media_default: Dölj media markerad som känslig setting_display_media_hide_all: Dölj alltid all media diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 0d9122efae4..e359b657d67 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -56,7 +56,6 @@ tr: scopes: Uygulamanın erişmesine izin verilen API'ler. Üst seviye bir kapsam seçtiyseniz, bireysel kapsam seçmenize gerek yoktur. setting_aggregate_reblogs: Yakın zamanda teşvik edilmiş gönderiler için yeni teşvikleri göstermeyin (yalnızca yeni alınan teşvikleri etkiler) setting_always_send_emails: Normalde, Mastodon'u aktif olarak kullanırken e-posta bildirimleri gönderilmeyecektir - setting_default_quote_policy: Bahsedilen kullanıcıların her zaman alıntı yapmasına izin verilir. Bu ayar yalnızca bir sonraki Mastodon sürümü ile oluşturulan gönderiler için geçerli olacaktır, ancak tercihinizi hazırlık aşamasında seçebilirsiniz setting_default_sensitive: Hassas medya varsayılan olarak gizlidir ve bir tıklama ile gösterilebilir setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle setting_display_media_hide_all: Medyayı her zaman gizle diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index 70230198967..b33c5f4ccca 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -56,7 +56,7 @@ vi: scopes: Ứng dụng sẽ được phép truy cập những API nào. Nếu bạn chọn quyền cấp cao nhất, không cần chọn quyền nhỏ. setting_aggregate_reblogs: Nếu một tút đã được đăng lại thì sẽ không hiện những lượt đăng lại khác trên bảng tin setting_always_send_emails: Bình thường thì sẽ không gửi khi bạn đang dùng Mastodon - setting_default_quote_policy: Thiết lập này chỉ hiệu lực đối với các tút được tạo bằng phiên bản Mastodon tiếp theo, nhưng bạn có thể chọn trước sẵn + setting_default_quote_policy: Thiết lập này chỉ hiệu lực đối với các tút được tạo bằng phiên bản Mastodon tiếp theo, nhưng bạn có thể chọn trước sẵn. setting_default_sensitive: Bắt buộc nhấn vào mới có thể xem setting_display_media_default: Click để xem setting_display_media_hide_all: Luôn ẩn diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index c6b6f0904b9..f754cb07e3b 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -56,7 +56,6 @@ zh-CN: scopes: 哪些 API 被允许使用。如果你勾选了更高一级的范围,就不用单独选中子项目了。 setting_aggregate_reblogs: 不显示最近已经被转嘟过的嘟文(只会影响新收到的转嘟) setting_always_send_emails: 一般情况下,如果你活跃使用 Mastodon,我们不会向你发送电子邮件通知 - setting_default_quote_policy: 总是允许引用被提及的用户。此设置将仅对下个Mastodon版本创建的帖子生效,但您可以在准备中选择您的偏好 setting_default_sensitive: 敏感内容默认隐藏,并在点击后显示 setting_display_media_default: 隐藏被标记为敏感内容的媒体 setting_display_media_hide_all: 始终隐藏媒体 diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index e02e264ce90..2b57647f9eb 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -56,7 +56,7 @@ zh-TW: scopes: 允許使應用程式存取的 API。 若您選擇最高階範圍,則無須選擇個別項目。 setting_aggregate_reblogs: 不顯示最近已被轉嘟之嘟文的最新轉嘟(只影響最新收到的嘟文) setting_always_send_emails: 一般情況下若您活躍使用 Mastodon ,我們不會寄送電子郵件通知 - setting_default_quote_policy: 已提及使用者總是能引用嘟文。此設定將僅生效於下一版本 Mastodon 建立之嘟文,但您可以預先選好您的偏好設定 + setting_default_quote_policy: 此設定將僅生效於下一版本 Mastodon 建立之嘟文,但您可以預先選好您的偏好設定。 setting_default_sensitive: 敏感內容媒體預設隱藏,且按一下即可重新顯示 setting_display_media_default: 隱藏標為敏感內容的媒體 setting_display_media_hide_all: 總是隱藏所有媒體 diff --git a/config/locales/sq.yml b/config/locales/sq.yml index a29344ac80f..871c1123a73 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1865,8 +1865,6 @@ sq: ownership: S’mund të fiksohen mesazhet e të tjerëve reblog: S’mund të fiksohet një përforcim quote_policies: - followers: Ndjekës dhe përdorues të përmendur - nobody: Vetëm përdorues të përmendur public: Këdo title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index d406eabba32..25bc593917a 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1880,8 +1880,6 @@ sv: ownership: Någon annans inlägg kan inte fästas reblog: En boost kan inte fästas quote_policies: - followers: Följare och omnämnda användare - nobody: Endast nämnda användare public: Alla title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 2b0164e9bb1..f697e3b199e 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1880,8 +1880,6 @@ tr: ownership: Başkasının gönderisi sabitlenemez reblog: Bir gönderi sabitlenemez quote_policies: - followers: Takipçiler ve bahsedilen kullanıcılar - nobody: Sadece bahsedilen kullanıcılar public: Herkes title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 33a779f417d..32625d46576 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1862,8 +1862,8 @@ vi: ownership: Không thể ghim tút của người khác reblog: Không thể ghim tút đăng lại quote_policies: - followers: Người được nhắc đến và người theo dõi - nobody: Người được nhắc đến + followers: Chỉ người theo dõi + nobody: Không ai public: Mọi người title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index c62dc8e3191..82d0a169cc9 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1827,8 +1827,6 @@ zh-CN: ownership: 不能置顶别人的嘟文 reblog: 不能置顶转嘟 quote_policies: - followers: 关注者和提及的用户 - nobody: 仅提及的用户 public: 所有人 title: "%{name}:“%{quote}”" visibilities: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 1dadf43932c..fc29a3b5310 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1864,8 +1864,8 @@ zh-TW: ownership: 不能釘選他人的嘟文 reblog: 不能釘選轉嘟 quote_policies: - followers: 跟隨者與已提及使用者 - nobody: 僅限已提及使用者 + followers: 僅限您之跟隨者 + nobody: 禁止任何人 public: 任何人 title: "%{name}:「%{quote}」" visibilities: From 29a5f059d2a98c41576697bad125ae63b230de25 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 1 Aug 2025 11:12:46 +0200 Subject: [PATCH 194/660] Add notifications for inbound quotes (#35618) --- app/models/notification.rb | 11 ++++++++++- app/models/notification_request.rb | 2 +- app/models/quote.rb | 2 ++ app/serializers/rest/notification_serializer.rb | 2 +- app/services/fan_out_on_write_service.rb | 7 +++++++ app/services/notify_service.rb | 2 +- 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/models/notification.rb b/app/models/notification.rb index e7ada3399aa..f70991d801b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -73,6 +73,9 @@ class Notification < ApplicationRecord 'admin.report': { filterable: false, }.freeze, + quote: { + filterable: true, + }.freeze, }.freeze TYPES = PROPERTIES.keys.freeze @@ -81,6 +84,7 @@ class Notification < ApplicationRecord status: :status, reblog: [status: :reblog], mention: [mention: :status], + quote: [quote: :status], favourite: [favourite: :status], poll: [poll: :status], update: :status, @@ -102,6 +106,7 @@ class Notification < ApplicationRecord belongs_to :account_relationship_severance_event, inverse_of: false belongs_to :account_warning, inverse_of: false belongs_to :generated_annual_report, inverse_of: false + belongs_to :quote, inverse_of: :notification end validates :type, inclusion: { in: TYPES } @@ -122,6 +127,8 @@ class Notification < ApplicationRecord favourite&.status when :mention mention&.status + when :quote + quote&.status when :poll poll&.status end @@ -174,6 +181,8 @@ class Notification < ApplicationRecord notification.mention.status = cached_status when :poll notification.poll.status = cached_status + when :quote + notification.quote.status = cached_status end end @@ -192,7 +201,7 @@ class Notification < ApplicationRecord return unless new_record? case activity_type - when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report' + when 'Status', 'Follow', 'Favourite', 'FollowRequest', 'Poll', 'Report', 'Quote' self.from_account_id = activity&.account_id when 'Mention' self.from_account_id = activity&.status&.account_id diff --git a/app/models/notification_request.rb b/app/models/notification_request.rb index eb9ff93ab72..d95fb58b476 100644 --- a/app/models/notification_request.rb +++ b/app/models/notification_request.rb @@ -49,6 +49,6 @@ class NotificationRequest < ApplicationRecord private def prepare_notifications_count - self.notifications_count = Notification.where(account: account, from_account: from_account, type: :mention, filtered: true).limit(MAX_MEANINGFUL_COUNT).count + self.notifications_count = Notification.where(account: account, from_account: from_account, type: [:mention, :quote], filtered: true).limit(MAX_MEANINGFUL_COUNT).count end end diff --git a/app/models/quote.rb b/app/models/quote.rb index b844805da73..4ee5e4d41ec 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -19,6 +19,8 @@ class Quote < ApplicationRecord include Paginable + has_one :notification, as: :activity, dependent: :destroy + BACKGROUND_REFRESH_INTERVAL = 1.week.freeze REFRESH_DEADLINE = 6.hours diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 320bc86961d..033bc1c0425 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -21,7 +21,7 @@ class REST::NotificationSerializer < ActiveModel::Serializer end def status_type? - [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type) + [:favourite, :reblog, :status, :mention, :poll, :update, :quote].include?(object.type) end def report_type? diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index f3aa479c153..41a4e210e15 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -40,6 +40,7 @@ class FanOutOnWriteService < BaseService deliver_to_self! unless @options[:skip_notifications] + notify_quoted_account! notify_mentioned_accounts! notify_about_update! if update? end @@ -69,6 +70,12 @@ class FanOutOnWriteService < BaseService FeedManager.instance.push_to_home(@account, @status, update: update?) if @account.local? end + def notify_quoted_account! + return unless @status.quote&.quoted_account&.local? && @status.quote&.accepted? + + LocalNotificationWorker.perform_async(@status.quote.quoted_account_id, @status.quote.id, 'Quote', 'quote') + end + def notify_mentioned_accounts! @status.active_mentions.where.not(id: @options[:silenced_account_ids] || []).joins(:account).merge(Account.local).select(:id, :account_id).reorder(nil).find_in_batches do |mentions| LocalNotificationWorker.push_bulk(mentions) do |mention| diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index f820f969a6e..95563698f45 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -247,7 +247,7 @@ class NotifyService < BaseService end def update_notification_request! - return unless @notification.type == :mention + return unless %i(mention quote).include?(@notification.type) notification_request = NotificationRequest.find_or_initialize_by(account_id: @recipient.id, from_account_id: @notification.from_account_id) notification_request.last_status_id = @notification.target_status.id From 62683213169381cb0109beb0a3f41ad5c8ef3c6f Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 1 Aug 2025 11:34:12 +0200 Subject: [PATCH 195/660] Ensure blocked users cannot quote (#35624) --- app/policies/status_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 5d01da42c61..b7463869959 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -21,7 +21,7 @@ class StatusPolicy < ApplicationPolicy # This is about requesting a quote post, not validating it def quote? - record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied + show? && record.quote_policy_for_account(current_account, preloaded_relations: @preloaded_relations) != :denied end def reblog? From bcab6a93187d3a466fabb98313fabae24ccc6098 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:57:13 +0200 Subject: [PATCH 196/660] Update dependency sidekiq-scheduler to v6 (#35596) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index ce775fc57bc..ee2369921de 100644 --- a/Gemfile +++ b/Gemfile @@ -84,7 +84,7 @@ gem 'sanitize', '~> 7.0' gem 'scenic', '~> 1.7' gem 'sidekiq', '< 8' gem 'sidekiq-bulk', '~> 0.2.0' -gem 'sidekiq-scheduler', '~> 5.0' +gem 'sidekiq-scheduler', '~> 6.0' gem 'sidekiq-unique-jobs', '> 8' gem 'simple_form', '~> 5.2' gem 'simple-navigation', '~> 4.4' diff --git a/Gemfile.lock b/Gemfile.lock index 73c2abda1cc..19a97ef4962 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -833,10 +833,9 @@ GEM redis-client (>= 0.22.2) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (5.0.6) + sidekiq-scheduler (6.0.1) rufus-scheduler (~> 3.2) - sidekiq (>= 6, < 8) - tilt (>= 1.4.0, < 3) + sidekiq (>= 7.3, < 9) sidekiq-unique-jobs (8.0.11) concurrent-ruby (~> 1.0, >= 1.0.5) sidekiq (>= 7.0.0, < 9.0.0) @@ -1082,7 +1081,7 @@ DEPENDENCIES shoulda-matchers sidekiq (< 8) sidekiq-bulk (~> 0.2.0) - sidekiq-scheduler (~> 5.0) + sidekiq-scheduler (~> 6.0) sidekiq-unique-jobs (> 8) simple-navigation (~> 4.4) simple_form (~> 5.2) From fba24cc4ebaf6e917f45e16ed908f34ede9822cd Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 1 Aug 2025 15:29:22 +0200 Subject: [PATCH 197/660] Add minute resolution to `DeliveryFailureTracker` (#35625) --- app/lib/delivery_failure_tracker.rb | 29 ++++-- app/views/admin/instances/show.html.haml | 2 +- spec/lib/delivery_failure_tracker_spec.rb | 110 ++++++++++++++++++---- 3 files changed, 114 insertions(+), 27 deletions(-) diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index 96292923f42..619340a3d16 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -3,14 +3,18 @@ class DeliveryFailureTracker include Redisable - FAILURE_DAYS_THRESHOLD = 7 + FAILURE_THRESHOLDS = { + days: 7, + minutes: 5, + }.freeze - def initialize(url_or_host) + def initialize(url_or_host, resolution: :days) @host = url_or_host.start_with?('https://', 'http://') ? Addressable::URI.parse(url_or_host).normalized_host : url_or_host + @resolution = resolution end def track_failure! - redis.sadd(exhausted_deliveries_key, today) + redis.sadd(exhausted_deliveries_key, failure_time) UnavailableDomain.create(domain: @host) if reached_failure_threshold? end @@ -24,6 +28,12 @@ class DeliveryFailureTracker end def days + raise TypeError, 'resolution is not in days' unless @resolution == :days + + failures + end + + def failures redis.scard(exhausted_deliveries_key) || 0 end @@ -32,7 +42,7 @@ class DeliveryFailureTracker end def exhausted_deliveries_days - @exhausted_deliveries_days ||= redis.smembers(exhausted_deliveries_key).sort.map { |date| Date.new(date.slice(0, 4).to_i, date.slice(4, 2).to_i, date.slice(6, 2).to_i) } + @exhausted_deliveries_days ||= redis.smembers(exhausted_deliveries_key).sort.map { |date| Date.new(date.slice(0, 4).to_i, date.slice(4, 2).to_i, date.slice(6, 2).to_i) }.uniq end alias reset! track_success! @@ -89,11 +99,16 @@ class DeliveryFailureTracker "exhausted_deliveries:#{@host}" end - def today - Time.now.utc.strftime('%Y%m%d') + def failure_time + case @resolution + when :days + Time.now.utc.strftime('%Y%m%d') + when :minutes + Time.now.utc.strftime('%Y%m%d%H%M') + end end def reached_failure_threshold? - days >= FAILURE_DAYS_THRESHOLD + failures >= FAILURE_THRESHOLDS[@resolution] end end diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index c977011e1ff..d241844ea2f 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -78,7 +78,7 @@ %h3= t('admin.instances.availability.title') %p - = t('admin.instances.availability.description_html', count: DeliveryFailureTracker::FAILURE_DAYS_THRESHOLD) + = t('admin.instances.availability.description_html', count: DeliveryFailureTracker::FAILURE_THRESHOLDS[:days]) .availability-indicator %ul.availability-indicator__graphic diff --git a/spec/lib/delivery_failure_tracker_spec.rb b/spec/lib/delivery_failure_tracker_spec.rb index 34912c8133e..6935dbccb43 100644 --- a/spec/lib/delivery_failure_tracker_spec.rb +++ b/spec/lib/delivery_failure_tracker_spec.rb @@ -3,37 +3,101 @@ require 'rails_helper' RSpec.describe DeliveryFailureTracker do - subject { described_class.new('http://example.com/inbox') } + context 'with the default resolution of :days' do + subject { described_class.new('http://example.com/inbox') } - describe '#track_success!' do - before do - subject.track_failure! - subject.track_success! + describe '#track_success!' do + before do + track_failure(7, :days) + subject.track_success! + end + + it 'marks URL as available again' do + expect(described_class.available?('http://example.com/inbox')).to be true + end + + it 'resets days to 0' do + expect(subject.days).to be_zero + end end - it 'marks URL as available again' do - expect(described_class.available?('http://example.com/inbox')).to be true + describe '#track_failure!' do + it 'marks URL as unavailable after 7 days of being called' do + track_failure(7, :days) + + expect(subject.days).to eq 7 + expect(described_class.available?('http://example.com/inbox')).to be false + end + + it 'repeated calls on the same day do not count' do + subject.track_failure! + subject.track_failure! + + expect(subject.days).to eq 1 + end end - it 'resets days to 0' do - expect(subject.days).to be_zero + describe '#exhausted_deliveries_days' do + it 'returns the days on which failures were recorded' do + track_failure(3, :days) + + expect(subject.exhausted_deliveries_days).to contain_exactly(3.days.ago.to_date, 2.days.ago.to_date, Date.yesterday) + end end end - describe '#track_failure!' do - it 'marks URL as unavailable after 7 days of being called' do - 6.times { |i| redis.sadd('exhausted_deliveries:example.com', i) } - subject.track_failure! + context 'with a resolution of :minutes' do + subject { described_class.new('http://example.com/inbox', resolution: :minutes) } - expect(subject.days).to eq 7 - expect(described_class.available?('http://example.com/inbox')).to be false + describe '#track_success!' do + before do + track_failure(5, :minutes) + subject.track_success! + end + + it 'marks URL as available again' do + expect(described_class.available?('http://example.com/inbox')).to be true + end + + it 'resets failures to 0' do + expect(subject.failures).to be_zero + end end - it 'repeated calls on the same day do not count' do - subject.track_failure! - subject.track_failure! + describe '#track_failure!' do + it 'marks URL as unavailable after 5 minutes of being called' do + track_failure(5, :minutes) - expect(subject.days).to eq 1 + expect(subject.failures).to eq 5 + expect(described_class.available?('http://example.com/inbox')).to be false + end + + it 'repeated calls within the same minute do not count' do + freeze_time + subject.track_failure! + subject.track_failure! + + expect(subject.failures).to eq 1 + end + end + + describe '#exhausted_deliveries_days' do + it 'returns the days on which failures were recorded' do + # Make sure this does not accidentally span two days when run + # around midnight + travel_to Time.zone.now.change(hour: 10) + track_failure(3, :minutes) + + expect(subject.exhausted_deliveries_days).to contain_exactly(Time.zone.today) + end + end + + describe '#days' do + it 'raises due to wrong resolution' do + assert_raises TypeError do + subject.days + end + end end end @@ -60,4 +124,12 @@ RSpec.describe DeliveryFailureTracker do expect(described_class.available?('http://foo.bar/inbox')).to be true end end + + def track_failure(times, unit) + times.times do + travel_to 1.send(unit).ago + subject.track_failure! + end + travel_back + end end From 483da67204078c4f59359be88e853d7514f22ef3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 1 Aug 2025 10:28:45 -0400 Subject: [PATCH 198/660] Remove unused `obscured_counter` helper method (#35627) --- app/helpers/home_helper.rb | 10 ---------- spec/helpers/home_helper_spec.rb | 34 -------------------------------- 2 files changed, 44 deletions(-) diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb index 7b9d3f4fc1a..79e28c983af 100644 --- a/app/helpers/home_helper.rb +++ b/app/helpers/home_helper.rb @@ -39,16 +39,6 @@ module HomeHelper end end - def obscured_counter(count) - if count <= 0 - '0' - elsif count == 1 - '1' - else - '1+' - end - end - def field_verified_class(verified) if verified 'verified' diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index a8f6d99f032..a056eae364d 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -41,40 +41,6 @@ RSpec.describe HomeHelper do end end - describe 'obscured_counter' do - context 'with a value of less than zero' do - let(:count) { -10 } - - it 'returns the correct string' do - expect(helper.obscured_counter(count)).to eq '0' - end - end - - context 'with a value of zero' do - let(:count) { 0 } - - it 'returns the correct string' do - expect(helper.obscured_counter(count)).to eq '0' - end - end - - context 'with a value of one' do - let(:count) { 1 } - - it 'returns the correct string' do - expect(helper.obscured_counter(count)).to eq '1' - end - end - - context 'with a value of more than one' do - let(:count) { 10 } - - it 'returns the correct string' do - expect(helper.obscured_counter(count)).to eq '1+' - end - end - end - describe 'field_verified_class' do subject { helper.field_verified_class(verified) } From 591df1f205c654381203b56d46498efc62370776 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 1 Aug 2025 16:55:25 +0200 Subject: [PATCH 199/660] Add support for local quote stamps (#35626) --- .../quote_authorizations_controller.rb | 28 +++++++ app/helpers/context_helper.rb | 6 ++ app/lib/activitypub/activity/create.rb | 2 +- app/lib/activitypub/tag_manager.rb | 7 ++ app/models/quote.rb | 1 + .../delete_quote_authorization_serializer.rb | 4 +- .../activitypub/note_serializer.rb | 5 +- .../quote_authorization_serializer.rb | 29 ++++++++ .../process_status_update_service.rb | 6 +- .../activitypub/verify_quote_service.rb | 10 +++ config/routes.rb | 1 + spec/lib/activitypub/activity/create_spec.rb | 2 +- .../activitypub/quote_authorizations_spec.rb | 49 ++++++++++++ ...ete_quote_authorization_serializer_spec.rb | 4 +- .../activitypub/note_serializer_spec.rb | 5 +- .../quote_authorization_serializer_spec.rb | 22 ++++++ .../process_status_update_service_spec.rb | 74 +++++++++++++++++++ spec/services/revoke_quote_service_spec.rb | 2 +- 18 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 app/controllers/activitypub/quote_authorizations_controller.rb create mode 100644 app/serializers/activitypub/quote_authorization_serializer.rb create mode 100644 spec/requests/activitypub/quote_authorizations_spec.rb create mode 100644 spec/serializers/activitypub/quote_authorization_serializer_spec.rb diff --git a/app/controllers/activitypub/quote_authorizations_controller.rb b/app/controllers/activitypub/quote_authorizations_controller.rb new file mode 100644 index 00000000000..fa635d636a6 --- /dev/null +++ b/app/controllers/activitypub/quote_authorizations_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class ActivityPub::QuoteAuthorizationsController < ActivityPub::BaseController + include Authorization + + vary_by -> { 'Signature' if authorized_fetch_mode? } + + before_action :require_account_signature!, if: :authorized_fetch_mode? + before_action :set_quote_authorization + + def show + expires_in 0, public: @quote.status.distributable? && public_fetch_mode? + render json: @quote, serializer: ActivityPub::QuoteAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' + end + + private + + def pundit_user + signed_request_account + end + + def set_quote_authorization + @quote = Quote.accepted.where(quoted_account: @account).find(params[:id]) + authorize @quote.status, :show? + rescue Mastodon::NotPermittedError + not_found + end +end diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index 77ddee1122c..885f578fd0d 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -39,6 +39,12 @@ module ContextHelper 'automaticApproval' => { '@id' => 'gts:automaticApproval', '@type' => '@id' }, 'manualApproval' => { '@id' => 'gts:manualApproval', '@type' => '@id' }, }, + quote_authorizations: { + 'gts' => 'https://gotosocial.org/ns#', + 'quoteAuthorization' => { '@id' => 'https://w3id.org/fep/044f#quoteAuthorization', '@type' => '@id' }, + 'interactingObject' => { '@id' => 'gts:interactingObject' }, + 'interactionTarget' => { '@id' => 'gts:interactionTarget' }, + }, }.freeze def full_context diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 3fc9269dd3a..ab84a5dd472 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -230,7 +230,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return if @quote_uri.blank? approval_uri = @status_parser.quote_approval_uri - approval_uri = nil if unsupported_uri_scheme?(approval_uri) + approval_uri = nil if unsupported_uri_scheme?(approval_uri) || TagManager.instance.local_url?(approval_uri) @quote = Quote.new(account: @account, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?) end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index 4d83a9b8238..975763e82fe 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -51,6 +51,13 @@ class ActivityPub::TagManager end end + def approval_uri_for(quote, check_approval: true) + return quote.approval_uri unless quote.quoted_account&.local? + return if check_approval && !quote.accepted? + + account_quote_authorization_url(quote.quoted_account, quote) + end + def key_uri_for(target) [uri_for(target), '#main-key'].join end diff --git a/app/models/quote.rb b/app/models/quote.rb index 4ee5e4d41ec..a6c9dd0caca 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -37,6 +37,7 @@ class Quote < ApplicationRecord before_validation :set_accounts before_validation :set_activity_uri, only: :create, if: -> { account.local? && quoted_account&.remote? } validates :activity_uri, presence: true, if: -> { account.local? && quoted_account&.remote? } + validates :approval_uri, absence: true, if: -> { quoted_account&.local? } validate :validate_visibility def accept! diff --git a/app/serializers/activitypub/delete_quote_authorization_serializer.rb b/app/serializers/activitypub/delete_quote_authorization_serializer.rb index ab914711650..150f2a4554b 100644 --- a/app/serializers/activitypub/delete_quote_authorization_serializer.rb +++ b/app/serializers/activitypub/delete_quote_authorization_serializer.rb @@ -7,11 +7,11 @@ class ActivityPub::DeleteQuoteAuthorizationSerializer < ActivityPub::Serializer attribute :virtual_object, key: :object def id - [object.approval_uri, '#delete'].join + [ActivityPub::TagManager.instance.approval_uri_for(object, check_approval: false), '#delete'].join end def virtual_object - object.approval_uri + ActivityPub::TagManager.instance.approval_uri_for(object, check_approval: false) end def type diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 95a869658c3..972f146bafc 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -204,7 +204,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer end def quote_authorization? - object.quote&.approval_uri.present? + object.quote.present? && ActivityPub::TagManager.instance.approval_uri_for(object.quote).present? end def quote @@ -213,8 +213,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer end def quote_authorization - # TODO: approval of local quotes may work differently, perhaps? - object.quote.approval_uri + ActivityPub::TagManager.instance.approval_uri_for(object.quote) end class MediaAttachmentSerializer < ActivityPub::Serializer diff --git a/app/serializers/activitypub/quote_authorization_serializer.rb b/app/serializers/activitypub/quote_authorization_serializer.rb new file mode 100644 index 00000000000..faef3dd6866 --- /dev/null +++ b/app/serializers/activitypub/quote_authorization_serializer.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class ActivityPub::QuoteAuthorizationSerializer < ActivityPub::Serializer + include RoutingHelper + + context_extensions :quote_authorizations + + attributes :id, :type, :attributed_to, :interacting_object, :interaction_target + + def id + ActivityPub::TagManager.instance.approval_uri_for(object) + end + + def type + 'QuoteAuthorization' + end + + def attributed_to + ActivityPub::TagManager.instance.uri_for(object.quoted_account) + end + + def interaction_target + ActivityPub::TagManager.instance.uri_for(object.quoted_status) + end + + def interacting_object + ActivityPub::TagManager.instance.uri_for(object.status) + end +end diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 064ccf0f337..0ada876d890 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -278,10 +278,10 @@ class ActivityPub::ProcessStatusUpdateService < BaseService return unless quote_uri.present? && @status.quote.present? quote = @status.quote - return if quote.quoted_status.present? && ActivityPub::TagManager.instance.uri_for(quote.quoted_status) != quote_uri + return if quote.quoted_status.present? && (ActivityPub::TagManager.instance.uri_for(quote.quoted_status) != quote_uri || quote.quoted_status.local?) approval_uri = @status_parser.quote_approval_uri - approval_uri = nil if unsupported_uri_scheme?(approval_uri) + approval_uri = nil if unsupported_uri_scheme?(approval_uri) || TagManager.instance.local_url?(approval_uri) quote.update(approval_uri: approval_uri, state: :pending, legacy: @status_parser.legacy_quote?) if quote.approval_uri != @status_parser.quote_approval_uri @@ -293,7 +293,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService if quote_uri.present? approval_uri = @status_parser.quote_approval_uri - approval_uri = nil if unsupported_uri_scheme?(approval_uri) + approval_uri = nil if unsupported_uri_scheme?(approval_uri) || TagManager.instance.local_url?(approval_uri) if @status.quote.present? # If the quoted post has changed, discard the old object and create a new one diff --git a/app/services/activitypub/verify_quote_service.rb b/app/services/activitypub/verify_quote_service.rb index 822abcf4022..2b10de9d9b3 100644 --- a/app/services/activitypub/verify_quote_service.rb +++ b/app/services/activitypub/verify_quote_service.rb @@ -13,6 +13,7 @@ class ActivityPub::VerifyQuoteService < BaseService @fetching_error = nil fetch_quoted_post_if_needed!(fetchable_quoted_uri, prefetched_body: prefetched_quoted_object) + return handle_local_quote! if quote.quoted_account&.local? return if fast_track_approval! || quote.approval_uri.blank? @json = fetch_approval_object(quote.approval_uri, prefetched_body: prefetched_approval) @@ -34,6 +35,15 @@ class ActivityPub::VerifyQuoteService < BaseService private + def handle_local_quote! + @quote.update!(approval_uri: nil) + if StatusPolicy.new(@quote.account, @quote.quoted_status).quote? + @quote.accept! + else + @quote.reject! + end + end + # FEP-044f defines rules that don't require the approval flow def fast_track_approval! return false if @quote.quoted_status_id.blank? diff --git a/config/routes.rb b/config/routes.rb index 2fff44851e0..49fcf3de792 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -115,6 +115,7 @@ Rails.application.routes.draw do resource :inbox, only: [:create] resources :collections, only: [:show] resource :followers_synchronization, only: [:show] + resources :quote_authorizations, only: [:show] end end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 74c9f107187..cdd5cb3194d 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -888,7 +888,7 @@ RSpec.describe ActivityPub::Activity::Create do end context 'with an unverifiable quote of a known post' do - let(:quoted_status) { Fabricate(:status) } + let(:quoted_status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com')) } let(:object_json) do build_object( diff --git a/spec/requests/activitypub/quote_authorizations_spec.rb b/spec/requests/activitypub/quote_authorizations_spec.rb new file mode 100644 index 00000000000..98daa3a79b7 --- /dev/null +++ b/spec/requests/activitypub/quote_authorizations_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'ActivityPub QuoteAuthorization endpoint' do + let(:account) { Fabricate(:account, domain: nil) } + let(:status) { Fabricate :status, account: account } + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + + before { Fabricate :favourite, status: status } + + describe 'GET /accounts/:account_username/quote_authorizations/:quote_id' do + context 'with an accepted quote' do + it 'returns http success and activity json' do + get account_quote_authorization_url(quote.quoted_account, quote) + + expect(response) + .to have_http_status(200) + expect(response.media_type) + .to eq 'application/activity+json' + + expect(response.parsed_body) + .to include(type: 'QuoteAuthorization') + end + end + + context 'with an incorrect quote authorization URL' do + it 'returns http not found' do + get account_quote_authorization_url(quote.account, quote) + + expect(response) + .to have_http_status(404) + end + end + + context 'with a rejected quote' do + before do + quote.reject! + end + + it 'returns http not found' do + get account_quote_authorization_url(quote.quoted_account, quote) + + expect(response) + .to have_http_status(404) + end + end + end +end diff --git a/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb index a7609644f17..48e3a4ddf73 100644 --- a/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb +++ b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb @@ -7,13 +7,13 @@ RSpec.describe ActivityPub::DeleteQuoteAuthorizationSerializer do describe 'serializing an object' do let(:status) { Fabricate(:status) } - let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted, approval_uri: "https://#{Rails.configuration.x.web_domain}/approvals/1234") } + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } it 'returns expected attributes' do expect(subject.deep_symbolize_keys) .to include( actor: eq(ActivityPub::TagManager.instance.uri_for(status.account)), - object: quote.approval_uri, + object: ActivityPub::TagManager.instance.approval_uri_for(quote, check_approval: false), type: 'Delete' ) end diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index d1af3f068f5..9c898e52121 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -44,8 +44,7 @@ RSpec.describe ActivityPub::NoteSerializer do context 'with a quote' do let(:quoted_status) { Fabricate(:status) } - let(:approval_uri) { 'https://example.com/foo/bar' } - let!(:quote) { Fabricate(:quote, status: parent, quoted_status: quoted_status, approval_uri: approval_uri) } + let!(:quote) { Fabricate(:quote, status: parent, quoted_status: quoted_status, state: :accepted) } it 'has the expected shape' do expect(subject).to include({ @@ -53,7 +52,7 @@ RSpec.describe ActivityPub::NoteSerializer do 'quote' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), 'quoteUri' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), '_misskey_quote' => ActivityPub::TagManager.instance.uri_for(quote.quoted_status), - 'quoteAuthorization' => approval_uri, + 'quoteAuthorization' => ActivityPub::TagManager.instance.approval_uri_for(quote), }) end end diff --git a/spec/serializers/activitypub/quote_authorization_serializer_spec.rb b/spec/serializers/activitypub/quote_authorization_serializer_spec.rb new file mode 100644 index 00000000000..6a157756934 --- /dev/null +++ b/spec/serializers/activitypub/quote_authorization_serializer_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::QuoteAuthorizationSerializer do + subject { serialized_record_json(quote, described_class, adapter: ActivityPub::Adapter) } + + describe 'serializing an object' do + let(:status) { Fabricate(:status) } + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } + + it 'returns expected attributes' do + expect(subject.deep_symbolize_keys) + .to include( + attributedTo: eq(ActivityPub::TagManager.instance.uri_for(status.account)), + interactionTarget: ActivityPub::TagManager.instance.uri_for(status), + interactingObject: ActivityPub::TagManager.instance.uri_for(quote.status), + type: 'QuoteAuthorization' + ) + end + end +end diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index a7e1b923832..74b8cef413a 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -564,6 +564,80 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do end end + context 'when an approved quote of a local post gets updated through an explicit update' do + let(:quoted_account) { Fabricate(:account) } + let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) } + let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, state: :accepted) } + let(:approval_uri) { ActivityPub::TagManager.instance.approval_uri_for(quote) } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri, + } + end + + it 'updates the quote post without changing the quote status' do + expect { subject.call(status, json, json) } + .to not_change(quote, :approval_uri) + .and not_change(quote, :state).from('accepted') + .and change(status, :text).from('Hello world').to('Hello universe') + end + end + + context 'when an unapproved quote of a local post gets updated through an explicit update and claims approval' do + let(:quoted_account) { Fabricate(:account) } + let(:quoted_status) { Fabricate(:status, account: quoted_account, quote_approval_policy: 0) } + let!(:quote) { Fabricate(:quote, status: status, quoted_status: quoted_status, state: :rejected) } + let(:approval_uri) { ActivityPub::TagManager.instance.approval_uri_for(quote) } + + let(:payload) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'foo', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + updated: '2021-09-08T22:39:25Z', + quote: ActivityPub::TagManager.instance.uri_for(quoted_status), + quoteAuthorization: approval_uri, + } + end + + it 'updates the quote post without changing the quote status' do + expect { subject.call(status, json, json) } + .to not_change(quote, :approval_uri) + .and not_change(quote, :state).from('rejected') + .and change(status, :text).from('Hello world').to('Hello universe') + end + end + context 'when the status has an existing verified quote and removes an approval link through an explicit update' do let(:quoted_account) { Fabricate(:account, domain: 'quoted.example.com') } let(:quoted_status) { Fabricate(:status, account: quoted_account) } diff --git a/spec/services/revoke_quote_service_spec.rb b/spec/services/revoke_quote_service_spec.rb index 282a589162c..c1dbcfda54e 100644 --- a/spec/services/revoke_quote_service_spec.rb +++ b/spec/services/revoke_quote_service_spec.rb @@ -10,7 +10,7 @@ RSpec.describe RevokeQuoteService do let(:status) { Fabricate(:status, account: alice) } - let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted, approval_uri: "https://#{Rails.configuration.x.web_domain}/approvals/1234") } + let(:quote) { Fabricate(:quote, quoted_status: status, state: :accepted) } before do hank.follow!(alice) From ab1a5b48226b301cf634f4d109af36c2d1667507 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 08:46:38 +0200 Subject: [PATCH 200/660] Update Node.js to 22.18 (#35621) 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 4a203c23d83..f1c8f6b0d0f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.17 +22.18 From bf46cffd9eb6a8aa3e623c9bfdadc3b67b4959e6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 08:48:16 +0200 Subject: [PATCH 201/660] Update peter-evans/create-pull-request action to v7.0.8 (#35648) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/crowdin-download-stable.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml index 6d9a0586298..8e18a9d0a0f 100644 --- a/.github/workflows/crowdin-download-stable.yml +++ b/.github/workflows/crowdin-download-stable.yml @@ -50,7 +50,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.6 + uses: peter-evans/create-pull-request@v7.0.8 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)' From 05cdd3f6eb1b53174ed18bdd8697cfff9649a146 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 4 Aug 2025 09:43:34 +0200 Subject: [PATCH 202/660] Track delivery failures to FASP (#35628) --- app/lib/fasp/request.rb | 4 ++++ app/models/fasp/provider.rb | 4 ++++ spec/lib/fasp/request_spec.rb | 23 +++++++++++++++++++++++ spec/models/fasp/provider_spec.rb | 8 ++++++++ 4 files changed, 39 insertions(+) diff --git a/app/lib/fasp/request.rb b/app/lib/fasp/request.rb index 2002e90bb06..51950a004a2 100644 --- a/app/lib/fasp/request.rb +++ b/app/lib/fasp/request.rb @@ -32,8 +32,12 @@ class Fasp::Request .send(verb, url, body:) validate!(response) + @provider.delivery_failure_tracker.track_success! response.parse if response.body.present? + rescue *::Mastodon::HTTP_CONNECTION_ERRORS + @provider.delivery_failure_tracker.track_failure! + raise end def request_headers(_verb, _url, body = '') diff --git a/app/models/fasp/provider.rb b/app/models/fasp/provider.rb index 37d0b581ca1..9f7be482fed 100644 --- a/app/models/fasp/provider.rb +++ b/app/models/fasp/provider.rb @@ -118,6 +118,10 @@ class Fasp::Provider < ApplicationRecord save! end + def delivery_failure_tracker + @delivery_failure_tracker ||= DeliveryFailureTracker.new(base_url, resolution: :minutes) + end + private def create_keypair diff --git a/spec/lib/fasp/request_spec.rb b/spec/lib/fasp/request_spec.rb index 9b354c8f44b..380b7951242 100644 --- a/spec/lib/fasp/request_spec.rb +++ b/spec/lib/fasp/request_spec.rb @@ -27,6 +27,14 @@ RSpec.describe Fasp::Request do 'Signature-Input' => /.+/, }) end + + it 'tracks that a successful connection was made' do + provider.delivery_failure_tracker.track_failure! + + expect do + subject.send(method, '/test_path') + end.to change(provider.delivery_failure_tracker, :failures).from(1).to(0) + end end context 'when the response is not signed' do @@ -55,6 +63,21 @@ RSpec.describe Fasp::Request do end end end + + context 'when the request raises an error' do + before do + stub_request(method, 'https://reqprov.example.com/fasp/test_path') + .to_raise(HTTP::ConnectionError) + end + + it "records the failure using the provider's delivery failure tracker" do + expect do + subject.send(method, '/test_path') + end.to raise_error(HTTP::ConnectionError) + + expect(provider.delivery_failure_tracker.failures).to eq 1 + end + end end describe '#get' do diff --git a/spec/models/fasp/provider_spec.rb b/spec/models/fasp/provider_spec.rb index 52df4638fdc..9fd2c4c2348 100644 --- a/spec/models/fasp/provider_spec.rb +++ b/spec/models/fasp/provider_spec.rb @@ -206,4 +206,12 @@ RSpec.describe Fasp::Provider do end end end + + describe '#delivery_failure_tracker' do + subject { Fabricate(:fasp_provider) } + + it 'returns a `DeliverFailureTracker` instance' do + expect(subject.delivery_failure_tracker).to be_a(DeliveryFailureTracker) + end + end end From 927cfea5aefb5f210b2ae8af307b812b91d0f4c3 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 09:49:08 +0200 Subject: [PATCH 203/660] Update dependency ruby-saml to v1.18.1 (#35650) --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 19a97ef4962..3c5fc08ff12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -805,7 +805,7 @@ GEM ruby-prof (1.7.2) base64 ruby-progressbar (1.13.0) - ruby-saml (1.18.0) + ruby-saml (1.18.1) nokogiri (>= 1.13.10) rexml ruby-vips (2.2.4) From 3caa318dfe6b5bb8c379a03941f1f42a84df0f7f Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 09:49:12 +0200 Subject: [PATCH 204/660] Fix WebUI crashing for accounts with `null` URL (#35651) --- app/javascript/mastodon/api_types/accounts.ts | 2 +- app/javascript/mastodon/models/account.ts | 7 ++++--- app/serializers/rest/account_serializer.rb | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/javascript/mastodon/api_types/accounts.ts b/app/javascript/mastodon/api_types/accounts.ts index b93054a1f6f..913a201fef4 100644 --- a/app/javascript/mastodon/api_types/accounts.ts +++ b/app/javascript/mastodon/api_types/accounts.ts @@ -37,7 +37,7 @@ export interface BaseApiAccountJSON { roles?: ApiAccountJSON[]; statuses_count: number; uri: string; - url: string; + url?: string; username: string; moved?: ApiAccountJSON; suspended?: boolean; diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts index 75a5c09b9d8..3b0c41be818 100644 --- a/app/javascript/mastodon/models/account.ts +++ b/app/javascript/mastodon/models/account.ts @@ -45,7 +45,7 @@ const AccountRoleFactory = ImmutableRecord({ // Account export interface AccountShape extends Required< - Omit + Omit > { emojis: ImmutableList; fields: ImmutableList; @@ -55,6 +55,7 @@ export interface AccountShape note_plain: string | null; hidden: boolean; moved: string | null; + url: string; } export type Account = RecordOf; @@ -148,8 +149,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) { note_emojified: emojify(accountNote, emojiMap), note_plain: unescapeHTML(accountNote), url: - accountJSON.url.startsWith('http://') || - accountJSON.url.startsWith('https://') + accountJSON.url?.startsWith('http://') || + accountJSON.url?.startsWith('https://') ? accountJSON.url : accountJSON.uri, }); diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 354d384464d..b102f79fdb9 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -65,7 +65,7 @@ class REST::AccountSerializer < ActiveModel::Serializer end def url - ActivityPub::TagManager.instance.url_for(object) + ActivityPub::TagManager.instance.url_for(object) || ActivityPub::TagManager.instance.uri_for(object) end def uri From 3d3d2c93d6b03050fb3d048276e6b2933ac33501 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:12:11 +0200 Subject: [PATCH 205/660] Update DefinitelyTyped types (non-major) (#35647) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/yarn.lock b/yarn.lock index 377f2999c85..6e55410e31c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3925,11 +3925,11 @@ __metadata: linkType: hard "@types/cors@npm:^2.8.16": - version: 2.8.18 - resolution: "@types/cors@npm:2.8.18" + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" dependencies: "@types/node": "npm:*" - checksum: 10c0/9dd1075de0e3a40c304826668960c797e67e597a734fb8e8ab404561f31ef2bd553ef5500eb86da7e91a344bee038a59931d2fbf182fbce09f13816f51fdd80e + checksum: 10c0/b5dd407040db7d8aa1bd36e79e5f3f32292f6b075abc287529e9f48df1a25fda3e3799ba30b4656667ffb931d3b75690c1d6ca71e39f7337ea6dfda8581916d0 languageName: node linkType: hard @@ -4016,14 +4016,14 @@ __metadata: linkType: hard "@types/express@npm:^4.17.17": - version: 4.17.22 - resolution: "@types/express@npm:4.17.22" + version: 4.17.23 + resolution: "@types/express@npm:4.17.23" dependencies: "@types/body-parser": "npm:*" "@types/express-serve-static-core": "npm:^4.17.33" "@types/qs": "npm:*" "@types/serve-static": "npm:*" - checksum: 10c0/15c10a5ebb40a0356baa95ed374a2150d862786c9fccbdd724df12acc9c8cb08fbe1d34b446b1bcef2dbe5305cb3013fb39fba791baa54ef6df8056482776abb + checksum: 10c0/60490cd4f73085007247e7d4fafad0a7abdafa34fa3caba2757512564ca5e094ece7459f0f324030a63d513f967bb86579a8682af76ae2fd718e889b0a2a4fe8 languageName: node linkType: hard @@ -4035,12 +4035,13 @@ __metadata: linkType: hard "@types/hoist-non-react-statics@npm:^3.3.1": - version: 3.3.6 - resolution: "@types/hoist-non-react-statics@npm:3.3.6" + version: 3.3.7 + resolution: "@types/hoist-non-react-statics@npm:3.3.7" dependencies: - "@types/react": "npm:*" hoist-non-react-statics: "npm:^3.3.0" - checksum: 10c0/149a4c217d81f21f8a1e152160a59d5b99b6a9aa6d354385d5f5bc02760cbf1e170a8442ba92eb653befff44b0c5bc2234bb77ce33e0d11a65f779e8bab5c321 + peerDependencies: + "@types/react": "*" + checksum: 10c0/ed8f4e88338f7d021d0f956adf6089d2a12b2e254a03c05292324f2e986d2376eb9efdb8a4f04596823e8fca88c9d06361d20dab4a2a00dc935fb36ac911de55 languageName: node linkType: hard @@ -4096,9 +4097,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.195": - version: 4.17.17 - resolution: "@types/lodash@npm:4.17.17" - checksum: 10c0/8e75df02a15f04d4322c5a503e4efd0e7a92470570ce80f17e9f11ce2b1f1a7c994009c9bcff39f07e0f9ffd8ccaff09b3598997c404b801abd5a7eee5a639dc + version: 4.17.20 + resolution: "@types/lodash@npm:4.17.20" + checksum: 10c0/98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f languageName: node linkType: hard @@ -4154,13 +4155,13 @@ __metadata: linkType: hard "@types/pg@npm:^8.6.6": - version: 8.15.4 - resolution: "@types/pg@npm:8.15.4" + version: 8.15.5 + resolution: "@types/pg@npm:8.15.5" dependencies: "@types/node": "npm:*" pg-protocol: "npm:*" pg-types: "npm:^2.2.0" - checksum: 10c0/7f9295cb2d934681bba84f7caad529c3b100d87e83ad0732c7fe496f4f79e42a795097321db54e010fcff22cb5e410cf683b4c9941907ee4564c822242816e91 + checksum: 10c0/19a3cc1811918753f8c827733648c3a85c7b0355bf207c44eb1a3b79b2e6a0d85cb5457ec550d860fc9be7e88c7587a3600958ec8c61fa1ad573061c63af93f0 languageName: node linkType: hard @@ -4172,9 +4173,9 @@ __metadata: linkType: hard "@types/prop-types@npm:*, @types/prop-types@npm:^15.7.5": - version: 15.7.14 - resolution: "@types/prop-types@npm:15.7.14" - checksum: 10c0/1ec775160bfab90b67a782d735952158c7e702ca4502968aa82565bd8e452c2de8601c8dfe349733073c31179116cf7340710160d3836aa8a1ef76d1532893b1 + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 languageName: node linkType: hard From 1228e000a1058e2437b00a0de95d8b278077c358 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:33:17 +0200 Subject: [PATCH 206/660] Update dependency pg to v1.6.1 (#35559) 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 3c5fc08ff12..2901f24ed24 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -607,7 +607,7 @@ GEM parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.6.0) + pg (1.6.1) pghero (3.7.0) activerecord (>= 7.1) playwright-ruby-client (1.54.0) From 895975e2abe4de55307e4067bbfc599e7920a5f2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:33:41 +0200 Subject: [PATCH 207/660] Update dependency haml_lint to v0.66.0 (#35649) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2901f24ed24..2b6ba0d4852 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -287,7 +287,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.65.1) + haml_lint (0.66.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -721,7 +721,7 @@ GEM connection_pool redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.10.0) + regexp_parser (2.11.0) reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) From 59e729e3fef1afaf611e1dd47ed32394fcfbe9e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:34:04 +0200 Subject: [PATCH 208/660] Update dependency ioredis to v5.7.0 (#35617) 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 6e55410e31c..8148fab1c86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2511,10 +2511,10 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:^1.1.1": - version: 1.2.0 - resolution: "@ioredis/commands@npm:1.2.0" - checksum: 10c0/a5d3c29dd84d8a28b7c67a441ac1715cbd7337a7b88649c0f17c345d89aa218578d2b360760017c48149ef8a70f44b051af9ac0921a0622c2b479614c4f65b36 +"@ioredis/commands@npm:^1.3.0": + version: 1.3.0 + resolution: "@ioredis/commands@npm:1.3.0" + checksum: 10c0/5ab990a8f69c20daf3d7d64307aa9f13ee727c92ab4c7664a6943bb500227667a0c368892e9c4913f06416377db47dba78d58627fe723da476d25f2c04a6d5aa languageName: node linkType: hard @@ -8353,10 +8353,10 @@ __metadata: linkType: hard "ioredis@npm:^5.3.2": - version: 5.6.1 - resolution: "ioredis@npm:5.6.1" + version: 5.7.0 + resolution: "ioredis@npm:5.7.0" dependencies: - "@ioredis/commands": "npm:^1.1.1" + "@ioredis/commands": "npm:^1.3.0" cluster-key-slot: "npm:^1.1.0" debug: "npm:^4.3.4" denque: "npm:^2.1.0" @@ -8365,7 +8365,7 @@ __metadata: redis-errors: "npm:^1.2.0" redis-parser: "npm:^3.0.0" standard-as-callback: "npm:^2.1.0" - checksum: 10c0/26ae49cf448e807e454a9bdea5a9dfdcf669e2fdbf2df341900a0fb693c5662fea7e39db3227ce8972d1bda0ba7da9b7410e5163b12d8878a579548d847220ac + checksum: 10c0/c63c521a953bfaf29f8c8871b122af38e439328336fa238f83bfbb066556f64daf69ed7a4ec01fc7b9ee1f0862059dd188b8c684150125d362d36642399b30ee languageName: node linkType: hard From f68bd21600aaab5a928de7eda56a132efeefd9f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:20:50 +0200 Subject: [PATCH 209/660] New Crowdin Translations (automated) (#35634) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/fi.json | 2 +- app/javascript/mastodon/locales/la.json | 7 +++ app/javascript/mastodon/locales/nan.json | 3 + app/javascript/mastodon/locales/pl.json | 70 +++++++++++++++++++++++- app/javascript/mastodon/locales/tr.json | 7 +++ config/locales/activerecord.pl.yml | 6 ++ config/locales/da.yml | 2 +- config/locales/el.yml | 2 + config/locales/et.yml | 3 + config/locales/fo.yml | 2 + config/locales/nan.yml | 54 ++++++++++++++++++ config/locales/pl.yml | 66 ++++++++++++++++++++++ config/locales/ru.yml | 34 ++++++------ config/locales/simple_form.da.yml | 2 +- config/locales/simple_form.el.yml | 1 + config/locales/simple_form.fo.yml | 1 + config/locales/simple_form.pl.yml | 12 ++++ config/locales/simple_form.tr.yml | 9 +++ config/locales/tr.yml | 29 +++++++++- 19 files changed, 290 insertions(+), 22 deletions(-) diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 6efe46693d4..9e52dcb069f 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -756,7 +756,7 @@ "reply_indicator.cancel": "Peruuta", "reply_indicator.poll": "Äänestys", "report.block": "Estä", - "report.block_explanation": "Et näe hänen julkaisujaan. Hän voi nähdä julkaisujasi eikä seurata sinua. Hän näkee, että olet estänyt hänet.", + "report.block_explanation": "Et näe hänen julkaisujaan. Hän ei voi nähdä julkaisujasi eikä seurata sinua. Hän näkee, että olet estänyt hänet.", "report.categories.legal": "Lakiseikat", "report.categories.other": "Muu", "report.categories.spam": "Roskaposti", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index 5d34aafafce..720e940996f 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -1,6 +1,7 @@ { "about.blocks": "Servī moderātī", "about.contact": "Ratio:", + "about.default_locale": "Default", "about.disclaimer": "Mastodon est software līberum, apertum fontem, et nōtam commercium Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Ratio abdere est", "about.domain_blocks.preamble": "Mastodon genērāliter sinit tē contentum ex aliīs servientibus in fedīversō vidēre et cum usoribus ab iīs interāgere. Haē sunt exceptionēs quae in hōc particulārī servientē factae sunt.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Limitātus", "about.domain_blocks.suspended.explanation": "Nulla data ab hōc servientē processābuntur, servābuntur aut commūtābuntur, faciendumque omnem interactionem aut communicātiōnem cum usoribus ab hōc servientē impossibilem.", "about.domain_blocks.suspended.title": "suspensus", + "about.language_label": "Linguae", "about.not_available": "Haec informātiō in hōc servientē nōn praebita est.", "about.powered_by": "Nuntii socīālēs decentralizātī ā {mastodon} sustentātī.", "about.rules": "Servo praecepta", @@ -41,7 +43,12 @@ "account.followers": "Sectatores", "account.followers.empty": "Nemo hunc usorem adhuc sequitur.", "account.followers_counter": "{count, plural, one {{counter} sectator} other {{counter} sectatores}}", + "account.followers_you_know_counter": "{counter} scis", + "account.following": "Sequentia", "account.following_counter": "{count, plural, one {{counter} sectans} other {{counter} sectans}}", + "account.follows.empty": "Hic usor adhuc neminem sequitur.", + "account.follows_you": "Sequitur te", + "account.go_to_profile": "Vade ad profile", "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:", "account.muted": "Confutatus", "account.requested_follow": "{name} postulavit ut te sequeretur", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index a5611c6c9de..40b3c06281c 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -876,6 +876,9 @@ "status.quote_error.filtered": "Lí所設定ê過濾器kā tse khàm起來", "status.quote_error.not_available": "鋪文bē當看", "status.quote_error.pending_approval": "鋪文當咧送", + "status.quote_error.pending_approval_popout.body": "因為無kâng ê服侍器有無kâng ê協定,佇聯邦宇宙分享ê引文可能愛開時間來顯示。", + "status.quote_error.pending_approval_popout.title": "Leh送引文?請sió等leh", + "status.quote_post_author": "引用 @{name} ê PO文ah", "status.read_more": "讀詳細", "status.reblog": "轉送", "status.reblog_private": "照原PO ê通看見ê範圍轉送", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index bbe5789d2a4..58c3f1fa574 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -1,6 +1,7 @@ { "about.blocks": "Serwery moderowane", "about.contact": "Kontakt:", + "about.default_locale": "Domyślny", "about.disclaimer": "Mastodon jest darmowym, otwartym oprogramowaniem i znakiem towarowym Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Powód niedostępny", "about.domain_blocks.preamble": "Domyślnie Mastodon pozwala ci przeglądać i reagować na treści od innych użytkowników z jakiegokolwiek serwera w fediwersum. Poniżej znajduje się lista wyjątków, które zostały stworzone na tym konkretnym serwerze.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Ograniczone", "about.domain_blocks.suspended.explanation": "Żadne dane z tego serwera nie będą przetwarzane, przechowywane lub wymieniane, co uniemożliwia jakąkolwiek interakcję lub komunikację z użytkownikami z tego serwera.", "about.domain_blocks.suspended.title": "Zawieszono", + "about.language_label": "Język", "about.not_available": "Ta informacja nie została udostępniona na tym serwerze.", "about.powered_by": "Zdecentralizowane media społecznościowe napędzane przez {mastodon}", "about.rules": "Regulamin serwera", @@ -19,13 +21,21 @@ "account.block_domain": "Blokuj wszystko z {domain}", "account.block_short": "Zablokuj", "account.blocked": "Zablokowany(-a)", + "account.blocking": "Blokowanie", "account.cancel_follow_request": "Nie obserwuj", "account.copy": "Skopiuj link do profilu", "account.direct": "Napisz bezpośrednio do @{name}", "account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}", + "account.domain_blocking": "Blokowanie domeny", "account.edit_profile": "Edytuj profil", "account.enable_notifications": "Powiadamiaj mnie o wpisach @{name}", "account.endorse": "Wyróżnij na profilu", + "account.familiar_followers_many": "Obserwowane przez: {name1}, {name2} i {othersCount, plural, one {jeszcze jedną osobę, którą znasz} few {# inne osoby, które znasz} many {# innych osób, które znasz} other {# innych osób, które znasz}}", + "account.familiar_followers_one": "Obserwowane przez {name1}", + "account.familiar_followers_two": "Obserwowane przez {name1} i {name2}", + "account.featured": "Wyróżnione", + "account.featured.accounts": "Profile", + "account.featured.hashtags": "Tagi", "account.featured_tags.last_status_at": "Ostatni post {date}", "account.featured_tags.last_status_never": "Brak postów", "account.follow": "Obserwuj", @@ -33,9 +43,11 @@ "account.followers": "Obserwujący", "account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.", "account.followers_counter": "{count, plural, one {{counter} obserwujący} few {{counter} obserwujących} many {{counter} obserwujących} other {{counter} obserwujących}}", + "account.followers_you_know_counter": "{counter} które znasz", "account.following": "Obserwowani", "account.following_counter": "{count, plural, one {{counter} obserwowany} few {{counter} obserwowanych} many {{counter} obserwowanych} other {{counter} obserwowanych}}", "account.follows.empty": "Ten użytkownik nie obserwuje jeszcze nikogo.", + "account.follows_you": "Obserwuje cię", "account.go_to_profile": "Przejdź do profilu", "account.hide_reblogs": "Ukryj podbicia od @{name}", "account.in_memoriam": "Ku pamięci.", @@ -50,18 +62,23 @@ "account.mute_notifications_short": "Wycisz powiadomienia", "account.mute_short": "Wycisz", "account.muted": "Wyciszony", + "account.muting": "Wyciszenie", + "account.mutual": "Obserwujecie siebie nazwajem", "account.no_bio": "Brak opisu.", "account.open_original_page": "Otwórz stronę oryginalną", "account.posts": "Wpisy", "account.posts_with_replies": "Wpisy i odpowiedzi", + "account.remove_from_followers": "Usuń {name} z obserwujących", "account.report": "Zgłoś @{name}", "account.requested": "Oczekująca prośba, kliknij aby anulować", "account.requested_follow": "{name} chce cię zaobserwować", + "account.requests_to_follow_you": "Prośby o obserwowanie", "account.share": "Udostępnij profil @{name}", "account.show_reblogs": "Pokazuj podbicia od @{name}", "account.statuses_counter": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}", "account.unblock": "Odblokuj @{name}", "account.unblock_domain": "Odblokuj domenę {domain}", + "account.unblock_domain_short": "Odblokuj", "account.unblock_short": "Odblokuj", "account.unendorse": "Nie wyświetlaj w profilu", "account.unfollow": "Nie obserwuj", @@ -202,6 +219,12 @@ "confirmations.delete_list.confirm": "Usuń", "confirmations.delete_list.message": "Czy na pewno chcesz trwale usunąć tę listę?", "confirmations.delete_list.title": "Usunąć listę?", + "confirmations.discard_draft.confirm": "Odrzuć i kontynuuj", + "confirmations.discard_draft.edit.cancel": "Wznów edytowanie", + "confirmations.discard_draft.edit.message": "Kontynuowanie spowoduje utratę wszystkich zmian wprowadzonych przez Ciebie w aktualnie edytowanym poście.", + "confirmations.discard_draft.edit.title": "Odrzucić zmiany w poście?", + "confirmations.discard_draft.post.cancel": "Wznów wersję roboczą", + "confirmations.discard_draft.post.title": "Anulować wersję roboczą?", "confirmations.discard_edit_media.confirm": "Odrzuć", "confirmations.discard_edit_media.message": "Masz niezapisane zmiany w opisie lub podglądzie, odrzucić je mimo to?", "confirmations.follow_to_list.confirm": "Zaobserwuj i dodaj do listy", @@ -218,6 +241,9 @@ "confirmations.redraft.confirm": "Usuń i popraw", "confirmations.redraft.message": "Czy na pewno chcesz usunąć i poprawić ten wpis? Polubienia, podbicia i komentarze pierwotnego wpisu zostaną utracone.", "confirmations.redraft.title": "Usunąć i poprawić wpis?", + "confirmations.remove_from_followers.confirm": "Usuń obserwującego", + "confirmations.remove_from_followers.message": "{name} przestanie Cię obserwować. Czy na pewno chcesz kontynuować?", + "confirmations.remove_from_followers.title": "Usunąć obserwującego?", "confirmations.unfollow.confirm": "Nie obserwuj", "confirmations.unfollow.message": "Czy na pewno nie chcesz obserwować {name}?", "confirmations.unfollow.title": "Cofnąć obserwację?", @@ -307,9 +333,15 @@ "errors.unexpected_crash.copy_stacktrace": "Skopiuj stacktrace do schowka", "errors.unexpected_crash.report_issue": "Zgłoś problem", "explore.suggested_follows": "Ludzie", + "explore.title": "Na czasie", "explore.trending_links": "Aktualności", "explore.trending_statuses": "Wpisy", "explore.trending_tags": "Hasztagi", + "featured_carousel.header": "{count, plural, one {Przypięty post} other {Przypięte posty}}", + "featured_carousel.next": "Następny", + "featured_carousel.post": "Opublikuj", + "featured_carousel.previous": "Poprzedni", + "featured_carousel.slide": "{index} z {total}", "filter_modal.added.context_mismatch_explanation": "To filtrowanie nie dotyczy kategorii, w której pojawił się ten wpis. Jeśli chcesz, aby wpis był filtrowany również w tym kontekście, musisz edytować ustawienia filtrowania.", "filter_modal.added.context_mismatch_title": "Niewłaściwy kontekst!", "filter_modal.added.expired_explanation": "Ta kategoria filtrowania wygasła, aby ją zastosować, należy zmienić datę wygaśnięcia.", @@ -362,6 +394,8 @@ "generic.saved": "Zapisano", "getting_started.heading": "Pierwsze kroki", "hashtag.admin_moderation": "Otwórz interfejs moderacji #{name}", + "hashtag.browse": "Przeglądaj posty z #{hashtag}", + "hashtag.browse_from_account": "Przeglądaj posty od @{name} z #{hashtag}", "hashtag.column_header.tag_mode.all": "i {additional}", "hashtag.column_header.tag_mode.any": "lub {additional}", "hashtag.column_header.tag_mode.none": "bez {additional}", @@ -374,7 +408,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} osoba} few {{counter} osoby} many {{counter} osób} other {{counter} osób}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}} dzisiaj", + "hashtag.feature": "Wyróżnij w profilu", "hashtag.follow": "Obserwuj hasztag", + "hashtag.mute": "Wycisz #{hashtag}", + "hashtag.unfeature": "Nie wyróżniaj w profilu", "hashtag.unfollow": "Przestań obserwować hashtag", "hashtags.and_other": "…i {count, plural, other {jeszcze #}}", "hints.profiles.followers_may_be_missing": "Niektórzy obserwujący ten profil mogą być niewidoczni.", @@ -383,6 +420,7 @@ "hints.profiles.see_more_followers": "Zobacz więcej obserwujących na {domain}", "hints.profiles.see_more_follows": "Zobacz więcej obserwowanych na {domain}", "hints.profiles.see_more_posts": "Zobacz więcej wpisów na {domain}", + "home.column_settings.show_quotes": "Pokaż cytaty", "home.column_settings.show_reblogs": "Pokazuj podbicia", "home.column_settings.show_replies": "Pokazuj odpowiedzi", "home.hide_announcements": "Ukryj ogłoszenia", @@ -456,6 +494,8 @@ "keyboard_shortcuts.translate": "aby przetłumaczyć wpis", "keyboard_shortcuts.unfocus": "Opuść pole tekstowe", "keyboard_shortcuts.up": "Przesuń w górę na liście", + "learn_more_link.got_it": "Rozumiem", + "learn_more_link.learn_more": "Dowiedz się więcej", "lightbox.close": "Zamknij", "lightbox.next": "Następne", "lightbox.previous": "Poprzednie", @@ -505,8 +545,10 @@ "mute_modal.you_wont_see_mentions": "Nie zobaczysz wpisów wzmiankujących tę osobę.", "mute_modal.you_wont_see_posts": "Nie zobaczysz wpisów tej osoby, ale ona może widzieć twoje.", "navigation_bar.about": "O serwerze", + "navigation_bar.account_settings": "Hasło i zabezpieczenia", "navigation_bar.administration": "Administracja", "navigation_bar.advanced_interface": "Otwórz w widoku zaawansowanym", + "navigation_bar.automated_deletion": "Automatyczne usuwanie postów", "navigation_bar.blocks": "Zablokowani", "navigation_bar.bookmarks": "Zakładki", "navigation_bar.direct": "Wzmianki bezpośrednie", @@ -516,13 +558,21 @@ "navigation_bar.follow_requests": "Prośby o obserwowanie", "navigation_bar.followed_tags": "Obserwowane hasztagi", "navigation_bar.follows_and_followers": "Obserwowani i obserwujący", + "navigation_bar.import_export": "Import i eksport", "navigation_bar.lists": "Listy", "navigation_bar.logout": "Wyloguj", "navigation_bar.moderation": "Moderacja", + "navigation_bar.more": "Więcej", "navigation_bar.mutes": "Wyciszeni", "navigation_bar.opened_in_classic_interface": "Wpisy, konta i inne określone strony są domyślnie otwierane w widoku klasycznym.", "navigation_bar.preferences": "Ustawienia", + "navigation_bar.privacy_and_reach": "Prywatność i zasięg", "navigation_bar.search": "Szukaj", + "navigation_bar.search_trends": "Szukaj / Na czasie", + "navigation_panel.collapse_followed_tags": "Zwiń menu obserwowanych hashtagów", + "navigation_panel.collapse_lists": "Zwiń menu listy", + "navigation_panel.expand_followed_tags": "Rozwiń menu obserwowanych hashtagów", + "navigation_panel.expand_lists": "Rozwiń menu listy", "not_signed_in_indicator.not_signed_in": "Zaloguj się, aby uzyskać dostęp.", "notification.admin.report": "{name} zgłosił {target}", "notification.admin.report_account": "{name} zgłosił(a) {count, plural, one {1 wpis} few {# wpisy} other {# wpisów}} z {target} w kategorii {category}", @@ -749,6 +799,7 @@ "report_notification.categories.violation": "Naruszenie zasad", "report_notification.categories.violation_sentence": "naruszenie zasad", "report_notification.open": "Otwórz zgłoszenie", + "search.clear": "Wyczyść wyszukiwanie", "search.no_recent_searches": "Brak ostatnich wyszukiwań", "search.placeholder": "Szukaj", "search.quick_action.account_search": "Profile pasujące do {x}", @@ -790,6 +841,8 @@ "status.bookmark": "Dodaj zakładkę", "status.cancel_reblog_private": "Cofnij podbicie", "status.cannot_reblog": "Ten wpis nie może zostać podbity", + "status.context.load_new_replies": "Dostępne są nowe odpowiedzi", + "status.context.loading": "Sprawdzanie kolejnych odpowiedzi", "status.continued_thread": "Ciąg dalszy wątku", "status.copy": "Skopiuj odnośnik do wpisu", "status.delete": "Usuń", @@ -815,6 +868,10 @@ "status.mute_conversation": "Wycisz konwersację", "status.open": "Rozszerz ten wpis", "status.pin": "Przypnij do profilu", + "status.quote_error.filtered": "Ukryte z powodu jednego z Twoich filtrów", + "status.quote_error.not_available": "Post niedostępny", + "status.quote_error.pending_approval": "Post oczekujący", + "status.quote_post_author": "Zacytowano post @{name}", "status.read_more": "Czytaj dalej", "status.reblog": "Podbij", "status.reblog_private": "Podbij dla odbiorców oryginalnego wpisu", @@ -844,8 +901,13 @@ "subscribed_languages.save": "Zapisz zmiany", "subscribed_languages.target": "Zmień subskrybowane języki dla {target}", "tabs_bar.home": "Strona główna", + "tabs_bar.menu": "Menu", "tabs_bar.notifications": "Powiadomienia", + "tabs_bar.publish": "Nowy post", + "tabs_bar.search": "Szukaj", + "terms_of_service.effective_as_of": "Obowiązuje od {date}", "terms_of_service.title": "Warunki korzystania z usługi", + "terms_of_service.upcoming_changes_on": "Nadchodzące zmiany od {date}", "time_remaining.days": "{number, plural, one {Pozostał # dzień} few {Pozostały # dni} many {Pozostało # dni} other {Pozostało # dni}}", "time_remaining.hours": "{number, plural, one {Pozostała # godzina} few {Pozostały # godziny} many {Pozostało # godzin} other {Pozostało # godzin}}", "time_remaining.minutes": "{number, plural, one {Pozostała # minuta} few {Pozostały # minuty} many {Pozostało # minut} other {Pozostało # minut}}", @@ -876,6 +938,12 @@ "video.expand": "Rozszerz film", "video.fullscreen": "Pełny ekran", "video.hide": "Ukryj film", + "video.mute": "Wycisz", "video.pause": "Pauzuj", - "video.play": "Odtwórz" + "video.play": "Odtwórz", + "video.skip_backward": "Skocz do tyłu", + "video.skip_forward": "Skocz do przodu", + "video.unmute": "Wyłącz wyciszenie", + "video.volume_down": "Zmniejsz głośność", + "video.volume_up": "Zwiększ głośność" } diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index fb2a9237bcb..a7fa70a28a5 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "bir gönderiyi çevirmek için", "keyboard_shortcuts.unfocus": "Aramada bir gönderiye odaklanmamak için", "keyboard_shortcuts.up": "Listede yukarıya çıkmak için", + "learn_more_link.got_it": "Anladım", + "learn_more_link.learn_more": "Daha fazla bilgi edin", "lightbox.close": "Kapat", "lightbox.next": "Sonraki", "lightbox.previous": "Önceki", @@ -873,6 +875,11 @@ "status.open": "Bu gönderiyi genişlet", "status.pin": "Profile sabitle", "status.quote_error.filtered": "Bazı filtrelerinizden dolayı gizlenmiştir", + "status.quote_error.not_available": "Gönderi kullanılamıyor", + "status.quote_error.pending_approval": "Gönderi beklemede", + "status.quote_error.pending_approval_popout.body": "Fediverse genelinde paylaşılan alıntıların görüntülenmesi zaman alabilir, çünkü farklı sunucuların farklı protokolleri vardır.", + "status.quote_error.pending_approval_popout.title": "Bekleyen bir teklif mi var? Sakin olun.", + "status.quote_post_author": "@{name} adlı kullanıcının bir gönderisini alıntıladı", "status.read_more": "Devamını okuyun", "status.reblog": "Yeniden paylaş", "status.reblog_private": "Özgün görünürlük ile yeniden paylaş", diff --git a/config/locales/activerecord.pl.yml b/config/locales/activerecord.pl.yml index 62edc03c0ea..29cace6db53 100644 --- a/config/locales/activerecord.pl.yml +++ b/config/locales/activerecord.pl.yml @@ -49,8 +49,14 @@ pl: attributes: reblog: taken: status już istnieje + terms_of_service: + attributes: + effective_date: + too_soon: jest zbyt wcześnie, musi być później niż %{date} user: attributes: + date_of_birth: + below_limit: jest poniżej granicy wiekowej email: blocked: używa niedozwolonego dostawcy poczty elektronicznej unreachable: wydaje się nie istnieć diff --git a/config/locales/da.yml b/config/locales/da.yml index 67e5a716227..1b56425ee2b 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1905,7 +1905,7 @@ da: ownership: Andres indlæg kan ikke fastgøres reblog: En fremhævelse kan ikke fastgøres quote_policies: - followers: Kun dine følgere + followers: Kun egne følgere nobody: Ingen public: Alle title: '%{name}: "%{quote}"' diff --git a/config/locales/el.yml b/config/locales/el.yml index 73e46ee13c0..2ad8bc6899c 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1905,6 +1905,8 @@ el: ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου reblog: Οι ενισχύσεις δεν καρφιτσώνονται quote_policies: + followers: Μόνο οι ακόλουθοί σου + nobody: Κανένας public: Όλοι title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/et.yml b/config/locales/et.yml index e9935cc72b5..7d96b4c57ed 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -325,6 +325,8 @@ et: create: Loo teadaanne title: Uus teadaanne preview: + disclaimer: Kuna kasutajd ei saa neist postiitustest keelduda, siis peaksid teavituskirjad keskenduma vaid olulistele teemadele nagi võimalik isiklike andmete leke või serveri tegevuse lõpetamine. + explanation_html: 'See e-kiri saadetakse %{display_count}-le kasutajale. E-kirjas sisaldub järgnev tekst:' title: Info teavituse üle vaatamine publish: Postita published_msg: Teadaande avaldamine õnnestus! @@ -992,6 +994,7 @@ et: explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga. title: Teenuse tingimuste seadistamine history: Ajalugu + notified_on_html: 'Kasutajad on teavitatud: %{date}' notify_users: Teata kasutajatele preview: explanation_html: 'See e-kiri saadetakse %{display_count}-le kasutajale, kes olid liitunud enne %{date}. E-kirjas sisaldub järgnev tekst:' diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 2dd500d694d..edac267254c 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1905,6 +1905,8 @@ fo: ownership: Postar hjá øðrum kunnu ikki festast reblog: Ein stimbran kann ikki festast quote_policies: + followers: Einans tey, ið fylgja tær + nobody: Eingin public: Øll title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/nan.yml b/config/locales/nan.yml index f6c4f0dc383..a6203584284 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -255,6 +255,7 @@ nan: create_relay_html: "%{name} 建立中繼 %{target}" create_unavailable_domain_html: "%{name} 停止送kàu域名 %{target}" create_user_role_html: "%{name} 建立 %{target} 角色" + create_username_block_html: "%{name} 加添用者ê名包含 %{target} ê規則ah" 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" @@ -268,6 +269,7 @@ nan: destroy_status_html: "%{name} kā %{target} ê PO文thâi掉" destroy_unavailable_domain_html: "%{name} 恢復送kàu域名 %{target}" destroy_user_role_html: "%{name} thâi掉 %{target} 角色" + destroy_username_block_html: "%{name} thâi掉用者ê名包含 %{target} ê規則ah" disable_2fa_user_html: "%{name} 停止使用者 %{target} 用雙因素驗證" disable_custom_emoji_html: "%{name} kā 新ê emoji %{target} 停止使用ah" disable_relay_html: "%{name} 停止使用中繼 %{target}" @@ -302,6 +304,7 @@ nan: update_report_html: "%{name} 更新 %{target} ê檢舉" update_status_html: "%{name} kā %{target} ê PO文更新" update_user_role_html: "%{name} 更改 %{target} 角色" + update_username_block_html: "%{name} 更新用者ê名包含 %{target} ê規則ah" deleted_account: thâi掉ê口座 empty: Tshuē無log。 filter_by_action: 照動作過濾 @@ -639,6 +642,57 @@ nan: other: "%{count} 篇筆記" action_log: 審查日誌 action_taken_by: 操作由 + actions: + delete_description_html: 受檢舉ê PO文ē thâi掉,而且ē用tsi̍t ue̍h橫tsuā記錄,幫tsān lí提升kâng tsi̍t ê用戶未來ê違規。 + mark_as_sensitive_description_html: 受檢舉ê PO文內ê媒體ē標做敏感,而且ē用tsi̍t ue̍h橫tsuā記錄,幫tsān lí提升kâng tsi̍t ê用戶未來ê違規。 + other_description_html: 看其他控制tsit ê口座ê所行,kap自訂聯絡受檢舉ê口座ê選項。 + resolve_description_html: Buē用行動控制受檢舉ê口座,mā無用橫tsuā記錄,而且tsit ê報告ē關掉。 + silence_description_html: 本口座kan-ta ē hōo早前跟tuè ê á是手動tshiau ê看見,大大限制看見ê範圍。設定隨時ē當回復。請關所有tuì tsit ê口座ê檢舉。 + suspend_description_html: Tsit ê口座kap伊ê內容ē bē當用,落尾ē thâi掉,mā bē當hām伊互動。30 kang以內通回復。請關所有tuì tsit ê口座ê檢舉。 + actions_description_html: 決定行siánn物行動來解決tsit ê檢舉。Nā lí tuì受檢舉ê口座採用處罰,電子phue通知ē送予in,除非選擇 Pùn-sò phue 類別。 + actions_description_remote_html: 決定行siánn物行動來解決tsit ê檢舉。Tse kan-ta ē影響 lí ê 服侍器hām tsit ê遠距離服侍器聯絡kap處理伊ê內容ê方法。 + actions_no_posts: Tsit份檢舉無beh thâi掉ê相關PO文 + add_to_report: 加添其他ê內容kàu檢舉 + already_suspended_badges: + local: 已經佇tsit ê服侍器停止權限ah + remote: 已經佇in ê服侍器停止權限ah + are_you_sure: Lí kám確定? + assign_to_self: 分配hōo家kī + assigned: 分配管理者 + by_target_domain: 受檢舉ê口座ê網域 + cancel: 取消 + category: 類別 + category_description_html: Tsit ê 受檢舉ê口座kap/á是內容,ē佇kap tsit ê口座ê聯絡內底引用。 + comment: + none: 無 + comment_description_html: 為著提供其他資訊,%{name} 寫: + confirm: 確認 + confirm_action: 確認kā %{acct} 審核ê動作 + created_at: 檢舉tī + delete_and_resolve: Thâi掉PO文 + forwarded: 轉送ah + forwarded_replies_explanation: 本報告是tuì別站ê用者送ê,關係別站ê內容。本報告轉hōo lí,因為受檢舉ê內容是回應lí ê服侍器ê用者。 + forwarded_to: 有轉送kàu %{domain} + mark_as_resolved: 標做「解決ah」 + mark_as_sensitive: 標做敏感 + mark_as_unresolved: 標做「無解決」 + no_one_assigned: 無lâng + notes: + create: 加添筆記 + create_and_resolve: 標「處理ah」,留筆記 + create_and_unresolve: 留筆記,koh重開 + delete: Thâi掉 + placeholder: 描述有行siánn物行動,á是其他關聯ê更新…… + title: 筆記 + notes_description_html: 檢視á是留筆記hōo別ê管理者kap未來ê家己 + processed_msg: '檢舉 #%{id} 處理成功ah' + quick_actions_description_html: 緊行行動,á是giú kàu下kha,看檢舉ê內容: + remote_user_placeholder: tuì %{instance} 來ê遠距離用者 + reopen: 重頭phah開檢舉 + report: '檢舉 #%{id}' + roles: + privileges: + manage_announcements: 管理公告 statuses: language: 語言 trends: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 36897280f5d..29de53637cd 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -196,6 +196,7 @@ pl: create_relay: Utwórz przekaźnik create_unavailable_domain: Utwórz niedostępną domenę create_user_role: Utwórz rolę + create_username_block: Utwórz zasadę nazwy użytkownika demote_user: Zdegraduj użytkownika destroy_announcement: Usuń ogłoszenie destroy_canonical_email_block: Usuń blokadę e-mail @@ -209,6 +210,7 @@ pl: destroy_status: Usuń wpis destroy_unavailable_domain: Usuń niedostępną domenę destroy_user_role: Zlikwiduj rolę + destroy_username_block: Usuń zasadę nazwy użytkownika disable_2fa_user: Wyłącz 2FA disable_custom_emoji: Wyłącz niestandardowe emoji disable_relay: Wyłącz przekaźnik @@ -243,6 +245,7 @@ pl: update_report: Wiadomości or raporcie update_status: Aktualizuj wpis update_user_role: Aktualizuj rolę + update_username_block: Zaktualizuj zasadę nazwy użytkownika actions: approve_appeal_html: "%{name} zatwierdził(-a) odwołanie decyzji moderacyjnej od %{target}" approve_user_html: "%{name} zatwierdził rejestrację od %{target}" @@ -261,6 +264,7 @@ pl: create_relay_html: "%{name} utworzył przekaźnik %{target}" create_unavailable_domain_html: "%{name} przestał(a) doręczać na domenę %{target}" create_user_role_html: "%{name} utworzył rolę %{target}" + create_username_block_html: Użytkownik %{name} dodał zasadę dla nazw użytkowników zawierających %{target} demote_user_html: "%{name} zdegradował(a) użytkownika %{target}" destroy_announcement_html: "%{name} usunął(-ęła) ogłoszenie %{target}" destroy_canonical_email_block_html: "%{name} odblokował(a) e-mail z hashem %{target}" @@ -274,6 +278,7 @@ pl: destroy_status_html: "%{name} usunął(-ęła) wpis użytkownika %{target}" destroy_unavailable_domain_html: "%{name} wznowił(a) doręczanie do domeny %{target}" destroy_user_role_html: "%{name} usunął rolę %{target}" + destroy_username_block_html: Użytkownik %{name} usunął zasadę dla nazw użytkowników zawierających %{target} disable_2fa_user_html: "%{name} wyłączył(a) uwierzytelnianie dwuskładnikowe użytkownikowi %{target}" disable_custom_emoji_html: "%{name} wyłączył(a) emoji %{target}" disable_relay_html: "%{name} wyłączył przekaźnik %{target}" @@ -308,6 +313,7 @@ pl: update_report_html: "%{target} zaktualizowany przez %{name}" update_status_html: "%{name} zaktualizował(a) wpis użytkownika %{target}" update_user_role_html: "%{name} zmienił rolę %{target}" + update_username_block_html: Użytkownik %{name} zaktualizował zasadę dla nazw użytkowników zawierających %{target} deleted_account: usunięte konto empty: Nie znaleziono aktywności w dzienniku. filter_by_action: Filtruj według działania @@ -315,6 +321,7 @@ pl: title: Dziennik działań administracyjnych unavailable_instance: "(domena niedostępna)" announcements: + back: Powrót do ogłoszeń destroyed_msg: Pomyślnie usunięto ogłoszenie! edit: title: Edytuj ogłoszenie @@ -323,6 +330,9 @@ pl: new: create: Utwórz ogłoszenie title: Nowe ogłoszenie + preview: + explanation_html: 'Wiadomość e-mail zostanie wysłana do %{display_count} użytkowników. Otrzymają oni wiadomość o następującej treści:' + title: Podgląd powiadomienia publish: Opublikuj published_msg: Pomyślnie opublikowano ogłoszenie! scheduled_for: Zaplanowano na %{time} @@ -491,6 +501,29 @@ pl: new: title: Importuj zablokowane domeny no_file: Nie wybrano pliku + fasp: + debug: + callbacks: + created_at: 'Utworzono:' + delete: Usuń + ip: Adres IP + providers: + active: Aktywne + base_url: Podstawowy adres URL + delete: Usuń + edit: Edytuj dostawców + finish_registration: Zakończ rejestrację + name: Nazwa + providers: Dostawca + registration_requested: Wymagana rejestracja + registrations: + confirm: Zatwierdź + reject: Odrzuć + save: Zapisz + sign_in: Zaloguj się + status: Status + title: Dostawcy usług pomocniczych Fediverse (Fediverse Auxiliary Service Providers) + title: FASP follow_recommendations: description_html: "Polecane obserwacje pomagają nowym użytkownikom szybko odnaleźć interesujące treści. Jeżeli użytkownik nie wchodził w interakcje z innymi wystarczająco często, aby powstały spersonalizowane rekomendacje, polecane są te konta. Są one obliczane każdego dnia na podstawie kombinacji kont o największej liczbie niedawnej aktywności i największej liczbie lokalnych obserwatorów dla danego języka." language: Dla języka @@ -565,6 +598,8 @@ pl: all: Wszystkie limited: Ograniczone title: Moderacja + moderation_notes: + title: Notatki moderacyjne private_comment: Prywatny komentarz public_comment: Publiczny komentarz purge: Wyczyść @@ -779,11 +814,16 @@ pl: title: Role rules: add_new: Dodaj zasadę + add_translation: Dodaj tłumaczenie delete: Usuń description_html: Chociaż większość twierdzi, że przeczytała i zgadza się z warunkami korzystania z usługi, zwykle ludzie nie czytają ich, dopóki nie pojawi się problem. Ułatw użytkownikom szybkie przejrzenie zasad serwera, umieszczając je na prostej liście punktowanej. Postaraj się, aby poszczególne zasady były krótkie i proste, ale staraj się też nie dzielić ich na wiele oddzielnych elementów. edit: Edytuj zasadę empty: Jeszcze nie zdefiniowano zasad serwera. + move_down: Przenieś w dół + move_up: Przenieś w górę title: Regulamin serwera + translation: Tłumaczenie + translations: Tłumaczenia settings: about: manage_rules: Zarządzaj regułami serwera @@ -809,6 +849,7 @@ pl: discovery: follow_recommendations: Polecane konta preamble: Prezentowanie interesujących treści ma kluczowe znaczenie dla nowych użytkowników, którzy mogą nie znać nikogo z Mastodona. Kontroluj, jak różne funkcje odkrywania działają na Twoim serwerze. + privacy: Prywatność profile_directory: Katalog profilów public_timelines: Publiczne osie czasu publish_statistics: Publikuj statystyki @@ -1066,6 +1107,22 @@ pl: other: Użyte przez %{count} osób w ciągu ostatniego tygodnia title: Rekomendacje i Trendy trending: Popularne + username_blocks: + add_new: Dodaj nową + comparison: + contains: Zawiera + equals: Równa się + contains_html: Zawiera %{string} + delete: Usuń + edit: + title: Edytuj zasadę nazwy użytkownika + matches_exactly_html: Równa się %{string} + new: + create: Dodaj zasadę + title: Utwórz zasadę nazwy użytkownika + not_permitted: Brak uprawnień + title: Zasady nazwy użytkownika + updated_msg: Pomyślnie zaktualizowano zasadę nazwy użytkownika warning_presets: add_new: Dodaj nowy delete: Usuń @@ -1332,6 +1389,10 @@ pl: basic_information: Podstawowe informacje hint_html: "Dostosuj to, co ludzie widzą na Twoim profilu publicznym i obok Twoich wpisów. Inne osoby są bardziej skłonne obserwować Cię i wchodzić z Tobą w interakcje, gdy masz wypełniony profil i zdjęcie profilowe." other: Inne + emoji_styles: + auto: Automatycznie + native: Natywny + twemoji: Twemoji errors: '400': Wysłane zgłoszenie jest nieprawidłowe lub uszkodzone. '403': Nie masz uprawnień, aby wyświetlić tę stronę. @@ -1899,12 +1960,17 @@ pl: edited_at_html: Edytowane %{date} errors: in_reply_not_found: Post, na który próbujesz odpowiedzieć, nie istnieje. + quoted_status_not_found: Wpis, który próbujesz zacytować, nie istnieje. over_character_limit: limit %{max} znaków przekroczony pin_errors: direct: Nie możesz przypiąć wpisu, który jest widoczny tylko dla wspomnianych użytkowników limit: Przekroczyłeś maksymalną liczbę przypiętych wpisów ownership: Nie możesz przypiąć cudzego wpisu reblog: Nie możesz przypiąć podbicia wpisu + quote_policies: + followers: Tylko obserwujący + nobody: Nikt + public: Wszyscy title: '%{name}: "%{quote}"' visibilities: direct: Bezpośredni diff --git a/config/locales/ru.yml b/config/locales/ru.yml index c825cac33dd..17ba89bef09 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1682,37 +1682,37 @@ ru: notification_mailer: admin: report: - subject: "%{name} отправил жалобу" + subject: Поступила жалоба от %{name} sign_up: - subject: "%{name} зарегистрирован" + subject: "%{name} зарегистрировался (-лась) на сервере" favourite: body: "%{name} добавил(а) ваш пост в избранное:" subject: "%{name} добавил(а) ваш пост в избранное" - title: Понравившийся статус + title: Ваш пост добавили в избранное follow: body: "%{name} теперь подписан(а) на вас!" subject: "%{name} теперь подписан(а) на вас" title: Новый подписчик follow_request: - action: Управление запросами на подписку + action: Перейти к запросам на подписку body: "%{name} отправил(а) вам запрос на подписку" subject: "%{name} хочет подписаться на вас" title: Новый запрос на подписку mention: action: Ответить - body: 'Вас упомянул(а) %{name} в:' + body: "%{name} упомянул(а) вас:" subject: "%{name} упомянул(а) вас" title: Новое упоминание poll: subject: Опрос %{name} завершился reblog: - body: 'Ваш пост был продвинут %{name}:' + body: "%{name} продвинул(а) ваш пост:" subject: "%{name} продвинул(а) ваш пост" - title: Новое продвижение + title: Ваш пост продвинули status: - subject: "%{name} только что запостил(а)" + subject: "%{name} опубликовал(а) новый пост" update: - subject: "%{name} изменил(а) пост" + subject: "%{name} отредактировал(а) пост" notifications: email_events: События для уведомлений по электронной почте email_events_hint: 'Выберите события, для которых вы хотели бы получать уведомления:' @@ -1891,17 +1891,17 @@ ru: user_domain_block: Вы заблокировали %{target_name} lost_followers: Потерянные подписчики lost_follows: Потерянные подписки - preamble: Вы можете потерять подписчиков и последователей, если заблокируете домен или, если ваши модераторы решат приостановить работу удаленного сервера. Когда это произойдет, вы сможете загрузить списки разорванных отношений, чтобы проверить их и, возможно, импортировать на другой сервер. + preamble: Когда вы блокируете сервер или это делают модераторы вашего сервера, вы теряете подписчиков и перестаёте быть подписаны на пользователей с заблокированного сервера. После блокировки вы сможете скачать списки пользователей, отношения с которыми были разорваны, чтобы рассмотреть их или чтобы импортировать их на другом сервере. purged: Информация об этом сервере была удалена администраторами вашего сервера. type: Событие statuses: attached: audio: - few: "%{count} аудиозаписи" - many: "%{count} аудиозаписей" - one: "%{count} аудиозапись" - other: "%{count} аудиозаписи" - description: 'Вложение: %{attached}' + few: "%{count} аудиофайла" + many: "%{count} аудиофайлов" + one: "%{count} аудиофайл" + other: "%{count} аудиофайлов" + description: Прикреплено %{attached} image: few: "%{count} изображения" many: "%{count} изображений" @@ -1913,14 +1913,14 @@ ru: one: "%{count} видео" other: "%{count} видео" boosted_from_html: Продвижение польз. %{acct_link} - content_warning: 'Спойлер: %{warning}' + content_warning: 'Предупреждение о содержании: %{warning}' default_language: Тот же, что язык интерфейса disallowed_hashtags: few: 'содержались запрещённые хэштеги: %{tags}' many: 'содержались запрещённые хэштеги: %{tags}' one: 'содержался запрещённый хэштег: %{tags}' other: 'содержались запрещённые хэштеги: %{tags}' - edited_at_html: Редактировано %{date} + edited_at_html: 'Дата последнего изменения: %{date}' errors: in_reply_not_found: Пост, на который вы пытаетесь ответить, не существует или удалён. over_character_limit: превышен лимит символов (%{max}) diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 98412270e98..c5a436c2e2f 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -56,7 +56,7 @@ da: scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige. setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser) setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon - setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg, der oprettes med den næste Mastodon-version, men du kan vælge din præference som forberedelse. + setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egne præference kan vælges som forberedelse. setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_hide_all: Skjul altid medier diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 717ed59b1da..b3c3c2c6d95 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -56,6 +56,7 @@ el: scopes: Ποια API θα επιτρέπεται στην εφαρμογή να χρησιμοποιήσεις. Αν επιλέξεις κάποιο υψηλό εύρος εφαρμογής, δε χρειάζεται να επιλέξεις και το καθένα ξεχωριστά. setting_aggregate_reblogs: Απόκρυψη των νέων αναρτήσεων για τις αναρτήσεις που έχουν ενισχυθεί πρόσφατα (επηρεάζει μόνο τις νέες ενισχύσεις) setting_always_send_emails: Κανονικά οι ειδοποιήσεις μέσω ηλεκτρονικού ταχυδρομείου δεν θα αποστέλλονται όταν χρησιμοποιείτε ενεργά το Mastodon + setting_default_quote_policy: Αυτή η ρύθμιση θα τεθεί σε ισχύ μόνο για αναρτήσεις που δημιουργήθηκαν με την επόμενη έκδοση του Mastodon, αλλά μπορείτε να επιλέξετε την προτίμησή σας κατά την προετοιμασία. setting_default_sensitive: Τα ευαίσθητα πολυμέσα είναι κρυμμένα και εμφανίζονται με ένα κλικ setting_display_media_default: Απόκρυψη ευαίσθητων πολυμέσων setting_display_media_hide_all: Μόνιμη απόκρυψη όλων των πολυμέσων diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index b9176cb6399..f620d54280a 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -56,6 +56,7 @@ fo: scopes: Hvørji API nýtsluskipanin fær atgongd til. Velur tú eitt vav á hægsta stigi, so er ikki neyðugt at velja tey einstøku. setting_aggregate_reblogs: Vís ikki nýggjar stimbranir fyri postar, sum nýliga eru stimbraðir (ávirkar einans stimbranir, ið eru móttiknar fyri kortum) setting_always_send_emails: Vanliga vera teldupostfráboðanir ikki sendar, tá tú virkin brúkar Mastodon + setting_default_quote_policy: Hendan stillingin verður bara virkin fyri postar, sum verða stovnaðir í næstu Mastodon útgávuni, men sum fyrireiking til tað, kanst tú velja tína stilling longu nú. setting_default_sensitive: Viðkvæmar miðlafílur eru fjaldar og kunnu avdúkast við einum klikki setting_display_media_default: Fjal miðlafílur, sum eru merktar sum viðkvæmar setting_display_media_hide_all: Fjal altíð miðlafílur diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index c049822b817..6faef6aa9ca 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -221,6 +221,7 @@ pl: setting_boost_modal: Pytaj o potwierdzenie przed podbiciem setting_default_language: Język wpisów setting_default_privacy: Widoczność wpisów + setting_default_quote_policy: Kto może cytować setting_default_sensitive: Zawsze oznaczaj zawartość multimedialną jako wrażliwą setting_delete_modal: Pytaj o potwierdzenie przed usunięciem wpisu setting_disable_hover_cards: Wyłącz podgląd profilu po najechaniu @@ -229,6 +230,7 @@ pl: setting_display_media_default: Domyślne setting_display_media_hide_all: Ukryj wszystko setting_display_media_show_all: Pokaż wszystko + setting_emoji_style: Styl emoji setting_expand_spoilers: Zawsze rozwijaj wpisy oznaczone ostrzeżeniem o zawartości setting_hide_network: Ukryj swoją sieć setting_missing_alt_text_modal: Pokaż okno potwierdzenia przed opublikowaniem materiałów bez pomocniczego opisu obrazów @@ -266,6 +268,7 @@ pl: favicon: Favicon mascot: Własna ikona media_cache_retention_period: Okres przechowywania pamięci podręcznej + min_age: Wymagany minimalny wiek peers_api_enabled: Opublikuj listę odkrytych serwerów w API profile_directory: Włącz katalog profilów registrations_mode: Kto może się zarejestrować @@ -331,6 +334,7 @@ pl: usable: Pozwól na umieszczanie tego hashtagu w lokalnych wpisach terms_of_service: changelog: Co się zmieniło? + effective_date: Data wejścia w życie text: Warunki korzystania z usługi terms_of_service_generator: admin_email: Adres e-mail przeznaczony do celów prawnych @@ -341,7 +345,11 @@ pl: dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich domain: Domena jurisdiction: Jurysdykcja + min_age: Wiek minimalny user: + date_of_birth_1i: Dzień + date_of_birth_2i: Miesiąc + date_of_birth_3i: Rok role: Rola time_zone: Strefa czasowa user_role: @@ -350,6 +358,10 @@ pl: name: Nazwa permissions_as_keys: Uprawnienia position: Priorytet + username_block: + allow_with_approval: Zezwól na rejestracje po zatwierdzeniu + comparison: Metoda porównania + username: Słowo do dopasowania webhook: events: Włączone zdarzenia template: Szablon zawartości diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index e359b657d67..2dffa398050 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -56,6 +56,7 @@ tr: scopes: Uygulamanın erişmesine izin verilen API'ler. Üst seviye bir kapsam seçtiyseniz, bireysel kapsam seçmenize gerek yoktur. setting_aggregate_reblogs: Yakın zamanda teşvik edilmiş gönderiler için yeni teşvikleri göstermeyin (yalnızca yeni alınan teşvikleri etkiler) setting_always_send_emails: Normalde, Mastodon'u aktif olarak kullanırken e-posta bildirimleri gönderilmeyecektir + setting_default_quote_policy: Bu ayar yalnızca bir sonraki Mastodon sürümüyle oluşturulan gönderiler için geçerli olacak, ancak hazırlık aşamasında tercihinizi seçebilirsiniz. setting_default_sensitive: Hassas medya varsayılan olarak gizlidir ve bir tıklama ile gösterilebilir setting_display_media_default: Hassas olarak işaretlenmiş medyayı gizle setting_display_media_hide_all: Medyayı her zaman gizle @@ -159,6 +160,10 @@ tr: name: Rolün, eğer rozet olarak görüntülenmesi ayarlandıysa kullanılacak herkese açık ismi permissions_as_keys: Bu role sahip kullanıcıların şunlara erişimi var... position: Belirli durumlarda çatışmayı çözmek için daha yüksek rol belirleyicidir. Bazı eylemler ancak daha düşük öncelikteki rollere uygulanabilir + username_block: + allow_with_approval: Kayıt işlemini tamamen engellemek yerine, eşleşen kayıtlar onayınızı gerektirecektir + comparison: Kısmi eşleşmeleri engellerken lütfen Scunthorpe Problemini aklınızda bulundurun + username: '"a" için "4" veya "e" için "3" gibi büyük/küçük harfe ve yaygın homogliflere bakılmaksızın eşleştirilecektir' webhook: events: Gönderilecek etkinlikleri seçin template: Değişken değerleme kullanarak kendi JSON yükünüzü oluşturun. Varsayılan JSON için boş bırakın. @@ -370,6 +375,10 @@ tr: name: Ad permissions_as_keys: İzinler position: Öncelik + username_block: + allow_with_approval: Onay ile kayıtlara izin ver + comparison: Karşılaştırma yöntemi + username: Eşleşecek kelime webhook: events: Etkin olaylar template: Yük şablonu diff --git a/config/locales/tr.yml b/config/locales/tr.yml index f697e3b199e..595e4bfcbf3 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1,7 +1,7 @@ --- tr: about: - about_mastodon_html: Jetub Maxücretsiz ve açık kaynaklı bir sosyal ağdır. Merkezi olmayan yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Jetub Max sunucusunu kurabilir ve sorunsuz bir şekilde Jetub Maxsosyal ağına dahil edebilir! + about_mastodon_html: 'Geleceğin sosyal ağı: Reklam yok, kurumsal gözetim yok, etik tasarım ve merkeziyetsizlik! Mastodon ile verilerinizin sahibi olun!' contact_missing: Ayarlanmadı contact_unavailable: Bulunamadı hosted_on: Mastodon %{domain} üzerinde barındırılıyor @@ -190,6 +190,7 @@ tr: create_relay: Aktarıcı Oluştur create_unavailable_domain: Mevcut Olmayan Alan Adı Oluştur create_user_role: Rol Oluştur + create_username_block: Kullanıcı Adı Kuralı Oluştur demote_user: Kullanıcıyı Düşür destroy_announcement: Duyuru Sil destroy_canonical_email_block: E-Posta Engelini Sil @@ -203,6 +204,7 @@ tr: destroy_status: Durumu Sil destroy_unavailable_domain: Mevcut Olmayan Alan Adı Sil destroy_user_role: Rolü Kaldır + destroy_username_block: Kullanıcı Adı Kuralını Sil disable_2fa_user: 2AD Kapat disable_custom_emoji: Özel İfadeyi Devre Dışı Bırak disable_relay: Aktarıcıyı Devre Dışı Bırak @@ -237,6 +239,7 @@ tr: update_report: Raporu Güncelle update_status: Durumu Güncelle update_user_role: Rolü Güncelle + update_username_block: Kullanıcı Adı Kuralını Güncelle actions: approve_appeal_html: "%{name}, %{target} kullanıcısının yönetim kararına itirazını kabul etti" approve_user_html: "%{name}, %{target} konumundan kaydı onayladı" @@ -255,6 +258,7 @@ tr: create_relay_html: "%{name}, %{target} aktarıcısını oluşturdu" create_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı durdurdu" create_user_role_html: "%{name}, %{target} rolünü oluşturdu" + create_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural ekledi" 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ı" @@ -268,6 +272,7 @@ tr: destroy_status_html: "%{name}, %{target} kullanıcısının gönderisini kaldırdı" destroy_unavailable_domain_html: "%{name}, %{target} alan adına teslimatı sürdürdü" destroy_user_role_html: "%{name}, %{target} rolünü sildi" + destroy_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural silindi" disable_2fa_user_html: "%{name}, %{target} kullanıcısının iki aşamalı doğrulama gereksinimini kapattı" disable_custom_emoji_html: "%{name}, %{target} emojisini devre dışı bıraktı" disable_relay_html: "%{name}, %{target} aktarıcısını devre dışı bıraktı" @@ -302,6 +307,7 @@ tr: update_report_html: "%{name}, %{target} raporunu güncelledi" update_status_html: "%{name}, %{target} kullanıcısının gönderisini güncelledi" update_user_role_html: "%{name}, %{target} rolünü değiştirdi" + update_username_block_html: "%{name}, %{target} içeren kullanıcı adları için kural güncellendi" deleted_account: hesap silindi empty: Kayıt bulunamadı. filter_by_action: Eyleme göre filtre @@ -1085,6 +1091,25 @@ tr: other: Geçen hafta %{count} kişi tarafından kullanıldı title: Öneriler ve Öne Çıkanlar trending: Öne çıkanlar + username_blocks: + add_new: Yeni ekle + block_registrations: Kayıtları engelle + comparison: + contains: İçerir + equals: Eşit + contains_html: "%{string} içerir" + created_msg: Kullanıcı adı kuralı başarıyla oluşturuldu + delete: Sil + edit: + title: Kullanıcı adı kuralını düzenle + matches_exactly_html: "%{string} değerine eşittir" + new: + create: Kural oluştur + title: Yeni kullanıcı adı kuralı oluştur + no_username_block_selected: Hiçbir kullanıcı adı kuralı değiştirilmedi çünkü hiçbiri seçilmedi + not_permitted: İzin verilmiyor + title: Kullanıcı adı kuralları + updated_msg: Kullanıcı adı kuralı başarıyla güncellendi warning_presets: add_new: Yeni ekle delete: Sil @@ -1880,6 +1905,8 @@ tr: ownership: Başkasının gönderisi sabitlenemez reblog: Bir gönderi sabitlenemez quote_policies: + followers: Yalnızca takipçileriniz + nobody: Hiç kimse public: Herkes title: '%{name}: "%{quote}"' visibilities: From 80aadc55dff3f1b1c00cceea12f94bcdd09458f6 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 12:44:59 +0200 Subject: [PATCH 210/660] Add missing mailer for quote notifications (#35652) --- app/mailers/notification_mailer.rb | 10 ++++++++- app/models/notification.rb | 1 + app/models/user_settings.rb | 1 + app/views/notification_mailer/quote.html.haml | 16 ++++++++++++++ app/views/notification_mailer/quote.text.erb | 5 +++++ .../preferences/notifications/show.html.haml | 1 + config/locales/en.yml | 4 ++++ config/locales/simple_form.en.yml | 1 + spec/mailers/notification_mailer_spec.rb | 21 +++++++++++++++++++ .../previews/notification_mailer_preview.rb | 6 ++++++ .../api/web/push_subscriptions_spec.rb | 1 + 11 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 app/views/notification_mailer/quote.html.haml create mode 100644 app/views/notification_mailer/quote.text.erb diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index a20992dcb56..54dde1bb0dd 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -6,7 +6,7 @@ class NotificationMailer < ApplicationMailer :routing before_action :process_params - with_options only: %i(mention favourite reblog) do + with_options only: %i(mention favourite reblog quote) do before_action :set_status after_action :thread_by_conversation! end @@ -27,6 +27,14 @@ class NotificationMailer < ApplicationMailer end end + def quote + return if @status.blank? + + locale_for_account(@me) do + mail subject: default_i18n_subject(name: @status.account.acct) + end + end + def follow locale_for_account(@me) do mail subject: default_i18n_subject(name: @account.acct) diff --git a/app/models/notification.rb b/app/models/notification.rb index f70991d801b..acef474a591 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -30,6 +30,7 @@ class Notification < ApplicationRecord 'FollowRequest' => :follow_request, 'Favourite' => :favourite, 'Poll' => :poll, + 'Quote' => :quote, }.freeze # Please update app/javascript/api_types/notification.ts if you change this diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index fd8659dc970..5558ffe04a4 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -43,6 +43,7 @@ class UserSettings setting :reblog, default: false setting :favourite, default: false setting :mention, default: true + setting :quote, default: true setting :follow_request, default: true setting :report, default: true setting :pending_account, default: true diff --git a/app/views/notification_mailer/quote.html.haml b/app/views/notification_mailer/quote.html.haml new file mode 100644 index 00000000000..1a2d8f8c76f --- /dev/null +++ b/app/views/notification_mailer/quote.html.haml @@ -0,0 +1,16 @@ += content_for :heading do + = render 'application/mailer/heading', + image_url: frontend_asset_url('images/mailer-new/heading/boost.png'), + subtitle: t('notification_mailer.quote.body', name: @status.account.pretty_acct), + title: t('notification_mailer.quote.title') +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-body-padding-td + %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-inner-card-td + = render 'status', status: @status, time_zone: @me.user_time_zone + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-padding-top-24 + = render 'application/mailer/button', text: t('notification_mailer.mention.action'), url: web_url("@#{@status.account.pretty_acct}/#{@status.id}") diff --git a/app/views/notification_mailer/quote.text.erb b/app/views/notification_mailer/quote.text.erb new file mode 100644 index 00000000000..6e3c67b7651 --- /dev/null +++ b/app/views/notification_mailer/quote.text.erb @@ -0,0 +1,5 @@ +<%= raw t('application_mailer.salutation', name: display_name(@me)) %> + +<%= raw t('notification_mailer.quote.body', name: @status.account.pretty_acct) %> + +<%= render 'status', status: @status %> diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index a8e179d0191..08bcc32e9b4 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -18,6 +18,7 @@ = ff.input :'notification_emails.reblog', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.reblog') = ff.input :'notification_emails.favourite', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.favourite') = ff.input :'notification_emails.mention', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.mention') + = ff.input :'notification_emails.quote', wrapper: :with_label, label: I18n.t('simple_form.labels.notification_emails.quote') .fields-group = ff.input :always_send_emails, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_always_send_emails'), hint: I18n.t('simple_form.hints.defaults.setting_always_send_emails') diff --git a/config/locales/en.yml b/config/locales/en.yml index 59272bb3542..06db5e3cfce 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1687,6 +1687,10 @@ en: title: New mention poll: subject: A poll by %{name} has ended + quote: + body: 'Your post was quoted by %{name}:' + subject: "%{name} quoted your post" + title: New quote reblog: body: 'Your post was boosted by %{name}:' subject: "%{name} boosted your post" diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 8da54f626f7..d79899e908b 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -329,6 +329,7 @@ en: follow_request: Someone requested to follow you mention: Someone mentioned you pending_account: New account needs review + quote: Someone quoted you reblog: Someone boosted your post report: New report is submitted software_updates: diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index 25eb4ada263..b88277367dd 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -51,6 +51,27 @@ RSpec.describe NotificationMailer do it_behaves_like 'delivery without status' end + describe 'quote' do + let(:quote) { Fabricate(:quote, state: :accepted, status: foreign_status, quoted_status: own_status) } + let(:notification) { Notification.create!(account: receiver.account, activity: quote) } + let(:mail) { prepared_mailer_for(own_status.account).quote } + + it_behaves_like 'localized subject', 'notification_mailer.quote.subject', name: 'bob' + + it 'renders the email' do + expect(mail) + .to be_present + .and(have_subject('bob quoted your post')) + .and(have_body_text('Your post was quoted by bob')) + .and(have_body_text('The body of the foreign status')) + .and have_thread_headers + .and have_standard_headers('quote').for(receiver) + end + + it_behaves_like 'delivery to non functional user' + it_behaves_like 'delivery without status' + end + describe 'follow' do let(:follow) { sender.follow!(receiver.account) } let(:notification) { Notification.create!(account: receiver.account, activity: follow) } diff --git a/spec/mailers/previews/notification_mailer_preview.rb b/spec/mailers/previews/notification_mailer_preview.rb index a63c20c27c5..ae2d6802bcf 100644 --- a/spec/mailers/previews/notification_mailer_preview.rb +++ b/spec/mailers/previews/notification_mailer_preview.rb @@ -33,6 +33,12 @@ class NotificationMailerPreview < ActionMailer::Preview mailer_for(activity.reblog.account, activity).reblog end + # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/quote + def quote + activity = Quote.first + mailer_for(activity.quoted_account, activity).quote + end + private def mailer_for(account, activity) diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb index 91d7b85bc58..05e6f28d1ff 100644 --- a/spec/requests/api/web/push_subscriptions_spec.rb +++ b/spec/requests/api/web/push_subscriptions_spec.rb @@ -29,6 +29,7 @@ RSpec.describe 'API Web Push Subscriptions' do mention: false, poll: true, status: false, + quote: true, }, }, } From c8ec6498308d4cdc528c35d160d207c9df1efe69 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Aug 2025 14:49:17 +0200 Subject: [PATCH 211/660] Fix "new replies available" miscounting previously known replies (#35654) --- app/workers/fetch_reply_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/fetch_reply_worker.rb b/app/workers/fetch_reply_worker.rb index 227d38a5f2d..4f11b75cc51 100644 --- a/app/workers/fetch_reply_worker.rb +++ b/app/workers/fetch_reply_worker.rb @@ -10,6 +10,6 @@ class FetchReplyWorker batch = WorkerBatch.new(options.delete('batch_id')) if options['batch_id'] result = FetchRemoteStatusService.new.call(child_url, **options.symbolize_keys) ensure - batch&.remove_job(jid, increment: result.present?) + batch&.remove_job(jid, increment: result&.previously_new_record?) end end From d8397040d790b9e7c3d5ba88017cd36642211bcc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Aug 2025 15:10:19 +0200 Subject: [PATCH 212/660] Fix allow with approval option not working on username blocks (#35655) --- app/models/username_block.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/username_block.rb b/app/models/username_block.rb index 227def66e14..18db4ca34ee 100644 --- a/app/models/username_block.rb +++ b/app/models/username_block.rb @@ -43,7 +43,7 @@ class UsernameBlock < ApplicationRecord def self.matches?(str, allow_with_approval: false) normalized_str = str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) - where(allow_with_approval: allow_with_approval).matches_exactly(normalized_str).or(matches_partially(normalized_str)).any? + matches_exactly(normalized_str).or(matches_partially(normalized_str)).where(allow_with_approval: allow_with_approval).any? end def to_log_human_identifier From eb273f904fe5af485203753ae790a9d66346f3ee Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Mon, 4 Aug 2025 16:04:40 +0200 Subject: [PATCH 213/660] Make `WorkerBatch` spec more robust (#35656) --- spec/models/worker_batch_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/worker_batch_spec.rb b/spec/models/worker_batch_spec.rb index 7c6b8aa8c6d..b204decb89e 100644 --- a/spec/models/worker_batch_spec.rb +++ b/spec/models/worker_batch_spec.rb @@ -54,7 +54,7 @@ RSpec.describe WorkerBatch do end it 'persists the job IDs' do - expect(subject.jobs).to eq %w(foo bar) + expect(subject.jobs).to contain_exactly('foo', 'bar') end end end @@ -67,7 +67,7 @@ RSpec.describe WorkerBatch do end it 'removes the job from pending jobs' do - expect(subject.jobs).to eq %w(bar baz) + expect(subject.jobs).to contain_exactly('bar', 'baz') end it 'decrements the number of pending jobs' do From 5ed9410de0c6ffdb1eb43a2f008a9118180f580c Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 16:15:02 +0200 Subject: [PATCH 214/660] Disable ActiveRecord query cache in `Create` critical path (#35662) --- app/lib/activitypub/activity/create.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index ab84a5dd472..9db7ec1a150 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -17,9 +17,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return reject_payload! if unsupported_object_type? || non_matching_uri_hosts?(@account.uri, object_uri) || tombstone_exists? || !related_to_local_activity? with_redis_lock("create:#{object_uri}") do - return if delete_arrived_first?(object_uri) || poll_vote? + Status.uncached do + return if delete_arrived_first?(object_uri) || poll_vote? - @status = find_existing_status + @status = find_existing_status + end if @status.nil? process_status From 49dcbd22d67abd8f522fcb39c6ed4639ce03562c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:19:48 +0200 Subject: [PATCH 215/660] Update eslint (non-major) (#35661) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 848 ++++++++++++++++++++++++++------------------------- 2 files changed, 429 insertions(+), 421 deletions(-) diff --git a/package.json b/package.json index 736f29fb814..ec7b918ef0a 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "eslint": "^9.23.0", "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", - "eslint-plugin-import": "~2.31.0", + "eslint-plugin-import": "~2.32.0", "eslint-plugin-jsdoc": "^52.0.0", "eslint-plugin-jsx-a11y": "~6.10.2", "eslint-plugin-promise": "~7.2.1", diff --git a/yarn.lock b/yarn.lock index 8148fab1c86..a8baa769411 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1794,31 +1794,31 @@ __metadata: languageName: node linkType: hard -"@emnapi/core@npm:^1.3.1": - version: 1.3.1 - resolution: "@emnapi/core@npm:1.3.1" +"@emnapi/core@npm:^1.4.3": + version: 1.4.5 + resolution: "@emnapi/core@npm:1.4.5" dependencies: - "@emnapi/wasi-threads": "npm:1.0.1" + "@emnapi/wasi-threads": "npm:1.0.4" tslib: "npm:^2.4.0" - checksum: 10c0/d3be1044ad704e2c486641bc18908523490f28c7d38bd12d9c1d4ce37d39dae6c4aecd2f2eaf44c6e3bd90eaf04e0591acc440b1b038cdf43cce078a355a0ea0 + checksum: 10c0/da4a57f65f325d720d0e0d1a9c6618b90c4c43a5027834a110476984e1d47c95ebaed4d316b5dddb9c0ed9a493ffeb97d1934f9677035f336d8a36c1f3b2818f languageName: node linkType: hard -"@emnapi/runtime@npm:^1.3.1": - version: 1.3.1 - resolution: "@emnapi/runtime@npm:1.3.1" +"@emnapi/runtime@npm:^1.4.3": + version: 1.4.5 + resolution: "@emnapi/runtime@npm:1.4.5" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/060ffede50f1b619c15083312b80a9e62a5b0c87aa8c1b54854c49766c9d69f8d1d3d87bd963a647071263a320db41b25eaa50b74d6a80dcc763c23dbeaafd6c + checksum: 10c0/37a0278be5ac81e918efe36f1449875cbafba947039c53c65a1f8fc238001b866446fc66041513b286baaff5d6f9bec667f5164b3ca481373a8d9cb65bfc984b languageName: node linkType: hard -"@emnapi/wasi-threads@npm:1.0.1": - version: 1.0.1 - resolution: "@emnapi/wasi-threads@npm:1.0.1" +"@emnapi/wasi-threads@npm:1.0.4": + version: 1.0.4 + resolution: "@emnapi/wasi-threads@npm:1.0.4" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/1e0c8036b8d53e9b07cc9acf021705ef6c86ab6b13e1acda7fffaf541a2d3565072afb92597419173ced9ea14f6bf32fce149106e669b5902b825e8b499e5c6c + checksum: 10c0/2c91a53e62f875800baf035c4d42c9c0d18e5afd9a31ca2aac8b435aeaeaeaac386b5b3d0d0e70aa7a5a9852bbe05106b1f680cd82cce03145c703b423d41313 languageName: node linkType: hard @@ -2152,30 +2152,30 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.19.2": - version: 0.19.2 - resolution: "@eslint/config-array@npm:0.19.2" +"@eslint/config-array@npm:^0.21.0": + version: 0.21.0 + resolution: "@eslint/config-array@npm:0.21.0" dependencies: "@eslint/object-schema": "npm:^2.1.6" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/dd68da9abb32d336233ac4fe0db1e15a0a8d794b6e69abb9e57545d746a97f6f542496ff9db0d7e27fab1438546250d810d90b1904ac67677215b8d8e7573f3d + checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f languageName: node linkType: hard -"@eslint/config-helpers@npm:^0.2.0": - version: 0.2.0 - resolution: "@eslint/config-helpers@npm:0.2.0" - checksum: 10c0/743a64653e13177029108f57ab47460ded08e3412c86216a14b7e8ab2dc79c2b64be45bf55c5ef29f83692a707dc34cf1e9217e4b8b4b272a0d9b691fdaf6a2a +"@eslint/config-helpers@npm:^0.3.0": + version: 0.3.0 + resolution: "@eslint/config-helpers@npm:0.3.0" + checksum: 10c0/013ae7b189eeae8b30cc2ee87bc5c9c091a9cd615579003290eb28bebad5d78806a478e74ba10b3fe08ed66975b52af7d2cd4b4b43990376412b14e5664878c8 languageName: node linkType: hard -"@eslint/core@npm:^0.12.0": - version: 0.12.0 - resolution: "@eslint/core@npm:0.12.0" +"@eslint/core@npm:^0.15.0, @eslint/core@npm:^0.15.1": + version: 0.15.1 + resolution: "@eslint/core@npm:0.15.1" dependencies: "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/d032af81195bb28dd800c2b9617548c6c2a09b9490da3c5537fd2a1201501666d06492278bb92cfccac1f7ac249e58601dd87f813ec0d6a423ef0880434fa0c3 + checksum: 10c0/abaf641940776638b8c15a38d99ce0dac551a8939310ec81b9acd15836a574cf362588eaab03ab11919bc2a0f9648b19ea8dee33bf12675eb5b6fd38bda6f25e languageName: node linkType: hard @@ -2196,10 +2196,10 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:9.23.0, @eslint/js@npm:^9.23.0": - version: 9.23.0 - resolution: "@eslint/js@npm:9.23.0" - checksum: 10c0/4e70869372b6325389e0ab51cac6d3062689807d1cef2c3434857571422ce11dde3c62777af85c382b9f94d937127598d605d2086787f08611351bf99faded81 +"@eslint/js@npm:9.32.0, @eslint/js@npm:^9.23.0": + version: 9.32.0 + resolution: "@eslint/js@npm:9.32.0" + checksum: 10c0/f71e8f9146638d11fb15238279feff98801120a4d4130f1c587c4f09b024ff5ec01af1ba88e97ba6b7013488868898a668f77091300cc3d4394c7a8ed32d2667 languageName: node linkType: hard @@ -2210,13 +2210,13 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.7": - version: 0.2.7 - resolution: "@eslint/plugin-kit@npm:0.2.7" +"@eslint/plugin-kit@npm:^0.3.4": + version: 0.3.4 + resolution: "@eslint/plugin-kit@npm:0.3.4" dependencies: - "@eslint/core": "npm:^0.12.0" + "@eslint/core": "npm:^0.15.1" levn: "npm:^0.4.1" - checksum: 10c0/0a1aff1ad63e72aca923217e556c6dfd67d7cd121870eb7686355d7d1475d569773528a8b2111b9176f3d91d2ea81f7413c34600e8e5b73d59e005d70780b633 + checksum: 10c0/64331ca100f62a0115d10419a28059d0f377e390192163b867b9019517433d5073d10b4ec21f754fa01faf832aceb34178745924baab2957486f8bf95fd628d2 languageName: node linkType: hard @@ -2362,26 +2362,6 @@ __metadata: languageName: node linkType: hard -"@formatjs/ts-transformer@npm:3.13.34": - version: 3.13.34 - resolution: "@formatjs/ts-transformer@npm:3.13.34" - dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@types/json-stable-stringify": "npm:^1.1.0" - "@types/node": "npm:^22.0.0" - chalk: "npm:^4.1.2" - json-stable-stringify: "npm:^1.1.1" - tslib: "npm:^2.8.0" - typescript: "npm:^5.6.0" - peerDependencies: - ts-jest: ^29 - peerDependenciesMeta: - ts-jest: - optional: true - checksum: 10c0/2e53af5a53cab71be0ba2fc16ba856c95bf336c063cc835486cd3a68d01013c5c08026b34667f4bdf99422e74faa8eb1f26d5fe8006f3a1ae9c77e065599362e - languageName: node - linkType: hard - "@formatjs/ts-transformer@npm:3.14.0": version: 3.14.0 resolution: "@formatjs/ts-transformer@npm:3.14.0" @@ -2683,7 +2663,7 @@ __metadata: eslint: "npm:^9.23.0" eslint-import-resolver-typescript: "npm:^4.2.5" eslint-plugin-formatjs: "npm:^5.3.1" - eslint-plugin-import: "npm:~2.31.0" + eslint-plugin-import: "npm:~2.32.0" eslint-plugin-jsdoc: "npm:^52.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" @@ -2835,14 +2815,14 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^0.2.7": - version: 0.2.7 - resolution: "@napi-rs/wasm-runtime@npm:0.2.7" +"@napi-rs/wasm-runtime@npm:^0.2.11": + version: 0.2.12 + resolution: "@napi-rs/wasm-runtime@npm:0.2.12" dependencies: - "@emnapi/core": "npm:^1.3.1" - "@emnapi/runtime": "npm:^1.3.1" - "@tybys/wasm-util": "npm:^0.9.0" - checksum: 10c0/04a5edd79144bfa4e821a373fb6d4939f10c578c5f3633b5e67a57d0f5e36a593f595834d26654ea757bba7cd80b6c42d0d1405d6a8460c5d774e8cd5c9548a4 + "@emnapi/core": "npm:^1.4.3" + "@emnapi/runtime": "npm:^1.4.3" + "@tybys/wasm-util": "npm:^0.10.0" + checksum: 10c0/6d07922c0613aab30c6a497f4df297ca7c54e5b480e00035e0209b872d5c6aab7162fc49477267556109c2c7ed1eb9c65a174e27e9b87568106a87b0a6e3ca7d languageName: node linkType: hard @@ -3823,12 +3803,12 @@ __metadata: languageName: node linkType: hard -"@tybys/wasm-util@npm:^0.9.0": - version: 0.9.0 - resolution: "@tybys/wasm-util@npm:0.9.0" +"@tybys/wasm-util@npm:^0.10.0": + version: 0.10.0 + resolution: "@tybys/wasm-util@npm:0.10.0" dependencies: tslib: "npm:^2.4.0" - checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d + checksum: 10c0/044feba55c1e2af703aa4946139969badb183ce1a659a75ed60bc195a90e73a3f3fc53bcd643497c9954597763ddb051fec62f80962b2ca6fc716ba897dc696e languageName: node linkType: hard @@ -4414,145 +4394,106 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.29.1" +"@typescript-eslint/eslint-plugin@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.38.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/type-utils": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" + "@typescript-eslint/scope-manager": "npm:8.38.0" + "@typescript-eslint/type-utils": "npm:8.38.0" + "@typescript-eslint/utils": "npm:8.38.0" + "@typescript-eslint/visitor-keys": "npm:8.38.0" graphemer: "npm:^1.4.0" - ignore: "npm:^5.3.1" + ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 + "@typescript-eslint/parser": ^8.38.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/a3ed7556edcac374cab622862f2f9adedc91ca305d6937db6869a0253d675858c296cb5413980e8404fc39737117faaf35b00c6804664b3c542bdc417502532f + checksum: 10c0/199b82e9f0136baecf515df7c31bfed926a7c6d4e6298f64ee1a77c8bdd7a8cb92a2ea55a5a345c9f2948a02f7be6d72530efbe803afa1892b593fbd529d0c27 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/parser@npm:8.29.1" +"@typescript-eslint/parser@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/parser@npm:8.38.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" + "@typescript-eslint/scope-manager": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/typescript-estree": "npm:8.38.0" + "@typescript-eslint/visitor-keys": "npm:8.38.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/af3570ff58c42c2014e5c117bebf91120737fb139d94415ca2711844990e95252c3006ccc699871fe3f592cc1a3f4ebfdc9dd5f6cb29b6b128c2524fcf311b75 + checksum: 10c0/5580c2a328f0c15f85e4a0961a07584013cc0aca85fe868486187f7c92e9e3f6602c6e3dab917b092b94cd492ed40827c6f5fea42730bef88eb17592c947adf4 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/project-service@npm:8.33.0" +"@typescript-eslint/project-service@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/project-service@npm:8.38.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.33.0" - "@typescript-eslint/types": "npm:^8.33.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.38.0" + "@typescript-eslint/types": "npm:^8.38.0" debug: "npm:^4.3.4" - checksum: 10c0/a863d9e3be5ffb53c9d57b25b7a35149dae01afd942dd7fc36bd72a4230676ae12d0f37a789cddaf1baf71e3b35f09436bebbd081336e667b4181b48d0afe8f5 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/scope-manager@npm:8.29.1" - dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - checksum: 10c0/8b87a04f01ebc13075e352509bca8f31c757c3220857fa473ac155f6bdf7f30fe82765d0c3d8e790f7fad394a32765bd9f716b97c08e17581d358c76086d51af - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/scope-manager@npm:8.33.0" - dependencies: - "@typescript-eslint/types": "npm:8.33.0" - "@typescript-eslint/visitor-keys": "npm:8.33.0" - checksum: 10c0/eb259add242ce40642e7272b414c92ae9407d97cb304981f17f0de0846d5c4ab47d41816ef13da3d3976fe0b7a74df291525be27e4fe4f0ab5d35e86d340faa0 - languageName: node - linkType: hard - -"@typescript-eslint/tsconfig-utils@npm:8.33.0, @typescript-eslint/tsconfig-utils@npm:^8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.33.0" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/6e9a8e73e65b925f908f31e00be4f1b8d7e89f45d97fa703f468115943c297fc2cc6f9daa0c12b9607f39186f033ac244515f11710df7e1df8302c815ed57389 + checksum: 10c0/87d2f55521e289bbcdc666b1f4587ee2d43039cee927310b05abaa534b528dfb1b5565c1545bb4996d7fbdf9d5a3b0aa0e6c93a8f1289e3fcfd60d246364a884 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/type-utils@npm:8.29.1" +"@typescript-eslint/scope-manager@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/scope-manager@npm:8.38.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" + "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/visitor-keys": "npm:8.38.0" + checksum: 10c0/ceaf489ea1f005afb187932a7ee363dfe1e0f7cc3db921283991e20e4c756411a5e25afbec72edd2095d6a4384f73591f4c750cf65b5eaa650c90f64ef9fe809 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.38.0, @typescript-eslint/tsconfig-utils@npm:^8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.38.0" + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/1a90da16bf1f7cfbd0303640a8ead64a0080f2b1d5969994bdac3b80abfa1177f0c6fbf61250bae082e72cf5014308f2f5cc98edd6510202f13420a7ffd07a84 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/type-utils@npm:8.38.0" + dependencies: + "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/typescript-estree": "npm:8.38.0" + "@typescript-eslint/utils": "npm:8.38.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.0.1" + ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/72cc01dbac866b0a7c7b1f637ad03ffd22f6d3617f70f06f485cf3096fddfc821fdc56de1a072cc6af70250c63698a3e5a910f67fe46eea941955b6e0da1b2bd + checksum: 10c0/27795c4bd0be395dda3424e57d746639c579b7522af1c17731b915298a6378fd78869e8e141526064b6047db2c86ba06444469ace19c98cda5779d06f4abd37c languageName: node linkType: hard -"@typescript-eslint/types@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/types@npm:8.29.1" - checksum: 10c0/bbcb9e4f38df4485092b51ac6bb62d65f321d914ab58dc0ff1eaa7787dc0b4a39e237c2617b9f2c2bcb91a343f30de523e3544f69affa1ee4287a3ef2fc10ce7 +"@typescript-eslint/types@npm:8.38.0, @typescript-eslint/types@npm:^8.34.1, @typescript-eslint/types@npm:^8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/types@npm:8.38.0" + checksum: 10c0/f0ac0060c98c0f3d1871f107177b6ae25a0f1846ca8bd8cfc7e1f1dd0ddce293cd8ac4a5764d6a767de3503d5d01defcd68c758cb7ba6de52f82b209a918d0d2 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/types@npm:8.33.0" - checksum: 10c0/348b64eb408719d7711a433fc9716e0c2aab8b3f3676f5a1cc2e00269044132282cf655deb6d0dd9817544116909513de3b709005352d186949d1014fad1a3cb - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:^8.33.0, @typescript-eslint/types@npm:^8.34.1": - version: 8.36.0 - resolution: "@typescript-eslint/types@npm:8.36.0" - checksum: 10c0/cacb941a0caad6ab556c416051b97ec33b364b7c8e0703e2729ae43f12daf02b42eef12011705329107752e3f1685ca82cfffe181d637f85907293cb634bee31 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.29.1" +"@typescript-eslint/typescript-estree@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.38.0" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/visitor-keys": "npm:8.29.1" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.0.1" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/33c46c667d9262e5625d5d0064338711b342e62c5675ded6811a2cb13ee5de2f71b90e9d0be5cb338b11b1a329c376a6bbf6c3d24fa8fb457b2eefc9f3298513 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.33.0" - dependencies: - "@typescript-eslint/project-service": "npm:8.33.0" - "@typescript-eslint/tsconfig-utils": "npm:8.33.0" - "@typescript-eslint/types": "npm:8.33.0" - "@typescript-eslint/visitor-keys": "npm:8.33.0" + "@typescript-eslint/project-service": "npm:8.38.0" + "@typescript-eslint/tsconfig-utils": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/visitor-keys": "npm:8.38.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -4561,163 +4502,166 @@ __metadata: ts-api-utils: "npm:^2.1.0" peerDependencies: typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/677b12b2e5780ffaef508bddbf8712fe2c3413f3d14fd8fd0cfbe22952a81c6642b3cc26984cf27fdfc3dd2457ae5f8aa04437d3b0ae32987a1895f9648ca7b2 + checksum: 10c0/00a00f6549877f4ae5c2847fa5ac52bf42cbd59a87533856c359e2746e448ed150b27a6137c92fd50c06e6a4b39e386d6b738fac97d80d05596e81ce55933230 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/utils@npm:8.29.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.29.1" - "@typescript-eslint/types": "npm:8.29.1" - "@typescript-eslint/typescript-estree": "npm:8.29.1" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/1b2704b769b0c9353cf26a320ecf9775ba51c94c7c30e2af80ca31f4cb230f319762ab06535fcb26b6963144bbeaa53233b34965907c506283861b013f5b95fc - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:^8.27.0, @typescript-eslint/utils@npm:^8.8.1": - version: 8.33.0 - resolution: "@typescript-eslint/utils@npm:8.33.0" +"@typescript-eslint/utils@npm:8.38.0, @typescript-eslint/utils@npm:^8.27.0, @typescript-eslint/utils@npm:^8.8.1": + version: 8.38.0 + resolution: "@typescript-eslint/utils@npm:8.38.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.33.0" - "@typescript-eslint/types": "npm:8.33.0" - "@typescript-eslint/typescript-estree": "npm:8.33.0" + "@typescript-eslint/scope-manager": "npm:8.38.0" + "@typescript-eslint/types": "npm:8.38.0" + "@typescript-eslint/typescript-estree": "npm:8.38.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/a0adb9e13d8f8d8f86ae2e905f3305ad60732e760364b291de66a857a551485d37c23e923299078a47f75d3cca643e1f2aefa010a0beb4cb0d08d0507c1038e1 + checksum: 10c0/e97a45bf44f315f9ed8c2988429e18c88e3369c9ee3227ee86446d2d49f7325abebbbc9ce801e178f676baa986d3e1fd4b5391f1640c6eb8944c123423ae43bb languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.29.1": - version: 8.29.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.29.1" +"@typescript-eslint/visitor-keys@npm:8.38.0": + version: 8.38.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.38.0" dependencies: - "@typescript-eslint/types": "npm:8.29.1" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/0c12e83c84a754161c89e594a96454799669979c7021a8936515ec574a1fa1d6e3119e0eacf502e07a0fa7254974558ea7a48901c8bfed3c46579a61b655e4f5 + "@typescript-eslint/types": "npm:8.38.0" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/071a756e383f41a6c9e51d78c8c64bd41cd5af68b0faef5fbaec4fa5dbd65ec9e4cd610c2e2cdbe9e2facc362995f202850622b78e821609a277b5b601a1d4ec languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.33.0": - version: 8.33.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.33.0" - dependencies: - "@typescript-eslint/types": "npm:8.33.0" - eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/41660f241e78314f69d251792f369ef1eeeab3b40fe4ab11b794d402c95bcb82b61d3e91763e7ab9b0f22011a7ac9c8f9dfd91734d61c9f4eaf4f7660555b53b +"@unrs/resolver-binding-android-arm-eabi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1" + conditions: os=android & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-darwin-arm64@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.3.2" +"@unrs/resolver-binding-android-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-android-arm64@npm:1.11.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@unrs/resolver-binding-darwin-arm64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.11.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@unrs/resolver-binding-darwin-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-darwin-x64@npm:1.3.2" +"@unrs/resolver-binding-darwin-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-darwin-x64@npm:1.11.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@unrs/resolver-binding-freebsd-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.3.2" +"@unrs/resolver-binding-freebsd-x64@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.11.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.3.2" +"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.11.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.3.2" +"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.11.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.3.2" +"@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.11.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-arm64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.3.2" +"@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.11.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.3.2" +"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.11.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-s390x-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.3.2" +"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.11.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.11.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-x64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.3.2" +"@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.11.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@unrs/resolver-binding-linux-x64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.3.2" +"@unrs/resolver-binding-linux-x64-musl@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.11.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@unrs/resolver-binding-wasm32-wasi@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.3.2" +"@unrs/resolver-binding-wasm32-wasi@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.11.1" dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.7" + "@napi-rs/wasm-runtime": "npm:^0.2.11" conditions: cpu=wasm32 languageName: node linkType: hard -"@unrs/resolver-binding-win32-arm64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.3.2" +"@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.11.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@unrs/resolver-binding-win32-ia32-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.3.2" +"@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.11.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@unrs/resolver-binding-win32-x64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.3.2" +"@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1": + version: 1.11.1 + resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.11.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -5177,17 +5121,19 @@ __metadata: languageName: node linkType: hard -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8": - version: 3.1.8 - resolution: "array-includes@npm:3.1.8" +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - is-string: "npm:^1.0.7" - checksum: 10c0/5b1004d203e85873b96ddc493f090c9672fd6c80d7a60b798da8a14bff8a670ff95db5aafc9abc14a211943f05220dacf8ea17638ae0af1a6a47b8c0b48ce370 + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/0235fa69078abeac05ac4250699c44996bc6f774a9cbe45db48674ce6bd142f09b327d31482ff75cf03344db4ea03eae23edb862d59378b484b47ed842574856 languageName: node linkType: hard @@ -5212,29 +5158,30 @@ __metadata: languageName: node linkType: hard -"array.prototype.findlastindex@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlastindex@npm:1.2.5" +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" + es-abstract: "npm:^1.23.9" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/962189487728b034f3134802b421b5f39e42ee2356d13b42d2ddb0e52057ffdcc170b9524867f4f0611a6f638f4c19b31e14606e8bcbda67799e26685b195aa3 + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 languageName: node linkType: hard -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.2": - version: 1.3.2 - resolution: "array.prototype.flat@npm:1.3.2" +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.2.0" - es-abstract: "npm:^1.22.1" - es-shim-unscopables: "npm:^1.0.0" - checksum: 10c0/a578ed836a786efbb6c2db0899ae80781b476200617f65a44846cb1ed8bd8b24c8821b83703375d8af639c689497b7b07277060024b9919db94ac3e10dc8a49b + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a languageName: node linkType: hard @@ -5702,7 +5649,7 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": version: 1.0.8 resolution: "call-bind@npm:1.0.8" dependencies: @@ -5714,7 +5661,7 @@ __metadata: languageName: node linkType: hard -"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3": +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": version: 1.0.4 resolution: "call-bound@npm:1.0.4" dependencies: @@ -6384,7 +6331,7 @@ __metadata: languageName: node linkType: hard -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.0, define-properties@npm:^1.2.1": +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" dependencies: @@ -6703,26 +6650,26 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.22.1, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9": - version: 1.23.9 - resolution: "es-abstract@npm:1.23.9" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": + version: 1.24.0 + resolution: "es-abstract@npm:1.24.0" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" data-view-buffer: "npm:^1.0.2" data-view-byte-length: "npm:^1.0.2" data-view-byte-offset: "npm:^1.0.1" es-define-property: "npm:^1.0.1" es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" + es-object-atoms: "npm:^1.1.1" es-set-tostringtag: "npm:^2.1.0" es-to-primitive: "npm:^1.3.0" function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.0" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" get-symbol-description: "npm:^1.1.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" @@ -6734,21 +6681,24 @@ __metadata: is-array-buffer: "npm:^3.0.5" is-callable: "npm:^1.2.7" is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" is-shared-array-buffer: "npm:^1.0.4" is-string: "npm:^1.1.1" is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.0" + is-weakref: "npm:^1.1.1" math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.3" + object-inspect: "npm:^1.13.4" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.7" own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.3" + regexp.prototype.flags: "npm:^1.5.4" safe-array-concat: "npm:^1.1.3" safe-push-apply: "npm:^1.0.0" safe-regex-test: "npm:^1.1.0" set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" string.prototype.trim: "npm:^1.2.10" string.prototype.trimend: "npm:^1.0.9" string.prototype.trimstart: "npm:^1.0.8" @@ -6757,8 +6707,8 @@ __metadata: typed-array-byte-offset: "npm:^1.0.4" typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.18" - checksum: 10c0/1de229c9e08fe13c17fe5abaec8221545dfcd57e51f64909599a6ae896df84b8fd2f7d16c60cb00d7bf495b9298ca3581aded19939d4b7276854a4b066f8422b + which-typed-array: "npm:^1.1.19" + checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 languageName: node linkType: hard @@ -6828,12 +6778,12 @@ __metadata: languageName: node linkType: hard -"es-shim-unscopables@npm:^1.0.0, es-shim-unscopables@npm:^1.0.2": - version: 1.0.2 - resolution: "es-shim-unscopables@npm:1.0.2" +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" dependencies: - hasown: "npm:^2.0.0" - checksum: 10c0/f495af7b4b7601a4c0cfb893581c352636e5c08654d129590386a33a0432cf13a7bdc7b6493801cadd990d838e2839b9013d1de3b880440cb537825e834fe783 + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb languageName: node linkType: hard @@ -6966,6 +6916,21 @@ __metadata: languageName: node linkType: hard +"eslint-import-context@npm:^0.1.8": + version: 0.1.9 + resolution: "eslint-import-context@npm:0.1.9" + dependencies: + get-tsconfig: "npm:^4.10.1" + stable-hash-x: "npm:^0.2.0" + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + checksum: 10c0/07851103443b70af681c5988e2702e681ff9b956e055e11d4bd9b2322847fa0d9e8da50c18fc7cb1165106b043f34fbd0384d7011c239465c4645c52132e56f3 + languageName: node + linkType: hard + "eslint-import-resolver-node@npm:^0.3.9": version: 0.3.9 resolution: "eslint-import-resolver-node@npm:0.3.9" @@ -6978,15 +6943,16 @@ __metadata: linkType: hard "eslint-import-resolver-typescript@npm:^4.2.5": - version: 4.2.5 - resolution: "eslint-import-resolver-typescript@npm:4.2.5" + version: 4.4.4 + resolution: "eslint-import-resolver-typescript@npm:4.4.4" dependencies: - debug: "npm:^4.4.0" - get-tsconfig: "npm:^4.10.0" + debug: "npm:^4.4.1" + eslint-import-context: "npm:^0.1.8" + get-tsconfig: "npm:^4.10.1" is-bun-module: "npm:^2.0.0" - stable-hash: "npm:^0.0.5" - tinyglobby: "npm:^0.2.12" - unrs-resolver: "npm:^1.3.2" + stable-hash-x: "npm:^0.2.0" + tinyglobby: "npm:^0.2.14" + unrs-resolver: "npm:^1.7.11" peerDependencies: eslint: "*" eslint-plugin-import: "*" @@ -6996,28 +6962,28 @@ __metadata: optional: true eslint-plugin-import-x: optional: true - checksum: 10c0/9134c4dd6e8b3cf1356d6bff68939153c81255c0ac7f694e829c3c7f5e785936591cfe43209d866c8a3b379d3a8dcd203651ec49bd99361fcb54dc0c2b9ce8fc + checksum: 10c0/3bf8ad77c21660f77a0e455555ab179420f68ae7a132906c85a217ccce51cb6680cf70027cab32a358d193e5b9e476f6ba2e595585242aa97d4f6435ca22104e languageName: node linkType: hard -"eslint-module-utils@npm:^2.12.0": - version: 2.12.0 - resolution: "eslint-module-utils@npm:2.12.0" +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10c0/4d8b46dcd525d71276f9be9ffac1d2be61c9d54cc53c992e6333cf957840dee09381842b1acbbb15fc6b255ebab99cd481c5007ab438e5455a14abe1a0468558 + checksum: 10c0/6f4efbe7a91ae49bf67b4ab3644cb60bc5bd7db4cb5521de1b65be0847ffd3fb6bce0dd68f0995e1b312d137f768e2a1f842ee26fe73621afa05f850628fdc40 languageName: node linkType: hard "eslint-plugin-formatjs@npm:^5.3.1": - version: 5.3.1 - resolution: "eslint-plugin-formatjs@npm:5.3.1" + version: 5.4.0 + resolution: "eslint-plugin-formatjs@npm:5.4.0" dependencies: "@formatjs/icu-messageformat-parser": "npm:2.11.2" - "@formatjs/ts-transformer": "npm:3.13.34" + "@formatjs/ts-transformer": "npm:3.14.0" "@types/eslint": "npm:^9.6.1" "@types/picomatch": "npm:^3" "@typescript-eslint/utils": "npm:^8.27.0" @@ -7027,42 +6993,42 @@ __metadata: unicode-emoji-utils: "npm:^1.2.0" peerDependencies: eslint: ^9.23.0 - checksum: 10c0/fb8ba06e0718cd098f2393aea04eb4a6037ca3ead1c9450bd38926d0adecba4cefdebfb661c56c36685e0f003331520c3330544c45803f397b827713ab5e1d7d + checksum: 10c0/5c74a53988df68ffed4e68bb58a4ee75cdcd92b7d94f699e2edbcdd8c2c45930f500c7211da0a4616714d7d83bbbdc105328e5aacf0c9c7582a78fdfc9fa2b55 languageName: node linkType: hard -"eslint-plugin-import@npm:~2.31.0": - version: 2.31.0 - resolution: "eslint-plugin-import@npm:2.31.0" +"eslint-plugin-import@npm:~2.32.0": + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" dependencies: "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.8" - array.prototype.findlastindex: "npm:^1.2.5" - array.prototype.flat: "npm:^1.3.2" - array.prototype.flatmap: "npm:^1.3.2" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.0" + eslint-module-utils: "npm:^2.12.1" hasown: "npm:^2.0.2" - is-core-module: "npm:^2.15.1" + is-core-module: "npm:^2.16.1" is-glob: "npm:^4.0.3" minimatch: "npm:^3.1.2" object.fromentries: "npm:^2.0.8" object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.0" + object.values: "npm:^1.2.1" semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.8" + string.prototype.trimend: "npm:^1.0.9" tsconfig-paths: "npm:^3.15.0" peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10c0/e21d116ddd1900e091ad120b3eb68c5dd5437fe2c930f1211781cd38b246f090a6b74d5f3800b8255a0ed29782591521ad44eb21c5534960a8f1fb4040fd913a + checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b languageName: node linkType: hard "eslint-plugin-jsdoc@npm:^52.0.0": - version: 52.0.1 - resolution: "eslint-plugin-jsdoc@npm:52.0.1" + version: 52.0.2 + resolution: "eslint-plugin-jsdoc@npm:52.0.2" dependencies: "@es-joy/jsdoccomment": "npm:~0.52.0" are-docs-informative: "npm:^0.0.2" @@ -7076,7 +7042,7 @@ __metadata: spdx-expression-parse: "npm:^4.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/0dac74a5ea1db8d15eea5a29a6dadd1361029230f414794abfd143b8cd0129d1cd862481d31eb0374b215d0841f41209fb8b4e36ac69b758094d7c77052432c0 + checksum: 10c0/e36d8c75a5a100f71f4f5287ce12ffdf15474194b2877dbe1e06c3c24a1c0c0c7f13c2d92cb289a80515b6fe1e43c4c0b19814b5c5b22a46ac2ca7df23ab55ad languageName: node linkType: hard @@ -7126,8 +7092,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.37.4": - version: 7.37.4 - resolution: "eslint-plugin-react@npm:7.37.4" + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" dependencies: array-includes: "npm:^3.1.8" array.prototype.findlast: "npm:^1.2.5" @@ -7139,7 +7105,7 @@ __metadata: hasown: "npm:^2.0.2" jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.8" + object.entries: "npm:^1.1.9" object.fromentries: "npm:^2.0.8" object.values: "npm:^1.2.1" prop-types: "npm:^15.8.1" @@ -7149,29 +7115,29 @@ __metadata: string.prototype.repeat: "npm:^1.0.0" peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10c0/4acbbdb19669dfa9a162ed8847c3ad1918f6aea1ceb675ee320b5d903b4e463fdef25e15233295b6d0a726fef2ea8b015c527da769c7690932ddc52d5b82ba12 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 languageName: node linkType: hard "eslint-plugin-storybook@npm:^9.0.4": - version: 9.0.4 - resolution: "eslint-plugin-storybook@npm:9.0.4" + version: 9.1.1 + resolution: "eslint-plugin-storybook@npm:9.1.1" dependencies: "@typescript-eslint/utils": "npm:^8.8.1" peerDependencies: eslint: ">=8" - storybook: ^9.0.4 - checksum: 10c0/b5dbcd15feab63d71f4bd5da26306043339620ddf64bb623de3a7542ee81828b4137af93e199c3e49fb0e5a76d582a21fb580626011ae2340dd6fc684f438358 + storybook: ^9.1.1 + checksum: 10c0/4cf80aa078633021b153a3a5b790a39c9919b5fa7203727c15d8ae066e75d6e134d7d718e66a6a5db9815275f32942a2deae1979aeb36be2543572507faced2c languageName: node linkType: hard -"eslint-scope@npm:^8.3.0": - version: 8.3.0 - resolution: "eslint-scope@npm:8.3.0" +"eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10c0/23bf54345573201fdf06d29efa345ab508b355492f6c6cc9e2b9f6d02b896f369b6dd5315205be94b8853809776c4d13353b85c6b531997b164ff6c3328ecf5b + checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0 languageName: node linkType: hard @@ -7182,7 +7148,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.2.0, eslint-visitor-keys@npm:^4.2.1": +"eslint-visitor-keys@npm:^4.2.1": version: 4.2.1 resolution: "eslint-visitor-keys@npm:4.2.1" checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 @@ -7190,17 +7156,17 @@ __metadata: linkType: hard "eslint@npm:^9.23.0": - version: 9.23.0 - resolution: "eslint@npm:9.23.0" + version: 9.32.0 + resolution: "eslint@npm:9.32.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.19.2" - "@eslint/config-helpers": "npm:^0.2.0" - "@eslint/core": "npm:^0.12.0" + "@eslint/config-array": "npm:^0.21.0" + "@eslint/config-helpers": "npm:^0.3.0" + "@eslint/core": "npm:^0.15.0" "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.23.0" - "@eslint/plugin-kit": "npm:^0.2.7" + "@eslint/js": "npm:9.32.0" + "@eslint/plugin-kit": "npm:^0.3.4" "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" "@humanwhocodes/retry": "npm:^0.4.2" @@ -7211,9 +7177,9 @@ __metadata: cross-spawn: "npm:^7.0.6" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.3.0" - eslint-visitor-keys: "npm:^4.2.0" - espree: "npm:^10.3.0" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -7235,11 +7201,11 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/9616c308dfa8d09db8ae51019c87d5d05933742214531b077bd6ab618baab3bec7938256c14dcad4dc47f5ba93feb0bc5e089f68799f076374ddea21b6a9be45 + checksum: 10c0/e8a23924ec5f8b62e95483002ca25db74e25c23bd9c6d98a9f656ee32f820169bee3bfdf548ec728b16694f198b3db857d85a49210ee4a035242711d08fdc602 languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.3.0, espree@npm:^10.4.0": +"espree@npm:^10.0.1, espree@npm:^10.4.0": version: 10.4.0 resolution: "espree@npm:10.4.0" dependencies: @@ -7592,12 +7558,12 @@ __metadata: languageName: node linkType: hard -"for-each@npm:^0.3.3": - version: 0.3.3 - resolution: "for-each@npm:0.3.3" +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" dependencies: - is-callable: "npm:^1.1.3" - checksum: 10c0/22330d8a2db728dbf003ec9182c2d421fbcd2969b02b4f97ec288721cda63eb28f2c08585ddccd0f77cb2930af8d958005c9e72f47141dc51816127a118f39aa + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee languageName: node linkType: hard @@ -7833,12 +7799,12 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.10.0": - version: 4.10.0 - resolution: "get-tsconfig@npm:4.10.0" +"get-tsconfig@npm:^4.10.1": + version: 4.10.1 + resolution: "get-tsconfig@npm:4.10.1" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/c9b5572c5118923c491c04285c73bd55b19e214992af957c502a3be0fc0043bb421386ffd45ca3433c0a7fba81221ca300479e8393960acf15d0ed4563f38a86 + checksum: 10c0/7f8e3dabc6a49b747920a800fb88e1952fef871cdf51b79e98db48275a5de6cdaf499c55ee67df5fa6fe7ce65f0063e26de0f2e53049b408c585aa74d39ffa21 languageName: node linkType: hard @@ -7925,9 +7891,9 @@ __metadata: linkType: hard "globals@npm:^16.0.0": - version: 16.0.0 - resolution: "globals@npm:16.0.0" - checksum: 10c0/8906d5f01838df64a81d6c2a7b7214312e2216cf65c5ed1546dc9a7d0febddf55ffa906cf04efd5b01eec2534d6f14859a89535d1a68241832810e41ef3fd5bb + version: 16.3.0 + resolution: "globals@npm:16.3.0" + checksum: 10c0/c62dc20357d1c0bf2be4545d6c4141265d1a229bf1c3294955efb5b5ef611145391895e3f2729f8603809e81b30b516c33e6c2597573844449978606aad6eb38 languageName: node linkType: hard @@ -8045,7 +8011,7 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.0, hasown@npm:^2.0.2": +"hasown@npm:^2.0.2": version: 2.0.2 resolution: "hasown@npm:2.0.2" dependencies: @@ -8223,17 +8189,17 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0, ignore@npm:^5.3.1": +"ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 languageName: node linkType: hard -"ignore@npm:^7.0.3": - version: 7.0.4 - resolution: "ignore@npm:7.0.4" - checksum: 10c0/90e1f69ce352b9555caecd9cbfd07abe7626d312a6f90efbbb52c7edca6ea8df065d66303863b30154ab1502afb2da8bc59d5b04e1719a52ef75bbf675c488eb +"ignore@npm:^7.0.0, ignore@npm:^7.0.3": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d languageName: node linkType: hard @@ -8447,14 +8413,14 @@ __metadata: languageName: node linkType: hard -"is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": +"is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f languageName: node linkType: hard -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.15.1, is-core-module@npm:^2.16.0": +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" dependencies: @@ -8571,6 +8537,13 @@ __metadata: languageName: node linkType: hard +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + "is-node-process@npm:^1.0.1, is-node-process@npm:^1.2.0": version: 1.2.0 resolution: "is-node-process@npm:1.2.0" @@ -8658,7 +8631,7 @@ __metadata: languageName: node linkType: hard -"is-string@npm:^1.0.7, is-string@npm:^1.1.1": +"is-string@npm:^1.1.1": version: 1.1.1 resolution: "is-string@npm:1.1.1" dependencies: @@ -8702,7 +8675,7 @@ __metadata: languageName: node linkType: hard -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.0": +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": version: 1.1.1 resolution: "is-weakref@npm:1.1.1" dependencies: @@ -9697,6 +9670,15 @@ __metadata: languageName: node linkType: hard +"napi-postinstall@npm:^0.3.0": + version: 0.3.2 + resolution: "napi-postinstall@npm:0.3.2" + bin: + napi-postinstall: lib/cli.js + checksum: 10c0/77c67eb9871d24afe7bad30e6115c441d099d6a0e42dc1c49c4a722ff682425e08dc6dd2b03eca10db9b547e724c38fb51325c35039e7ac10dcb714bb88d7326 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -9821,7 +9803,7 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.3": +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": version: 1.13.4 resolution: "object-inspect@npm:1.13.4" checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 @@ -9849,14 +9831,15 @@ __metadata: languageName: node linkType: hard -"object.entries@npm:^1.1.8": - version: 1.1.8 - resolution: "object.entries@npm:1.1.8" +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" dependencies: - call-bind: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/db9ea979d2956a3bc26c262da4a4d212d36f374652cc4c13efdd069c1a519c16571c137e2893d1c46e1cb0e15c88fd6419eaf410c945f329f09835487d7e65d3 + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db languageName: node linkType: hard @@ -9883,7 +9866,7 @@ __metadata: languageName: node linkType: hard -"object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" dependencies: @@ -11614,7 +11597,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.3": +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": version: 1.5.4 resolution: "regexp.prototype.flags@npm:1.5.4" dependencies: @@ -12456,10 +12439,10 @@ __metadata: languageName: node linkType: hard -"stable-hash@npm:^0.0.5": - version: 0.0.5 - resolution: "stable-hash@npm:0.0.5" - checksum: 10c0/ca670cb6d172f1c834950e4ec661e2055885df32fee3ebf3647c5df94993b7c2666a5dbc1c9a62ee11fc5c24928579ec5e81bb5ad31971d355d5a341aab493b3 +"stable-hash-x@npm:^0.2.0": + version: 0.2.0 + resolution: "stable-hash-x@npm:0.2.0" + checksum: 10c0/c757df58366ee4bb266a9486b8932eab7c1ba730469eaf4b68d2dee404814e9f84089c44c9b5205f8c7d99a0ab036cce2af69139ce5ed44b635923c011a8aea8 languageName: node linkType: hard @@ -12535,6 +12518,16 @@ __metadata: languageName: node linkType: hard +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + "storybook@npm:^9.0.4": version: 9.0.4 resolution: "storybook@npm:9.0.4" @@ -12665,7 +12658,7 @@ __metadata: languageName: node linkType: hard -"string.prototype.trimend@npm:^1.0.8, string.prototype.trimend@npm:^1.0.9": +"string.prototype.trimend@npm:^1.0.9": version: 1.0.9 resolution: "string.prototype.trimend@npm:1.0.9" dependencies: @@ -13112,7 +13105,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14": +"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14": version: 0.2.14 resolution: "tinyglobby@npm:0.2.14" dependencies: @@ -13244,7 +13237,7 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.0.1, ts-api-utils@npm:^2.1.0": +"ts-api-utils@npm:^2.1.0": version: 2.1.0 resolution: "ts-api-utils@npm:2.1.0" peerDependencies: @@ -13417,16 +13410,17 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.28.0, typescript-eslint@npm:^8.29.1": - version: 8.29.1 - resolution: "typescript-eslint@npm:8.29.1" + version: 8.38.0 + resolution: "typescript-eslint@npm:8.38.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.29.1" - "@typescript-eslint/parser": "npm:8.29.1" - "@typescript-eslint/utils": "npm:8.29.1" + "@typescript-eslint/eslint-plugin": "npm:8.38.0" + "@typescript-eslint/parser": "npm:8.38.0" + "@typescript-eslint/typescript-estree": "npm:8.38.0" + "@typescript-eslint/utils": "npm:8.38.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/31319c891d224ec8d7cf96ad7e6c84480b3d17d4c46c5beccca06fc7891f41eabd5593e44867e69dbfb79459f5545c2cc2e985c950bdd7b4e7c3bb1ec8941030 + checksum: 10c0/486b9862ee08f7827d808a2264ce03b58087b11c4c646c0da3533c192a67ae3fcb4e68d7a1e69d0f35a1edc274371a903a50ecfe74012d5eaa896cb9d5a81e0b languageName: node linkType: hard @@ -13601,26 +13595,35 @@ __metadata: languageName: node linkType: hard -"unrs-resolver@npm:^1.3.2": - version: 1.3.2 - resolution: "unrs-resolver@npm:1.3.2" +"unrs-resolver@npm:^1.7.11": + version: 1.11.1 + resolution: "unrs-resolver@npm:1.11.1" dependencies: - "@unrs/resolver-binding-darwin-arm64": "npm:1.3.2" - "@unrs/resolver-binding-darwin-x64": "npm:1.3.2" - "@unrs/resolver-binding-freebsd-x64": "npm:1.3.2" - "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.3.2" - "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.3.2" - "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.3.2" - "@unrs/resolver-binding-linux-arm64-musl": "npm:1.3.2" - "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.3.2" - "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.3.2" - "@unrs/resolver-binding-linux-x64-gnu": "npm:1.3.2" - "@unrs/resolver-binding-linux-x64-musl": "npm:1.3.2" - "@unrs/resolver-binding-wasm32-wasi": "npm:1.3.2" - "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.3.2" - "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.3.2" - "@unrs/resolver-binding-win32-x64-msvc": "npm:1.3.2" + "@unrs/resolver-binding-android-arm-eabi": "npm:1.11.1" + "@unrs/resolver-binding-android-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-arm64": "npm:1.11.1" + "@unrs/resolver-binding-darwin-x64": "npm:1.11.1" + "@unrs/resolver-binding-freebsd-x64": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.11.1" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.11.1" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.11.1" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.11.1" + napi-postinstall: "npm:^0.3.0" dependenciesMeta: + "@unrs/resolver-binding-android-arm-eabi": + optional: true + "@unrs/resolver-binding-android-arm64": + optional: true "@unrs/resolver-binding-darwin-arm64": optional: true "@unrs/resolver-binding-darwin-x64": @@ -13637,6 +13640,10 @@ __metadata: optional: true "@unrs/resolver-binding-linux-ppc64-gnu": optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true "@unrs/resolver-binding-linux-s390x-gnu": optional: true "@unrs/resolver-binding-linux-x64-gnu": @@ -13651,7 +13658,7 @@ __metadata: optional: true "@unrs/resolver-binding-win32-x64-msvc": optional: true - checksum: 10c0/f9b6d18193bcaae7ef9e284a74c85d4cb3d8c833851f1b23254a947297e672826223d82798dbff818455fefeda02084340aca904300fd5060468c2f243767cc1 + checksum: 10c0/c91b112c71a33d6b24e5c708dab43ab80911f2df8ee65b87cd7a18fb5af446708e98c4b415ca262026ad8df326debcc7ca6a801b2935504d87fd6f0b9d70dce1 languageName: node linkType: hard @@ -14148,17 +14155,18 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18": - version: 1.1.18 - resolution: "which-typed-array@npm:1.1.18" +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" dependencies: available-typed-arrays: "npm:^1.0.7" call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - for-each: "npm:^0.3.3" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" gopd: "npm:^1.2.0" has-tostringtag: "npm:^1.0.2" - checksum: 10c0/0412f4a91880ca1a2a63056187c2e3de6b129b2b5b6c17bc3729f0f7041047ae48fb7424813e51506addb2c97320003ee18b8c57469d2cde37983ef62126143c + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f languageName: node linkType: hard From 0153a239dbecd59df6b36c5469f85a340daf4dc1 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 16:34:05 +0200 Subject: [PATCH 216/660] Avoid nested transactions when fetching quote posts (#35657) --- app/lib/activitypub/activity/create.rb | 15 ++++++++++----- .../activitypub/process_status_update_service.rb | 6 ++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 9db7ec1a150..c47c2afc523 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -66,6 +66,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity resolve_thread(@status) resolve_unresolved_mentions(@status) fetch_replies(@status) + fetch_and_verify_quote distribute forward_for_reply end @@ -206,11 +207,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity @quote.status = status @quote.save - - embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context']) - ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id], depth: @options[:depth]) - rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS - ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] }) end def process_tags @@ -380,6 +376,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity Rails.logger.warn "Error fetching replies: #{e}" end + def fetch_and_verify_quote + return if @quote.nil? + + embedded_quote = safe_prefetched_embed(@account, @status_parser.quoted_object, @json['context']) + ActivityPub::VerifyQuoteService.new.call(@quote, fetchable_quoted_uri: @quote_uri, prefetched_quoted_object: embedded_quote, request_id: @options[:request_id], depth: @options[:depth]) + rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS + ActivityPub::RefetchAndVerifyQuoteWorker.perform_in(rand(30..600).seconds, @quote.id, @quote_uri, { 'request_id' => @options[:request_id] }) + end + def conversation_from_uri(uri) return nil if uri.nil? return Conversation.find_by(id: OStatus::TagManager.instance.unique_tag_to_local_id(uri, 'Conversation')) if OStatus::TagManager.instance.local_id?(uri) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 0ada876d890..023eef19a02 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -18,6 +18,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @poll_changed = false @quote_changed = false @request_id = request_id + @quote = nil # Only native types can be updated at the moment return @status if !expected_type? || already_updated_more_recently? @@ -49,6 +50,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService create_edits! end + fetch_and_verify_quote!(@quote, @status_parser.quote_uri) if @quote.present? download_media_files! queue_poll_notifications! @@ -312,10 +314,10 @@ class ActivityPub::ProcessStatusUpdateService < BaseService @quote_changed = true end + @quote = quote quote.save - - fetch_and_verify_quote!(quote, quote_uri) elsif @status.quote.present? + @quote = nil @status.quote.destroy! @quote_changed = true end From 32791c97456ad88940b656a3432f6207cf96b807 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 17:27:46 +0200 Subject: [PATCH 217/660] Accept remote quotes of local quotes according to set policy (#35629) --- app/lib/activitypub/activity/quote_request.rb | 23 ++++++++++- .../accept_quote_request_serializer.rb | 23 +++++++++++ .../activitypub/quote_request_serializer.rb | 1 + .../activity/quote_request_spec.rb | 41 ++++++++++++++++++- .../accept_quote_request_serializer_spec.rb | 25 +++++++++++ 5 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 app/serializers/activitypub/accept_quote_request_serializer.rb create mode 100644 spec/serializers/activitypub/accept_quote_request_serializer_spec.rb diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 2de03df1580..40a38b09d36 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -9,12 +9,31 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity quoted_status = status_from_uri(object_uri) return if quoted_status.nil? || !quoted_status.account.local? || !quoted_status.distributable? - # For now, we don't support being quoted by external servers - reject_quote_request!(quoted_status) + if Mastodon::Feature.outgoing_quotes_enabled? && StatusPolicy.new(@account, quoted_status).quote? + accept_quote_request!(quoted_status) + else + reject_quote_request!(quoted_status) + end end private + def accept_quote_request!(quoted_status) + status = status_from_uri(@json['instrument']) + # TODO: import inlined quote post if possible + status ||= ActivityPub::FetchRemoteStatusService.new.call(@json['instrument'], on_behalf_of: @account.followers.local.first, request_id: @options[:request_id]) + # TODO: raise if status is nil + + # Sanity check + return unless status.quote.quoted_status == quoted_status + + status.quote.update!(activity_uri: @json['id']) + status.quote.accept! + + json = Oj.dump(serialize_payload(status.quote, ActivityPub::AcceptQuoteRequestSerializer)) + ActivityPub::DeliveryWorker.perform_async(json, quoted_status.account_id, @account.inbox_url) + end + def reject_quote_request!(quoted_status) quote = Quote.new( quoted_status: quoted_status, diff --git a/app/serializers/activitypub/accept_quote_request_serializer.rb b/app/serializers/activitypub/accept_quote_request_serializer.rb new file mode 100644 index 00000000000..26ecd099b64 --- /dev/null +++ b/app/serializers/activitypub/accept_quote_request_serializer.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ActivityPub::AcceptQuoteRequestSerializer < ActivityPub::Serializer + attributes :id, :type, :actor, :result + + has_one :object, serializer: ActivityPub::QuoteRequestSerializer + + def id + [ActivityPub::TagManager.instance.uri_for(object.quoted_account), '#accepts/quote_requests/', object.id].join + end + + def type + 'Accept' + end + + def actor + ActivityPub::TagManager.instance.uri_for(object.quoted_account) + end + + def result + ActivityPub::TagManager.instance.approval_uri_for(object) + end +end diff --git a/app/serializers/activitypub/quote_request_serializer.rb b/app/serializers/activitypub/quote_request_serializer.rb index 840b653a1c7..2b3363fb576 100644 --- a/app/serializers/activitypub/quote_request_serializer.rb +++ b/app/serializers/activitypub/quote_request_serializer.rb @@ -23,6 +23,7 @@ class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer end def instrument + # TODO: inline object? ActivityPub::TagManager.instance.uri_for(object.status) end end diff --git a/spec/lib/activitypub/activity/quote_request_spec.rb b/spec/lib/activitypub/activity/quote_request_spec.rb index dac0b438cbd..24a245c8981 100644 --- a/spec/lib/activitypub/activity/quote_request_spec.rb +++ b/spec/lib/activitypub/activity/quote_request_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe ActivityPub::Activity::QuoteRequest do +RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do let(:sender) { Fabricate(:account, domain: 'example.com') } let(:recipient) { Fabricate(:account) } let(:quoted_post) { Fabricate(:status, account: recipient) } @@ -47,5 +47,44 @@ RSpec.describe ActivityPub::Activity::QuoteRequest do end, recipient.id, sender.inbox_url) end end + + context 'when trying to quote a quotable local status' do + let(:status_json) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'https://example.com/unknown-status', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + quote: ActivityPub::TagManager.instance.uri_for(quoted_post), + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + }.deep_stringify_keys + end + + before do + stub_request(:get, 'https://example.com/unknown-status').to_return(status: 200, body: Oj.dump(status_json), headers: { 'Content-Type': 'application/activity+json' }) + quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + end + + it 'accepts the quote and sends an Accept activity' do + expect { subject.perform } + .to change { quoted_post.reload.quotes.accepted.count }.by(1) + .and enqueue_sidekiq_job(ActivityPub::DeliveryWorker) + .with(satisfying do |body| + outgoing_json = Oj.load(body) + outgoing_json['type'] == 'Accept' && %w(type id actor object instrument).all? { |key| json[key] == outgoing_json['object'][key] } + end, recipient.id, sender.inbox_url) + end + end end end diff --git a/spec/serializers/activitypub/accept_quote_request_serializer_spec.rb b/spec/serializers/activitypub/accept_quote_request_serializer_spec.rb new file mode 100644 index 00000000000..986d9112b79 --- /dev/null +++ b/spec/serializers/activitypub/accept_quote_request_serializer_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::AcceptQuoteRequestSerializer do + subject { serialized_record_json(record, described_class, adapter: ActivityPub::Adapter) } + + describe 'serializing an object' do + let(:record) { Fabricate(:quote, state: :accepted) } + + it 'returns expected attributes' do + expect(subject.deep_symbolize_keys) + .to include( + actor: eq(ActivityPub::TagManager.instance.uri_for(record.quoted_account)), + id: match("#accepts/quote_requests/#{record.id}"), + object: include( + type: 'QuoteRequest', + instrument: ActivityPub::TagManager.instance.uri_for(record.status), + object: ActivityPub::TagManager.instance.uri_for(record.quoted_status) + ), + type: 'Accept' + ) + end + end +end From cb0b608fa7d23e5e09015662c82b88488470adf1 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 18:06:59 +0200 Subject: [PATCH 218/660] Ensure quoted user is given access to see the post (#35665) --- app/lib/activitypub/activity/quote_request.rb | 1 + app/models/quote.rb | 6 ++++++ app/services/post_status_service.rb | 10 +++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 40a38b09d36..424aafe993b 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -27,6 +27,7 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity # Sanity check return unless status.quote.quoted_status == quoted_status + status.quote.ensure_quoted_access status.quote.update!(activity_uri: @json['id']) status.quote.accept! diff --git a/app/models/quote.rb b/app/models/quote.rb index a6c9dd0caca..fea812924cc 100644 --- a/app/models/quote.rb +++ b/app/models/quote.rb @@ -56,6 +56,12 @@ class Quote < ApplicationRecord accepted? || !legacy? end + def ensure_quoted_access + status.mentions.create!(account: quoted_account, silent: true) + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique + nil + end + def schedule_refresh_if_stale! return unless quoted_status_id.present? && approval_uri.present? && updated_at <= BACKGROUND_REFRESH_INTERVAL.ago diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 73e78f00478..75116e2a266 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -93,14 +93,10 @@ class PostStatusService < BaseService def attach_quote!(status) return if @quoted_status.nil? - # NOTE: for now this is only for convenience in testing, as we don't support the request flow nor serialize quotes in ActivityPub - # we only support incoming quotes so far - status.quote = Quote.create(quoted_status: @quoted_status, status: status) - if @quoted_status.local? && StatusPolicy.new(@status.account, @quoted_status).quote? - # TODO: produce a QuoteAuthorization - status.quote.accept! - end + status.quote.ensure_quoted_access + + status.quote.accept! if @quoted_status.local? && StatusPolicy.new(@status.account, @quoted_status).quote? end def safeguard_mentions!(status) From 28b0e5ee787988426da5805374040ec05ffae3cd Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 4 Aug 2025 19:15:46 +0200 Subject: [PATCH 219/660] Provides legacy fallback for browser that don't support regex flag v (#35659) --- .../mastodon/features/emoji/constants.ts | 11 ----- .../mastodon/features/emoji/render.ts | 10 +++-- .../mastodon/features/emoji/utils.ts | 43 ++++++++++++++++--- app/javascript/mastodon/polyfills/index.ts | 11 +++++ package.json | 1 + yarn.lock | 8 ++++ 6 files changed, 63 insertions(+), 21 deletions(-) diff --git a/app/javascript/mastodon/features/emoji/constants.ts b/app/javascript/mastodon/features/emoji/constants.ts index a5ec9e6e2b4..09022371b22 100644 --- a/app/javascript/mastodon/features/emoji/constants.ts +++ b/app/javascript/mastodon/features/emoji/constants.ts @@ -15,17 +15,6 @@ export const SKIN_TONE_CODES = [ 0x1f3ff, // Dark skin tone ] as const; -// TODO: Test and create fallback for browsers that do not handle the /v flag. -export const UNICODE_EMOJI_REGEX = /\p{RGI_Emoji}/v; -// See: https://www.unicode.org/reports/tr51/#valid-emoji-tag-sequences -export const UNICODE_FLAG_EMOJI_REGEX = - /\p{RGI_Emoji_Flag_Sequence}|\p{RGI_Emoji_Tag_Sequence}/v; -export const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; -export const ANY_EMOJI_REGEX = new RegExp( - `(${UNICODE_EMOJI_REGEX.source}|${CUSTOM_EMOJI_REGEX.source})`, - 'gv', -); - // Emoji rendering modes. A mode is what we are using to render emojis, a style is what the user has selected. export const EMOJI_MODE_NATIVE = 'native'; export const EMOJI_MODE_NATIVE_WITH_FLAGS = 'native-flags'; diff --git a/app/javascript/mastodon/features/emoji/render.ts b/app/javascript/mastodon/features/emoji/render.ts index 6486e65a709..8d2299fd89e 100644 --- a/app/javascript/mastodon/features/emoji/render.ts +++ b/app/javascript/mastodon/features/emoji/render.ts @@ -9,7 +9,6 @@ import { EMOJI_TYPE_UNICODE, EMOJI_TYPE_CUSTOM, EMOJI_STATE_MISSING, - ANY_EMOJI_REGEX, } from './constants'; import { searchCustomEmojisByShortcodes, @@ -32,7 +31,12 @@ import type { LocaleOrCustom, UnicodeEmojiToken, } from './types'; -import { emojiLogger, stringHasAnyEmoji, stringHasUnicodeFlags } from './utils'; +import { + anyEmojiRegex, + emojiLogger, + stringHasAnyEmoji, + stringHasUnicodeFlags, +} from './utils'; const log = emojiLogger('render'); @@ -207,7 +211,7 @@ export function tokenizeText(text: string): TokenizedText { const tokens = []; let lastIndex = 0; - for (const match of text.matchAll(ANY_EMOJI_REGEX)) { + for (const match of text.matchAll(anyEmojiRegex())) { if (match.index > lastIndex) { tokens.push(text.slice(lastIndex, match.index)); } diff --git a/app/javascript/mastodon/features/emoji/utils.ts b/app/javascript/mastodon/features/emoji/utils.ts index 89f8d926466..ce359199296 100644 --- a/app/javascript/mastodon/features/emoji/utils.ts +++ b/app/javascript/mastodon/features/emoji/utils.ts @@ -1,23 +1,32 @@ import debug from 'debug'; -import { - CUSTOM_EMOJI_REGEX, - UNICODE_EMOJI_REGEX, - UNICODE_FLAG_EMOJI_REGEX, -} from './constants'; +import { emojiRegexPolyfill } from '@/mastodon/polyfills'; export function emojiLogger(segment: string) { return debug(`emojis:${segment}`); } export function stringHasUnicodeEmoji(input: string): boolean { - return UNICODE_EMOJI_REGEX.test(input); + return new RegExp(EMOJI_REGEX, supportedFlags()).test(input); } export function stringHasUnicodeFlags(input: string): boolean { - return UNICODE_FLAG_EMOJI_REGEX.test(input); + if (supportsRegExpSets()) { + return new RegExp( + '\\p{RGI_Emoji_Flag_Sequence}|\\p{RGI_Emoji_Tag_Sequence}', + 'v', + ).test(input); + } + return new RegExp( + // First range is regional indicator symbols, + // Second is a black flag + 0-9|a-z tag chars + cancel tag. + // See: https://en.wikipedia.org/wiki/Regional_indicator_symbol + '(?:\uD83C[\uDDE6-\uDDFF]){2}|\uD83C\uDFF4(?:\uDB40[\uDC30-\uDC7A])+\uDB40\uDC7F', + ).test(input); } +// Constant as this is supported by all browsers. +const CUSTOM_EMOJI_REGEX = /:([a-z0-9_]+):/i; export function stringHasCustomEmoji(input: string) { return CUSTOM_EMOJI_REGEX.test(input); } @@ -25,3 +34,23 @@ export function stringHasCustomEmoji(input: string) { export function stringHasAnyEmoji(input: string) { return stringHasUnicodeEmoji(input) || stringHasCustomEmoji(input); } + +export function anyEmojiRegex() { + return new RegExp( + `${EMOJI_REGEX}|${CUSTOM_EMOJI_REGEX.source}`, + supportedFlags('gi'), + ); +} + +function supportsRegExpSets() { + return 'unicodeSets' in RegExp.prototype; +} + +function supportedFlags(flags = '') { + if (supportsRegExpSets()) { + return `${flags}v`; + } + return flags; +} + +const EMOJI_REGEX = emojiRegexPolyfill?.source ?? '\\p{RGI_Emoji}'; diff --git a/app/javascript/mastodon/polyfills/index.ts b/app/javascript/mastodon/polyfills/index.ts index c001421c363..0ff0dd72690 100644 --- a/app/javascript/mastodon/polyfills/index.ts +++ b/app/javascript/mastodon/polyfills/index.ts @@ -20,5 +20,16 @@ export function loadPolyfills() { loadIntlPolyfills(), // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- those properties might not exist in old browsers, even if they are always here in types needsExtraPolyfills && importExtraPolyfills(), + loadEmojiPolyfills(), ]); } + +// In the case of no /v support, rely on the emojibase data. +async function loadEmojiPolyfills() { + if (!('unicodeSets' in RegExp.prototype)) { + emojiRegexPolyfill = (await import('emojibase-regex/emoji')).default; + } +} + +// Null unless polyfill is needed. +export let emojiRegexPolyfill: RegExp | null = null; diff --git a/package.json b/package.json index ec7b918ef0a..6109b42875b 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", "emojibase-data": "^16.0.3", + "emojibase-regex": "^16.0.0", "escape-html": "^1.0.3", "fast-glob": "^3.3.3", "fuzzysort": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index a8baa769411..26f7cd409e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2659,6 +2659,7 @@ __metadata: emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" emojibase-data: "npm:^16.0.3" + emojibase-regex: "npm:^16.0.0" escape-html: "npm:^1.0.3" eslint: "npm:^9.23.0" eslint-import-resolver-typescript: "npm:^4.2.5" @@ -6565,6 +6566,13 @@ __metadata: languageName: node linkType: hard +"emojibase-regex@npm:^16.0.0": + version: 16.0.0 + resolution: "emojibase-regex@npm:16.0.0" + checksum: 10c0/8ee5ff798e51caa581434b1cb2f9737e50195093c4efa1739df21a50a5496f80517924787d865e8cf7d6a0b4c90dbedc04bdc506dcbcc582e14cdf0bb47af0f0 + languageName: node + linkType: hard + "emojibase@npm:^16.0.0": version: 16.0.0 resolution: "emojibase@npm:16.0.0" From 570c9d16be2f2e0e95da6723aa5281f600424fba Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 4 Aug 2025 19:16:12 +0200 Subject: [PATCH 220/660] Performance regression fixes (#35664) --- .dockerignore | 2 ++ .../mastodon/features/emoji/emoji_html.tsx | 19 ++++++++++++++++++- .../mastodon/features/emoji/hooks.ts | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index 9d990ab9ce6..fe87f6e6006 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,6 +5,7 @@ .gitattributes .gitignore .github +.vscode public/system public/assets public/packs @@ -20,6 +21,7 @@ postgres14 redis elasticsearch chart +storybook-static .yarn/ !.yarn/patches !.yarn/plugins diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx index fdda62a3e61..ed6f7a20465 100644 --- a/app/javascript/mastodon/features/emoji/emoji_html.tsx +++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx @@ -1,5 +1,7 @@ import type { ComponentPropsWithoutRef, ElementType } from 'react'; +import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; + import { useEmojify } from './hooks'; import type { CustomEmojiMapArg } from './types'; @@ -12,7 +14,7 @@ type EmojiHTMLProps = Omit< as?: Element; }; -export const EmojiHTML = ({ +export const ModernEmojiHTML = ({ extraEmojis, htmlString, as: asElement, // Rename for syntax highlighting @@ -29,3 +31,18 @@ export const EmojiHTML = ({ ); }; + +export const EmojiHTML = ( + props: EmojiHTMLProps, +) => { + if (isModernEmojiEnabled()) { + return ; + } + const Wrapper = props.as ?? 'div'; + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/emoji/hooks.ts b/app/javascript/mastodon/features/emoji/hooks.ts index 47af37b3731..3f397f4ef03 100644 --- a/app/javascript/mastodon/features/emoji/hooks.ts +++ b/app/javascript/mastodon/features/emoji/hooks.ts @@ -8,7 +8,6 @@ import { isModernEmojiEnabled } from '@/mastodon/utils/environment'; import { toSupportedLocale } from './locale'; import { determineEmojiMode } from './mode'; -import { emojifyElement } from './render'; import type { CustomEmojiMapArg, EmojiAppState, @@ -39,6 +38,7 @@ export function useEmojify(text: string, extraEmojis?: CustomEmojiMapArg) { async (input: string) => { const wrapper = document.createElement('div'); wrapper.innerHTML = input; + const { emojifyElement } = await import('./render'); const result = await emojifyElement(wrapper, appState, extra); if (result) { setEmojifiedText(result.innerHTML); From 081d38679f965ac20ff1a6c72e2fc1108c34ac30 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 20:12:37 +0200 Subject: [PATCH 221/660] Add quote notifications to WebUI (#35653) --- .../mastodon/api_types/notifications.ts | 2 ++ .../notifications/components/notification.jsx | 35 ++++++++++++++++++- .../components/notification_group.tsx | 6 ++++ .../components/notification_quote.tsx | 33 +++++++++++++++++ app/javascript/mastodon/locales/en.json | 1 + .../mastodon/models/notification_group.ts | 4 +++ .../400-24px/format_quote-fill.svg | 1 + .../material-icons/400-24px/format_quote.svg | 1 + 8 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 app/javascript/mastodon/features/notifications_v2/components/notification_quote.tsx create mode 100644 app/javascript/material-icons/400-24px/format_quote-fill.svg create mode 100644 app/javascript/material-icons/400-24px/format_quote.svg diff --git a/app/javascript/mastodon/api_types/notifications.ts b/app/javascript/mastodon/api_types/notifications.ts index 190d8c83966..69fd17a2563 100644 --- a/app/javascript/mastodon/api_types/notifications.ts +++ b/app/javascript/mastodon/api_types/notifications.ts @@ -13,6 +13,7 @@ export const allNotificationTypes = [ 'favourite', 'reblog', 'mention', + 'quote', 'poll', 'status', 'update', @@ -28,6 +29,7 @@ export type NotificationWithStatusType = | 'reblog' | 'status' | 'mention' + | 'quote' | 'poll' | 'update'; diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index b38e5da1594..ced09881a43 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -8,9 +8,9 @@ import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; - import EditIcon from '@/material-icons/400-24px/edit.svg?react'; import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react'; +import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react'; import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react'; @@ -42,6 +42,7 @@ const messages = defineMessages({ adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' }, relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' }, moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'You have received a moderation warning' }, + quote: { id: 'notification.label.quote', defaultMessage: '{name} quoted your post'} }); const notificationForScreenReader = (intl, message, timestamp) => { @@ -251,6 +252,36 @@ class Notification extends ImmutablePureComponent { ); } + renderQuote (notification, link) { + const { intl, unread } = this.props; + + return ( + +
+
+ + + + + +
+ +
+
+ ); + } + renderStatus (notification, link) { const { intl, unread, status } = this.props; @@ -467,6 +498,8 @@ class Notification extends ImmutablePureComponent { return this.renderFollowRequest(notification, account, link); case 'mention': return this.renderMention(notification); + case 'quote': + return this.renderQuote(notification); case 'favourite': return this.renderFavourite(notification, link); case 'reblog': diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx index f0f2139ad21..eba39e17b7a 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group.tsx @@ -15,6 +15,7 @@ import { NotificationFollowRequest } from './notification_follow_request'; import { NotificationMention } from './notification_mention'; import { NotificationModerationWarning } from './notification_moderation_warning'; import { NotificationPoll } from './notification_poll'; +import { NotificationQuote } from './notification_quote'; import { NotificationReblog } from './notification_reblog'; import { NotificationSeveredRelationships } from './notification_severed_relationships'; import { NotificationStatus } from './notification_status'; @@ -91,6 +92,11 @@ export const NotificationGroup: React.FC<{ ); break; + case 'quote': + content = ( + + ); + break; case 'follow': content = ( diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_quote.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_quote.tsx new file mode 100644 index 00000000000..595bed806c6 --- /dev/null +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_quote.tsx @@ -0,0 +1,33 @@ +import { FormattedMessage } from 'react-intl'; + +import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react'; +import type { NotificationGroupQuote } from 'mastodon/models/notification_group'; + +import type { LabelRenderer } from './notification_group_with_status'; +import { NotificationWithStatus } from './notification_with_status'; + +const quoteLabelRenderer: LabelRenderer = (displayName) => ( + +); + +export const NotificationQuote: React.FC<{ + notification: NotificationGroupQuote; + unread: boolean; +}> = ({ notification, unread }) => { + return ( + + ); +}; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 329ac90589e..c91516695b0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -600,6 +600,7 @@ "notification.label.mention": "Mention", "notification.label.private_mention": "Private mention", "notification.label.private_reply": "Private reply", + "notification.label.quote": "{name} quoted your post", "notification.label.reply": "Reply", "notification.mention": "Mention", "notification.mentioned_you": "{name} mentioned you", diff --git a/app/javascript/mastodon/models/notification_group.ts b/app/javascript/mastodon/models/notification_group.ts index d98e755aa2c..9394cbbed88 100644 --- a/app/javascript/mastodon/models/notification_group.ts +++ b/app/javascript/mastodon/models/notification_group.ts @@ -36,6 +36,7 @@ export type NotificationGroupFavourite = export type NotificationGroupReblog = BaseNotificationWithStatus<'reblog'>; export type NotificationGroupStatus = BaseNotificationWithStatus<'status'>; export type NotificationGroupMention = BaseNotificationWithStatus<'mention'>; +export type NotificationGroupQuote = BaseNotificationWithStatus<'quote'>; export type NotificationGroupPoll = BaseNotificationWithStatus<'poll'>; export type NotificationGroupUpdate = BaseNotificationWithStatus<'update'>; export type NotificationGroupFollow = BaseNotification<'follow'>; @@ -87,6 +88,7 @@ export type NotificationGroup = | NotificationGroupReblog | NotificationGroupStatus | NotificationGroupMention + | NotificationGroupQuote | NotificationGroupPoll | NotificationGroupUpdate | NotificationGroupFollow @@ -137,6 +139,7 @@ export function createNotificationGroupFromJSON( case 'reblog': case 'status': case 'mention': + case 'quote': case 'poll': case 'update': { const { status_id: statusId, ...groupWithoutStatus } = group; @@ -209,6 +212,7 @@ export function createNotificationGroupFromNotificationJSON( case 'reblog': case 'status': case 'mention': + case 'quote': case 'poll': case 'update': return { diff --git a/app/javascript/material-icons/400-24px/format_quote-fill.svg b/app/javascript/material-icons/400-24px/format_quote-fill.svg new file mode 100644 index 00000000000..f4afa3ed17f --- /dev/null +++ b/app/javascript/material-icons/400-24px/format_quote-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/format_quote.svg b/app/javascript/material-icons/400-24px/format_quote.svg new file mode 100644 index 00000000000..c354385ea93 --- /dev/null +++ b/app/javascript/material-icons/400-24px/format_quote.svg @@ -0,0 +1 @@ + \ No newline at end of file From 5318957ab3b22e74e531b8c65a73089e13328f4d Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 4 Aug 2025 22:58:59 +0200 Subject: [PATCH 222/660] Fix serialization of quote post notification groups (#35670) --- app/serializers/rest/notification_group_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb index f4af842e38d..347659bdfef 100644 --- a/app/serializers/rest/notification_group_serializer.rb +++ b/app/serializers/rest/notification_group_serializer.rb @@ -24,7 +24,7 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer end def status_type? - [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type) + [:favourite, :reblog, :status, :mention, :poll, :update, :quote].include?(object.type) end def report_type? From 813126825604264b657cc4d82b333ddacfe8f4c9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 5 Aug 2025 03:42:08 -0400 Subject: [PATCH 223/660] Replace `unprocessable_entity` -> `unprocessable_content` (#35658) --- app/controllers/api/v2/search_controller.rb | 2 +- app/controllers/application_controller.rb | 4 ++-- .../webauthn_credentials_controller.rb | 2 +- spec/controllers/application_controller_spec.rb | 10 +++++----- spec/requests/api/v2/search_spec.rb | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index 3ca13cc4275..c00ddf92cc0 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -20,7 +20,7 @@ class Api::V2::SearchController < Api::BaseController @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer rescue Mastodon::SyntaxError - unprocessable_entity + unprocessable_content rescue ActiveRecord::RecordNotFound not_found end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 42abe990483..82d9e8380fc 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from ActionController::RoutingError, ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::UnknownFormat, with: :not_acceptable - rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity + rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_content rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error) @@ -123,7 +123,7 @@ class ApplicationController < ActionController::Base respond_with_error(410) end - def unprocessable_entity + def unprocessable_content respond_with_error(422) end diff --git a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb index b01f08ed8fd..83dedb411d4 100644 --- a/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb +++ b/app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb @@ -52,7 +52,7 @@ module Settings end else flash[:error] = I18n.t('webauthn_credentials.create.error') - status = :unprocessable_entity + status = :unprocessable_content end else flash[:error] = t('webauthn_credentials.create.error') diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index cd4181a00da..ea182a047f1 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -219,16 +219,16 @@ RSpec.describe ApplicationController do it_behaves_like 'error response', 410 end - describe 'unprocessable_entity' do + describe 'unprocessable_content' do controller do - def route_unprocessable_entity - unprocessable_entity + def route_unprocessable_content + unprocessable_content end end subject do - routes.draw { get 'route_unprocessable_entity' => 'anonymous#route_unprocessable_entity' } - get 'route_unprocessable_entity' + routes.draw { get 'route_unprocessable_content' => 'anonymous#route_unprocessable_content' } + get 'route_unprocessable_content' end it_behaves_like 'error response', 422 diff --git a/spec/requests/api/v2/search_spec.rb b/spec/requests/api/v2/search_spec.rb index 9c9f37e0988..6beab4c8c7d 100644 --- a/spec/requests/api/v2/search_spec.rb +++ b/spec/requests/api/v2/search_spec.rb @@ -98,7 +98,7 @@ RSpec.describe 'Search API' do context 'when search raises syntax error' do before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) } - it 'returns http unprocessable_entity' do + it 'returns http unprocessable_content' do get '/api/v2/search', headers: headers, params: params expect(response).to have_http_status(422) From 54fd1c1f9bc3c3c8f6209f750ab1f9345d932684 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:19:55 +0200 Subject: [PATCH 224/660] New Crowdin Translations (automated) (#35674) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/ca.json | 8 ++++++++ app/javascript/mastodon/locales/da.json | 1 + app/javascript/mastodon/locales/el.json | 1 + app/javascript/mastodon/locales/es-AR.json | 1 + app/javascript/mastodon/locales/es-MX.json | 3 ++- app/javascript/mastodon/locales/es.json | 1 + app/javascript/mastodon/locales/hu.json | 1 + app/javascript/mastodon/locales/it.json | 1 + app/javascript/mastodon/locales/nl.json | 1 + app/javascript/mastodon/locales/tr.json | 1 + app/javascript/mastodon/locales/vi.json | 1 + app/javascript/mastodon/locales/zh-TW.json | 1 + config/locales/ca.yml | 4 ++++ config/locales/cs.yml | 4 ++++ config/locales/da.yml | 6 +++++- config/locales/de.yml | 4 ++++ config/locales/el.yml | 4 ++++ config/locales/es-AR.yml | 4 ++++ config/locales/es-MX.yml | 4 ++++ config/locales/es.yml | 4 ++++ config/locales/fi.yml | 4 ++++ config/locales/fr-CA.yml | 4 ++++ config/locales/fr.yml | 4 ++++ config/locales/gl.yml | 4 ++++ config/locales/he.yml | 4 ++++ config/locales/hu.yml | 6 ++++++ config/locales/ia.yml | 10 ++++++++++ config/locales/is.yml | 4 ++++ config/locales/it.yml | 6 ++++++ config/locales/nl.yml | 4 ++++ config/locales/simple_form.ca.yml | 1 + config/locales/simple_form.cs.yml | 1 + config/locales/simple_form.da.yml | 1 + config/locales/simple_form.de.yml | 1 + config/locales/simple_form.el.yml | 1 + config/locales/simple_form.es-AR.yml | 1 + config/locales/simple_form.es-MX.yml | 3 ++- config/locales/simple_form.es.yml | 1 + config/locales/simple_form.fi.yml | 1 + config/locales/simple_form.fr-CA.yml | 2 ++ config/locales/simple_form.fr.yml | 2 ++ config/locales/simple_form.gl.yml | 1 + config/locales/simple_form.he.yml | 1 + config/locales/simple_form.hu.yml | 2 ++ config/locales/simple_form.is.yml | 1 + config/locales/simple_form.it.yml | 2 ++ config/locales/simple_form.nl.yml | 1 + config/locales/simple_form.tr.yml | 1 + config/locales/simple_form.uk.yml | 1 + config/locales/simple_form.vi.yml | 1 + config/locales/simple_form.zh-TW.yml | 1 + config/locales/tr.yml | 6 +++++- config/locales/uk.yml | 2 ++ config/locales/vi.yml | 4 ++++ config/locales/zh-TW.yml | 4 ++++ 55 files changed, 144 insertions(+), 4 deletions(-) diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 5bee7f604c7..bb255756e66 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -497,6 +497,8 @@ "keyboard_shortcuts.translate": "per a traduir una publicació", "keyboard_shortcuts.unfocus": "Descentra l'àrea de composició de text/cerca", "keyboard_shortcuts.up": "Apuja a la llista", + "learn_more_link.got_it": "Entesos", + "learn_more_link.learn_more": "Per a saber-ne més", "lightbox.close": "Tanca", "lightbox.next": "Següent", "lightbox.previous": "Anterior", @@ -597,6 +599,7 @@ "notification.label.mention": "Menció", "notification.label.private_mention": "Menció privada", "notification.label.private_reply": "Resposta en privat", + "notification.label.quote": "{name} ha citat la vostra publicació", "notification.label.reply": "Resposta", "notification.mention": "Menció", "notification.mentioned_you": "{name} us ha mencionat", @@ -872,6 +875,11 @@ "status.open": "Amplia el tut", "status.pin": "Fixa en el perfil", "status.quote_error.filtered": "No es mostra a causa d'un dels vostres filtres", + "status.quote_error.not_available": "Publicació no disponible", + "status.quote_error.pending_approval": "Publicació pendent", + "status.quote_error.pending_approval_popout.body": "Les citacions compartides a través del Fediverse poden trigar en aparèixer, perquè diferents servidors tenen diferents protocols.", + "status.quote_error.pending_approval_popout.title": "Publicació pendent? Mantinguem la calma", + "status.quote_post_author": "S'ha citat una publicació de @{name}", "status.read_more": "Més informació", "status.reblog": "Impulsa", "status.reblog_private": "Impulsa amb la visibilitat original", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index ff3ba99e8d6..645808695fc 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -600,6 +600,7 @@ "notification.label.mention": "Omtale", "notification.label.private_mention": "Privat omtale", "notification.label.private_reply": "Privat svar", + "notification.label.quote": "{name} citerede dit indlæg", "notification.label.reply": "Svar", "notification.mention": "Omtale", "notification.mentioned_you": "{name} omtalte dig", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 79aa5874b5b..a2dfbb8bd38 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -600,6 +600,7 @@ "notification.label.mention": "Επισήμανση", "notification.label.private_mention": "Ιδιωτική επισήμανση", "notification.label.private_reply": "Ιδιωτική απάντηση", + "notification.label.quote": "Ο/Η {name} έκανε παράθεση της ανάρτησής σου", "notification.label.reply": "Απάντηση", "notification.mention": "Επισήμανση", "notification.mentioned_you": "Ο χρήστης {name} σε επισήμανε", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 84536740b2e..75460017daf 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -600,6 +600,7 @@ "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Respuesta privada", + "notification.label.quote": "{name} citó tu mensaje", "notification.label.reply": "Respuesta", "notification.mention": "Mención", "notification.mentioned_you": "{name} te mencionó", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 834c1f33bf2..d0c2290717a 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -600,6 +600,7 @@ "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Respuesta privada", + "notification.label.quote": "{name} citó tu publicación", "notification.label.reply": "Respuesta", "notification.mention": "Mención", "notification.mentioned_you": "{name} te mencionó", @@ -877,7 +878,7 @@ "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.not_available": "Publicación no disponible", "status.quote_error.pending_approval": "Publicación pendiente", - "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que los diferentes servidores tienen diferentes protocolos.", + "status.quote_error.pending_approval_popout.body": "Las citas compartidas en el Fediverso pueden tardar en mostrarse, ya que cada servidor tiene un protocolo diferente.", "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", "status.quote_post_author": "Ha citado una publicación de @{name}", "status.read_more": "Leer más", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 475f4ca7068..62c296ad52f 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -600,6 +600,7 @@ "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Respuesta privada", + "notification.label.quote": "{name} citó tu publicación", "notification.label.reply": "Respuesta", "notification.mention": "Mención", "notification.mentioned_you": "{name} te ha mencionado", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 8f93fab6d4c..397c9d14c5a 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -600,6 +600,7 @@ "notification.label.mention": "Említés", "notification.label.private_mention": "Privát említés", "notification.label.private_reply": "Privát válasz", + "notification.label.quote": "{name} idézte a bejegyzésedet", "notification.label.reply": "Válasz", "notification.mention": "Említés", "notification.mentioned_you": "{name} megemlített", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5168665f6a2..9edc9ae567f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -600,6 +600,7 @@ "notification.label.mention": "Menziona", "notification.label.private_mention": "Menzione privata", "notification.label.private_reply": "Rispondi in privato", + "notification.label.quote": "{name} ha citato il tuo post", "notification.label.reply": "Rispondi", "notification.mention": "Menziona", "notification.mentioned_you": "{name} ti ha menzionato", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 54a1eac1ba8..22cae4b455f 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -600,6 +600,7 @@ "notification.label.mention": "Vermelding", "notification.label.private_mention": "Privébericht", "notification.label.private_reply": "Privéreactie", + "notification.label.quote": "{name} heeft jouw bericht geciteerd", "notification.label.reply": "Reactie", "notification.mention": "Vermelding", "notification.mentioned_you": "Je bent vermeld door {name}", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index a7fa70a28a5..27d5f72c9c4 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -600,6 +600,7 @@ "notification.label.mention": "Bahsetme", "notification.label.private_mention": "Özel bahsetme", "notification.label.private_reply": "Özel yanıt", + "notification.label.quote": "{name} gönderini yeniden paylaştı", "notification.label.reply": "Yanıt", "notification.mention": "Bahsetme", "notification.mentioned_you": "{name} sizden söz etti", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 5062434ab99..fc67e1d7ae7 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -600,6 +600,7 @@ "notification.label.mention": "Lượt nhắc", "notification.label.private_mention": "Nhắn riêng", "notification.label.private_reply": "Trả lời riêng", + "notification.label.quote": "{name} đã trích dẫn tút của bạn", "notification.label.reply": "Trả lời", "notification.mention": "Nhắc đến bạn", "notification.mentioned_you": "{name} nhắc đến bạn", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index b086b55b833..cafd061818e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -600,6 +600,7 @@ "notification.label.mention": "提及", "notification.label.private_mention": "私訊", "notification.label.private_reply": "私訊回嘟", + "notification.label.quote": "{name} 已引用您的嘟文", "notification.label.reply": "回嘟", "notification.mention": "提及", "notification.mentioned_you": "{name} 已提及您", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 2057e4cc3d5..7dfa928f57b 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1660,6 +1660,10 @@ ca: title: Nova menció poll: subject: Ha finalitzat l'enquesta de %{name} + quote: + body: 'La vostra publicació ha estat citada per %{name}:' + subject: "%{name} ha citat la vostra publicació" + title: Nova citació reblog: body: "%{name} ha impulsat el teu estat:" subject: "%{name} ha impulsat el teu estat" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index e4ebcd744c7..5582a02740e 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1765,6 +1765,10 @@ cs: title: Nová zmínka poll: subject: Anketa od %{name} skončila + quote: + body: 'Váš příspěvek citoval účet %{name}:' + subject: "%{name} citovali váš příspěvek" + title: Nová citace reblog: body: 'Uživatel %{name} boostnul váš příspěvek:' subject: Uživatel %{name} boostnul váš příspěvek diff --git a/config/locales/da.yml b/config/locales/da.yml index 1b56425ee2b..85de82443e9 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1687,6 +1687,10 @@ da: title: Ny omtale poll: subject: En afstemning fra %{name} er afsluttet + quote: + body: 'Dit indlæg blev citeret af %{name}:' + subject: "%{name} citerede dit indlæg" + title: Nyt citat reblog: body: 'Dit indlæg blev fremhævet af %{name}:' subject: "%{name} fremhævede dit indlæg" @@ -1905,7 +1909,7 @@ da: ownership: Andres indlæg kan ikke fastgøres reblog: En fremhævelse kan ikke fastgøres quote_policies: - followers: Kun egne følgere + followers: Kun dine følgere nobody: Ingen public: Alle title: '%{name}: "%{quote}"' diff --git a/config/locales/de.yml b/config/locales/de.yml index adef04dfa85..69ff2ac5454 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -1687,6 +1687,10 @@ de: title: Neue Erwähnung poll: subject: Eine Umfrage von %{name} ist beendet + quote: + body: 'Dein Beitrag wurde von %{name} zitiert:' + subject: "%{name} zitierte deinen Beitrag" + title: Neuer zitierter Beitrag reblog: body: 'Dein Beitrag wurde von %{name} geteilt:' subject: "%{name} teilte deinen Beitrag" diff --git a/config/locales/el.yml b/config/locales/el.yml index 2ad8bc6899c..cddba5cdfbe 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -1687,6 +1687,10 @@ el: title: Νέα επισήμανση poll: subject: Μια δημοσκόπηση του %{name} έληξε + quote: + body: 'Η ανάρτησή σου παρατέθηκε από %{name}:' + subject: Ο/Η %{name} έκανε παράθεση της ανάρτησής σου + title: Νέα παράθεση reblog: body: 'Η ανάρτησή σου ενισχύθηκε από τον/την %{name}:' subject: Ο/Η %{name} ενίσχυσε την ανάρτηση σου diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index f01db94590c..04578632ac7 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -1687,6 +1687,10 @@ es-AR: title: Nueva mención poll: subject: Terminó una encuesta de %{name} + quote: + body: 'Tu mensaje fue citado por %{name}:' + subject: "%{name} citó tu mensaje" + title: Nueva cita reblog: body: "%{name} adhirió a tu mensaje:" subject: "%{name} adhirió a tu mensaje" diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index e7a6e3df629..10186de91ed 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1687,6 +1687,10 @@ es-MX: title: Nueva mención poll: subject: Una encuesta de %{name} ha terminado + quote: + body: 'Tu publicación fue citada por %{name}:' + subject: "%{name} citó tu publicación" + title: Nueva cita reblog: body: 'Tu publicación fue impulsada por %{name}:' subject: "%{name} ha impulsado tu publicación" diff --git a/config/locales/es.yml b/config/locales/es.yml index 6c076d0b51d..2536f42669a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1687,6 +1687,10 @@ es: title: Nueva mención poll: subject: Una encuesta de %{name} ha terminado + quote: + body: 'Tu publicación ha sido citada por %{name}:' + subject: "%{name} citó tu publicación" + title: Nueva cita reblog: body: 'Tu publicación fue impulsada por %{name}:' subject: "%{name} impulsó tu publicación" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 6d7773a1f64..190a58b8ee0 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1685,6 +1685,10 @@ fi: title: Uusi maininta poll: subject: Äänestys käyttäjältä %{name} on päättynyt + quote: + body: "%{name} lainasi julkaisuasi:" + subject: "%{name} lainasi julkaisuasi" + title: Uusi lainaus reblog: body: "%{name} tehosti julkaisuasi:" subject: "%{name} tehosti julkaisuasi" diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 38de6ee8612..c7da245c4a9 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1852,6 +1852,10 @@ fr-CA: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_policies: + followers: Vos abonné·es seulement + nobody: Personne + public: Tout le monde title: "%{name} : « %{quote} »" visibilities: direct: Direct diff --git a/config/locales/fr.yml b/config/locales/fr.yml index da5224fcf5e..918812a8077 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1852,6 +1852,10 @@ fr: limit: Vous avez déjà épinglé le nombre maximum de messages ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas reblog: Un partage ne peut pas être épinglé + quote_policies: + followers: Vos abonné·es seulement + nobody: Personne + public: Tout le monde title: "%{name} : « %{quote} »" visibilities: direct: Direct diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 4a2f593d09e..45d0e62b6af 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1687,6 +1687,10 @@ gl: title: Nova mención poll: subject: A enquisa de %{name} rematou + quote: + body: 'A túa publicación foi citada por %{name}:' + subject: "%{name} citou a túa publicación" + title: Nova cita reblog: body: 'A túa publicación promovida por %{name}:' subject: "%{name} promoveu a túa publicación" diff --git a/config/locales/he.yml b/config/locales/he.yml index 53f42bfa3bc..46be493307c 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1765,6 +1765,10 @@ he: title: אזכור חדש poll: subject: סקר מאת %{name} הסתיים + quote: + body: 'הודעתך צוטטה על ידי %{name}:' + subject: "%{name} ציטט.ה את הודעתך" + title: ציטוט חדש reblog: body: 'הודעתך הודהדה על ידי %{name}:' subject: הודעתך הודהדה על ידי%{name} diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 4e8f9e80513..454f5ce0176 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1687,6 +1687,10 @@ hu: title: Új említés poll: subject: "%{name} szavazása véget ért" + quote: + body: 'A bejegyzésedet %{name} idézte:' + subject: "%{name} idézte a bejegyzésedet" + title: Új idézet reblog: body: 'A bejegyzésedet %{name} megtolta:' subject: "%{name} megtolta a bejegyzésedet" @@ -1905,6 +1909,8 @@ hu: ownership: Nem tűzheted ki valaki más bejegyzését reblog: Megtolt bejegyzést nem tudsz kitűzni quote_policies: + followers: Csak a követőid + nobody: Senki public: Mindenki title: "%{name}: „%{quote}”" visibilities: diff --git a/config/locales/ia.yml b/config/locales/ia.yml index f2b00a0e555..1c7de3f3822 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -1042,6 +1042,16 @@ ia: other: Usate per %{count} personas in le ultime septimana title: Recommendationes e tendentias trending: In tendentia + username_blocks: + edit: + title: Modificar regula de nomine de usator + new: + create: Crear regula + title: Crear nove regula de nomine de usator + no_username_block_selected: Necun regula de usator ha essite cambiate perque necun ha essite seligite + not_permitted: Non permittite + title: Regulas de nomine de usator + updated_msg: Regula de nomine de usator actualisate con successo warning_presets: add_new: Adder nove delete: Deler diff --git a/config/locales/is.yml b/config/locales/is.yml index 5feccdc9bd6..91260629159 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -1691,6 +1691,10 @@ is: title: Ný tilvísun poll: subject: Könnun frá %{name} er lokið + quote: + body: "%{name} vitnaði í færsluna þína:" + subject: "%{name} vitnaði í færsluna þína" + title: Ný tilvitnun reblog: body: "%{name} endurbirti færsluna þína:" subject: "%{name} endurbirti færsluna þína" diff --git a/config/locales/it.yml b/config/locales/it.yml index cef3224f63a..669947d5e85 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -1689,6 +1689,10 @@ it: title: Nuova menzione poll: subject: Un sondaggio da %{name} è terminato + quote: + body: 'Il tuo post è stato citato da %{name}:' + subject: "%{name} ha citato il tuo post" + title: Nuova citazione reblog: body: 'Il tuo status è stato condiviso da %{name}:' subject: "%{name} ha condiviso il tuo status" @@ -1907,6 +1911,8 @@ it: ownership: Non puoi fissare in cima un post di qualcun altro reblog: Un toot condiviso non può essere fissato in cima quote_policies: + followers: Solo i tuoi seguaci + nobody: Nessuno public: Tutti title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 85fe706c44c..206992ec8c0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1687,6 +1687,10 @@ nl: title: Nieuwe vermelding poll: subject: Een peiling van %{name} is beëindigd + quote: + body: 'Jouw bericht werd door %{name} geciteerd:' + subject: "%{name} heeft jouw bericht geciteerd" + title: Nieuw citaat reblog: body: 'Jouw bericht werd door %{name} geboost:' subject: "%{name} boostte jouw bericht" diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 64958711dab..f979e683233 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -323,6 +323,7 @@ ca: follow_request: Algú sol·licita seguir-te mention: Algú et menciona pending_account: Un nou compte necessita revisió + quote: Algú us ha citat reblog: Algú comparteix el teu estat report: S'ha enviat l'informe nou software_updates: diff --git a/config/locales/simple_form.cs.yml b/config/locales/simple_form.cs.yml index 5ca0de9dc82..b318f15709d 100644 --- a/config/locales/simple_form.cs.yml +++ b/config/locales/simple_form.cs.yml @@ -331,6 +331,7 @@ cs: follow_request: Někdo požádal o možnost vás sledovat mention: Někdo vás zmínil pending_account: Je třeba posoudit nový účet + quote: Někdo vás citoval reblog: Někdo boostnul váš příspěvek report: Je odesláno nové hlášení software_updates: diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index c5a436c2e2f..58f6a129bb1 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -329,6 +329,7 @@ da: follow_request: Nogen anmodede om at følge dig mention: Nogen omtalte dig pending_account: Ny konto kræver gennemgang + quote: Nogle citerede dig reblog: Nogen fremhævede dit indlæg report: Ny anmeldelse indsendt software_updates: diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 998befb1113..62185c80f0e 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -329,6 +329,7 @@ de: follow_request: Jemand möchte mir folgen mention: Ich wurde erwähnt pending_account: Ein neues Konto muss überprüft werden + quote: Jemand zitierte dich reblog: Mein Beitrag wurde geteilt report: Eine neue Meldung wurde eingereicht software_updates: diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index b3c3c2c6d95..fcbe7dc32d1 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -329,6 +329,7 @@ el: follow_request: Κάποιος ζήτησε να σε ακολουθήσει mention: Κάποιος σε επισήμανε pending_account: Νέος λογαριασμός χρειάζεται αναθεώρηση + quote: Κάποιος σε παρέθεσε reblog: Κάποιος ενίσχυσε την ανάρτηση σου report: Υποβλήθηκε νέα αναφορά software_updates: diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index 0b7dde5cb8c..f3972d01a99 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -329,6 +329,7 @@ es-AR: follow_request: Una cuenta solicita seguirte mention: Una cuenta te menciona pending_account: Una nueva cuenta necesita revisión + quote: Alguien te citó reblog: Una cuenta adhiere a tu mensaje report: Se envió una nueva denuncia software_updates: diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index 0aee8fab9d8..6b20f6e6aea 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -56,7 +56,7 @@ es-MX: scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente - setting_default_quote_policy: Este ajuste solo tendrá efecto en publicaciones creadas con la próxima versión de Mastodon, pero puedes configurarlo previamente. + setting_default_quote_policy: Esta configuración solo tendrá efecto en las publicaciones creadas con la próxima versión de Mastodon, pero puedes seleccionar tu preferencia como preparación. setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un clic setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia @@ -329,6 +329,7 @@ es-MX: follow_request: Enviar correo electrónico cuando alguien solicita seguirte mention: Enviar correo electrónico cuando alguien te mencione pending_account: Enviar correo electrónico cuando una nueva cuenta necesita revisión + quote: Alguien te citó reblog: Enviar correo electrónico cuando alguien comparta su publicación report: Nuevo reporte enviado software_updates: diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index ce2a74c3e10..53115056f47 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -329,6 +329,7 @@ es: follow_request: Enviar correo electrónico cuando alguien solicita seguirte mention: Enviar correo electrónico cuando alguien te mencione pending_account: Enviar correo electrónico cuando una nueva cuenta necesita revisión + quote: Alguien te ha citado reblog: Enviar correo electrónico cuando alguien comparta su publicación report: Nuevo informe enviado software_updates: diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 5edb75cdb90..7ccf8ab5f7f 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -328,6 +328,7 @@ fi: follow_request: Joku pyysi lupaa seurata sinua mention: Joku mainitsi sinut pending_account: Uusi tili tarvitsee tarkastuksen + quote: Joku lainasi sinua reblog: Joku tehosti julkaisuasi report: Uusi raportti lähetettiin software_updates: diff --git a/config/locales/simple_form.fr-CA.yml b/config/locales/simple_form.fr-CA.yml index 8856c8906c1..51a5b432a3b 100644 --- a/config/locales/simple_form.fr-CA.yml +++ b/config/locales/simple_form.fr-CA.yml @@ -56,6 +56,7 @@ fr-CA: scopes: À quelles APIs l’application sera autorisée à accéder. Si vous sélectionnez une permission générale, vous n’avez pas besoin de sélectionner les permissions plus précises. setting_aggregate_reblogs: Ne pas afficher les nouveaux partages pour les messages déjà récemment partagés (n’affecte que les partages futurs) setting_always_send_emails: Normalement, les notifications par courriel ne seront pas envoyées lorsque vous utilisez Mastodon activement + setting_default_quote_policy: Ce paramètre ne prendra effet que pour les messages créés avec la prochaine version de Mastodon, mais vous pouvez sélectionner votre préférence en avance. setting_default_sensitive: Les médias sensibles sont cachés par défaut et peuvent être révélés d’un simple clic setting_display_media_default: Masquer les médias marqués comme sensibles setting_display_media_hide_all: Toujours masquer les médias @@ -228,6 +229,7 @@ fr-CA: setting_boost_modal: Demander confirmation avant de partager un message setting_default_language: Langue de publication setting_default_privacy: Confidentialité des messages + setting_default_quote_policy: Autoriser les citations pour setting_default_sensitive: Toujours marquer les médias comme sensibles setting_delete_modal: Demander confirmation avant de supprimer un message setting_disable_hover_cards: Désactiver l'aperçu du profil au survol diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 53c4fbfba72..90218f8b8ef 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -56,6 +56,7 @@ fr: scopes: À quelles APIs l’application sera autorisée à accéder. Si vous sélectionnez une permission générale, vous n’avez pas besoin de sélectionner les permissions plus précises. setting_aggregate_reblogs: Ne pas afficher les nouveaux partages pour les messages déjà récemment partagés (n’affecte que les partages futurs) setting_always_send_emails: Normalement, les notifications par courriel ne seront pas envoyées lorsque vous utilisez Mastodon activement + setting_default_quote_policy: Ce paramètre ne prendra effet que pour les messages créés avec la prochaine version de Mastodon, mais vous pouvez sélectionner votre préférence en avance. setting_default_sensitive: Les médias sensibles sont cachés par défaut et peuvent être révélés d’un simple clic setting_display_media_default: Masquer les médias marqués comme sensibles setting_display_media_hide_all: Toujours masquer les médias @@ -228,6 +229,7 @@ fr: setting_boost_modal: Demander confirmation avant de partager un message setting_default_language: Langue de publication setting_default_privacy: Confidentialité des messages + setting_default_quote_policy: Autoriser les citations pour setting_default_sensitive: Toujours marquer les médias comme sensibles setting_delete_modal: Demander confirmation avant de supprimer un message setting_disable_hover_cards: Désactiver l'aperçu du profil au survol diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 18c259f22ce..1225e67b739 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -329,6 +329,7 @@ gl: follow_request: Enviar un correo cando alguén solicita seguirte mention: Enviar un correo cando alguén te menciona pending_account: Enviar un correo cando unha nova conta precisa revisión + quote: Citoute alguén reblog: Enviar un correo cando alguén promociona a tua mensaxe report: Enviouse unha nova denuncia software_updates: diff --git a/config/locales/simple_form.he.yml b/config/locales/simple_form.he.yml index 78c168f6fd5..69c9f00cc59 100644 --- a/config/locales/simple_form.he.yml +++ b/config/locales/simple_form.he.yml @@ -331,6 +331,7 @@ he: follow_request: מישהו.י ביקש.ה לעקוב אחריך mention: שליחת דוא"ל כשפונים אלייך pending_account: נדרשת סקירה של חשבון חדש + quote: שליחת דוא"ל כשמצטטים הודעה שלך reblog: שליחת דוא"ל כשמהדהדים הודעה שלך report: דו"ח חדש הוגש software_updates: diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml index 7e148d706c9..f34d9a5232f 100644 --- a/config/locales/simple_form.hu.yml +++ b/config/locales/simple_form.hu.yml @@ -56,6 +56,7 @@ hu: scopes: Mely API-kat érheti el az alkalmazás. Ha felső szintű hatáskört választasz, nem kell egyesével kiválasztanod az alatta lévőeket. setting_aggregate_reblogs: Ne mutassunk megtolásokat olyan bejegyzésekhez, melyeket nemrég toltak meg (csak új megtolásokra lép életbe) setting_always_send_emails: Alapesetben nem küldünk e-mail-értesítéseket, ha aktívan használod a Mastodont + setting_default_quote_policy: A beállítás csak a Mastodon következő verziójával készült bejegyzésekre lesz hatással, de előre kiválaszthatod az előnyben részesített beállítást. setting_default_sensitive: A kényes médiatartalmat alapesetben elrejtjük, de egyetlen kattintással előhozható setting_display_media_default: Kényes tartalomnak jelölt média elrejtése setting_display_media_hide_all: Média elrejtése mindig @@ -328,6 +329,7 @@ hu: follow_request: E-mail küldése, amikor valaki követni szeretne téged mention: E-mail küldése, amikor valaki megemlít téged pending_account: E-mail küldése, ha új fiókot kell engedélyezni + quote: E-mail küldése, amikor valaki idéz téged reblog: Valaki megtolta a bejegyzésedet report: Új bejelentést küldtek be software_updates: diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index 49b3d6da35d..fc559af7ba7 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -329,6 +329,7 @@ is: follow_request: Einhver hefur beðið um að fylgjast með þér mention: Einhver minntist á þig pending_account: Nýr notandaaðgangur þarfnast yfirferðar + quote: Einhver vitnaði í þig reblog: Einhver endurbirti færsluna þína report: Ný kæra hefur verið send inn software_updates: diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index 0dc781c74e7..f349680a366 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -56,6 +56,7 @@ it: scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole. setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni) setting_always_send_emails: Normalmente le notifiche e-mail non vengono inviate quando si utilizza attivamente Mastodon + setting_default_quote_policy: Questa impostazione avrà effetto solo per i post creati con la prossima versione di Mastodon, ma puoi selezionare le tue preferenze in fase di preparazione. setting_default_sensitive: Media con contenuti sensibili sono nascosti in modo predefinito e possono essere rivelati con un click setting_display_media_default: Nascondi media segnati come sensibili setting_display_media_hide_all: Nascondi sempre tutti i media @@ -328,6 +329,7 @@ it: follow_request: Invia email quando qualcuno chiede di seguirti mention: Invia email quando qualcuno ti menziona pending_account: Invia e-mail quando un nuovo account richiede l'approvazione + quote: Qualcuno ti ha citato reblog: Qualcuno ha condiviso il tuo post report: Una nuova segnalazione è stata inviata software_updates: diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 1f0a4de9120..53307d7e11a 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -329,6 +329,7 @@ nl: follow_request: Wanneer iemand jou wil volgen mention: Je bent door iemand vermeld pending_account: Wanneer een nieuw account moet worden beoordeeld + quote: Iemand heeft jou geciteerd reblog: Wanneer iemand jouw bericht heeft geboost report: Nieuwe rapportage is ingediend software_updates: diff --git a/config/locales/simple_form.tr.yml b/config/locales/simple_form.tr.yml index 2dffa398050..e403d34e605 100644 --- a/config/locales/simple_form.tr.yml +++ b/config/locales/simple_form.tr.yml @@ -329,6 +329,7 @@ tr: follow_request: Biri seni takip etmek istedi mention: Birisi senden bahsetti pending_account: Yeni hesabın incelenmesi gerekiyor + quote: Birisi senden alıntı yaptı reblog: Birisi gönderini boostladı report: Yeni rapor gönderildi software_updates: diff --git a/config/locales/simple_form.uk.yml b/config/locales/simple_form.uk.yml index 8a0e9012811..1fb1c542f15 100644 --- a/config/locales/simple_form.uk.yml +++ b/config/locales/simple_form.uk.yml @@ -317,6 +317,7 @@ uk: follow_request: Коли хтось запитує дозвіл підписатися mention: Коли хтось згадує вас pending_account: Надсилати електронного листа, коли новий обліковий запис потребує розгляду + quote: Хтось процитував вас reblog: Коли хтось поширює ваш допис report: Нову скаргу надіслано software_updates: diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index b33c5f4ccca..b2e79420e8f 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -328,6 +328,7 @@ vi: follow_request: Ai đó yêu cầu theo dõi bạn mention: Ai đó nhắc đến bạn pending_account: Phê duyệt tài khoản mới + quote: Ai đó trích dẫn bạn reblog: Ai đó đăng lại tút của bạn report: Ai đó gửi báo cáo software_updates: diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 2b57647f9eb..8fc48dfa98a 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -328,6 +328,7 @@ zh-TW: follow_request: 當有使用者請求跟隨您時,傳送電子郵件通知 mention: 當有使用者於嘟文提及您時,傳送電子郵件通知 pending_account: 有新的帳號需要審核 + quote: 當有使用者引用您的嘟文時 reblog: 當有使用者轉嘟您的嘟文時,傳送電子郵件通知 report: 新回報已遞交 software_updates: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 595e4bfcbf3..5b8dd33720d 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1596,7 +1596,7 @@ tr: password: parola sign_in_token: e-posta güvenlik kodu webauthn: güvenlik anahtarları - description_html: Eğer tanımadığınız bir faaliyet görüyorsanız, parolanızı değiştirmeyi ve iki aşamalı kimlik doğrulamayı etkinleştirmeyi düşünün. + description_html: Eğer tanımadığınız bir faaliyet görürseniz, parolanızı değiştirmeyi ve iki aşamalı kimlik doğrulamayı etkinleştirmeyi değerlendirin. empty: Kimlik doğrulama geçmişi yok failed_sign_in_html: "%{method} yöntemiyle %{ip} (%{browser}) adresinden başarısız oturum açma girişimi" successful_sign_in_html: "%{method} yöntemiyle %{ip} (%{browser}) adresinden başarılı oturum açma" @@ -1687,6 +1687,10 @@ tr: title: Yeni bahsetme poll: subject: Anket %{name} tarafından sonlandırıldı + quote: + body: "%{name} durumunuzu yeniden paylaştı:" + subject: "%{name} gönderini yeniden paylaştı" + title: Yeni alıntı reblog: body: "%{name} durumunuzu yeniden paylaştı:" subject: "%{name} durumunuzu yeniden paylaştı" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index ad3926fbebb..ed0ce8d7b55 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1633,6 +1633,8 @@ uk: title: Нова згадка poll: subject: Опитування від %{name} завершено + quote: + body: 'Ваш пост був процитований %{name}:' reblog: body: "%{name} поширює ваш допис:" subject: "%{name} поширив ваш статус" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 32625d46576..aa2b47a890e 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1648,6 +1648,10 @@ vi: title: Lượt nhắc mới poll: subject: Vốt của %{name} đã kết thúc + quote: + body: 'Tút của bạn được trích dẫn bởi %{name}:' + subject: "%{name} vừa trích dẫn tút của bạn" + title: Trích dẫn mới reblog: body: Tút của bạn vừa được %{name} đăng lại subject: "%{name} vừa đăng lại tút của bạn" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index fc29a3b5310..dbc9c6b8a2e 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -1650,6 +1650,10 @@ zh-TW: title: 新的提及 poll: subject: 由 %{name} 發起的投票已結束 + quote: + body: 您的嘟文被 %{name} 引用: + subject: "%{name} 已引用您的嘟文" + title: 新引用 reblog: body: 您的嘟文被 %{name} 轉嘟: subject: "%{name} 已轉嘟您的嘟文" From 9c0a10f66240fc8de5e91fe0cf9ca21ee61387a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 10:19:58 +0200 Subject: [PATCH 225/660] Update dependency capybara-playwright-driver to v0.5.7 (#35675) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2b6ba0d4852..475172934ac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,7 +144,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - capybara-playwright-driver (0.5.6) + capybara-playwright-driver (0.5.7) addressable capybara playwright-ruby-client (>= 1.16.0) @@ -438,7 +438,7 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0722) + mime-types-data (3.2025.0729) mini_mime (1.1.5) mini_portile2 (2.8.9) minitest (5.25.5) @@ -610,7 +610,7 @@ GEM pg (1.6.1) pghero (3.7.0) activerecord (>= 7.1) - playwright-ruby-client (1.54.0) + playwright-ruby-client (1.54.1) concurrent-ruby (>= 1.1.6) mime-types (>= 3.0) pp (0.6.2) From 1fd3510b32cee73b376efb3d316673a6c70f9475 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 5 Aug 2025 11:42:58 +0200 Subject: [PATCH 226/660] Change the quote revocation REST API endpoint to return updated quote post (#35677) --- app/controllers/api/v1/statuses/quotes_controller.rb | 2 +- spec/requests/api/v1/statuses/quotes_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/statuses/quotes_controller.rb b/app/controllers/api/v1/statuses/quotes_controller.rb index 7dd91e9a2ee..962855884ec 100644 --- a/app/controllers/api/v1/statuses/quotes_controller.rb +++ b/app/controllers/api/v1/statuses/quotes_controller.rb @@ -19,7 +19,7 @@ class Api::V1::Statuses::QuotesController < Api::V1::Statuses::BaseController RevokeQuoteService.new.call(@quote) - render_empty # TODO: do we want to return something? an updated status? + render json: @quote.status, serializer: REST::StatusSerializer end private diff --git a/spec/requests/api/v1/statuses/quotes_spec.rb b/spec/requests/api/v1/statuses/quotes_spec.rb index bbf697ce323..9456556ce99 100644 --- a/spec/requests/api/v1/statuses/quotes_spec.rb +++ b/spec/requests/api/v1/statuses/quotes_spec.rb @@ -108,6 +108,12 @@ RSpec.describe 'API V1 Statuses Quotes' do expect(response) .to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body) + .to match( + a_hash_including(id: quote.status.id.to_s, quote: a_hash_including(state: 'revoked')) + ) end end From edece2a197c3745dfc497fca8ceee47beee69aca Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 5 Aug 2025 14:53:04 +0200 Subject: [PATCH 227/660] Merge commit from fork --- config/initializers/rack_attack.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index f558ee5fe0e..853b99d32fd 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -126,7 +126,7 @@ class Rack::Attack end throttle('throttle_email_confirmations/email', limit: 5, period: 30.minutes) do |req| - if req.post? && req.path_matches?('/auth/password') + if req.post? && req.path_matches?('/auth/confirmation') req.params.dig('user', 'email').presence elsif req.post? && req.path == '/api/v1/emails/confirmations' req.authenticated_user_id From ffeb5da9912a3b7db0deb8aec429e661ef609c59 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 5 Aug 2025 15:32:20 +0200 Subject: [PATCH 228/660] Bump version to v4.4.3 (#35687) --- CHANGELOG.md | 22 ++++++++++++++++++++++ docker-compose.yml | 6 +++--- lib/mastodon/version.rb | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97a7234382c..b3af469bb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to this project will be documented in this file. +## [4.4.3] - 2025-08-05 + +### Security + +- Update dependencies +- Fix incorrect rate-limit handling [GHSA-84ch-6436-c7mg](https://github.com/mastodon/mastodon/security/advisories/GHSA-84ch-6436-c7mg) + +### Fixed + +- Fix race condition caused by ActiveRecord query cache in `Create` critical path (#35662 by @ClearlyClaire) +- Fix race condition caused by quote post processing (#35657 by @ClearlyClaire) +- Fix WebUI crashing for accounts with `null` URL (#35651 by @ClearlyClaire) +- Fix friends-of-friends recommendations suggesting already-requested accounts (#35604 by @ClearlyClaire) +- Fix synchronous recursive fetching of deeply-nested quoted posts (#35600 by @ClearlyClaire) +- Fix “Expand this post” link including user `@undefined` (#35478 by @ClearlyClaire) + +### Changed + +- Change `StatusReachFinder` to consider quotes as well as reblogs (#35601 by @ClearlyClaire) +- Add restrictions on which quote posts can trend (#35507 by @ClearlyClaire) +- Change quote verification to not bypass authorization flow for mentions (#35528 by @ClearlyClaire) + ## [4.4.2] - 2025-07-23 ### Security diff --git a/docker-compose.yml b/docker-compose.yml index 8fe42fa0c9a..39823fc812c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: web: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.2 + image: ghcr.io/mastodon/mastodon:v4.4.3 restart: always env_file: .env.production command: bundle exec puma -C config/puma.rb @@ -83,7 +83,7 @@ services: # build: # dockerfile: ./streaming/Dockerfile # context: . - image: ghcr.io/mastodon/mastodon-streaming:v4.4.2 + image: ghcr.io/mastodon/mastodon-streaming:v4.4.3 restart: always env_file: .env.production command: node ./streaming/index.js @@ -102,7 +102,7 @@ services: sidekiq: # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes # build: . - image: ghcr.io/mastodon/mastodon:v4.4.2 + image: ghcr.io/mastodon/mastodon:v4.4.3 restart: always env_file: .env.production command: bundle exec sidekiq diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 7cb5dec6772..cb8ff3f462c 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -17,7 +17,7 @@ module Mastodon end def default_prerelease - 'alpha.1' + 'alpha.2' end def prerelease From a968849e9c70944c160d8e15b903409095d762f2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 5 Aug 2025 10:10:28 -0400 Subject: [PATCH 229/660] Use `normalizes` api for `UsernameBlock` username change (#35606) --- app/models/username_block.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/models/username_block.rb b/app/models/username_block.rb index 18db4ca34ee..33169a47f82 100644 --- a/app/models/username_block.rb +++ b/app/models/username_block.rb @@ -29,10 +29,12 @@ class UsernameBlock < ApplicationRecord validates :username, presence: true, uniqueness: true scope :matches_exactly, ->(str) { where(exact: true).where(normalized_username: str) } - scope :matches_partially, ->(str) { where(exact: false).where(Arel::Nodes.build_quoted(str).matches(Arel::Nodes.build_quoted('%').concat(arel_table[:normalized_username]).concat(Arel::Nodes.build_quoted('%')))) } + scope :matches_partially, ->(str) { where(exact: false).where(Arel::Nodes.build_quoted(normalize_value_for(:normalized_username, str)).matches(Arel::Nodes.build_quoted('%').concat(arel_table[:normalized_username]).concat(Arel::Nodes.build_quoted('%')))) } before_save :set_normalized_username + normalizes :normalized_username, with: ->(value) { value.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) } + def comparison exact? ? 'equals' : 'contains' end @@ -42,8 +44,7 @@ class UsernameBlock < ApplicationRecord end def self.matches?(str, allow_with_approval: false) - normalized_str = str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) - matches_exactly(normalized_str).or(matches_partially(normalized_str)).where(allow_with_approval: allow_with_approval).any? + matches_exactly(str).or(matches_partially(str)).where(allow_with_approval: allow_with_approval).any? end def to_log_human_identifier @@ -53,10 +54,6 @@ class UsernameBlock < ApplicationRecord private def set_normalized_username - self.normalized_username = normalize(username) - end - - def normalize(str) - str.downcase.gsub(Regexp.union(HOMOGLYPHS.keys), HOMOGLYPHS) + self.normalized_username = username end end From a4c05c694fd88ded8b0a977d80b54ec7cd142add Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 5 Aug 2025 18:15:16 +0200 Subject: [PATCH 230/660] Fix quote notification filtering and add settings (#35690) --- .../mastodon/actions/notification_groups.ts | 4 +++- app/javascript/mastodon/actions/notifications.js | 2 +- .../notifications/components/column_settings.jsx | 11 +++++++++++ app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/reducers/settings.js | 3 +++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts index 43863254818..19d8750fa72 100644 --- a/app/javascript/mastodon/actions/notification_groups.ts +++ b/app/javascript/mastodon/actions/notification_groups.ts @@ -161,7 +161,9 @@ export const processNewNotificationForGroups = createAppAsyncThunk( if (!showInColumn) return; if ( - (notification.type === 'mention' || notification.type === 'update') && + (notification.type === 'mention' || + notification.type === 'update' || + notification.type === 'quote') && notification.status?.filtered ) { const filters = notification.status.filtered.filter((result) => diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 2499b8da1d7..cbfddc750f3 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -31,7 +31,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { let filtered = false; - if (['mention', 'status'].includes(notification.type) && notification.status.filtered) { + if (['mention', 'status', 'quote'].includes(notification.type) && notification.status.filtered) { const filters = notification.status.filtered.filter(result => result.filter.context.includes('notifications')); if (filters.some(result => result.filter.filter_action === 'hide')) { diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index 9616adcb937..b1f4e598185 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -143,6 +143,17 @@ class ColumnSettings extends PureComponent {
+
+

+ +
+ + {showPushSettings && } + + +
+
+

diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index c91516695b0..aa18fc39ce1 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Mentions:", "notifications.column_settings.poll": "Poll results:", "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.quote": "Quotes:", "notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.show": "Show in column", "notifications.column_settings.sound": "Play sound", diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 9e2ee5f55ab..cea8949f23c 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -36,6 +36,7 @@ const initialState = ImmutableMap({ follow_request: false, favourite: false, reblog: false, + quote: false, mention: false, poll: false, status: false, @@ -59,6 +60,7 @@ const initialState = ImmutableMap({ follow_request: false, favourite: true, reblog: true, + quote: true, mention: true, poll: true, status: true, @@ -72,6 +74,7 @@ const initialState = ImmutableMap({ follow_request: false, favourite: true, reblog: true, + quote: true, mention: true, poll: true, status: true, From 4c2ddbf2c4113e7bf5e89e7ac2681b2937fdeef3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 6 Aug 2025 04:42:53 -0400 Subject: [PATCH 231/660] Update rubocop to version 1.79.2 (#35688) --- .rubocop_todo.yml | 2 +- Gemfile.lock | 2 +- app/lib/emoji_formatter.rb | 4 +--- spec/models/form/import_spec.rb | 14 +++++++------- .../accounts_statuses_cleanup_scheduler_spec.rb | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8706ca0ddda..625fbf17ab4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.79.0. +# using RuboCop version 1.79.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new diff --git a/Gemfile.lock b/Gemfile.lock index 475172934ac..b554211a3f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -765,7 +765,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.4) - rubocop (1.79.1) + rubocop (1.79.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) diff --git a/app/lib/emoji_formatter.rb b/app/lib/emoji_formatter.rb index a5f5103fffd..c193df9bb65 100644 --- a/app/lib/emoji_formatter.rb +++ b/app/lib/emoji_formatter.rb @@ -45,9 +45,7 @@ class EmojiFormatter i += 1 if inside_shortname && text[i] == ':' - # https://github.com/rubocop/rubocop/issues/14383 - # False positive in line below, remove disable when resolved - inside_shortname = false # rubocop:disable Lint/UselessAssignment + inside_shortname = false shortcode = text[(shortname_start_index + 1)..(i - 1)] char_after = text[i + 1] diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index dd8fd35a05f..d682e13ecb9 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -258,13 +258,13 @@ RSpec.describe Form::Import do end end - it_behaves_like 'on successful import', 'following', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) - it_behaves_like 'on successful import', 'following', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) - it_behaves_like 'on successful import', 'blocking', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) - it_behaves_like 'on successful import', 'blocking', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) - it_behaves_like 'on successful import', 'muting', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) - it_behaves_like 'on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', (%w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } }) - it_behaves_like 'on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', (%w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } }) + it_behaves_like('on successful import', 'following', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like('on successful import', 'following', 'overwrite', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like('on successful import', 'blocking', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like('on successful import', 'blocking', 'overwrite', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like('on successful import', 'muting', 'merge', 'imports.txt', %w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like('on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', %w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } }) + it_behaves_like('on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', %w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } }) it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [ { 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil }, diff --git a/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb index 55b66629e06..1cd01eb3b90 100644 --- a/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb +++ b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb @@ -112,7 +112,7 @@ RSpec.describe Scheduler::AccountsStatusesCleanupScheduler do expect { subject.perform } .to change(Status, :count).by(-subject.compute_budget) # Cleanable statuses - .and (not_change { account_bob.statuses.count }) # No cleanup policy for account + .and not_change { account_bob.statuses.count } # No cleanup policy for account .and(not_change { account_dave.statuses.count }) # Disabled cleanup policy end From fcbd4b7afbaba4a599150fbb89c6d9b33b29e338 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:43:14 +0200 Subject: [PATCH 232/660] Update dependency annotaterb to v4.18.0 (#35676) 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 b554211a3f5..943334b03d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,7 +90,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) - annotaterb (4.17.0) + annotaterb (4.18.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) ast (2.4.3) From 6f6e7d8d49143e168a4fa257d9bca21fc7804b5f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Aug 2025 09:06:22 +0000 Subject: [PATCH 233/660] New Crowdin Translations (automated) (#35694) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 2 ++ app/javascript/mastodon/locales/da.json | 1 + app/javascript/mastodon/locales/de.json | 2 ++ app/javascript/mastodon/locales/el.json | 1 + app/javascript/mastodon/locales/es-AR.json | 1 + app/javascript/mastodon/locales/es-MX.json | 1 + app/javascript/mastodon/locales/es.json | 1 + app/javascript/mastodon/locales/fi.json | 2 ++ app/javascript/mastodon/locales/gl.json | 1 + app/javascript/mastodon/locales/he.json | 1 + app/javascript/mastodon/locales/is.json | 2 ++ app/javascript/mastodon/locales/it.json | 1 + app/javascript/mastodon/locales/kab.json | 8 ++++++ app/javascript/mastodon/locales/nl.json | 1 + app/javascript/mastodon/locales/pt-PT.json | 9 ++++++ app/javascript/mastodon/locales/uk.json | 1 + app/javascript/mastodon/locales/vi.json | 1 + app/javascript/mastodon/locales/zh-TW.json | 1 + config/locales/da.yml | 4 +-- config/locales/kab.yml | 13 +++++++++ config/locales/pt-PT.yml | 31 ++++++++++++++++++++ config/locales/ru.yml | 33 +++++++++++++--------- config/locales/simple_form.ca.yml | 1 + config/locales/simple_form.da.yml | 4 +-- config/locales/simple_form.kab.yml | 7 +++++ config/locales/simple_form.pt-PT.yml | 10 +++++++ config/locales/uk.yml | 9 ++++++ 27 files changed, 131 insertions(+), 18 deletions(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 17fb3eb2b84..690a35990e4 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -600,6 +600,7 @@ "notification.label.mention": "Zmínka", "notification.label.private_mention": "Soukromá zmínka", "notification.label.private_reply": "Privátní odpověď", + "notification.label.quote": "{name} citovali váš příspěvek", "notification.label.reply": "Odpověď", "notification.mention": "Zmínka", "notification.mentioned_you": "{name} vás zmínil", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Zmínky:", "notifications.column_settings.poll": "Výsledky anket:", "notifications.column_settings.push": "Push oznámení", + "notifications.column_settings.quote": "Citace:", "notifications.column_settings.reblog": "Boosty:", "notifications.column_settings.show": "Zobrazit ve sloupci", "notifications.column_settings.sound": "Přehrát zvuk", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 645808695fc..b401c13d32b 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Omtaler:", "notifications.column_settings.poll": "Afstemningsresultater:", "notifications.column_settings.push": "Push-notifikationer", + "notifications.column_settings.quote": "Citater:", "notifications.column_settings.reblog": "Fremhævelser:", "notifications.column_settings.show": "Vis i kolonne", "notifications.column_settings.sound": "Afspil lyd", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 90251442849..0ada8ba8c13 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -600,6 +600,7 @@ "notification.label.mention": "Erwähnung", "notification.label.private_mention": "Private Erwähnung", "notification.label.private_reply": "Private Antwort", + "notification.label.quote": "{name} zitierte deinen Beitrag", "notification.label.reply": "Antwort", "notification.mention": "Erwähnung", "notification.mentioned_you": "{name} erwähnte dich", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Erwähnungen:", "notifications.column_settings.poll": "Umfrageergebnisse:", "notifications.column_settings.push": "Push-Benachrichtigungen", + "notifications.column_settings.quote": "Zitate:", "notifications.column_settings.reblog": "Geteilte Beiträge:", "notifications.column_settings.show": "In dieser Spalte anzeigen", "notifications.column_settings.sound": "Ton abspielen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index a2dfbb8bd38..58de44bce39 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Επισημάνσεις:", "notifications.column_settings.poll": "Αποτελέσματα δημοσκόπησης:", "notifications.column_settings.push": "Ειδοποιήσεις Push", + "notifications.column_settings.quote": "Παραθέσεις:", "notifications.column_settings.reblog": "Ενισχύσεις:", "notifications.column_settings.show": "Εμφάνισε σε στήλη", "notifications.column_settings.sound": "Αναπαραγωγή ήχου", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 75460017daf..41051d493a6 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Menciones:", "notifications.column_settings.poll": "Resultados de la encuesta:", "notifications.column_settings.push": "Notificaciones push", + "notifications.column_settings.quote": "Citas:", "notifications.column_settings.reblog": "Adhesiones:", "notifications.column_settings.show": "Mostrar en columna", "notifications.column_settings.sound": "Reproducir sonido", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index d0c2290717a..80a0138d93c 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Menciones:", "notifications.column_settings.poll": "Resultados de la votación:", "notifications.column_settings.push": "Notificaciones push", + "notifications.column_settings.quote": "Citas:", "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar en columna", "notifications.column_settings.sound": "Reproducir sonido", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 62c296ad52f..18ba22e95a5 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Menciones:", "notifications.column_settings.poll": "Resultados de la votación:", "notifications.column_settings.push": "Notificaciones push", + "notifications.column_settings.quote": "Citas:", "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar en columna", "notifications.column_settings.sound": "Reproducir sonido", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 9e52dcb069f..2f7c13bfaf8 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -600,6 +600,7 @@ "notification.label.mention": "Maininta", "notification.label.private_mention": "Yksityismaininta", "notification.label.private_reply": "Yksityinen vastaus", + "notification.label.quote": "{name} lainasi julkaisuasi", "notification.label.reply": "Vastaus", "notification.mention": "Maininta", "notification.mentioned_you": "{name} mainitsi sinut", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Maininnat:", "notifications.column_settings.poll": "Äänestystulokset:", "notifications.column_settings.push": "Puskuilmoitukset", + "notifications.column_settings.quote": "Lainaukset:", "notifications.column_settings.reblog": "Tehostukset:", "notifications.column_settings.show": "Näytä sarakkeessa", "notifications.column_settings.sound": "Äänimerkki", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 4cd1f9718f6..c1da87693e3 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -600,6 +600,7 @@ "notification.label.mention": "Mención", "notification.label.private_mention": "Mención privada", "notification.label.private_reply": "Resposta privada", + "notification.label.quote": "{name} citou a túa publicación", "notification.label.reply": "Resposta", "notification.mention": "Mención", "notification.mentioned_you": "{name} mencionoute", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e9a755fab72..725130e75b6 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -600,6 +600,7 @@ "notification.label.mention": "אזכור", "notification.label.private_mention": "אזכור פרטי", "notification.label.private_reply": "תשובה בפרטי", + "notification.label.quote": "{name} ציטט.ה את הודעתך", "notification.label.reply": "תשובה", "notification.mention": "אזכור", "notification.mentioned_you": "אוזכרת על ידי {name}", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 54dcc70a093..b27db1c57ad 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -600,6 +600,7 @@ "notification.label.mention": "Minnst á", "notification.label.private_mention": "Einkaspjall", "notification.label.private_reply": "Einkasvar", + "notification.label.quote": "{name} vitnaði í færsluna þína", "notification.label.reply": "Svara", "notification.mention": "Minnst á", "notification.mentioned_you": "{name} minntist á þig", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Tilvísanir:", "notifications.column_settings.poll": "Niðurstöður könnunar:", "notifications.column_settings.push": "Ýti-tilkynningar", + "notifications.column_settings.quote": "Tilvitnanir:", "notifications.column_settings.reblog": "Endurbirtingar:", "notifications.column_settings.show": "Sýna í dálki", "notifications.column_settings.sound": "Spila hljóð", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 9edc9ae567f..072ee445b5f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Menzioni:", "notifications.column_settings.poll": "Risultati del sondaggio:", "notifications.column_settings.push": "Notifiche push", + "notifications.column_settings.quote": "Citazioni:", "notifications.column_settings.reblog": "Reblog:", "notifications.column_settings.show": "Mostra nella colonna", "notifications.column_settings.sound": "Riproduci suono", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 52305fec2b6..b0bee442cf3 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -34,6 +34,7 @@ "account.followers": "Imeḍfaren", "account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.", "account.followers_counter": "{count, plural, one {{counter} n umḍfar} other {{counter} n yimeḍfaren}}", + "account.followers_you_know_counter": "Tessneḍ {counter}", "account.following": "Yeṭṭafaṛ", "account.following_counter": "{count, plural, one {{counter} yettwaḍfaren} other {{counter} yettwaḍfaren}}", "account.follows.empty": "Ar tura, amseqdac-agi ur yeṭṭafaṛ yiwen.", @@ -51,10 +52,12 @@ "account.mute_notifications_short": "Susem ilɣa", "account.mute_short": "Sgugem", "account.muted": "Yettwasgugem", + "account.mutual": "Temmeḍfaṛem", "account.no_bio": "Ulac aglam i d-yettunefken.", "account.open_original_page": "Ldi asebter anasli", "account.posts": "Tisuffaɣ", "account.posts_with_replies": "Tisuffaɣ d tririyin", + "account.remove_from_followers": "Kkes {name} seg ineḍfaren", "account.report": "Cetki ɣef @{name}", "account.requested": "Di laɛḍil ad yettwaqbel. Ssit i wakken ad yefsex usuter n uḍfar", "account.requested_follow": "{name} yessuter ad k·m-yeḍfer", @@ -85,6 +88,7 @@ "annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas", "annual_report.summary.most_used_hashtag.none": "Ula yiwen", "annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin", + "annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!", "audio.hide": "Ffer amesli", "block_modal.show_less": "Ssken-d drus", "block_modal.show_more": "Ssken-d ugar", @@ -352,6 +356,8 @@ "keyboard_shortcuts.toot": "i wakken attebdud tajewwaqt tamaynut", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "i tulin ɣer d asawen n tebdart", + "learn_more_link.got_it": "Gziɣ-t", + "learn_more_link.learn_more": "Issin ugar", "lightbox.close": "Mdel", "lightbox.next": "Ɣer zdat", "lightbox.previous": "Ɣer deffir", @@ -417,6 +423,8 @@ "notification.admin.sign_up": "Ijerred {name}", "notification.annual_report.view": "Wali #Wrapstodon", "notification.favourite": "{name} yesmenyaf addad-ik·im", + "notification.favourite_pm": "{name} yesmenyef abdar-ik·im uslig", + "notification.favourite_pm.name_and_others_with_link": "{name} akked {count, plural, one {# nnayeḍ} other {# nniḍen}} rnan abdar-ik·im uslig ar ismenyafen-nsen", "notification.follow": "iṭṭafar-ik·em-id {name}", "notification.follow.name_and_others": "{name} akked {count, plural, one {# nniḍen} other {# nniḍen}} iḍfeṛ-k·m-id", "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 22cae4b455f..3ae1fd3f24e 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Vermeldingen:", "notifications.column_settings.poll": "Peilingresultaten:", "notifications.column_settings.push": "Pushmeldingen", + "notifications.column_settings.quote": "Citaten:", "notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.show": "In kolom tonen", "notifications.column_settings.sound": "Geluid afspelen", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 8b0f2cbddf2..cf2e828a45e 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -498,6 +498,8 @@ "keyboard_shortcuts.translate": "traduzir uma publicação", "keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa", "keyboard_shortcuts.up": "mover para cima na lista", + "learn_more_link.got_it": "Entendido", + "learn_more_link.learn_more": "Saber mais", "lightbox.close": "Fechar", "lightbox.next": "Próximo", "lightbox.previous": "Anterior", @@ -598,6 +600,7 @@ "notification.label.mention": "Menção", "notification.label.private_mention": "Menção privada", "notification.label.private_reply": "Resposta privada", + "notification.label.quote": "{name} citou a sua publicação", "notification.label.reply": "Resposta", "notification.mention": "Menção", "notification.mentioned_you": "{name} mencionou-te", @@ -655,6 +658,7 @@ "notifications.column_settings.mention": "Menções:", "notifications.column_settings.poll": "Resultados da sondagem:", "notifications.column_settings.push": "Notificações \"push\"", + "notifications.column_settings.quote": "Citações:", "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostrar na coluna", "notifications.column_settings.sound": "Reproduzir som", @@ -873,6 +877,11 @@ "status.open": "Expandir esta publicação", "status.pin": "Afixar no perfil", "status.quote_error.filtered": "Oculto devido a um dos seus filtros", + "status.quote_error.not_available": "Publicação indisponível", + "status.quote_error.pending_approval": "Publicação pendente", + "status.quote_error.pending_approval_popout.body": "As citações partilhadas no Fediverso podem demorar algum tempo a ser exibidas, uma vez que diferentes servidores têm protocolos diferentes.", + "status.quote_error.pending_approval_popout.title": "Citação pendente? Mantenha a calma", + "status.quote_post_author": "Citou uma publicação de @{name}", "status.read_more": "Ler mais", "status.reblog": "Impulsionar", "status.reblog_private": "Impulsionar com a visibilidade original", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 38401f18fa5..f33558e6e87 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -321,6 +321,7 @@ "explore.trending_links": "Новини", "explore.trending_statuses": "Дописи", "explore.trending_tags": "Хештеґи", + "featured_carousel.next": "Далі", "filter_modal.added.context_mismatch_explanation": "Ця категорія фільтра не застосовується до контексту, в якому ви отримали доступ до цього допису. Якщо ви хочете, щоб дописи також фільтрувалися за цим контекстом, вам доведеться редагувати фільтр.", "filter_modal.added.context_mismatch_title": "Невідповідність контексту!", "filter_modal.added.expired_explanation": "Категорія цього фільтра застаріла, Вам потрібно змінити дату закінчення терміну дії, щоб застосувати її.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index fc67e1d7ae7..2d7b6bbf312 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Lượt nhắc đến:", "notifications.column_settings.poll": "Kết quả vốt:", "notifications.column_settings.push": "Thông báo đẩy", + "notifications.column_settings.quote": "Trích dẫn:", "notifications.column_settings.reblog": "Lượt đăng lại:", "notifications.column_settings.show": "Báo trên thanh bên", "notifications.column_settings.sound": "Kèm âm báo", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index cafd061818e..f917ad623cc 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "提及:", "notifications.column_settings.poll": "投票結果:", "notifications.column_settings.push": "推播通知", + "notifications.column_settings.quote": "引用嘟文:", "notifications.column_settings.reblog": "轉嘟:", "notifications.column_settings.show": "於欄位中顯示", "notifications.column_settings.sound": "播放音效", diff --git a/config/locales/da.yml b/config/locales/da.yml index 85de82443e9..20e3173c5ab 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1428,7 +1428,7 @@ da: keywords: Nøgleord statuses: Individuelle indlæg statuses_hint_html: Dette filter gælder for udvalgte, individuelle indlæg, uanset om de matcher nøgleordene nedenfor. Gennemgå eller fjern indlæg fra filteret. - title: Redigere filter + title: Rediger filter errors: deprecated_api_multiple_keywords: Disse parametre kan ikke ændres fra denne applikation, da de gælder for flere end ét filternøgleord. Brug en nyere applikation eller webgrænsefladen. invalid_context: Ingen eller ugyldig kontekst angivet @@ -1748,7 +1748,7 @@ da: privacy: Privatliv privacy_hint_html: Styr, hvor meget du vil afsløre til gavn for andre. Folk opdager interessante profiler og apps ved at gennemse andres følgere og se, hvilke apps de sender fra, men du foretrækker måske at holde det skjult. reach: Rækkevidde - reach_hint_html: Indstil om du vil blive opdaget og fulgt af nye mennesker. Ønsker du, at dine indlæg skal vises på Udforsk-siden? Ønsker du, at andre skal se dig i deres følg-anbefalinger? Ønsker du at acceptere alle nye følgere automatisk, eller vil du have detaljeret kontrol over hver og en? + reach_hint_html: Indstil, om du vil opdages og følges af nye personer. Vil du have, at dine indlæg skal vises på Udforsk-siden? Vil du have, at andre skal kunne se dig i deres følg-anbefalinger? Vil du acceptere alle nye følgere automatisk eller have detaljeret kontrol over hver enkelt? search: Søgning search_hint_html: Indstil hvordan du vil findes. Ønsker du, at folk skal finde dig gennem hvad du har skrevet offentligt? Vil du have folk udenfor Mastodon til at finde din profil, når de søger på nettet? Vær opmærksom på, at det ikke kan garanteres at dine offentlige indlæg er udelukket fra alle søgemaskiner. title: Fortrolighed og rækkevidde diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 96f69dcbdc4..4685887a968 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -485,12 +485,20 @@ kab: title: Ihacṭagen inezzaɣ trending_rank: 'Anezzuɣ #%{rank}' trending: Inezzaɣ + username_blocks: + add_new: Rnu amaynut + comparison: + contains: Igber + new: + create: Rnu alugen + title: Rnu alugen n useqdac amaynut warning_presets: add_new: Rnu amaynut delete: Kkes webhooks: delete: Kkes enable: Rmed + enabled: D urmid admin_mailer: new_report: body: "%{reporter} yettwazen ɣef %{target}" @@ -623,6 +631,7 @@ kab: other: Ayen nniḍen emoji_styles: auto: Awurman + twemoji: Twemoji errors: '404': Asebter i tettnadiḍ ulac-it da. '500': @@ -858,6 +867,8 @@ kab: other: "%{count} n tbidyutin" edited_at_html: Tettwaẓreg ass n %{date} quote_policies: + followers: Ala wid i k·m-yeṭṭafaṛen + nobody: Ula yiwen public: Yal yiwen title: '%{name} : "%{quote}"' visibilities: @@ -904,6 +915,8 @@ kab: user_mailer: appeal_approved: action: Iɣewwaṛen n umiḍan + suspicious_sign_in: + change_password: snifel awal-ik·im n uɛeddi terms_of_service_changed: sign_off: Agraw n %{domain} warning: diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index e8c357ee0fa..3be630dbcdc 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -190,6 +190,7 @@ pt-PT: create_relay: Criar retransmissor create_unavailable_domain: Criar domínio indisponível create_user_role: Criar função + create_username_block: Criar Regra de Nome de Utilizador demote_user: Despromover utilizador destroy_announcement: Eliminar mensagem de manutenção destroy_canonical_email_block: Eliminar bloqueio de e-mail @@ -203,6 +204,7 @@ pt-PT: destroy_status: Eliminar publicação destroy_unavailable_domain: Eliminar domínio indisponível destroy_user_role: Eliminar função + destroy_username_block: Eliminar Regra de Nome de Utilizador disable_2fa_user: Desativar 2FA disable_custom_emoji: Desativar emoji personalizado disable_relay: Desativar retransmissor @@ -237,6 +239,7 @@ pt-PT: update_report: Atualizar denúncia update_status: Atualizar publicação update_user_role: Atualizar função + update_username_block: Atualizar Regra de Nome de Utilizador actions: approve_appeal_html: "%{name} aprovou a contestação da decisão de moderação de %{target}" approve_user_html: "%{name} aprovou a inscrição de %{target}" @@ -255,6 +258,7 @@ pt-PT: create_relay_html: "%{name} criou o retransmissor %{target}" create_unavailable_domain_html: "%{name} parou as entregas ao domínio %{target}" create_user_role_html: "%{name} criou a função %{target}" + create_username_block_html: "%{name} adicionou regra para nomes de utilizadores que contêm %{target}" 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}" @@ -268,6 +272,7 @@ pt-PT: destroy_status_html: "%{name} removeu a publicação de %{target}" destroy_unavailable_domain_html: "%{name} retomou as entregas ao domínio %{target}" destroy_user_role_html: "%{name} eliminou a função %{target}" + destroy_username_block_html: "%{name} removeu regra para nomes de utilizadores que contêm %{target}" disable_2fa_user_html: "%{name} desativou o requerimento de autenticação em dois passos para o utilizador %{target}" disable_custom_emoji_html: "%{name} desativou o emoji %{target}" disable_relay_html: "%{name} desativou o retransmissor %{target}" @@ -302,6 +307,7 @@ pt-PT: update_report_html: "%{name} atualizou a denúncia %{target}" update_status_html: "%{name} atualizou a publicação de %{target}" update_user_role_html: "%{name} alterou a função %{target}" + update_username_block_html: "%{name} atualizou regra para nomes de utilizadores que contêm %{target}" deleted_account: conta eliminada empty: Não foram encontrados registos. filter_by_action: Filtrar por ação @@ -1085,6 +1091,25 @@ pt-PT: other: Utilizada por %{count} pessoas na última semana title: Recomendações e destaques trending: Em destaque + username_blocks: + add_new: Adicionar novo + block_registrations: Bloquear inscrições + comparison: + contains: Contém + equals: Igual a + contains_html: Contém %{string} + created_msg: Regra de nome de utilizador criada com sucesso + delete: Eliminar + edit: + title: Editar regra de nome de utilizador + matches_exactly_html: Igual a %{string} + new: + create: Criar regra + title: Criar nova regra de utilizador + no_username_block_selected: Não foi alterada nenhuma regra de nome de utilizador, pois nenhuma foi selecionada + not_permitted: Não permitido + title: Regras de nome de utilizador + updated_msg: Regra de nome de utilizador atualizada com sucesso warning_presets: add_new: Adicionar novo delete: Eliminar @@ -1662,6 +1687,10 @@ pt-PT: title: Nova menção poll: subject: A sondagem de %{name} terminou + quote: + body: 'A sua publicação foi citada por %{name}:' + subject: "%{name} citou a sua publicação" + title: Nova citação reblog: body: 'A tua publicação foi impulsionada por %{name}:' subject: "%{name} impulsionou a tua publicação" @@ -1880,6 +1909,8 @@ pt-PT: ownership: Não podem ser fixadas publicações de outras pessoas reblog: Não é possível fixar um impulso quote_policies: + followers: Apenas os seus seguidores + nobody: Ninguém public: Todos title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 17ba89bef09..a25ac88c547 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1922,24 +1922,29 @@ ru: other: 'содержались запрещённые хэштеги: %{tags}' edited_at_html: 'Дата последнего изменения: %{date}' errors: - in_reply_not_found: Пост, на который вы пытаетесь ответить, не существует или удалён. - over_character_limit: превышен лимит символов (%{max}) + in_reply_not_found: Пост, на который вы собирались ответить, был удалён или не существует. + quoted_status_not_found: Пост, который вы собирались процитировать, был удалён или не существует. + over_character_limit: превышает максимально допустимую длину (%{max} символов) pin_errors: - direct: Сообщения, видимые только упомянутым пользователям, не могут быть закреплены - limit: Вы закрепили максимально возможное число постов + direct: Нельзя закрепить пост, который доступен только тем, кто был упомянут в нём + limit: Вы достигли максимального количества постов, которые можно закрепить в профиле ownership: Нельзя закрепить чужой пост - reblog: Нельзя закрепить продвинутый пост - title: '%{name}: "%{quote}"' + reblog: Нельзя закрепить продвижение + quote_policies: + followers: Только ваши подписчики + nobody: Никто + public: Кто угодно + title: "%{name}: «%{quote}»" visibilities: - direct: Адресованный - private: Для подписчиков - private_long: Показывать только подписчикам - public: Для всех - public_long: Показывать всем - unlisted: Скрывать из лент - unlisted_long: Показывать всем, но не отображать в публичных лентах + direct: Личное упоминание + private: Только для подписчиков + private_long: Доступен только вашим подписчикам + public: Публичный + public_long: Доступен кому угодно + unlisted: Скрытый + unlisted_long: Доступен кому угодно, но не отображается в публичных лентах statuses_cleanup: - enabled: Автоматически удалять устаревшие посты + enabled: Автоматически удалять старые посты enabled_hint: Автоматически удаляет ваши посты после того, как они достигли определённого возрастного порога, за некоторыми исключениями ниже. exceptions: Исключения explanation: Из-за того, что удаление постов — это ресурсоёмкий процесс, оно производится медленно со временем, когда сервер менее всего загружен. По этой причине, посты могут удаляться не сразу, а спустя определённое время, по достижению возрастного порога. diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index f979e683233..ee24975b5db 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -56,6 +56,7 @@ ca: scopes: API permeses per a accedir a l'aplicació. Si selecciones un àmbit de nivell superior, no cal que en seleccionis un d'individual. setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts) setting_always_send_emails: Normalment, no s'enviarà cap notificació per correu electrònic mentre facis servir Mastodon + setting_default_quote_policy: Aquesta configuració només tindrà efecte en les publicacions creades amb la següent versió de Mastodon, però podeu seleccionar-la en preparació. setting_default_sensitive: El contingut sensible està ocult per defecte i es pot mostrar fent-hi clic setting_display_media_default: Amaga el contingut gràfic marcat com a sensible setting_display_media_hide_all: Oculta sempre tot el contingut multimèdia diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 58f6a129bb1..296ce49131c 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -10,7 +10,7 @@ da: indexable: Dine offentlige indlæg vil kunne vises i Mastodon-søgeresultater. Folk, som har interageret med dem, vil kunne finde dem uanset. note: 'Du kan @omtale andre personer eller #hashtags.' show_collections: Folk vil kunne se, hvem du følger, og hvem der følger dig. Personer, som du følger, vil kunne se, at du følger dem. - unlocked: Man vil kunne følges af folk uden først at godkende dem. Ønsker man at gennemgå Følg-anmodninger og individuelt acceptere/afvise nye følgere, så fjern markeringen. + unlocked: Folk vil kunne følge dig uden at anmode om godkendelse. Fjern markeringen, hvis du vil gennemgå anmodninger om at følge, og vælge, om du vil acceptere eller afvise nye følgere. account_alias: acct: Angiv brugernavn@domain for den konto, hvorfra du vil flytte account_migration: @@ -329,7 +329,7 @@ da: follow_request: Nogen anmodede om at følge dig mention: Nogen omtalte dig pending_account: Ny konto kræver gennemgang - quote: Nogle citerede dig + quote: Nogen citerede dig reblog: Nogen fremhævede dit indlæg report: Ny anmeldelse indsendt software_updates: diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index c0ff7e598e9..d1a288e2139 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -27,6 +27,8 @@ kab: username: Tzemreḍ ad tesqedceḍ isekkilen, uṭṭunen akked yijerriden n wadda featured_tag: name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :' + form_challenge: + current_password: Tkecmeḍ ɣer temnaḍt taɣellsant imports: data: Afaylu CSV id yusan seg uqeddac-nniḍen n Maṣṭudun invite_request: @@ -143,7 +145,12 @@ kab: name: Ahacṭag terms_of_service: text: Tiwtilin n useqdec + terms_of_service_generator: + domain: Taɣult user: + date_of_birth_1i: Ass + date_of_birth_2i: Ayyur + date_of_birth_3i: Aseggas role: Tamlilt time_zone: Tamnaḍt tasragant user_role: diff --git a/config/locales/simple_form.pt-PT.yml b/config/locales/simple_form.pt-PT.yml index 0e65d82fd83..adc58a7aed0 100644 --- a/config/locales/simple_form.pt-PT.yml +++ b/config/locales/simple_form.pt-PT.yml @@ -56,6 +56,7 @@ pt-PT: scopes: Quais as API a que a aplicação terá permissão para aceder. Se selecionar um âmbito de nível superior, não precisa de selecionar âmbitos individuais. setting_aggregate_reblogs: Não mostrar os novos impulsos para publicações que tenham sido recentemente impulsionadas (apenas afeta os impulsos recentemente recebidos) setting_always_send_emails: Normalmente as notificações por e-mail não serão enviadas quando estiver a utilizar ativamente o Mastodon + setting_default_quote_policy: Esta configuração só terá efeito nas publicações criadas com a próxima versão do Mastodon, mas pode desde já selecionar a sua preferência. setting_default_sensitive: Os multimédia sensíveis são ocultados por predefinição e podem ser revelados com um clique/toque setting_display_media_default: Esconder multimédia marcada como sensível setting_display_media_hide_all: Esconder sempre toda a multimédia @@ -159,6 +160,10 @@ pt-PT: name: Nome público da função, se esta estiver definida para ser apresentada com um emblema permissions_as_keys: Utilizadores com esta função terão acesso a... position: Funções mais altas decidem a resolução de conflitos em certas situações. Certas ações só podem ser executadas com certas funções com uma menor prioridade + username_block: + allow_with_approval: Em vez de impedir totalmente a inscrição, as inscrições correspondentes exigirão a sua aprovação + comparison: Tenha em atenção o Problema de Scunthorpe ao bloquear correspondências parciais + username: Terá correspondência independentemente das maiúsculas e minúsculas e de homógrafos comuns, como "4" para "a" ou "3" para "e" webhook: events: Selecione os eventos a enviar template: Componha o seu próprio conteúdo JSON utilizando a interpolação de variáveis. Deixar em branco para o JSON predefinido. @@ -324,6 +329,7 @@ pt-PT: follow_request: Alguém pediu para ser seu seguidor mention: Alguém o mencionou pending_account: Uma nova conta aguarda aprovação + quote: Alguém o citou reblog: Alguém impulsionou uma publicação sua report: Uma nova denúncia foi submetida software_updates: @@ -370,6 +376,10 @@ pt-PT: name: Nome permissions_as_keys: Permissões position: Prioridade + username_block: + allow_with_approval: Permitir inscrições com aprovação + comparison: Método de comparação + username: Palavra a corresponder webhook: events: Eventos ativados template: Modelo de conteúdo diff --git a/config/locales/uk.yml b/config/locales/uk.yml index ed0ce8d7b55..721655d9a43 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -496,6 +496,15 @@ uk: new: title: Імпорт блокувань домену no_file: Файл не вибрано + fasp: + debug: + callbacks: + delete: Видалити + providers: + delete: Видалити + registrations: + confirm: Підтвердити + save: Зберегти follow_recommendations: description_html: "Слідувати рекомендаціям та допомогти новим користувачам швидко знайти цікавий вміст. Коли користувачі не взаємодіяли з іншими людьми достатньо, щоб сформувати персоналізовані рекомендації, радимо замість цього вказувати ці облікові записи. Вони щоденно переобчислюються з масиву облікових записів з найбільшою кількістю недавніх взаємодій і найбільшою кількістю місцевих підписників розраховується для цієї мови." language: Для мови From c8f263c419368ccc469e32a462bb308e94b2ae3c Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 6 Aug 2025 13:31:36 +0200 Subject: [PATCH 234/660] Export interaction policies for local posts over ActivityPub (#35697) --- .../activitypub/note_serializer.rb | 21 ++++++++++++++++++- .../activitypub/note_serializer_spec.rb | 15 +++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 972f146bafc..05e3cf1bd21 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -3,7 +3,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer include FormattingHelper - context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :quotes + context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :quotes, :interaction_policies attributes :id, :type, :summary, :in_reply_to, :published, :url, @@ -35,6 +35,8 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer attribute :quote, key: :quote_uri, if: :quote? attribute :quote_authorization, if: :quote_authorization? + attribute :interaction_policy, if: -> { Mastodon::Feature.outgoing_quotes_enabled? } + def id ActivityPub::TagManager.instance.uri_for(object) end @@ -216,6 +218,23 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer ActivityPub::TagManager.instance.approval_uri_for(object.quote) end + def interaction_policy + approved_uris = [] + + # On outgoing posts, only automatic approval is supported + policy = object.quote_approval_policy >> 16 + approved_uris << ActivityPub::TagManager::COLLECTIONS[:public] if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public]) + approved_uris << ActivityPub::TagManager.instance.followers_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers]) + approved_uris << ActivityPub::TagManager.instance.following_uri_for(object.account) if policy.anybits?(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followed]) + approved_uris << ActivityPub::TagManager.instance.uri_for(object.account) if approved_uris.empty? + + { + canQuote: { + automaticApproval: approved_uris, + }, + } + end + class MediaAttachmentSerializer < ActivityPub::Serializer context_extensions :blurhash, :focal_point diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb index 9c898e52121..d5d02a0d495 100644 --- a/spec/serializers/activitypub/note_serializer_spec.rb +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -56,4 +56,19 @@ RSpec.describe ActivityPub::NoteSerializer do }) end end + + context 'with a quote policy', feature: :outgoing_quotes do + let(:parent) { Fabricate(:status, quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) } + + it 'has the expected shape' do + expect(subject).to include({ + 'type' => 'Note', + 'interactionPolicy' => a_hash_including( + 'canQuote' => a_hash_including( + 'automaticApproval' => [ActivityPub::TagManager.instance.followers_uri_for(parent.account)] + ) + ), + }) + end + end end From 55a98580aacecb2c4ef69fdf8720a3189c250fdf Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 6 Aug 2025 13:52:56 +0200 Subject: [PATCH 235/660] Add UI for revoking quote posts (#35689) --- .../mastodon/actions/interactions_typed.ts | 22 ++++++++- app/javascript/mastodon/api/interactions.ts | 5 ++ .../mastodon/components/status_action_bar.jsx | 24 ++++++++-- .../mastodon/containers/status_container.jsx | 4 ++ .../features/status/components/action_bar.jsx | 23 +++++++-- .../mastodon/features/status/index.jsx | 7 +++ .../components/confirmation_modals/index.ts | 1 + .../confirmation_modals/revoke_quote.tsx | 48 +++++++++++++++++++ .../features/ui/components/modal_root.jsx | 2 + app/javascript/mastodon/locales/en.json | 4 ++ 10 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/components/confirmation_modals/revoke_quote.tsx diff --git a/app/javascript/mastodon/actions/interactions_typed.ts b/app/javascript/mastodon/actions/interactions_typed.ts index f58faffa86d..832ea189104 100644 --- a/app/javascript/mastodon/actions/interactions_typed.ts +++ b/app/javascript/mastodon/actions/interactions_typed.ts @@ -1,4 +1,8 @@ -import { apiReblog, apiUnreblog } from 'mastodon/api/interactions'; +import { + apiReblog, + apiUnreblog, + apiRevokeQuote, +} from 'mastodon/api/interactions'; import type { StatusVisibility } from 'mastodon/models/status'; import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; @@ -33,3 +37,19 @@ export const unreblog = createDataLoadingThunk( return discardLoadData; }, ); + +export const revokeQuote = createDataLoadingThunk( + 'status/revoke_quote', + ({ + statusId, + quotedStatusId, + }: { + statusId: string; + quotedStatusId: string; + }) => apiRevokeQuote(quotedStatusId, statusId), + (data, { dispatch, discardLoadData }) => { + dispatch(importFetchedStatus(data)); + + return discardLoadData; + }, +); diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts index 118b5f06d20..5ffa5d15076 100644 --- a/app/javascript/mastodon/api/interactions.ts +++ b/app/javascript/mastodon/api/interactions.ts @@ -8,3 +8,8 @@ export const apiReblog = (statusId: string, visibility: StatusVisibility) => export const apiUnreblog = (statusId: string) => apiRequestPost(`v1/statuses/${statusId}/unreblog`); + +export const apiRevokeQuote = (quotedStatusId: string, statusId: string) => + apiRequestPost( + `v1/statuses/${quotedStatusId}/quotes/${statusId}/revoke`, + ); diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index ec5a9780cb7..663fc53407c 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -67,21 +67,28 @@ const messages = defineMessages({ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, filter: { id: 'status.filter', defaultMessage: 'Filter this post' }, openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, + revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' }, }); -const mapStateToProps = (state, { status }) => ({ - relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), -}); +const mapStateToProps = (state, { status }) => { + const quotedStatusId = status.getIn(['quote', 'quoted_status']); + return ({ + relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), + quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null, + }); +}; class StatusActionBar extends ImmutablePureComponent { static propTypes = { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, + quotedAccountId: ImmutablePropTypes.string, onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onRevokeQuote: PropTypes.func, onDirect: PropTypes.func, onMention: PropTypes.func, onMute: PropTypes.func, @@ -110,6 +117,7 @@ class StatusActionBar extends ImmutablePureComponent { updateOnProps = [ 'status', 'relationship', + 'quotedAccountId', 'withDismiss', ]; @@ -190,6 +198,10 @@ class StatusActionBar extends ImmutablePureComponent { } }; + handleRevokeQuoteClick = () => { + this.props.onRevokeQuote(this.props.status); + } + handleBlockClick = () => { const { status, relationship, onBlock, onUnblock } = this.props; const account = status.get('account'); @@ -241,7 +253,7 @@ class StatusActionBar extends ImmutablePureComponent { }; render () { - const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; + const { status, relationship, quotedAccountId, intl, withDismiss, withCounters, scrollKey } = this.props; const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -291,6 +303,10 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick }); menu.push(null); + if (quotedAccountId === me) { + menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true }); + } + if (relationship && relationship.get('muting')) { menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick }); } else { diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 0fb5f255607..f3bc8fe5ff3 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -111,6 +111,10 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ } }, + onRevokeQuote (status) { + dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }})); + }, + onEdit (status) { dispatch((_, getState) => { let state = getState(); diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 81f8163fffe..5d6625fc103 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -61,22 +61,29 @@ const messages = defineMessages({ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, + revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' }, }); -const mapStateToProps = (state, { status }) => ({ - relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), -}); +const mapStateToProps = (state, { status }) => { + const quotedStatusId = status.getIn(['quote', 'quoted_status']); + return ({ + relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]), + quotedAccountId: quotedStatusId ? state.getIn(['statuses', quotedStatusId, 'account']) : null, + }); +}; class ActionBar extends PureComponent { static propTypes = { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, + quotedAccountId: ImmutablePropTypes.string, onReply: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired, onBookmark: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, + onRevokeQuote: PropTypes.func, onEdit: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -113,6 +120,10 @@ class ActionBar extends PureComponent { this.props.onDelete(this.props.status); }; + handleRevokeQuoteClick = () => { + this.props.onRevokeQuote(this.props.status); + } + handleRedraftClick = () => { this.props.onDelete(this.props.status, true); }; @@ -193,7 +204,7 @@ class ActionBar extends PureComponent { }; render () { - const { status, relationship, intl } = this.props; + const { status, relationship, quotedAccountId, intl } = this.props; const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -237,6 +248,10 @@ class ActionBar extends PureComponent { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); menu.push(null); + if (quotedAccountId === me) { + menu.push({ text: intl.formatMessage(messages.revokeQuote, { name: account.get('username') }), action: this.handleRevokeQuoteClick, dangerous: true }); + } + if (relationship && relationship.get('muting')) { menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick }); } else { diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 4a714fa2c59..7bdcdb89711 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -259,6 +259,12 @@ class Status extends ImmutablePureComponent { } }; + handleRevokeQuoteClick = (status) => { + const { dispatch } = this.props; + + dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }})); + }; + handleEditClick = (status) => { const { dispatch, askReplyConfirmation } = this.props; @@ -635,6 +641,7 @@ class Status extends ImmutablePureComponent { onReblog={this.handleReblogClick} onBookmark={this.handleBookmarkClick} onDelete={this.handleDeleteClick} + onRevokeQuote={this.handleRevokeQuoteClick} onEdit={this.handleEditClick} onDirect={this.handleDirectClick} onMention={this.handleMentionClick} diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts index 25ffb3b6291..139b6f8ba25 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/index.ts @@ -10,3 +10,4 @@ export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; export { ConfirmMissingAltTextModal } from './missing_alt_text'; +export { ConfirmRevokeQuoteModal } from './revoke_quote'; diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/revoke_quote.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/revoke_quote.tsx new file mode 100644 index 00000000000..83964aa5fe8 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/revoke_quote.tsx @@ -0,0 +1,48 @@ +import { useCallback } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import { revokeQuote } from 'mastodon/actions/interactions_typed'; +import { useAppDispatch } from 'mastodon/store'; + +import type { BaseConfirmationModalProps } from './confirmation_modal'; +import { ConfirmationModal } from './confirmation_modal'; + +const messages = defineMessages({ + revokeQuoteTitle: { + id: 'confirmations.revoke_quote.title', + defaultMessage: 'Remove post?', + }, + revokeQuoteMessage: { + id: 'confirmations.revoke_quote.message', + defaultMessage: 'This action cannot be undone.', + }, + revokeQuoteConfirm: { + id: 'confirmations.revoke_quote.confirm', + defaultMessage: 'Remove post', + }, +}); + +export const ConfirmRevokeQuoteModal: React.FC< + { + statusId: string; + quotedStatusId: string; + } & BaseConfirmationModalProps +> = ({ statusId, quotedStatusId, onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const onConfirm = useCallback(() => { + void dispatch(revokeQuote({ quotedStatusId, statusId })); + }, [dispatch, statusId, quotedStatusId]); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 4a98de0a31a..3b7a24faaf4 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -37,6 +37,7 @@ import { ConfirmLogOutModal, ConfirmFollowToListModal, ConfirmMissingAltTextModal, + ConfirmRevokeQuoteModal, } from './confirmation_modals'; import { ImageModal } from './image_modal'; import MediaModal from './media_modal'; @@ -59,6 +60,7 @@ export const MODAL_COMPONENTS = { 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), 'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), + 'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }), 'MUTE': MuteModal, 'BLOCK': BlockModal, 'DOMAIN_BLOCK': DomainBlockModal, diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index aa18fc39ce1..6b796f9f81d 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Remove follower", "confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?", "confirmations.remove_from_followers.title": "Remove follower?", + "confirmations.revoke_quote.confirm": "Remove post", + "confirmations.revoke_quote.message": "This action cannot be undone.", + "confirmations.revoke_quote.title": "Remove post?", "confirmations.unfollow.confirm": "Unfollow", "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", "confirmations.unfollow.title": "Unfollow user?", @@ -896,6 +899,7 @@ "status.reply": "Reply", "status.replyAll": "Reply to thread", "status.report": "Report @{name}", + "status.revoke_quote": "Remove my post from @{name}’s post", "status.sensitive_warning": "Sensitive content", "status.share": "Share", "status.show_less_all": "Show less for all", From 6e48322055cf09e1b133f6193e29661e486a1353 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 6 Aug 2025 08:19:43 -0400 Subject: [PATCH 236/660] Add spec for `CanonicalEmailBlock.matching_email` scope (#35692) --- spec/models/canonical_email_block_spec.rb | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/spec/models/canonical_email_block_spec.rb b/spec/models/canonical_email_block_spec.rb index c63483f968f..0b8dcfb9c19 100644 --- a/spec/models/canonical_email_block_spec.rb +++ b/spec/models/canonical_email_block_spec.rb @@ -3,6 +3,36 @@ require 'rails_helper' RSpec.describe CanonicalEmailBlock do + describe 'Associations' do + it { is_expected.to belong_to(:reference_account).class_name('Account').optional } + end + + describe 'Scopes' do + describe '.matching_email' do + subject { described_class.matching_email(email) } + + let!(:block) { Fabricate :canonical_email_block, email: 'test@example.com' } + + context 'when email is exact match' do + let(:email) { 'test@example.com' } + + it { is_expected.to contain_exactly(block) } + end + + context 'when email does not match' do + let(:email) { 'test@example.ORG' } + + it { is_expected.to be_empty } + end + + context 'when email is different but normalizes to same hash' do + let(:email) { 'te.st+more@EXAMPLE.com' } + + it { is_expected.to contain_exactly(block) } + end + end + end + describe '#email=' do let(:target_hash) { '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b' } From 9ec99ffef123e796c4ddbcdf0ed14ad06b76e209 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 6 Aug 2025 16:23:12 +0200 Subject: [PATCH 237/660] Add `quote_approval_policy` parameter when posting and editing statuses (#35699) --- app/controllers/api/v1/statuses_controller.rb | 22 +++++++++- app/services/post_status_service.rb | 2 + app/services/update_status_service.rb | 1 + spec/requests/api/v1/statuses_spec.rb | 41 ++++++++++++++++++- spec/services/post_status_service_spec.rb | 6 +++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 57977e14b8a..fdf1e7a4685 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -82,6 +82,7 @@ class Api::V1::StatusesController < Api::BaseController text: status_params[:status], thread: @thread, quoted_status: @quoted_status, + quote_approval_policy: quote_approval_policy, media_ids: status_params[:media_ids], sensitive: status_params[:sensitive], spoiler_text: status_params[:spoiler_text], @@ -113,7 +114,8 @@ class Api::V1::StatusesController < Api::BaseController sensitive: status_params[:sensitive], language: status_params[:language], spoiler_text: status_params[:spoiler_text], - poll: status_params[:poll] + poll: status_params[:poll], + quote_approval_policy: quote_approval_policy ) render json: @status, serializer: REST::StatusSerializer @@ -180,6 +182,7 @@ class Api::V1::StatusesController < Api::BaseController :status, :in_reply_to_id, :quoted_status_id, + :quote_approval_policy, :sensitive, :spoiler_text, :visibility, @@ -202,6 +205,23 @@ class Api::V1::StatusesController < Api::BaseController ) end + def quote_approval_policy + # TODO: handle `nil` separately + return nil unless Mastodon::Feature.outgoing_quotes_enabled? && status_params[:quote_approval_policy].present? + + case status_params[:quote_approval_policy] + when 'public' + Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 + when 'followers' + Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16 + when 'nobody' + 0 + else + # TODO: raise more useful message + raise ActiveRecord::RecordInvalid + end + end + def serializer_for_status @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 75116e2a266..103186b21e7 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -19,6 +19,7 @@ class PostStatusService < BaseService # @option [String] :text Message # @option [Status] :thread Optional status to reply to # @option [Status] :quoted_status Optional status to quote + # @option [String] :quote_approval_policy Approval policy for quotes, one of `public`, `followers` or `nobody` # @option [Boolean] :sensitive # @option [String] :visibility # @option [String] :spoiler_text @@ -215,6 +216,7 @@ class PostStatusService < BaseService language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale), application: @options[:application], rate_limit: @options[:with_rate_limit], + quote_approval_policy: @options[:quote_approval_policy], }.compact end diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 7837d37c959..4b871211a46 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -115,6 +115,7 @@ class UpdateStatusService < BaseService @status.spoiler_text = @options[:spoiler_text] || '' if @options.key?(:spoiler_text) @status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text) @status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale) + @status.quote_approval_policy = @options[:quote_approval_policy] if @options[:quote_approval_policy].present? # We raise here to rollback the entire transaction raise NoChangesSubmittedError unless significant_changes? diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index ac15ae24623..ba18623302e 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -158,6 +158,31 @@ RSpec.describe '/api/v1/statuses' do end end + context 'with a quote policy', feature: :outgoing_quotes do + let(:quoted_status) { Fabricate(:status, account: user.account) } + let(:params) do + { + status: 'Hello world, this is a self-quote', + quote_approval_policy: 'followers', + } + end + + it 'returns post with appropriate quote policy, as well as rate limit headers', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body[:quote_approval]).to include({ + automatic: ['followers'], + manual: [], + current_user: 'automatic', + }) + expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s + expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s + end + end + context 'with a self-quote post', feature: :outgoing_quotes do let(:quoted_status) { Fabricate(:status, account: user.account) } let(:params) do @@ -307,9 +332,10 @@ RSpec.describe '/api/v1/statuses' do describe 'PUT /api/v1/statuses/:id' do subject do - put "/api/v1/statuses/#{status.id}", headers: headers, params: { status: 'I am updated' } + put "/api/v1/statuses/#{status.id}", headers: headers, params: params end + let(:params) { { status: 'I am updated' } } let(:scopes) { 'write:statuses' } let(:status) { Fabricate(:status, account: user.account) } @@ -323,6 +349,19 @@ RSpec.describe '/api/v1/statuses' do .to start_with('application/json') expect(status.reload.text).to eq 'I am updated' end + + context 'when updating only the quote policy' do + let(:params) { { status: status.text, quote_approval_policy: 'public' } } + + it 'updates the status', :aggregate_failures, feature: :outgoing_quotes do + expect { subject } + .to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + end + end end end diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 7e47506a9f7..64bb5e32e21 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -160,6 +160,12 @@ RSpec.describe PostStatusService do expect(status.language).to eq 'en' end + it 'creates a status with the quote approval policy set' do + status = create_status_with_options(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + + expect(status.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + end + it 'processes mentions' do mention_service = instance_double(ProcessMentionsService) allow(mention_service).to receive(:call) From 4838085d662287d6647e9ea568b65963429cdd7f Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 6 Aug 2025 16:54:03 +0200 Subject: [PATCH 238/660] Bundle quotes and mentions in the same quickfilter bar since quotes don't have their own icon (#35700) --- app/javascript/mastodon/actions/notification_groups.ts | 7 +++++-- app/javascript/mastodon/selectors/notifications.ts | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts index 19d8750fa72..7e162e5e51c 100644 --- a/app/javascript/mastodon/actions/notification_groups.ts +++ b/app/javascript/mastodon/actions/notification_groups.ts @@ -31,7 +31,9 @@ import { NOTIFICATIONS_FILTER_SET } from './notifications'; import { saveSettings } from './settings'; function excludeAllTypesExcept(filter: string) { - return allNotificationTypes.filter((item) => item !== filter); + return allNotificationTypes.filter( + (item) => item !== filter && !(item === 'quote' && filter === 'mention'), + ); } function getExcludedTypes(state: RootState) { @@ -156,7 +158,8 @@ export const processNewNotificationForGroups = createAppAsyncThunk( const showInColumn = activeFilter === 'all' ? notificationShows[notification.type] !== false - : activeFilter === notification.type; + : activeFilter === notification.type || + (activeFilter === 'mention' && notification.type === 'quote'); if (!showInColumn) return; diff --git a/app/javascript/mastodon/selectors/notifications.ts b/app/javascript/mastodon/selectors/notifications.ts index ea640406ea1..8c808a2dffe 100644 --- a/app/javascript/mastodon/selectors/notifications.ts +++ b/app/javascript/mastodon/selectors/notifications.ts @@ -26,7 +26,10 @@ const filterNotificationsByAllowedTypes = ( ); } return notifications.filter( - (item) => item.type === 'gap' || allowedType === item.type, + (item) => + item.type === 'gap' || + allowedType === item.type || + (allowedType === 'mention' && item.type === 'quote'), ); }; From ac59772dc6cda646258e61debfd792f9057c1c39 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 6 Aug 2025 17:20:16 +0200 Subject: [PATCH 239/660] Change icon of quote notification mails (#35701) --- .../images/mailer-new/heading/README.md | 2 ++ .../images/mailer-new/heading/quote.png | Bin 0 -> 1837 bytes app/views/notification_mailer/quote.html.haml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 app/javascript/images/mailer-new/heading/quote.png diff --git a/app/javascript/images/mailer-new/heading/README.md b/app/javascript/images/mailer-new/heading/README.md index ecd4b949e76..9fb6841f14b 100644 --- a/app/javascript/images/mailer-new/heading/README.md +++ b/app/javascript/images/mailer-new/heading/README.md @@ -1 +1,3 @@ Images in this folder are based on [Tabler.io icons](https://tabler.io/icons). + +Seems to be 1.5 width icons scaled to 64×64px and centered above a blue square with round corners (24px). diff --git a/app/javascript/images/mailer-new/heading/quote.png b/app/javascript/images/mailer-new/heading/quote.png new file mode 100644 index 0000000000000000000000000000000000000000..c2af73282f1eaf770fa34b513c919b8a98309dfc GIT binary patch literal 1837 zcma)7X*3&%7EUO!CA4Bf2(?vO>lC%rR*5AHV=0ZT2|;D3B|)sAwme3?>R`rRw5Wz6 z#=gXEIz{alaPq4zKr+EQ z)K4RX^q_Ft2o3=7w`0(E?nV`DErbNPM92*99B_(`9x$f!`*rllsozvSe>w^>Kf8{6 z2jV}&+%u%KK(p)jqOU^X7Um<7+zh)DtM40CoJOt*d=yc=ERp*iy~%aiNw4^z1Sh(^ zzTFkvan3n9{p(ATvHn5HZWnHww%f6Bo-xy@s(_P}AuTCygoJn9lqnk2= z4<7xCX^Nyy6bRdpCnJc#0n0JXgD#c8*=dJLPgi}1T5rqd$42E;vxYfgh9X9JL9YbXmNzGCK>b;`psvAIe2TQdMQTM9ZsM=?@2-yuo#+at9rT5_Dl z(S@8mj54#`urjJ3nVF*&3w>$8w!p%L!ZhYH?Hi?dfO)Y6u1cNeF}-0#FX!Q>zSVM%aeE37cJO!YgtKyV$j^ zjPS9z6Xo6zcTbUaWhn~D+y>{l`m%IJ5t@DU0dQffZcsmeT6oAqo2YfE^>!I-V@hv> z<`5ri{x^T$z$q-e5+WztR#qt8J?sy5+cUnPcyz{8|8}J!8?zq zOu9Qeqw?7#7oMi%q>Bj%mWe%tBz~xO>SNH-d|U=@ZpxbH`n>2-Pj4dD0PlJg%S!JY z_H(G{Kf;yX>Z{Q6Hz+C!if<4Ds#z7$^BjA0no*4mc>HLAHuA)$k=N)0MNWY*ZpgIm z=@64rjd;TL2-aVA(BpSZV%|>xbrqnyJx^27r2qrLccd%N{;g#8UkT6b zCGeh~IiH&6CGpa<%ZmIZEnSGZ6Cqpzc+RP_!)S>sjH!J&+Do=1Ro4i)zUG;;3u&{y z6v;^wS2?Z41z3X1DlB_5S|-m)^6hUkBqre0nS*3U;8+oG(9KiszNg3(Ztr!sT;?%Y z1}AIUbqTuWL#|{Nxn}R58nx7lcuoT2yfB{(VO2x2>^orU{7p+%XneRg&B4@_) zxIhzwYt-WH!Imd1Oc8SW$^>|?Cjv-Vze1Rk~R;t%Q?UqD5`@npT(IObWEm=5-2iMlt zC-mfVg?m|mD!XJC4)gQM>G%{$WPj_&;Kf4M9*0bDeS7=4=E1Bwi=oo-I3sRO6H4Iy z(>C^Y+!sVs)7paswI%NEg}ks}dF#_AXF6Xy^w>(D0_pR<{Rx&4)`I_DzgtWU*_dL%R(alc7sqC;+A-^g=LMFtlKg0p!BXRx z%{V+k5%z2OH(H0yLsnT;co(e^lwQn1v^nMC-azT&2{;AijK{rKaa^uz&x5t(JCWfu zWMFwhQ<7-pt-qoZw2{SY`%K*kHOB%mEw#CYbl`$(eS44YYpY2BkOx=-g8cAH!K>|o z#IaV0tu*{;=_lKd<(2p+yIK9;K{@h20R3$od9J8$o`R$2=iwc8hC4|C{Z}geiM*u>T zkA|!Z@)~chnbZVWuK$c3)Eja6be_mthinBDA#apN4@1u0G#@4;>ZzWg`lpL7UL62q LVUMme$0z*_$oXbM literal 0 HcmV?d00001 diff --git a/app/views/notification_mailer/quote.html.haml b/app/views/notification_mailer/quote.html.haml index 1a2d8f8c76f..139f4b2e8de 100644 --- a/app/views/notification_mailer/quote.html.haml +++ b/app/views/notification_mailer/quote.html.haml @@ -1,6 +1,6 @@ = content_for :heading do = render 'application/mailer/heading', - image_url: frontend_asset_url('images/mailer-new/heading/boost.png'), + image_url: frontend_asset_url('images/mailer-new/heading/quote.png'), subtitle: t('notification_mailer.quote.body', name: @status.account.pretty_acct), title: t('notification_mailer.quote.title') %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } From e615d2f069ffbddfe406b78ea5bed38e3d2d2c25 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 7 Aug 2025 10:02:29 +0200 Subject: [PATCH 240/660] Change quote to be fetched with quoted account rather than random follower (#35709) --- app/lib/activitypub/activity/quote_request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 424aafe993b..9f18a1367b5 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -21,7 +21,7 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity def accept_quote_request!(quoted_status) status = status_from_uri(@json['instrument']) # TODO: import inlined quote post if possible - status ||= ActivityPub::FetchRemoteStatusService.new.call(@json['instrument'], on_behalf_of: @account.followers.local.first, request_id: @options[:request_id]) + status ||= ActivityPub::FetchRemoteStatusService.new.call(@json['instrument'], on_behalf_of: quoted_status.account, request_id: @options[:request_id]) # TODO: raise if status is nil # Sanity check From d4e07841829379b3bc88f4de3c7a33626a8c9b0f Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 7 Aug 2025 10:03:15 +0200 Subject: [PATCH 241/660] Fix quote revocation not being streamed (#35710) --- app/lib/activitypub/activity/delete.rb | 1 + app/services/revoke_quote_service.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/app/lib/activitypub/activity/delete.rb b/app/lib/activitypub/activity/delete.rb index 69b7bd03546..ce36cfe763f 100644 --- a/app/lib/activitypub/activity/delete.rb +++ b/app/lib/activitypub/activity/delete.rb @@ -61,6 +61,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity ActivityPub::Forwarder.new(@account, @json, @quote.status).forward! @quote.reject! + DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) end def forwarder diff --git a/app/services/revoke_quote_service.rb b/app/services/revoke_quote_service.rb index 8f5dc8f9105..f4bc07c9da9 100644 --- a/app/services/revoke_quote_service.rb +++ b/app/services/revoke_quote_service.rb @@ -8,11 +8,18 @@ class RevokeQuoteService < BaseService @account = quote.quoted_account @quote.reject! + distribute_update! distribute_stamp_deletion! end private + def distribute_update! + return if @quote.status_id.nil? + + DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) + end + def distribute_stamp_deletion! ActivityPub::DeliveryWorker.push_bulk(inboxes, limit: 1_000) do |inbox_url| [signed_activity_json, @account.id, inbox_url] From 39a3ffaf2ff56b5b91d6fa519f7516ac9f9c8739 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 08:46:11 +0000 Subject: [PATCH 242/660] New Crowdin Translations (automated) (#35708) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/az.json | 280 ++++++++++++++++++++- app/javascript/mastodon/locales/ca.json | 3 + app/javascript/mastodon/locales/cs.json | 4 + app/javascript/mastodon/locales/da.json | 4 + app/javascript/mastodon/locales/de.json | 6 +- app/javascript/mastodon/locales/eo.json | 16 ++ app/javascript/mastodon/locales/es-AR.json | 4 + app/javascript/mastodon/locales/es-MX.json | 4 + app/javascript/mastodon/locales/es.json | 4 + app/javascript/mastodon/locales/fa.json | 11 + app/javascript/mastodon/locales/fo.json | 6 + app/javascript/mastodon/locales/ga.json | 2 + app/javascript/mastodon/locales/gl.json | 5 + app/javascript/mastodon/locales/he.json | 5 + app/javascript/mastodon/locales/hu.json | 1 + app/javascript/mastodon/locales/is.json | 4 + app/javascript/mastodon/locales/nl.json | 4 + app/javascript/mastodon/locales/pt-PT.json | 4 + app/javascript/mastodon/locales/tr.json | 5 + app/javascript/mastodon/locales/uk.json | 4 + app/javascript/mastodon/locales/vi.json | 4 + app/javascript/mastodon/locales/zh-TW.json | 4 + config/locales/activerecord.az.yml | 76 ++++++ config/locales/az.yml | 167 ++++++++++++ config/locales/devise.az.yml | 74 +++++- config/locales/doorkeeper.az.yml | 25 ++ config/locales/fa.yml | 11 + config/locales/fo.yml | 4 + config/locales/ga.yml | 4 + config/locales/lt.yml | 22 ++ config/locales/ru.yml | 36 +-- config/locales/simple_form.az.yml | 31 +++ config/locales/simple_form.fa.yml | 2 + config/locales/simple_form.fo.yml | 1 + config/locales/simple_form.ga.yml | 1 + 35 files changed, 797 insertions(+), 41 deletions(-) diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index 72cc1edd022..b9371f10a78 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -1,13 +1,15 @@ { - "about.blocks": "Moderasiya olunan serverlər", + "about.blocks": "Moderasiya edilmiş serverlər", "about.contact": "Əlaqə:", - "about.disclaimer": "Mastodon pulsuz, açıq-mənbəli proqram təminatıdır və Mastodon gGmbH-nin əmtəə nişanıdır.", - "about.domain_blocks.no_reason_available": "Səbəb naməlumdur", - "about.domain_blocks.preamble": "Mastodon adətən fediversedəki hər hansısa bir serverdən olan məzmuna baxmaq və istifadəçilərlə qarşılıqlı əlaqədə olmaq imkanı verir. Bunlar bu serverdə edilmiş istisnalardır.", + "about.default_locale": "İlkin", + "about.disclaimer": "Mastodon ödənişsiz, açıq-mənbəli yazılımdır və Mastodon gGmbH-nin əmtəə nişanıdır.", + "about.domain_blocks.no_reason_available": "Səbəb mövcud deyil", + "about.domain_blocks.preamble": "Mastodon, adətən fediverse-dəki hər hansısa bir serverdən məzmuna baxmağınıza və istifadəçilərlə qarşılıqlı əlaqədə olmağınıza imkanı verir. Bunlar, bu serverdə edilmiş istisnalardır.", "about.domain_blocks.silenced.explanation": "Siz bu serverdəki profilləri və məzmunu xüsusi olaraq axtarmasanız və ya izləməsəniz ümumiyyətlə görməyəcəksiniz.", "about.domain_blocks.silenced.title": "Məhdudlaşdırılmış", - "about.domain_blocks.suspended.explanation": "Bu serverdən heç bir data emal edilməyəcək, saxlanılmayacaq və ya mübadilə edilməyəcək və bu serverdən olan istifadəçilərlə hər hansı qarşılıqlı əlaqə qeyri-mümkün olacaq.", + "about.domain_blocks.suspended.explanation": "Bu serverdəki heç bir veri emal edilməyəcək, saxlanılmayacaq və ya mübadilə edilməyəcək, bu serverdəki istifadəçilərlə hər hansısa bir qarşılıqlı əlaqə və ya ünsiyyət mümkünsüz olacaq.", "about.domain_blocks.suspended.title": "Qadağa qoyulub", + "about.language_label": "Dil", "about.not_available": "Bu məlumat bu serverdə əlçatan edilməyib.", "about.powered_by": "{mastodon} tərəfindən təchiz edilən desentralizasiya edilmiş sosial media", "about.rules": "Server qaydaları", @@ -19,6 +21,7 @@ "account.block_domain": "{domain} domenini blokla", "account.block_short": "Blok", "account.blocked": "Bloklanıb", + "account.blocking": "Əngəlləmə", "account.cancel_follow_request": "İzləməni ləğv et", "account.copy": "Profil linkini kopyala", "account.direct": "@{name} istifadəçisini fərdi olaraq etiketlə", @@ -27,6 +30,11 @@ "account.edit_profile": "Profili redaktə et", "account.enable_notifications": "@{name} paylaşım edəndə mənə bildiriş göndər", "account.endorse": "Profildə seçilmişlərə əlavə et", + "account.familiar_followers_many": "{name1}, {name2} və tanıdığınız digər {othersCount, plural, one {digər bir nəfər} other {# nəfər}} izləyir", + "account.familiar_followers_one": "{name1} izləyir", + "account.familiar_followers_two": "{name1} və {name2} izləyir", + "account.featured": "Seçilmiş", + "account.featured.accounts": "Profillər", "account.featured.hashtags": "Etiketler", "account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub", "account.featured_tags.last_status_never": "Paylaşım yoxdur", @@ -35,9 +43,11 @@ "account.followers": "İzləyicilər", "account.followers.empty": "Bu istifadəçini hələ ki, heç kim izləmir.", "account.followers_counter": "{count, plural, one {{counter} izləyici} other {{counter} izləyici}}", + "account.followers_you_know_counter": "bildiyiniz {counter}", "account.following": "İzləyir", "account.following_counter": "{count, plural, one {{counter} izləyir} other {{counter} izləyir}}", "account.follows.empty": "Bu istifadəçi hələ ki, heç kimi izləmir.", + "account.follows_you": "Sizi izləyir", "account.go_to_profile": "Profilə get", "account.hide_reblogs": "@{name} istifadəçisindən olan gücləndirmələri gizlət", "account.in_memoriam": "Xatirə.", @@ -52,18 +62,23 @@ "account.mute_notifications_short": "Bildirişləri səssizləşdir", "account.mute_short": "Səssizləşdir", "account.muted": "Səssizləşdirilib", + "account.muting": "Səssizə alınır", + "account.mutual": "Bir-birinizi izləyirsiniz", "account.no_bio": "Təsvir göstərilməyib.", "account.open_original_page": "Orijinal səhifəni aç", "account.posts": "Paylaşım", "account.posts_with_replies": "Paylaşım və cavablar", + "account.remove_from_followers": "{name} - izləyicilərdən çıxart", "account.report": "@{name} istifadəçisini şikayət et", "account.requested": "Təsdiq edilməsi gözlənilir. İzləmə sorğusunu ləğv etmək üçün kliklə", "account.requested_follow": "{name} sizi izləmək sorğusu göndərib", + "account.requests_to_follow_you": "Sizi izləmək istəyir", "account.share": "@{name} profilini paylaş", "account.show_reblogs": "@{name} istifadəçisindən olan gücləndirmələri göstər", "account.statuses_counter": "{count, plural, one {{counter} paylaşım} other {{counter} paylaşım}}", "account.unblock": "@{name} blokunu aç", "account.unblock_domain": "{domain} domeninin blokunu aç", + "account.unblock_domain_short": "Əngəldən çıxart", "account.unblock_short": "Bloku aç", "account.unendorse": "Profildə seçilmişlərə əlavə etmə", "account.unfollow": "İzləmədən çıxar", @@ -162,11 +177,11 @@ "column.pins": "Bərkidilmiş paylaşımlar", "column.public": "Federasiya zaman qrafiki", "column_back_button.label": "Geriyə", - "column_header.hide_settings": "Parametrləri gizlət", + "column_header.hide_settings": "Ayarları gizlət", "column_header.moveLeft_settings": "Sütunu sola köçür", "column_header.moveRight_settings": "Sütunu sağa köçür", "column_header.pin": "Bərkit", - "column_header.show_settings": "Parametrləri göstər", + "column_header.show_settings": "Ayarları göstər", "column_header.unpin": "Bərkitmə", "column_search.cancel": "İmtina", "community.column_settings.local_only": "Sadəcə lokalda", @@ -192,7 +207,7 @@ "compose_form.poll.type": "Stil", "compose_form.publish": "Paylaş", "compose_form.reply": "Cavabla", - "compose_form.save_changes": "Yenilə", + "compose_form.save_changes": "Güncəllə", "compose_form.spoiler.marked": "Məzmun xəbərdarlığını sil", "compose_form.spoiler.unmarked": "Məzmun xəbərdarlığı əlavə et", "compose_form.spoiler_placeholder": "Məzmun xəbərdarlığı (məcburi deyil)", @@ -204,6 +219,13 @@ "confirmations.delete_list.confirm": "Sil", "confirmations.delete_list.message": "Bu siyahını həmişəlik silmək istədiyinizə əminsiniz?", "confirmations.delete_list.title": "Siyahı silinsin?", + "confirmations.discard_draft.confirm": "Silib davam et", + "confirmations.discard_draft.edit.cancel": "Düzəliş etməyə davam", + "confirmations.discard_draft.edit.message": "Davam etsəniz, hazırda düzəliş etdiyiniz göndərişdəki bütün dəyişikliklər silinəcək.", + "confirmations.discard_draft.edit.title": "Göndərişinizə etdiyiniz bütün dəyişikliklər silinsin?", + "confirmations.discard_draft.post.cancel": "Qaralama kimi davam etdir", + "confirmations.discard_draft.post.message": "Davam etsəniz, hazırda tərtib etdiyiniz göndəriş silinəcək.", + "confirmations.discard_draft.post.title": "Qaralama göndərişiniz silinsin?", "confirmations.discard_edit_media.confirm": "Ləğv et", "confirmations.discard_edit_media.message": "Media təsvirində və ya önizləmədə yadda saxlanmamış dəyişiklikləriniz var, ləğv edilsin?", "confirmations.follow_to_list.confirm": "İzlə və siyahıya əlavə et", @@ -213,13 +235,19 @@ "confirmations.logout.message": "Çıxmaq istədiyinizə əminsiniz?", "confirmations.logout.title": "Çıxış edilsin?", "confirmations.missing_alt_text.confirm": "Alternativ mətn əlavə et", - "confirmations.missing_alt_text.message": "Paylaşımınız alternativ mətn ehtiva etmir. Təsvir əlavə etmək onun daha çox insan üçün əlçatan olmasına kömək edir.", + "confirmations.missing_alt_text.message": "Göndərişinizdə alternativ mətn yoxdur. Daha çox insanın məzmununuza erişməsinə kömək etmək üçün açıqlama əlavə edin.", "confirmations.missing_alt_text.secondary": "Yenə də paylaş", "confirmations.missing_alt_text.title": "Alternativ mətn əlavə edilsin?", "confirmations.mute.confirm": "Səssizləşdir", "confirmations.redraft.confirm": "Sil və qaralamaya köçür", "confirmations.redraft.message": "Bu paylaşımı silmək və qaralamaya köçürmək istədiyinizə əminsiniz? Bəyənmələr və gücləndirmələr itəcək və orijinal paylaşıma olan cavablar tənha qalacaq.", "confirmations.redraft.title": "Paylaşım silinsin & qaralamaya köçürülsün?", + "confirmations.remove_from_followers.confirm": "İzləyicini çıxart", + "confirmations.remove_from_followers.message": "{name} sizi izləməyəcək. Davam etmək istədiyinizə əminsiniz?", + "confirmations.remove_from_followers.title": "İzləyici çıxarılsın?", + "confirmations.revoke_quote.confirm": "Göndərişi sil", + "confirmations.revoke_quote.message": "Bu əməliyyatın geri dönüşü yoxdur.", + "confirmations.revoke_quote.title": "Göndəriş silinsin?", "confirmations.unfollow.confirm": "İzləmədən çıxar", "confirmations.unfollow.message": "{name} izləmədən çıxmaq istədiyinizə əminsiniz?", "confirmations.unfollow.title": "İstifadəçi izləmədən çıxarılsın?", @@ -232,12 +260,12 @@ "conversation.with": "{names} ilə", "copy_icon_button.copied": "Mübadilə buferinə köçürüldü", "copypaste.copied": "Kopyalandı", - "copypaste.copy_to_clipboard": "Kopyala", + "copypaste.copy_to_clipboard": "Lövhəyə kopyala", "directory.federated": "Bilinən fediversedən", "directory.local": "Sadəcə {domain}", "directory.new_arrivals": "Yeni gələnlər", "directory.recently_active": "Bayaq aktiv olanlar", - "disabled_account_banner.account_settings": "Hesab parametrləri", + "disabled_account_banner.account_settings": "Hesab ayarları", "disabled_account_banner.text": "Sizin hesabınız {disabledAccount} hal-hazırda deaktiv edilib.", "dismissable_banner.community_timeline": "Bunlar, hesabları {domain} serverində yerləşən insanların ən son ictimai paylaşımlarıdır.", "dismissable_banner.dismiss": "Bağla", @@ -273,7 +301,7 @@ "emoji_button.food": "Yemək və içki", "emoji_button.label": "Emoji daxil et", "emoji_button.nature": "Təbiət", - "emoji_button.not_found": "Uyğun emoji tapılmadı", + "emoji_button.not_found": "Uyuşan emoji tapılmadı", "emoji_button.objects": "Obyektlər", "emoji_button.people": "İnsanlar", "emoji_button.recent": "Tez-tez istifadə edilən", @@ -296,6 +324,232 @@ "empty_column.follow_requests": "İzləmə sorğularınız yoxdur. Qəbul etdikdə burada görəcəksiniz.", "empty_column.followed_tags": "Heç bir heşteq izləmirsiniz. İzlədikdə burada görünəcək.", "empty_column.hashtag": "Bu heşteqdə hələ ki, heç nə yoxdur.", + "empty_column.notification_requests": "Hamısı hazırdır! Burada heç nə yoxdur. Yeni bildiriş aldığınız zaman, ayarlarınıza görə burada görünəcək.", + "errors.unexpected_crash.report_issue": "Problemi bildir", + "explore.suggested_follows": "İnsanlar", + "explore.title": "Trendlər", + "explore.trending_links": "Xəbərlər", + "explore.trending_statuses": "Göndərişlər", + "explore.trending_tags": "Mövzu etiketləri", + "featured_carousel.header": "{count, plural, one {Sancılmış göndəriş} other {Sancılmış göndərişlər}}", + "featured_carousel.next": "Növbəti", + "featured_carousel.post": "Göndəriş", + "featured_carousel.previous": "Əvvəlki", + "featured_carousel.slide": "{index}/{total}", + "filter_modal.added.context_mismatch_explanation": "Bu filtr kateqoriyası, bu göndərişdə erişdiyiniz kontekstə aid deyil. Əgər göndərişin bu kontekstdə də filtrlənməsini istəyirsinizsə, filtrə düzəliş etməyiniz lazımdır.", + "filter_modal.added.context_mismatch_title": "Kontekst uyuşmur!", + "filter_modal.added.expired_explanation": "Bu filtr kateqoriyasının vaxtı bitib, filtri tətbiq etmək üçün bitmə tarixini dəyişdirməlisiniz.", + "filter_modal.added.expired_title": "Vaxtı bitmiş filtr!", + "filter_modal.added.review_and_configure": "Bu filt kateqoriyasını incələmək və daha detallı konfiqurasiya etmək üçün {settings_link} ünvanına gedin.", + "filter_modal.added.review_and_configure_title": "Filtr ayarları", + "filter_modal.added.settings_link": "ayarlar səhifəsi", + "filter_modal.added.short_explanation": "Bu göndəriş, aşağıdakı filtr kateqoriyasına əlavə edilib: {title}.", + "filter_modal.added.title": "Filtr əlavə edilib!", + "filter_warning.matches_filter": "“{title}” filtri ilə uyuşur", "follow_suggestions.hints.friends_of_friends": "Bu profil izlədiyiniz insanlar arasında populyardır.", - "follow_suggestions.hints.most_followed": "Bu profil {domain} serverində ən çox izlənilənlərdən biridir." + "follow_suggestions.hints.most_followed": "Bu profil {domain} serverində ən çox izlənilənlərdən biridir.", + "generic.saved": "Saxlanıldı", + "getting_started.heading": "Başlayaq", + "hashtag.admin_moderation": "#{name} üçün moderasiya interfeysini aç", + "hashtag.browse": "#{hashtag} göndərişlərinə bax", + "hashtag.browse_from_account": "@{name} - #{hashtag} göndərişlərinə bax", + "hashtag.column_header.tag_mode.all": "və {additional}", + "hashtag.column_header.tag_mode.any": "və ya {additional}", + "hashtag.column_header.tag_mode.none": "{additional} olmadan", + "hashtag.column_settings.select.no_options_message": "Heç bir təklif tapılmadı", + "hashtag.column_settings.select.placeholder": "Mövzu etiketlərini daxil edin…", + "hashtag.column_settings.tag_mode.all": "Bunların hamısı", + "hashtag.column_settings.tag_mode.any": "Bunlardan hər hansısa biri", + "hashtag.column_settings.tag_mode.none": "Bunların heç biri", + "hashtag.column_settings.tag_toggle": "Bu sütun üçün əlavə etiketləri daxil et", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} iştirakçı} other {{counter} iştirakçı}}", + "hashtag.counter_by_uses": "{count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}", + "hashtag.counter_by_uses_today": "Bu gün {count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}", + "hashtag.follow": "Mövzu etiketini izlə", + "hashtag.mute": "#{hashtag} - səssizə al", + "hashtag.unfollow": "Mövzu etiketini izləmə", + "hashtags.and_other": "…və daha {count, plural, one {}other {# ədəd}}", + "hints.profiles.followers_may_be_missing": "Bu profilin izləyiciləri əskik ola bilər.", + "hints.profiles.follows_may_be_missing": "Bu profilin izləyənləri əskik ola bilər.", + "home.column_settings.show_quotes": "Sitatları göstər", + "home.column_settings.show_replies": "Cavabları göstər", + "home.hide_announcements": "Elanları gizlət", + "home.pending_critical_update.body": "Lütfən Mastodon serverinizi mümkün olan ən qısa müddətdə güncəlləyin!", + "home.pending_critical_update.link": "Güncəlləmələrə bax", + "home.pending_critical_update.title": "Kritik güvənlik güncəlləməsi mövcuddur!", + "home.show_announcements": "Elanları göstər", + "ignore_notifications_modal.ignore": "Bildirişləri yox say", + "ignore_notifications_modal.limited_accounts_title": "Moderasiya edilmiş hesabların bildirişləri yox sayılsın?", + "ignore_notifications_modal.new_accounts_title": "Yeni hesabların bildirişləri yox sayılsın?", + "ignore_notifications_modal.not_followers_title": "Sizi izləməyən şəxslərin bildirişləri yox sayılsın?", + "ignore_notifications_modal.not_following_title": "İzləmədiyiniz şəxslərin bildirişləri yox sayılsın?", + "ignore_notifications_modal.private_mentions_title": "İstənilməyən Şəxsi Adçəkmələrdən gələn bildirişlər yox sayılsın?", + "info_button.label": "Kömək", + "interaction_modal.action.favourite": "Davam etmək üçün hesabınızdan sevimlilərə əlavə etməlisiniz.", + "interaction_modal.action.follow": "Davam etmək üçün hesabınızdan izləməlisiniz.", + "interaction_modal.action.reblog": "Davam etmək üçün hesabınızdan təkrar göndərməlisiniz.", + "interaction_modal.action.reply": "Davam etmək üçün hesabınızdan cavab verməlisiniz.", + "interaction_modal.action.vote": "Davam etmək üçün hesabınızdan səs verməlisiniz.", + "keyboard_shortcuts.profile": "Müəllifin profilini aç", + "keyboard_shortcuts.reply": "Göndərişə cavab ver", + "learn_more_link.got_it": "Anladım", + "learn_more_link.learn_more": "Daha ətraflı", + "lightbox.close": "Bağla", + "lightbox.next": "Növbəti", + "lightbox.previous": "Əvvəlki", + "lightbox.zoom_in": "Həqiqi ölçüyə qayıt", + "limited_account_hint.action": "Yenə də profili göstər", + "limited_account_hint.title": "Bu profil, {domain} moderatorları tərəfindən gizlədildi.", + "navigation_bar.account_settings": "Parol və təhlükəsizlik", + "navigation_bar.moderation": "Moderasiya", + "not_signed_in_indicator.not_signed_in": "Bu resursa erişmək üçün giriş etməlisiniz.", + "notification.moderation-warning.learn_more": "Daha ətraflı", + "notification.moderation_warning": "Bir moderasiya xəbərdarlığı aldınız", + "notification.moderation_warning.action_delete_statuses": "Bəzi göndərişləriniz silindi.", + "notification.moderation_warning.action_disable": "Hesabınız sıradan çıxarılıb.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Bəzi göndərişləriniz həssas olaraq işarələnib.", + "notification.moderation_warning.action_none": "Hesabınız bir moderasiya xəbərdarlığı aldı.", + "notification.moderation_warning.action_sensitive": "Göndərişləriniz artıq həssas olaraq işarələnəcək.", + "notification.moderation_warning.action_silence": "Hesabınız məhdudlaşdırılıb.", + "notification.moderation_warning.action_suspend": "Hesabınızın fəaliyyəti dayandırılıb.", + "notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {bir bildiriş sorğusunu} other {# bildiriş sorğusunu}} bağlamaq üzrəsiniz. {count, plural, one {Ona} other {Onlara}} yenidən asanlıqla erişə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", + "notification_requests.explainer_for_limited_account": "Hesab, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", + "notification_requests.explainer_for_limited_remote_account": "Hesab və ya onun serveri, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", + "notifications.filter.statuses": "İzlədiyiniz şəxslərdən güncəlləmələr", + "notifications.policy.filter_limited_accounts_hint": "Server moderatorları tərəfindən məhdudlaşdırılıb", + "notifications.policy.filter_limited_accounts_title": "Moderasiya edilmiş hesablar", + "password_confirmation.exceeds_maxlength": "Parol təsdiqi, maksimum parol uzunluğunu aşır", + "password_confirmation.mismatching": "Parol təsdiqi uyuşmur", + "privacy_policy.last_updated": "Son güncəlləmə {date}", + "report.category.subtitle": "Ən çox uyuşanı seçin", + "report_notification.categories.spam_sentence": "spam", + "report_notification.categories.violation": "Qayda pozuntusu", + "report_notification.categories.violation_sentence": "qayda pozuntusu", + "report_notification.open": "Hesabatı aç", + "search.clear": "Axtarışı təmizlə", + "search.no_recent_searches": "Son axtarışlar yoxdur", + "search.placeholder": "Axtar", + "search.quick_action.account_search": "Uyuşan profillər {x}", + "search.quick_action.go_to_account": "{x} profilinə get", + "search.quick_action.go_to_hashtag": "{x} mövzu etiketinə get", + "search.quick_action.open_url": "URL-ni Mastodon-da aç", + "search.quick_action.status_search": "Uyuşan göndərişlər {x}", + "search.search_or_paste": "Axtar və ya URL-ni yapışdır", + "search_popout.full_text_search_disabled_message": "{domain} domenində mövcud deyil.", + "search_popout.full_text_search_logged_out_message": "Yalnız giriş edildiyi zaman əlçatandır.", + "search_popout.language_code": "ISO dil kodu", + "search_popout.options": "Axtarış seçimləri", + "search_popout.quick_actions": "Cəld əməliyyatlar", + "search_popout.recent": "Son axtarışlar", + "search_popout.specific_date": "müəyyən tarix", + "search_popout.user": "istifadəçi", + "search_results.accounts": "Profillər", + "search_results.all": "Hamısı", + "search_results.hashtags": "Mövzu etiketləri", + "search_results.no_results": "Nəticə yoxdur.", + "search_results.no_search_yet": "Göndərişləri, profilləri və ya mövzu etiketlərini axtarmağa çalışın.", + "search_results.see_all": "Hamısına bax", + "search_results.statuses": "Göndərişlər", + "search_results.title": "\"{q}\" axtar", + "server_banner.about_active_users": "Son 30 gündə bu serveri istifadə edənlər (aylıq aktiv istifadəçilər)", + "server_banner.active_users": "aktiv istifadəçilər", + "server_banner.is_one_of_many": "{domain}, fediverse-də iştirak etmək üçün istifadə edə biləcəyiniz bir neçə müstəqil Mastodon serverlərindən biridir.", + "server_banner.server_stats": "Server statistikaları:", + "sign_in_banner.create_account": "Hesab yarat", + "sign_in_banner.follow_anyone": "fediverse-dəki hər kəsi izləyin və hamısına xronoloji ardıcıllıqla baxın. Heç bir alqoritm, reklam və ya klikləmə tələsi yoxdur.", + "sign_in_banner.mastodon_is": "Mastodon, baş verənlərdən xəbərdar olmağın ən yaxşı yoldur.", + "sign_in_banner.sign_in": "Giriş", + "sign_in_banner.sso_redirect": "Giriş və ya Qeydiyyat", + "status.admin_account": "@{name} üçün moderasiya interfeysini aç", + "status.admin_domain": "{domain} üçün moderasiya interfeysini aç", + "status.admin_status": "Moderasiya interfeysində bu göndərişi aç", + "status.block": "Əngəllə: @{name}", + "status.bookmark": "Əlfəcin", + "status.context.load_new_replies": "Yeni cavablar mövcuddur", + "status.context.loading": "Daha çox cavab yoxlanılır", + "status.delete": "Sil", + "status.direct": "Şəxsi olaraq adını çək: @{name}", + "status.direct_indicator": "Şəxsi olaraq adını çək", + "status.edit": "Düzəliş et", + "status.edited": "Son düzəliş {date}", + "status.edited_x_times": "{count, plural, one {{count} dəfə} other {{count} dəfə}} düzəliş edilib", + "status.favourite": "Sevimli", + "status.favourites": "{count, plural, one {sevimli} other {sevimli}}", + "status.filter": "Bu göndərişi filtrlə", + "status.history.created": "{name}, {date} yaratdı", + "status.history.edited": "{name}, {date} düzəliş etdi", + "status.load_more": "Daha çoxunu yüklə", + "status.media.open": "Açmaq üçün kliklə", + "status.media.show": "Göstərmək üçün kliklə", + "status.media_hidden": "Media gizlidir", + "status.mention": "Adını çək: @{name}", + "status.more": "Daha çox", + "status.mute": "@{name} - səssizə al", + "status.mute_conversation": "Danışığın səsini kəs", + "status.open": "Bu göndərişi genişləndir", + "status.quote_error.filtered": "Bəzi filtrlərinizə görə gizlidir", + "status.quote_error.not_available": "Göndəriş əlçatmazdır", + "status.quote_error.pending_approval": "Göndəriş gözləmədədir", + "status.read_more": "Daha çoxunu oxu", + "status.remove_bookmark": "Əlfəcini sil", + "status.remove_favourite": "Sevimlilərdən sil", + "status.replied_to": "Cavab verildi: {name}", + "status.reply": "Cavabla", + "status.report": "Bildir: @{name}", + "status.sensitive_warning": "Həssas məzmun", + "status.share": "Paylaş", + "status.show_less_all": "Hamısı üçün daha az göstər", + "status.show_more_all": "Hamısı üçün daha çox göstər", + "status.show_original": "Orijinalı göstər", + "status.translate": "Tərcümə et", + "status.translated_from_with": "{provider} ilə {lang} dilindən tərcümə edilib", + "status.uncached_media_warning": "Önizləmə mövcud deyil", + "status.unmute_conversation": "Danışığın səsini aç", + "subscribed_languages.save": "Dəyişiklikləri saxla", + "subscribed_languages.target": "{target} üçün abunə olunmuş dilləri dəyişdir", + "tabs_bar.home": "Ana səhifə", + "tabs_bar.menu": "Menyu", + "tabs_bar.notifications": "Bildirişlər", + "tabs_bar.publish": "Yeni göndəriş", + "tabs_bar.search": "Axtar", + "terms_of_service.effective_as_of": "{date} etibarilə qüvvədə", + "terms_of_service.title": "Xidmət Şərtləri", + "terms_of_service.upcoming_changes_on": "{date} tarixində ediləcək dəyişikliklər", + "time_remaining.days": "{number, plural, one {# gün} other {# gün}} qalıb", + "time_remaining.hours": "{number, plural, one {# saat} other {# saat}} qalıb", + "time_remaining.minutes": "{number, plural, one {# dəqiqə} other {# dəqiqə}} qalıb", + "time_remaining.moments": "Bir neçə dəqiqə qalıb", + "time_remaining.seconds": "{number, plural, one {# saniyə} other {# saniyə}} qalıb", + "trends.trending_now": "İndi trenddədir", + "ui.beforeunload": "Mastodon-u tərk etsəniz, qaralamanız itəcək.", + "units.short.billion": "{count} mlyrd", + "units.short.million": "{count} mlyn", + "units.short.thousand": "{count} min", + "upload_area.title": "Yükləmək üçün sürüklə və burax", + "upload_button.label": "Təsvir, video və ya səs faylı əlavə et", + "upload_error.limit": "Fayl yükləmə limiti aşılıb.", + "upload_error.poll": "Anketlərdə fayl yükləməyə icazə verilmir.", + "upload_form.drag_and_drop.instructions": "Bir media qoşmasını daşımaq üçün boşluq və ya enter düyməsinə basın. Sürükləmə zamanı, media qoşmasını hər hansısa bir yönə hərəkət etdirmək üçün ox düymələrini istifadə edin. Media qoşmasını yeni mövqeyinə buraxmaq üçün təkrar boşluq və ya enter düyməsinə basın, ləğv etmək üçün escape düyməsinə basın.", + "upload_form.drag_and_drop.on_drag_cancel": "Sürükləmə ləğv edilib. {item} media qoşması buraxıldı.", + "upload_form.drag_and_drop.on_drag_end": "{item} media qoşması buraxıldı.", + "upload_form.drag_and_drop.on_drag_over": "{item} media qoşması daşındı.", + "upload_form.drag_and_drop.on_drag_start": "{item} media qoşması alındı.", + "upload_form.edit": "Düzəliş et", + "upload_progress.label": "Yüklənir...", + "upload_progress.processing": "Emal edilir…", + "username.taken": "Bu istifadəçi adı götürülüb. Başqasını sınayın", + "video.close": "Videonu bağla", + "video.download": "Faylı endir", + "video.exit_fullscreen": "Tam ekrandan çıx", + "video.expand": "Videonu genişləndir", + "video.fullscreen": "Tam ekran", + "video.hide": "Videonu gizlət", + "video.mute": "Səsi kəs", + "video.pause": "Fasilə ver", + "video.play": "Oxut", + "video.skip_backward": "Geri ötür", + "video.skip_forward": "İrəli ötür", + "video.unmute": "Səsi aç", + "video.volume_down": "Həcmi azalt", + "video.volume_up": "Həcmi artır" } diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index bb255756e66..103b4f3715d 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -245,6 +245,7 @@ "confirmations.remove_from_followers.confirm": "Elimina el seguidor", "confirmations.remove_from_followers.message": "{name} deixarà de seguir-vos. Tirem endavant?", "confirmations.remove_from_followers.title": "Eliminem el seguidor?", + "confirmations.revoke_quote.message": "Aquesta acció no es pot desfer.", "confirmations.unfollow.confirm": "Deixa de seguir", "confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?", "confirmations.unfollow.title": "Deixar de seguir l'usuari?", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Mencions:", "notifications.column_settings.poll": "Resultats de l’enquesta:", "notifications.column_settings.push": "Notificacions push", + "notifications.column_settings.quote": "Cites:", "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Mostra a la columna", "notifications.column_settings.sound": "Reprodueix so", @@ -894,6 +896,7 @@ "status.reply": "Respon", "status.replyAll": "Respon al fil", "status.report": "Denuncia @{name}", + "status.revoke_quote": "Elimina la meva publicació de la de @{name}", "status.sensitive_warning": "Contingut sensible", "status.share": "Comparteix", "status.show_less_all": "Mostra'n menys per a tot", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 690a35990e4..a6e82882953 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Odstranit sledujícího", "confirmations.remove_from_followers.message": "{name} vás přestane sledovat. Jste si jisti, že chcete pokračovat?", "confirmations.remove_from_followers.title": "Odstranit sledujícího?", + "confirmations.revoke_quote.confirm": "Odstranit příspěvek", + "confirmations.revoke_quote.message": "Tuto akci nelze vrátit zpět.", + "confirmations.revoke_quote.title": "Odstranit příspěvek?", "confirmations.unfollow.confirm": "Přestat sledovat", "confirmations.unfollow.message": "Opravdu chcete {name} přestat sledovat?", "confirmations.unfollow.title": "Přestat sledovat uživatele?", @@ -896,6 +899,7 @@ "status.reply": "Odpovědět", "status.replyAll": "Odpovědět na vlákno", "status.report": "Nahlásit @{name}", + "status.revoke_quote": "Odstranit můj příspěvek z příspěvku @{name}", "status.sensitive_warning": "Citlivý obsah", "status.share": "Sdílet", "status.show_less_all": "Zobrazit méně pro všechny", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index b401c13d32b..96a6eecf8c6 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Fjern følger", "confirmations.remove_from_followers.message": "{name} vil ikke længere følge dig. Er du sikker på, at du vil fortsætte?", "confirmations.remove_from_followers.title": "Fjern følger?", + "confirmations.revoke_quote.confirm": "Fjern indlæg", + "confirmations.revoke_quote.message": "Denne handling kan ikke fortrydes.", + "confirmations.revoke_quote.title": "Fjern indlæg?", "confirmations.unfollow.confirm": "Følg ikke længere", "confirmations.unfollow.message": "Er du sikker på, at du ikke længere vil følge {name}?", "confirmations.unfollow.title": "Følg ikke længere bruger?", @@ -896,6 +899,7 @@ "status.reply": "Besvar", "status.replyAll": "Svar alle", "status.report": "Anmeld @{name}", + "status.revoke_quote": "Fjern mit indlæg fra @{name}'s indlæg", "status.sensitive_warning": "Følsomt indhold", "status.share": "Del", "status.show_less_all": "Vis mindre for alle", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0ada8ba8c13..018f66935cd 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Follower entfernen", "confirmations.remove_from_followers.message": "{name} wird dir nicht länger folgen. Bist du dir sicher?", "confirmations.remove_from_followers.title": "Follower entfernen?", + "confirmations.revoke_quote.confirm": "Beitrag entfernen", + "confirmations.revoke_quote.message": "Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmations.revoke_quote.title": "Beitrag entfernen?", "confirmations.unfollow.confirm": "Entfolgen", "confirmations.unfollow.message": "Möchtest du {name} wirklich entfolgen?", "confirmations.unfollow.title": "Profil entfolgen?", @@ -362,7 +365,7 @@ "filter_modal.select_filter.subtitle": "Einem vorhandenen Filter hinzufügen oder einen neuen erstellen", "filter_modal.select_filter.title": "Diesen Beitrag filtern", "filter_modal.title.status": "Beitrag per Filter ausblenden", - "filter_warning.matches_filter": "Übereinstimmend mit dem Filter „{title}“", + "filter_warning.matches_filter": "Ausgeblendet wegen des Filters „{title}“", "filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem Profil, das dir möglicherweise bekannt ist} one {einem Profil, das dir möglicherweise bekannt ist} other {# Profilen, die dir möglicherweise bekannt sind}}", "filtered_notifications_banner.title": "Gefilterte Benachrichtigungen", "firehose.all": "Alle Server", @@ -896,6 +899,7 @@ "status.reply": "Antworten", "status.replyAll": "Allen antworten", "status.report": "@{name} melden", + "status.revoke_quote": "Meinen zitierten Beitrag aus dem Beitrag von @{name} entfernen", "status.sensitive_warning": "Inhaltswarnung", "status.share": "Teilen", "status.show_less_all": "Alles einklappen", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 5027ce48b39..938e9e706b6 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -40,6 +40,7 @@ "account.followers": "Sekvantoj", "account.followers.empty": "Ankoraŭ neniu sekvas ĉi tiun uzanton.", "account.followers_counter": "{count, plural, one{{counter} sekvanto} other {{counter} sekvantoj}}", + "account.followers_you_know_counter": "Vi scias {counter}", "account.following": "Sekvatoj", "account.following_counter": "{count, plural, one {{counter} sekvato} other {{counter} sekvatoj}}", "account.follows.empty": "La uzanto ankoraŭ ne sekvas iun ajn.", @@ -215,6 +216,11 @@ "confirmations.delete_list.confirm": "Forigi", "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?", "confirmations.delete_list.title": "Ĉu forigi liston?", + "confirmations.discard_draft.confirm": "Forĵetu kaj daŭrigu", + "confirmations.discard_draft.edit.cancel": "Daŭrigi redaktadon", + "confirmations.discard_draft.edit.title": "Ĉu forĵeti ŝanĝojn al via afiŝo?", + "confirmations.discard_draft.post.cancel": "Daŭrigi malneton", + "confirmations.discard_draft.post.title": "Ĉu forĵeti vian malneton?", "confirmations.discard_edit_media.confirm": "Forĵeti", "confirmations.discard_edit_media.message": "Vi havas nekonservitajn ŝanĝojn de la priskribo aŭ la antaŭvidigo de la vidaŭdaĵo, ĉu vi forĵetu ilin malgraŭe?", "confirmations.follow_to_list.confirm": "Sekvi kaj aldoni al listo", @@ -234,6 +240,9 @@ "confirmations.remove_from_followers.confirm": "Forigi sekvanton", "confirmations.remove_from_followers.message": "{name} ne plu sekvos vin. Ĉu vi certas ke vi volas daŭri?", "confirmations.remove_from_followers.title": "Forigi sekvanton?", + "confirmations.revoke_quote.confirm": "Forigi afiŝon", + "confirmations.revoke_quote.message": "Ĉi tiu ago ne povas esti malfarita.", + "confirmations.revoke_quote.title": "Ĉu forigi afiŝon?", "confirmations.unfollow.confirm": "Ne plu sekvi", "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?", "confirmations.unfollow.title": "Ĉu ĉesi sekvi uzanton?", @@ -484,6 +493,8 @@ "keyboard_shortcuts.translate": "Traduki afiŝon", "keyboard_shortcuts.unfocus": "Senfokusigi verki tekstareon/serĉon", "keyboard_shortcuts.up": "Movu supren en la listo", + "learn_more_link.got_it": "Komprenite", + "learn_more_link.learn_more": "Lernu pli", "lightbox.close": "Fermi", "lightbox.next": "Antaŭen", "lightbox.previous": "Malantaŭen", @@ -577,6 +588,7 @@ "notification.label.mention": "Mencii", "notification.label.private_mention": "Privata mencio", "notification.label.private_reply": "Privata respondo", + "notification.label.quote": "{name} citis vian afiŝon", "notification.label.reply": "Respondi", "notification.mention": "Mencii", "notification.mentioned_you": "{name} menciis vin", @@ -634,6 +646,7 @@ "notifications.column_settings.mention": "Mencioj:", "notifications.column_settings.poll": "Balotenketaj rezultoj:", "notifications.column_settings.push": "Puŝsciigoj", + "notifications.column_settings.quote": "Citaĵoj:", "notifications.column_settings.reblog": "Diskonigoj:", "notifications.column_settings.show": "Montri en kolumno", "notifications.column_settings.sound": "Eligi sonon", @@ -851,6 +864,9 @@ "status.mute_conversation": "Silentigi konversacion", "status.open": "Pligrandigu ĉi tiun afiŝon", "status.pin": "Alpingli al la profilo", + "status.quote_error.not_available": "Afiŝo ne disponebla", + "status.quote_error.pending_approval": "Pritraktata afiŝo", + "status.quote_error.pending_approval_popout.title": "Ĉu pritraktata citaĵo? Restu trankvila", "status.read_more": "Legi pli", "status.reblog": "Diskonigi", "status.reblog_private": "Diskonigi kun la sama videbleco", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 41051d493a6..2b2ce14d5bc 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Quitar seguidor", "confirmations.remove_from_followers.message": "{name} dejará de seguirte. ¿Estás seguro de que querés continuar?", "confirmations.remove_from_followers.title": "¿Quitar seguidor?", + "confirmations.revoke_quote.confirm": "Eliminar mensaje", + "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", + "confirmations.revoke_quote.title": "¿Eliminar mensaje?", "confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.message": "¿Estás seguro que querés dejar de seguir a {name}?", "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", @@ -896,6 +899,7 @@ "status.reply": "Responder", "status.replyAll": "Responder al hilo", "status.report": "Denunciar a @{name}", + "status.revoke_quote": "Eliminar mi mensaje de la cita de @{name}", "status.sensitive_warning": "Contenido sensible", "status.share": "Compartir", "status.show_less_all": "Mostrar menos para todo", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 80a0138d93c..317f5311088 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Eliminar seguidor", "confirmations.remove_from_followers.message": "{name} dejará de seguirte. ¿Estás seguro de que quieres continuar?", "confirmations.remove_from_followers.title": "¿Eliminar seguidor?", + "confirmations.revoke_quote.confirm": "Eliminar publicación", + "confirmations.revoke_quote.message": "Esta acción no se puede deshacer.", + "confirmations.revoke_quote.title": "¿Deseas eliminar la publicación?", "confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", @@ -896,6 +899,7 @@ "status.reply": "Responder", "status.replyAll": "Responder al hilo", "status.report": "Reportar @{name}", + "status.revoke_quote": "Eliminar mi publicación de la cita de @{name}", "status.sensitive_warning": "Contenido sensible", "status.share": "Compartir", "status.show_less_all": "Mostrar menos para todo", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 18ba22e95a5..d23f1f5c128 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Eliminar seguidor", "confirmations.remove_from_followers.message": "{name} dejará de seguirte. ¿Estás seguro de que quieres continuar?", "confirmations.remove_from_followers.title": "¿Eliminar seguidor?", + "confirmations.revoke_quote.confirm": "Eliminar publicación", + "confirmations.revoke_quote.message": "Esta acción no tiene vuelta atrás.", + "confirmations.revoke_quote.title": "¿Eliminar la publicación?", "confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.message": "¿Seguro que quieres dejar de seguir a {name}?", "confirmations.unfollow.title": "¿Dejar de seguir al usuario?", @@ -896,6 +899,7 @@ "status.reply": "Responder", "status.replyAll": "Responder al hilo", "status.report": "Reportar a @{name}", + "status.revoke_quote": "Eliminar mi publicación de la cita de {name}", "status.sensitive_warning": "Contenido sensible", "status.share": "Compartir", "status.show_less_all": "Mostrar menos para todo", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 9677454e0b8..798be24ad65 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "برداشتن پی‌گیرنده", "confirmations.remove_from_followers.message": "دیگر {name} پیتان نخواهد گرفت. مطمئنید که می‌خواهید ادامه دهید؟", "confirmations.remove_from_followers.title": "برداشتن پی‌گیرنده؟", + "confirmations.revoke_quote.confirm": "حذف فرسته", + "confirmations.revoke_quote.message": "این اقدام قابل بازگشت نیست.", + "confirmations.revoke_quote.title": "آیا فرسته را حذف کنم؟", "confirmations.unfollow.confirm": "پی‌نگرفتن", "confirmations.unfollow.message": "مطمئنید که می‌خواهید به پی‌گیری از {name} پایان دهید؟", "confirmations.unfollow.title": "ناپی‌گیری کاربر؟", @@ -498,6 +501,8 @@ "keyboard_shortcuts.translate": "برای ترجمه یک پست", "keyboard_shortcuts.unfocus": "برداشتن تمرکز از ناحیهٔ نوشتن یا جست‌وجو", "keyboard_shortcuts.up": "بالا بردن در سیاهه", + "learn_more_link.got_it": "متوجه شدم", + "learn_more_link.learn_more": "دانستن بیش‌تر", "lightbox.close": "بستن", "lightbox.next": "بعدی", "lightbox.previous": "قبلی", @@ -598,6 +603,7 @@ "notification.label.mention": "اشاره", "notification.label.private_mention": "اشارهٔ خصوصی", "notification.label.private_reply": "پاسخ خصوصی", + "notification.label.quote": "{name} فرسته‌تان را نقل کرد", "notification.label.reply": "پاسخ", "notification.mention": "اشاره", "notification.mentioned_you": "‫{name}‬ از شما نام برد", @@ -655,6 +661,7 @@ "notifications.column_settings.mention": "اشاره‌ها:", "notifications.column_settings.poll": "نتایج نظرسنجی:", "notifications.column_settings.push": "آگاهی‌های ارسالی", + "notifications.column_settings.quote": "نقل‌قول‌ها:", "notifications.column_settings.reblog": "تقویت‌ها:", "notifications.column_settings.show": "نمایش در ستون", "notifications.column_settings.sound": "پخش صدا", @@ -873,6 +880,9 @@ "status.open": "گسترش این فرسته", "status.pin": "سنجاق به نمایه", "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", + "status.quote_error.not_available": "فرسته در دسترس نیست", + "status.quote_error.pending_approval_popout.body": "نقل‌قول‌هایی که در سراسر فدیورس هم‌رسانی می‌شوند ممکن است زمان‌بر باشند تا نمایش داده شوند، چون کارسازهای مختلف از شیوه‌نامه‌های متفاوتی استفاده می‌کنند.", + "status.quote_post_author": "فرسته‌ای از @{name} نقل شد", "status.read_more": "بیشتر بخوانید", "status.reblog": "تقویت", "status.reblog_private": "تقویت برای مخاطبان نخستین", @@ -887,6 +897,7 @@ "status.reply": "پاسخ", "status.replyAll": "پاسخ به رشته", "status.report": "گزارش ‎@{name}", + "status.revoke_quote": "حذف فرسته‌ام از فرسته @{name}", "status.sensitive_warning": "محتوای حساس", "status.share": "هم‌رسانی", "status.show_less_all": "نمایش کمتر همه", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 808a1147be2..7caaf0c2305 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Strika fylgjara", "confirmations.remove_from_followers.message": "{name} fer ikki longur at fylgja tær. Er tú vís/ur í at tú vilt halda fram?", "confirmations.remove_from_followers.title": "Strika fylgjara?", + "confirmations.revoke_quote.confirm": "Strika post", + "confirmations.revoke_quote.message": "Hendan atgerðin kann ikki angrast.", + "confirmations.revoke_quote.title": "Strika post?", "confirmations.unfollow.confirm": "Fylg ikki", "confirmations.unfollow.message": "Ert tú vís/ur í, at tú vil steðga við at fylgja {name}?", "confirmations.unfollow.title": "Gevst at fylgja brúkara?", @@ -600,6 +603,7 @@ "notification.label.mention": "Umrøða", "notification.label.private_mention": "Privat umrøða", "notification.label.private_reply": "Privat svar", + "notification.label.quote": "{name} siteraði postin hjá tær", "notification.label.reply": "Svara", "notification.mention": "Umrøð", "notification.mentioned_you": "{name} nevndi teg", @@ -657,6 +661,7 @@ "notifications.column_settings.mention": "Umrøður:", "notifications.column_settings.poll": "Úrslit frá atkvøðugreiðslu:", "notifications.column_settings.push": "Trýstifráboðanir", + "notifications.column_settings.quote": "Sitatir:", "notifications.column_settings.reblog": "Stimbranir:", "notifications.column_settings.show": "Vís í teigi", "notifications.column_settings.sound": "Spæl ljóð", @@ -894,6 +899,7 @@ "status.reply": "Svara", "status.replyAll": "Svara tráðnum", "status.report": "Melda @{name}", + "status.revoke_quote": "Strika postin hjá mær frá postinum hjá @{name}", "status.sensitive_warning": "Viðkvæmt tilfar", "status.share": "Deil", "status.show_less_all": "Vís øllum minni", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index afdc15e99d1..1f7c3845e1c 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -600,6 +600,7 @@ "notification.label.mention": "Luaigh", "notification.label.private_mention": "Lua príobháideach", "notification.label.private_reply": "Freagra príobháideach", + "notification.label.quote": "Luaigh {name} do phost", "notification.label.reply": "Freagra", "notification.mention": "Luaigh", "notification.mentioned_you": "Luaigh {name} tú", @@ -657,6 +658,7 @@ "notifications.column_settings.mention": "Tráchtanna:", "notifications.column_settings.poll": "Torthaí suirbhéanna:", "notifications.column_settings.push": "Brúfhógraí", + "notifications.column_settings.quote": "Luachain:", "notifications.column_settings.reblog": "Moltaí:", "notifications.column_settings.show": "Taispeáin i gcolún", "notifications.column_settings.sound": "Seinn an fhuaim", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index c1da87693e3..c6d0fbd8066 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Quitar seguidora", "confirmations.remove_from_followers.message": "{name} vai deixar de seguirte. É isto o que queres?", "confirmations.remove_from_followers.title": "Quitar seguidora?", + "confirmations.revoke_quote.confirm": "Eliminar publicación", + "confirmations.revoke_quote.message": "Esta acción non se pode desfacer.", + "confirmations.revoke_quote.title": "Eliminar publicación?", "confirmations.unfollow.confirm": "Deixar de seguir", "confirmations.unfollow.message": "Tes certeza de querer deixar de seguir a {name}?", "confirmations.unfollow.title": "Deixar de seguir á usuaria?", @@ -658,6 +661,7 @@ "notifications.column_settings.mention": "Mencións:", "notifications.column_settings.poll": "Resultados da enquisa:", "notifications.column_settings.push": "Notificacións emerxentes", + "notifications.column_settings.quote": "Citacións:", "notifications.column_settings.reblog": "Promocións:", "notifications.column_settings.show": "Amosar en columna", "notifications.column_settings.sound": "Reproducir son", @@ -895,6 +899,7 @@ "status.reply": "Responder", "status.replyAll": "Responder ao tema", "status.report": "Denunciar @{name}", + "status.revoke_quote": "Retirar a miña publicación da cita de @{name}", "status.sensitive_warning": "Contido sensíbel", "status.share": "Compartir", "status.show_less_all": "Amosar menos para todos", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index 725130e75b6..bdb23bc4cbe 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "הסרת עוקב", "confirmations.remove_from_followers.message": "{name} יוסר/תוסר ממעקב אחריך. האם להמשיך?", "confirmations.remove_from_followers.title": "להסיר עוקב/עוקבת?", + "confirmations.revoke_quote.confirm": "הסרת הודעה", + "confirmations.revoke_quote.message": "פעולה זו אינה הפיכה.", + "confirmations.revoke_quote.title": "הסרת הודעה?", "confirmations.unfollow.confirm": "הפסקת מעקב", "confirmations.unfollow.message": "להפסיק מעקב אחרי {name}?", "confirmations.unfollow.title": "לבטל מעקב אחר המשתמש.ת?", @@ -658,6 +661,7 @@ "notifications.column_settings.mention": "פניות:", "notifications.column_settings.poll": "תוצאות סקר:", "notifications.column_settings.push": "התראות בדחיפה", + "notifications.column_settings.quote": "ציטוטים:", "notifications.column_settings.reblog": "הדהודים:", "notifications.column_settings.show": "הצגה בטור", "notifications.column_settings.sound": "שמע מופעל", @@ -895,6 +899,7 @@ "status.reply": "תגובה", "status.replyAll": "תגובה לשרשור", "status.report": "דיווח על @{name}", + "status.revoke_quote": "הסירו את הודעתי מההודעה של @{name}", "status.sensitive_warning": "תוכן רגיש", "status.share": "שיתוף", "status.show_less_all": "להציג פחות מהכל", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 397c9d14c5a..60740438e18 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -658,6 +658,7 @@ "notifications.column_settings.mention": "Megemlítések:", "notifications.column_settings.poll": "Szavazási eredmények:", "notifications.column_settings.push": "Leküldéses értesítések", + "notifications.column_settings.quote": "Idézetek:", "notifications.column_settings.reblog": "Megtolások:", "notifications.column_settings.show": "Megjelenítés az oszlopban", "notifications.column_settings.sound": "Hang lejátszása", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index b27db1c57ad..a3c9e8733cc 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Fjarlægja fylgjanda", "confirmations.remove_from_followers.message": "{name} mun hætta að fylgjast með þér. Ertu viss um að þú viljir halda áfram?", "confirmations.remove_from_followers.title": "Fjarlægja fylgjanda?", + "confirmations.revoke_quote.confirm": "Fjarlægja færslu", + "confirmations.revoke_quote.message": "Þessa aðgerð er ekki hægt að afturkalla.", + "confirmations.revoke_quote.title": "Fjarlægja færslu?", "confirmations.unfollow.confirm": "Hætta að fylgja", "confirmations.unfollow.message": "Ertu viss um að þú viljir hætta að fylgjast með {name}?", "confirmations.unfollow.title": "Hætta að fylgjast með viðkomandi?", @@ -896,6 +899,7 @@ "status.reply": "Svara", "status.replyAll": "Svara þræði", "status.report": "Kæra @{name}", + "status.revoke_quote": "Fjarlægja færsluna mína úr færslu frá @{name}", "status.sensitive_warning": "Viðkvæmt efni", "status.share": "Deila", "status.show_less_all": "Sýna minna fyrir allt", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 3ae1fd3f24e..b5f4871078d 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Volger verwijderen", "confirmations.remove_from_followers.message": "{name} zal je niet meer volgen. Weet je zeker dat je wilt doorgaan?", "confirmations.remove_from_followers.title": "Volger verwijderen?", + "confirmations.revoke_quote.confirm": "Bericht verwijderen", + "confirmations.revoke_quote.message": "Deze actie kan niet ongedaan worden gemaakt.", + "confirmations.revoke_quote.title": "Bericht verwijderen?", "confirmations.unfollow.confirm": "Ontvolgen", "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?", "confirmations.unfollow.title": "Gebruiker ontvolgen?", @@ -896,6 +899,7 @@ "status.reply": "Reageren", "status.replyAll": "Op iedereen reageren", "status.report": "@{name} rapporteren", + "status.revoke_quote": "Mijn bericht uit het bericht van @{name} verwijderen", "status.sensitive_warning": "Gevoelige inhoud", "status.share": "Delen", "status.show_less_all": "Alles minder tonen", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index cf2e828a45e..fd9d259751f 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Remover seguidor", "confirmations.remove_from_followers.message": "{name} vai parar de seguir-te. Tens a certeza que prentedes continuar?", "confirmations.remove_from_followers.title": "Remover seguidor?", + "confirmations.revoke_quote.confirm": "Remover publicação", + "confirmations.revoke_quote.message": "Esta ação é irreversível.", + "confirmations.revoke_quote.title": "Remover publicação?", "confirmations.unfollow.confirm": "Deixar de seguir", "confirmations.unfollow.message": "De certeza que queres deixar de seguir {name}?", "confirmations.unfollow.title": "Deixar de seguir o utilizador?", @@ -896,6 +899,7 @@ "status.reply": "Responder", "status.replyAll": "Responder à conversa", "status.report": "Denunciar @{name}", + "status.revoke_quote": "Remover a minha publicação da publicação de @{name}", "status.sensitive_warning": "Conteúdo sensível", "status.share": "Partilhar", "status.show_less_all": "Ocultar conteúdo sensível em todas", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 27d5f72c9c4..4043695a160 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Takipçi kaldır", "confirmations.remove_from_followers.message": "{name} sizi takip etmeyi bırakacaktır. Devam etmek istediğinize emin misiniz?", "confirmations.remove_from_followers.title": "Takipçiyi kaldır?", + "confirmations.revoke_quote.confirm": "Gönderiyi kaldır", + "confirmations.revoke_quote.message": "Bu işlem geri alınamaz.", + "confirmations.revoke_quote.title": "Gönderiyi silmek ister misiniz?", "confirmations.unfollow.confirm": "Takibi bırak", "confirmations.unfollow.message": "{name} adlı kullanıcıyı takibi bırakmak istediğinden emin misin?", "confirmations.unfollow.title": "Kullanıcıyı takipten çık?", @@ -658,6 +661,7 @@ "notifications.column_settings.mention": "Bahsetmeler:", "notifications.column_settings.poll": "Anket sonuçları:", "notifications.column_settings.push": "Anlık bildirimler", + "notifications.column_settings.quote": "Alıntılar:", "notifications.column_settings.reblog": "Yeniden paylaşanlar:", "notifications.column_settings.show": "Sütunda göster", "notifications.column_settings.sound": "Ses çal", @@ -895,6 +899,7 @@ "status.reply": "Yanıtla", "status.replyAll": "Konuyu yanıtla", "status.report": "@{name} adlı kişiyi bildir", + "status.revoke_quote": "@{name}'nin gönderisinden benim gönderimi kaldır", "status.sensitive_warning": "Hassas içerik", "status.share": "Paylaş", "status.show_less_all": "Hepsi için daha az göster", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index f33558e6e87..bee00c0f06f 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -227,6 +227,9 @@ "confirmations.redraft.confirm": "Видалити та виправити", "confirmations.redraft.message": "Ви впевнені, що хочете видалити цей допис та переписати його? Додавання у вибране та поширення буде втрачено, а відповіді на оригінальний допис залишаться без першоджерела.", "confirmations.redraft.title": "Видалити та переробити допис?", + "confirmations.revoke_quote.confirm": "Видалити публікацію", + "confirmations.revoke_quote.message": "Цю дію не можна скасувати.", + "confirmations.revoke_quote.title": "Видалити публікацію?", "confirmations.unfollow.confirm": "Відписатися", "confirmations.unfollow.message": "Ви впевнені, що хочете відписатися від {name}?", "confirmations.unfollow.title": "Відписатися від користувача?", @@ -862,6 +865,7 @@ "status.reply": "Відповісти", "status.replyAll": "Відповісти на ланцюжок", "status.report": "Поскаржитися на @{name}", + "status.revoke_quote": "Видалити мою публікацію з допису @{name}", "status.sensitive_warning": "Делікатний вміст", "status.share": "Поділитися", "status.show_less_all": "Згорнути для всіх", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 2d7b6bbf312..37e8ecee8d4 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Xóa người theo dõi", "confirmations.remove_from_followers.message": "{name} sẽ không còn theo dõi bạn.Bạn có chắc tiếp tục?", "confirmations.remove_from_followers.title": "Xóa người theo dõi?", + "confirmations.revoke_quote.confirm": "Gỡ tút", + "confirmations.revoke_quote.message": "Hành động này không thể hoàn tác.", + "confirmations.revoke_quote.title": "Gỡ tút?", "confirmations.unfollow.confirm": "Bỏ theo dõi", "confirmations.unfollow.message": "Bạn có chắc muốn bỏ theo dõi {name}?", "confirmations.unfollow.title": "Bỏ theo dõi", @@ -896,6 +899,7 @@ "status.reply": "Trả lời", "status.replyAll": "Trả lời", "status.report": "Báo cáo @{name}", + "status.revoke_quote": "Gỡ tút của tôi khỏi trích dẫn của @{name}", "status.sensitive_warning": "Nhạy cảm", "status.share": "Chia sẻ", "status.show_less_all": "Thu gọn", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index f917ad623cc..9dc2998504e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "移除跟隨者", "confirmations.remove_from_followers.message": "{name} 將會停止跟隨您。您確定要繼續嗎?", "confirmations.remove_from_followers.title": "是否移除該跟隨者?", + "confirmations.revoke_quote.confirm": "移除嘟文", + "confirmations.revoke_quote.message": "此動作無法復原。", + "confirmations.revoke_quote.title": "您是否確定移除嘟文?", "confirmations.unfollow.confirm": "取消跟隨", "confirmations.unfollow.message": "您確定要取消跟隨 {name} 嗎?", "confirmations.unfollow.title": "是否取消跟隨該使用者?", @@ -896,6 +899,7 @@ "status.reply": "回覆", "status.replyAll": "回覆討論串", "status.report": "檢舉 @{name}", + "status.revoke_quote": "將我的嘟文自 @{name} 之嘟文中移除", "status.sensitive_warning": "敏感內容", "status.share": "分享", "status.show_less_all": "隱藏所有內容警告與額外標籤", diff --git a/config/locales/activerecord.az.yml b/config/locales/activerecord.az.yml index e9ba86bc793..1f7f9375c91 100644 --- a/config/locales/activerecord.az.yml +++ b/config/locales/activerecord.az.yml @@ -1 +1,77 @@ +--- az: + activerecord: + attributes: + poll: + expires_at: Son tarix + options: Seçimlər + user: + agreement: Xidmət razılaşması + email: E-poçt ünvanı + locale: Lokal + password: Parol + user/account: + username: İstifadəçi adı + user/invite_request: + text: Səbəb + errors: + attributes: + domain: + invalid: yararlı bir domen adı deyil + messages: + invalid_domain_on_line: "%{value} yararlı bir domen adı deyil" + models: + account: + attributes: + fields: + fields_with_values_missing_labels: əskik etiketli dəyərlər ehtiva edir + username: + invalid: yalnız hərf, rəqəm və altdan xətt ehtiva etməlidir + reserved: rezerv edilib + admin/webhook: + attributes: + url: + invalid: yararlı bir URL deyil + doorkeeper/application: + attributes: + website: + invalid: yararlı bir URL deyil + import: + attributes: + data: + malformed: yanlış formatdadır + list_account: + attributes: + account_id: + taken: artıq siyahıdadır + must_be_following: izlənilən bir hesab olmalıdır + status: + attributes: + reblog: + taken: göndərişi artıq mövcuddur + terms_of_service: + attributes: + effective_date: + too_soon: çox tezdir, %{date} tarixindən sonra olmalıdır + user: + attributes: + date_of_birth: + below_limit: yaş limitinin altındadır + email: + blocked: icazə verilməyən bir e-poçt provayderi istifadə edir + unreachable: mövcud olaraq görünmür + role_id: + elevated: hazırkı rolunuzdan yüksək ola bilməz + user_role: + attributes: + permissions_as_keys: + dangerous: təməl rol üçün güvənli olmayan icazələri ehtiva edir + elevated: hazırkı rolunuzun sahib olmadığı icazələri ehtiva edə bilməz + own_role: hazırkı rolunuzla dəyişdirilə bilməz + position: + elevated: hazırkı rolunuzdan yüksək ola bilməz + own_role: hazırkı rolunuzla dəyişdirilə bilməz + webhook: + attributes: + events: + invalid_permissions: hüquqlarınız olmayan tədbirləri ehtiva edə bilməz diff --git a/config/locales/az.yml b/config/locales/az.yml index e9ba86bc793..516f03f6040 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -1 +1,168 @@ +--- az: + about: + about_mastodon_html: 'Gələcəyin sosial şəbəkəsi: Reklam yoxdur, korporativ müşahidə yoxdur, etik dizayn və mərkəziyyətsizlik! Mastodon ilə öz verilərinizə sahib çıxın!' + admin: + account_actions: + title: "%{acct} üzərində moderasiya əməliyyatını icra et" + account_moderation_notes: + created_msg: Moderasiya notu uğurla yaradıldı! + destroyed_msg: Moderasiya notu uğurla məhv edildi! + accounts: + delete: Veriləri sil + destroyed_msg: "%{username} - verilərinin tezliklə silinməsi növbədədir" + moderation: + title: Moderasiya + moderation_notes: Moderasiya notları + remote_suspension_irreversible: Bu hesabın veriləri geri qaytarılmayacaq şəkildə silinib. + remote_suspension_reversible_hint_html: Hesabın fəaliyyəti öz serverində dayandırılıb və verilər %{date} tarixində tamamilə silinəcək. O vaxta qədər, uzaq server hər hansısa mənfi təsir olmadan bu hesabı bərpa edə bilər. Hesabın bütün verilərini dərhal silmək istəyirsinizsə, bunu aşağıdan edə bilərsiniz. + reset_password: Parolu sıfırla + security_measures: + only_password: Yalnız parol + password_and_2fa: Parol və 2FA + suspension_irreversible: Bu hesabın veriləri geri qaytarılmayacaq şəkildə silinib. Hesabı istifadəyə yararlı etmək üçün hesab fəaliyyətinin dayandırılma prosesini ləğv edə bilərsiniz, ancaq daha əvvəl sahib olduğunuz heç bir veri geri qaytarılmayacaq. + action_logs: + action_types: + reset_password_user: Parolu sıfırla + actions: + approve_appeal_html: "%{name}, moderasiya qərarına %{target} tərəfindən verilən etirazı təsdiqlədi" + reject_appeal_html: "%{name}, moderasiya qərarına %{target} tərəfindən verilən etirazı rədd etdi" + reset_password_user_html: "%{name}, %{target} istifadəçisinin parolunu sıfırladı" + dashboard: + software: Yazılım + instances: + moderation: + title: Moderasiya + moderation_notes: + create: Moderasiya notu əlavə et + description_html: Notlara baxın, gələcəkdə özünüz və digər moderatorlar üçün notlar buraxın + title: Moderasiya notları + reports: + actions: + suspend_description_html: Hesab və onun bütün məzmunları əlçatmaz olacaq və nəticədə silinəcək və onunla əlaqə qurmaq mümkün olmayacaq. 30 gün ərzində geri qaytarıla bilər. Bu hesaba aid bütün hesabatları bağlayır. + assigned: Təyin edilmiş moderator + confirm_action: "@%{acct} üzərindəki moderasiya əməliyyatını təsdiqlə" + notes_description_html: Notlara baxın, gələcəkdə özünüz və digər moderatorlar üçün notlar buraxın + quick_actions_description_html: 'Cəld bir əməliyyat edin və ya bildirilən məzmuna baxmaq üçün aşağı diyirlərin:' + roles: + categories: + moderation: Moderasiya + privileges: + delete_user_data: İstifadəçi verilərini sil + delete_user_data_description: İstifadəçilərin, digər istifadəçilərin verilərini gecikmə olmadan silməsinə icazə verir + manage_appeals_description: İstifadəçilərin moderasiya əməliyyatlarına etdiyi etirazları incələməsinə icazə verir + manage_reports_description: İstifadəçilərin hesabatları incələməsinə və bunlara qarşı moderasiya əməliyyatlarını icra etməsinə icazə verir + manage_settings: Ayarları idarə et + manage_settings_description: İstifadəçilərin sayt ayarlarını dəyişdirməsinə icazə verir + manage_taxonomies_description: İstifadəçilərin trend məzmunu incələməsinə və mövzu etiketləri ayarlarını güncəlləməsinə icazə verir + manage_user_access: İstifadəçi erişimini idarə et + manage_user_access_description: İstifadəçilərin, digər istifadəçilərin iki faktorlu kimlik doğrulamasını sıradan çıxartmasına, onların e-poçt ünvanlarını dəyişdirməsinə və onların parolunu sıfırlamasına icazə verir + manage_users_description: İstifadəçilərin digər istifadəçilərin detallarını görməsinə və onlara qarşı moderasiya əməliyyatlarını icra etməsinə icazə verir + view_dashboard_description: İstifadəçilərin idarəetmə lövhəsinə və müxtəlif metriklərə erişməsinə icazə verir + view_devops_description: İstifadəçilərin Sidekiq və pgHero idarəetmə lövhələrinə erişməsinə icazə verir + settings: + registrations: + moderation_recommandation: Hər kəs üçün qeydiyyatı açmazdan əvvəl lütfən əmin olun ki, adekvat və reaktiv moderasiya komandanız var! + registrations_mode: + warning_hint: Moderasiya komandanızın spam və zərərli qeydiyyatları vaxtında idarə edə biləcəyinə əmin deyilsinizsə, “Qeydiyyat üçün təsdiq tələb olunur”dan istifadə etməyi tövsiyə edirik. + title: Server ayarları + statuses: + metadata: Meta veri + system_checks: + elasticsearch_version_check: + message_html: 'Uyumlu olmayan Elasticsearch versiyası: %{value}' + tags: + updated_msg: Mövzu etiketi ayarları uğurla güncəlləndi + admin_mailer: + auto_close_registrations: + body: Son vaxtlarda moderator fəaliyyətinin olmamasına görə, %{instance} üzərindəki qeydiyyatlar avtomatik olaraq manual yoxlanış tələb edəcək şəkildə dəyişdirilib, beləliklə %{instance} potensial zərərli aktyorlar tərəfindən istifadə edilən platforma çevrilməyəcək. İstənilən vaxt açıq qeydiyyat rejiminə qaytara bilərsiniz. + new_appeal: + body: "%{target}, %{date} tarixində %{action_taken_by} tərəfindən verilmiş %{type} moderasiya qərarına etiraz edir. Yazılanlar:" + next_steps: Moderasiya qərarını geri almaq üçün etirazı təsdiqləyə, ya da etirazı yox saya bilərsiniz. + subject: "%{username}, %{instance} üzərindəki bir moderasiya qərarına etiraz edir" + appearance: + animations_and_accessibility: Animasiyalar və erişiləbilənlik + applications: + regenerate_token: Erişim tokenini təkrar yarat + token_regenerated: Erişim tokeni uğurla yaradıldı + your_token: Erişim tokeniniz + auth: + confirmations: + wrong_email_hint: Əgər bu e-poçt ünvanı doğru deyilsə, hesab ayarlarında onu dəyişdirə bilərsiniz. + forgot_password: Parolunuzu unutmusunuz? + invalid_reset_password_token: Parol sıfırlama tokeni yararsızdır və ya vaxtı bitib. Lütfən yenisini tələb edin. + link_to_otp: Telefonunuzdan iki faktorlu kodu və ya bir geri qaytarma kodunu daxil edin + reset_password: Parolu sıfırla + rules: + preamble: Bunlar, %{domain} moderatorları tərəfindən təyin edilib və tətbiq edilib. + preamble_invited: Davam etməzdən əvvəl, lütfən %{domain} moderatorları tərəfindən təyin edilmiş qaydaları nəzərdən keçirin. + set_new_password: Yeni parol təyin et + status: + self_destruct: "%{domain} bağlandığı üçün, hesabınıza yalnız məhdud erişiminiz olacaq." + challenge: + hint_html: "İpucu: Sonrakı bir saat ərzində sizdən parolu soruşmayacağıq." + invalid_password: Yararsız parol + prompt: Davam etmək üçün parolu təsdiqlə + deletes: + confirm_password: Kimliyinizi doğrulamaq üçün hazırkı parolunuzu daxil edin + exports: + archive_takeout: + hint_html: "Göndərişlərinizin və yüklədiyiniz medianın bir arxivini tələb edə bilərsiniz. Xaricə köçürülmüş verilər, istənilən uyumlu yazılım tərəfindən oxuna bilən ActivityPub formatında olacaq. Hər 7 gündə bir dəfə arxiv tələb edə bilərsiniz." + filters: + edit: + statuses_hint_html: Bu filtr, aşağıdakı açar sözləri ilə uyuşmasından asılı olmayaraq fərdi göndərişləri seçmək üçün tətbiq olunur. Göndərişləri incələyin və ya filtrdən silin. + generic: + all_matching_items_selected_html: + one: Axtarışınızla uyuşan %{count} element seçilib. + other: Axtarışınızla uyuşan %{count} element seçilib. + select_all_matching_items: + one: Axtarışınızla uyuşan <0>%{count} elementi seçin. + other: Axtarışınızla uyuşan <0>%{count} elementi seçin. + imports: + errors: + incompatible_type: Seçilmiş daxilə köçürmə növü ilə uyumlu deyil + invites: + prompt: Bu serverə erişim icazəsi vermək üçün keçid yaradın və başqaları ilə paylaşın + login_activities: + authentication_methods: + password: parol + description_html: Əgər tanımadığınız bir fəaliyyəti görsəniz, parolunuzu dəyişdirməyi və iki faktorlu kimlik doğrulamanı fəallaşdırmağı düşünə bilərsiniz + migrations: + warning: + disabled_account: Hazırkı hesabınız daha sonra istifadəyə yararsız olacaq. Ancaq, verilərin xaricə köçürülməsinə, həmçinin təkrar aktivləşdirmə prosesinə erişə biləcəksiniz. + moderation: + title: Moderasiya + sessions: + browsers: + edge: Microsoft Edge + settings: + account_settings: Hesab ayarları + development: Gəlişdirmə + strikes: Moderasiya pozuntuları + statuses_cleanup: + enabled_hint: Aşağıdakı istisnalardan heç birinə uyuşmadığı müddətcə, göndərişləriniz qeyd edilmiş yaş həddinə çatdıqda avtomatik silinir + tags: + does_not_match_previous_name: əvvəlki adla uyuşmur + two_factor_authentication: + generate_recovery_codes: Geri qaytarma kodlarını yarat + lost_recovery_codes: Geri qaytarma kodları, telefonunuzu itirdiyiniz halda hesabınıza yenidən erişməyinizə imkan verir. Geri qaytarma kodlarınızı itirsəniz, onları təkrar yarada bilərsiniz. Köhnə geri qaytarma kodlarınız yararsız sayılacaq. + recovery_codes: Geri qaytarma kodlarını nüsxələ + recovery_codes_regenerated: Geri qaytarma kodları uğurla yaradıldı + recovery_instructions_html: Telefonunuza erişə bilmirsinizsə, hesabınıza təkrar erişə bilmək üçün aşağıdakı geri qaytarma kodlarından birini istifadə edə bilərsiniz. Geri qaytarma kodlarını etibarlı yerdə saxlayın. Misal üçün, bunları çap edib digər vacib sənədlərin yanında saxlaya bilərsiniz. + user_mailer: + appeal_approved: + action: Hesab ayarları + suspicious_sign_in: + change_password: parolu dəyişdir + subject: Hesabınıza yeni bir IP ünvanından erişildi + warning: + explanation: + disable: Artıq hesabınızı istifadə edə bilməzsiniz, ancaq profiliniz və digər veriləriniz olduğu kimi qalacaq. Verilərinizin bir nüsxəsini tələb edə, hesab ayarlarınızı dəyişdirə və ya hesabınızı silə bilərsiniz. + suspend: Hesabınızı artıq istifadə edə bilməzsiniz, profiliniz və digər veriləriniz artıq əlçatmazdır. Təxminən 30 gün ərzində verilər tamamilə silinənə qədər verilərinizin bir nüsxəsini tələb etmək üçün hələ də hesabınıza giriş edə bilərsiniz, ancaq hesab fəaliyyətinin dayandırılması prosesini ləğv etməyinizi önləmək üçün bəzi təməl veriləri saxlayacağıq. + welcome: + feature_creativity: Mastodon özünüzü onlayn mühitdə ifadə etməyinizə kömək edəcək səs, video və şəkil göndərişləri, erişiləbilənlik açıqlamaları, anketlər, məzmun xəbərdarlıqları, animasiyalı avatarlar, özəl emojilər, kiçik şəkli kəsmə nəzarəti və daha çoxunu dəstəkləyir. Öz sənətinizi, musiqinizi və ya podkastınızı dərc edirsinizsə, Mastodon sizin üçün buradadır. + feature_moderation_title: Olmalı olduğu şəkildə moderasiya + users: + go_to_sso_account_settings: Kimlik provayderinizin hesab ayarlarına gedin + otp_lost_help_html: Hər ikisinə də erişə bilmirsinizsə, %{email} ilə əlaqə saxlayın + seamless_external_login: Xarici bir server üzərindən giriş etdiyiniz üçün parol və e-poçt ayarları mövcud deyil. diff --git a/config/locales/devise.az.yml b/config/locales/devise.az.yml index 6a02cd7e497..d472634f0a1 100644 --- a/config/locales/devise.az.yml +++ b/config/locales/devise.az.yml @@ -4,16 +4,16 @@ az: confirmations: confirmed: E-poçt ünvanınız uğurla təsdiqləndi. send_instructions: Bir neçə dəqiqə ərzində e-poçt ünvanınızı necə təsdiqləyəcəyinizə dair təlimatları olan bir e-məktub alacaqsınız. Bu e-məktubu almamısınızsa, spam qovluğunuzu yoxlayın. - send_paranoid_instructions: E-poçt ünvanınız verilənlər bazamızda varsa, bir neçə dəqiqədən sonra e-poçt ünvanınızı necə təsdiqləyəcəyinizə dair təlimatları olan bir e-məktub alacaqsınız. Bu e-məktubu almamısınızsa, spam qovluğunuzu yoxlayın. + send_paranoid_instructions: E-poçt ünvanınız veri bazamızda varsa, bir neçə dəqiqə sonra e-poçt ünvanınızı necə təsdiqləyəcəyinizə dair təlimatları olan bir e-poçt alacaqsınız. Bu e-poçtu almamısınızsa, spam qovluğunuzu yoxlayın. failure: already_authenticated: Siz artıq daxil olmusunuz. inactive: Hesabınız hələ aktivləşdirilməyib. - invalid: Səhv %{authentication_keys} və ya parol. + invalid: Yararsız %{authentication_keys} və ya parol. last_attempt: Hesabınız blok olmamışdan əvvəl bir dəfə də cəhdiniz var. - locked: Hesabınız bloklandı. - not_found_in_database: Səhv %{authentication_keys} və ya parol. + locked: Hesabınız kilidlənib. + not_found_in_database: Yararsız %{authentication_keys} və ya parol. omniauth_user_creation_failure: Bu kimlik üçün hesab yaradarkən xəta. - pending: Hesabınız hələ yoxlanışdadır. + pending: Hesabınız hələ incələnir. timeout: Sessiyanın vaxtı bitdi. Xahiş edirik davam etmək üçün yenidən daxil olun. unauthenticated: Davam etmək üçün daxil olmaq və ya qeydiyyatdan keçmək lazımdır. unconfirmed: Davam etmək üçün e-poçt ünvanınızı təsdiqləməlisiniz. @@ -22,25 +22,81 @@ az: action: E-poçt ünvanını təsdiqlə action_with_app: Təsdiqlə və %{app}-a geri qayıt explanation: Siz %{host} saytında bu e-poçt ilə hesab yaratmısınız. Onu aktivləşdirməkdən bir klik uzaqlıqdasınız. Əgər bu siz olmamısınızsa, zəhmət olmasa, bu e-məktuba məhəl qoymayın. - explanation_when_pending: Bu e-poçt ünvanı ilə %{host} saytına dəvət üçün müraciət etmisiniz. Siz e-poçt ünvanınızı təsdiqlədikdən sonra müraciətinizi nəzərdən keçirəcəyik. Siz məlumatlarınızı dəyişdirmək və ya hesabınızı silmək üçün daxil ola bilərsiniz, lakin hesabınız təsdiqlənənə qədər əksər funksiyaları istifadə edə bilməzsiniz. Müraciətiniz rədd edilərsə, məlumatlarınız silinəcək, buna görə də sizdən heç bir tədbir tələb olunmayacaq. Əgər bu siz deyildinizsə, zəhmət, bu e-məktuba məhəl qoymayın. + explanation_when_pending: Bu e-poçt ünvanı ilə %{host} ünvanına dəvət üçün müraciət etmisiniz. E-poçt ünvanınızı təsdiqlədikdən sonra müraciətinizi nəzərdən keçirəcəyik. Hesab məlumatlarını dəyişdirmək və ya hesabınızı silmək üçün giriş edə bilərsiniz, ancaq hesabınız təsdiqlənənə qədər əksər funksiyalara erişə bilməyəcəksiniz. Müraciətinizə rədd cavabı gəlsə, veriləriniz silinəcək, sizdən heç bir əməliyyat etməyiniz istənilməyəcək. Əgər bu siz deyilsinizsə, lütfən bu e-poçtu yox sayın. extra_html: Həmçinin zəhmət olmasa, serverin qaydalarınıistifadə şərtlərini oxuyun. subject: 'Mastodon: %{instance} üçün təsdiqlənmə təlimatları' title: E-poçt ünvanını təsdiqlə email_changed: explanation: 'Hesabınız üçün e-poçt ünvanı buna dəyişdirilir:' - extra: E-poçtunuzu dəyişməmisinizsə, çox güman ki, kimsə hesabınıza giriş əldə edib. Zəhmət olmasa, parolunuzu dərhal dəyişdirin və ya hesabınıza daxil ola bilməyəcəksinizsə, server admini ilə əlaqə saxlayın. + extra: E-poçtunuzu dəyişməmisinizsə, çox güman ki, kimsə hesabınıza erişib. Hesabınıza giriş edə bilmirsinizsə, lütfən parolunuzu dərhal dəyişdirin və ya server admini ilə əlaqə saxlayın. subject: 'Mastodon: E-poçt dəyişdirildi' title: Yeni e-poçt ünvanı password_change: explanation: Hesabınızın parolu dəyişdirilib. - extra: Parolunuzu dəyişməmisinizsə, çox güman ki, kimsə hesabınıza giriş əldə edib. Zəhmət olmasa, parolunuzu dərhal dəyişdirin və ya hesabınıza daxil ola bilməyəcəksinizsə, server admini ilə əlaqə saxlayın. + extra: Parolunuzu dəyişməmisinizsə, çox güman ki, kimsə hesabınıza erişib. Hesabınıza giriş edə bilmirsinizsə, lütfən parolunuzu dərhal dəyişdirin və ya server admini ilə əlaqə saxlayın. subject: 'Mastodon: Parol dəyişdirildi' title: Parol dəyişdirildi reconfirmation_instructions: explanation: E-poçtunuzu dəyişdirmək üçün yeni ünvanı təsdiqləyin. - extra: Əgər bu dəyişiklik sizin tərəfinizdən deyilsə, zəhmət olmasa, bu e-məktuba məhəl qoymayın. Siz yuxarıdakı linkə daxil olana qədər Mastodon hesabının e-poçt ünvanı dəyişməyəcək. + extra: Bu dəyişikliyi siz etməmisinizsə, lütfən bu e-poçtu yox sayın. Yuxarıdakı keçidə erişənə qədər Mastodon hesabının e-poçt ünvanı dəyişməyəcək. subject: 'Mastodon: %{instance} üçün e-poçtu təsdiqlə' title: E-poçt ünvanını təsdiqlə reset_password_instructions: action: Parolu dəyiş explanation: Siz hesabınız üçün yeni parol tələb etmisiniz. + extra: Bunu siz tələb etməmisinizsə, lütfən bu e-poçtu yox sayın. Parolunuz, yuxarıdakı keçidə erişənə və siz yeni birini yaradana qədər dəyişməyəcək. + subject: 'Mastodon: Parol sıfırlama təlimatları' + title: Parolu sıfırla + two_factor_disabled: + explanation: Giriş etmək, indi yalnız e-poçt ünvanı və parol ilə mümkündür. + subject: 'Mastodon: İki faktorlu kimlik doğrulama sıradan çıxarılıb' + subtitle: İki faktorlu kimlik doğrulama hesabınız üçün sıradan çıxarılıb. + title: 2FA sıradan çıxarılıb + two_factor_enabled: + explanation: Giriş etmək üçün cütləşdirilmiş TOTP tətbiqi tərəfindən yaradılmış bir token tələb olunur. + subject: 'Mastodon: İki faktorlu kimlik doğrulama fəaldır' + subtitle: İki faktorlu kimlik doğrulama hesabınız üçün fəallaşdırılıb. + title: 2FA fəaldır + two_factor_recovery_codes_changed: + explanation: Əvvəlki geri qaytarma kodları yararsız sayıldı və yeniləri yaradıldı. + subject: 'Mastodon: İki faktorlu geri qaytarma kodları təkrar yaradılıb' + subtitle: Əvvəlki geri qaytarma kodları yararsız sayıldı və yeniləri yaradıldı. + title: 2FA geri qaytarma kodları dəyişdirilib + unlock_instructions: + subject: 'Mastodon: Kilid açma təlimatları' + webauthn_credential: + added: + explanation: Aşağıdakı güvənlik açarı, hesabınıza əlavə edilib + subject: 'Mastodon: Yeni güvənlik açarı' + title: Yeni bir güvənlik açarı əlavə edilib + deleted: + explanation: Aşağıdakı güvənlik açarı, hesabınızdan silinib + subject: 'Mastodon: Güvənlik açarı silindi' + title: Güvənlik açarlarınızdan biri silinib + webauthn_disabled: + explanation: Güvənlik açarı ilə kimlik doğrulama hesabınız üçün sıradan çıxarılıb. + extra: Giriş, artıq yalnız cütləşdirilmiş TOTP tətbiqi tərəfindən yaradılmış token ilə mümkündür. + subject: 'Mastodon: Güvənlik açarları ilə kimlik doğrulama sıradan çıxarılıb' + title: Güvənlik açarları sıradan çıxarılıb + webauthn_enabled: + explanation: Güvənlik açarı ilə kimlik doğrulama hesabınız üçün fəallaşdırılıb. + extra: Güvənlik açarınız artıq giriş üçün istifadə edilə bilər + subject: 'Mastodon: Güvənlik açarı ilə kimlik doğrulama fəaldır' + title: Güvənlik açarları fəaldır + omniauth_callbacks: + failure: '%{kind} üzərindən kimliyiniz doğrulana bilmədi, çünki "%{reason}".' + success: "%{kind} hesabından kimliyiniz uğurla doğrulandı." + passwords: + no_token: Parol sıfırlama e-poçtunu istifadə etmədən bu səhifəyə erişə bilməzsiniz. Əgər parol sıfırlama e-poçtu ilə bura gəlmisinizsə, lütfən verilmiş tam URL-ni istifadə etdiyinizə əmin olun. + send_instructions: E-poçt ünvanınız veri bazamızda varsa, bir neçə dəqiqə sonra e-poçt ünvanınızda parolu geri qaytarma keçidini alacaqsınız. Bu e-poçtu almamısınızsa, spam qovluğunuzu yoxlayın. + send_paranoid_instructions: E-poçt ünvanınız veri bazamızda varsa, bir neçə dəqiqə sonra e-poçt ünvanınızda parolu geri qaytarma keçidini alacaqsınız. Bu e-poçtu almamısınızsa, spam qovluğunuzu yoxlayın. + updated: Parolunuz uğurla dəyişdirildi. Artıq hesabınıza daxil olmusunuz. + updated_not_active: Parolunuz uğurla dəyişdirildi. + registrations: + destroyed: Xudahafiz! Hesabınız uğurla ləğv edilib. Sizi tezliklə yenidən görməyi ümid edirik. + update_needs_confirmation: Hesabınızı uğurla güncəllədiniz, ancaq yeni e-poçt ünvanını doğrulamağımız lazımdır. Lütfən e-poçtunuzu yoxlayın, yeni e-poçt ünvanınızı təsdiqləmək üçün təsdiq keçidini izləyin. Əgər bu e-poçtu almamısınızsa spam qovluğunu yoxlayın. + updated: Hesabınız uğurla güncəllənib. + sessions: + already_signed_out: Uğurla hesabdan çıxış edildi. + signed_in: Uğurla hesaba daxil olundu. + signed_out: Uğurla hesabdan çıxış edildi. diff --git a/config/locales/doorkeeper.az.yml b/config/locales/doorkeeper.az.yml index e9ba86bc793..aac8511a1cc 100644 --- a/config/locales/doorkeeper.az.yml +++ b/config/locales/doorkeeper.az.yml @@ -1 +1,26 @@ +--- az: + doorkeeper: + errors: + messages: + invalid_token: + expired: Erişim tokeninin vaxtı bitib + revoked: Erişim tokeni ləğv edilib + unknown: Erişim tokeni yararsızdır + grouped_scopes: + access: + read: Yalnız oxuma erişimi + read/write: Oxuma və yazma erişimi + write: Yalnız yazma erişimi + title: + all: Mastodon hesabınıza tam erişim + scopes: + admin:read: serverdəki bütün veriləri oxuma + admin:write: serverdəki bütün veriləri dəyişdirmə + admin:write:accounts: hesablarda moderasiya əməliyyatlarını icra et + admin:write:canonical_email_blocks: + admin:write:domain_allows: domen icazələri üzərində moderasiya əməliyyatlarını icra et + admin:write:ip_blocks: IP əngəlləmələri üzrə moderasiya əməliyyatlarını icra et + admin:write:reports: hesabatlarda moderasiya əməliyyatlarını icra et + read: hesabınızın bütün verilərini oxuma + write: hesabınızın bütün verilərini dəyişdirmə diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 64a29da0953..913f3848844 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -190,6 +190,7 @@ fa: create_relay: ایجاد رله create_unavailable_domain: ایجاد دامنهٔ ناموجود create_user_role: ایجاد نقش + create_username_block: ایجاد قانون نام‌کاربری demote_user: تنزل کاربر destroy_announcement: حذف اعلامیه destroy_canonical_email_block: حذف انسداد رایانامه @@ -203,6 +204,7 @@ fa: destroy_status: حذف وضعیت destroy_unavailable_domain: حذف دامنهٔ ناموجود destroy_user_role: نابودی نقش + destroy_username_block: حذف قانون نام‌کاربری disable_2fa_user: از کار انداختن ورود دومرحله‌ای disable_custom_emoji: از کار انداختن شکلک سفارشی disable_relay: غیرفعال‌سازی رله @@ -237,6 +239,7 @@ fa: update_report: به‌روز رسانی گزارش update_status: به‌روز رسانی وضعیت update_user_role: به روزرسانی نقش + update_username_block: به‌روزرسانی قانون نام‌کاربری actions: approve_appeal_html: "%{name} درخواست تجدیدنظر تصمیم مدیر را از %{target} پذیرفت" approve_user_html: "%{name} ثبت نام %{target} را تایید کرد" @@ -1085,6 +1088,8 @@ fa: other: در هفته گذشته توسط %{count} نفر استفاده شده است title: توصیه ها و روندها trending: پرطرفدار + username_blocks: + delete: حذف warning_presets: add_new: افزودن تازه delete: زدودن @@ -1662,6 +1667,10 @@ fa: title: اشارهٔ جدید poll: subject: نظرسنجی‌ای از %{name} پایان یافت + quote: + body: 'فرسته‌تان توسط %{name} نقل شد:' + subject: "%{name} فرسته‌تان را نقل کرد" + title: نقل‌قول جدید reblog: body: "%{name} فرستهٔ شما را تقویت کرد:" subject: "%{name} فرستهٔ شما را تقویت کرد" @@ -1880,6 +1889,8 @@ fa: ownership: نوشته‌های دیگران را نمی‌توان ثابت کرد reblog: تقویت نمی‌تواند سنجاق شود quote_policies: + followers: تنها پی‌گیرندگانتان + nobody: هیچ‌کس public: هرکسی title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/fo.yml b/config/locales/fo.yml index edac267254c..b4400bfd03e 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1687,6 +1687,10 @@ fo: title: Nýggj umrøða poll: subject: Ein spurnarkanning hjá %{name} er endað + quote: + body: 'Postur tín var siteraður av %{name}:' + subject: "%{name} siteraði postin hjá tær" + title: Nýggj sitering reblog: body: 'Postur tín var stimbraður av %{name}:' subject: "%{name} stimbraði tín post" diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 5e7da40a585..8fc85b9a8db 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1804,6 +1804,10 @@ ga: title: Lua nua poll: subject: Tháinig deireadh le vótaíocht le %{name} + quote: + body: 'Luaigh %{name} do phost:' + subject: Luaigh %{name} do phost + title: Luachan nua reblog: body: 'Treisíodh do phostáil le %{name}:' subject: Mhol %{name} do phostáil diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 3b31b138b8f..e6fb94944b7 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -188,6 +188,7 @@ lt: create_relay: Kurti perdavimą create_unavailable_domain: Kurti nepasiekiamą domeną create_user_role: Kurti vaidmenį + create_username_block: Kurti naudotojo vardo taisyklę demote_user: Pažeminti naudotoją destroy_announcement: Ištrinti skelbimą destroy_custom_emoji: Ištrinti pasirinktinį jaustuką @@ -199,6 +200,7 @@ lt: destroy_status: Ištrinti įrašą destroy_unavailable_domain: Ištrinti nepasiekiamą domeną destroy_user_role: Sunaikinti vaidmenį + destroy_username_block: Ištrinti naudotojo vardo taisyklę disable_2fa_user: Išjungti 2FA disable_custom_emoji: Išjungti pasirinktinį jaustuką disable_relay: Išjungti perdavimą @@ -231,6 +233,7 @@ lt: update_report: Atnaujinti ataskaitą update_status: Atnaujinti įrašą update_user_role: Atnaujinti vaidmenį + update_username_block: Atnaujinti naudotojo vardo taisyklę actions: approve_appeal_html: "%{name} patvirtino prižiūjimo veiksmo apeliaciją iš %{target}" approve_user_html: "%{name} patvirtino registraciją iš %{target}" @@ -245,6 +248,7 @@ lt: create_relay_html: "%{name} sukūrė perdavimą %{target}" create_unavailable_domain_html: "%{name} sustabdė tiekimą į domeną %{target}" create_user_role_html: "%{name} sukūrė %{target} vaidmenį" + create_username_block_html: "%{name} pridėjo taisyklę naudotojo vardams, turintiems %{target}" demote_user_html: "%{name} pažemino naudotoją %{target}" destroy_announcement_html: "%{name} ištrynė skelbimą %{target}" destroy_custom_emoji_html: "%{name} ištrynė jaustuką %{target}" @@ -256,6 +260,7 @@ lt: destroy_status_html: "%{name} pašalino įrašą %{target}" destroy_unavailable_domain_html: "%{name} pratęsė tiekimą į domeną %{target}" destroy_user_role_html: "%{name} ištrynė %{target} vaidmenį" + destroy_username_block_html: "%{name} pašalino taisyklę naudotojo vardams, turintiems %{target}" disable_2fa_user_html: "%{name} išjungė dviejų veiksnių reikalavimą naudotojui %{target}" disable_custom_emoji_html: "%{name} išjungė jaustuką %{target}" disable_relay_html: "%{name} išjungė perdavimą %{target}" @@ -287,6 +292,7 @@ lt: update_report_html: "%{name} atnaujino ataskaitą %{target}" update_status_html: "%{name} atnaujino įrašą %{target}" update_user_role_html: "%{name} pakeitė %{target} vaidmenį" + update_username_block_html: "%{name} atnaujino taisyklę naudotojo vardams, turintiems %{target}" deleted_account: ištrinta paskyra empty: Žurnalų nerasta. filter_by_action: Filtruoti pagal veiksmą @@ -746,6 +752,22 @@ lt: trending_rank: 'Tendencinga #%{rank}' title: Rekomendacijos ir tendencijos trending: Tendencinga + username_blocks: + add_new: Pridėti naują + block_registrations: Blokuoti registracijas + comparison: + contains: Yra + equals: Lygus + contains_html: Yra %{string} + created_msg: Sėkmingai sukurta naudotojo vardo taisyklė. + delete: Ištrinti + edit: + title: Redaguoti naudotojo vardo taisyklę + matches_exactly_html: Lygus %{string} + new: + create: Kurti taisyklę + title: Kurti naują naudotojo vardo taisyklę + no_username_block_selected: Jokios naudotojo vardo taisyklės nebuvo pakeistos, nes nė viena buvo pasirinkta. warning_presets: add_new: Pridėti naują delete: Ištrinti diff --git a/config/locales/ru.yml b/config/locales/ru.yml index a25ac88c547..001e48c650c 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1945,25 +1945,25 @@ ru: unlisted_long: Доступен кому угодно, но не отображается в публичных лентах statuses_cleanup: enabled: Автоматически удалять старые посты - enabled_hint: Автоматически удаляет ваши посты после того, как они достигли определённого возрастного порога, за некоторыми исключениями ниже. + enabled_hint: По истечении определённого срока с момента публикации ваши посты, кроме соответствующих отмеченным ниже исключениям, будут автоматически удалены exceptions: Исключения - explanation: Из-за того, что удаление постов — это ресурсоёмкий процесс, оно производится медленно со временем, когда сервер менее всего загружен. По этой причине, посты могут удаляться не сразу, а спустя определённое время, по достижению возрастного порога. - ignore_favs: Игнорировать избранное - ignore_reblogs: Игнорировать продвижения + explanation: Удаление постов — это ресурсоёмкий процесс, поэтому оно производится постепенно, с течением времени, когда сервер менее всего загружен. По этой причине посты могут удаляться не сразу по прошествии установленного срока, а спустя некоторое время. + ignore_favs: Не учитывать добавление в избранное + ignore_reblogs: Не учитывать продвижения interaction_exceptions: Исключения на основе взаимодействий interaction_exceptions_explanation: 'Обратите внимание: нет никаких гарантий, что посты будут удалены, после того, как они единожды перешли порог по отметкам «избранного» или продвижений.' - keep_direct: Не удалять адресованные посты - keep_direct_hint: Не удалять ваши посты с «адресованной» видимостью. + keep_direct: Не удалять личные сообщения + keep_direct_hint: Те ваши посты, которые видны только упомянутым в них людям, не будут удалены keep_media: Не удалять посты с вложениями - keep_media_hint: Не удалять ваши посты, содержащие любые медийные вложения. + keep_media_hint: Те ваши посты, которые содержат медиавложения, не будут удалены keep_pinned: Не удалять закреплённые посты - keep_pinned_hint: Не удалять ваши посты, которые закреплены в профиле. + keep_pinned_hint: Те ваши посты, которые вы закрепили в своём профиле, не будут удалены keep_polls: Не удалять опросы - keep_polls_hint: Не удалять ваши посты с опросами. - keep_self_bookmark: Не удалять закладки - keep_self_bookmark_hint: Не удалять ваши посты с закладками. - keep_self_fav: Оставить посты, отмеченные «избранными» - keep_self_fav_hint: Не удалять ваши посты, если вы отметили их как «избранные». + keep_polls_hint: Те ваши посты, которые содержат опросы, не будут удалены + keep_self_bookmark: Не удалять посты, добавленные в закладки + keep_self_bookmark_hint: Те ваши посты, которые вы добавили в закладки, не будут удалены + keep_self_fav: Не удалять посты, добавленные в избранное + keep_self_fav_hint: Те ваши посты, которые вы добавили в избранное, не будут удалены min_age: '1209600': 2 недели '15778476': 6 месяцев @@ -1973,11 +1973,11 @@ ru: '604800': 1 неделя '63113904': 2 года '7889238': 3 месяца - min_age_label: Возрастной порог - min_favs: Порог отметок «избранного» - min_favs_hint: Не удалять ваши посты, у которых количество отметок «избранного» достигло указанного выше значения. Оставьте поле пустым, чтобы удалять посты независимо от количества отметок - min_reblogs: Порог продвижений - min_reblogs_hint: Не удаляет ваши посты, количество продвижений которых достигло указанного выше значения. Оставьте поле пустым, чтобы удалять посты независимо от количества продвижений. + min_age_label: Интервал между публикацией и удалением поста + min_favs: Количество звёздочек, при котором пост не будет удалён + min_favs_hint: Те ваши посты, которые были добавлены в избранное столько раз, сколько вы укажете выше, не будут удалены. Оставьте поле пустым, чтобы удалять посты без учёта количества звёздочек + min_reblogs: Количество продвижений, при котором пост не будет удалён + min_reblogs_hint: Те ваши посты, которые были продвинуты столько раз, сколько вы укажете выше, не будут удалены. Оставьте поле пустым, чтобы удалять посты без учёта количества продвижений stream_entries: sensitive_content: Содержимое деликатного характера strikes: diff --git a/config/locales/simple_form.az.yml b/config/locales/simple_form.az.yml index e9ba86bc793..b03505358f8 100644 --- a/config/locales/simple_form.az.yml +++ b/config/locales/simple_form.az.yml @@ -1 +1,32 @@ +--- az: + simple_form: + hints: + admin_account_action: + include_statuses: İstifadəçi, hansı göndərişlərin moderasiya əməliyyatına və ya xəbərdarlığına səbəb olduğunu görəcək + defaults: + current_password: Təhlükəsizlik səbəblərinə görə lütfən hazırkı hesabın parolunu daxil edin + phrase: Mətndəki böyük-kiçik hərfdən və ya göndərişin məzmun xəbərdarlığından asılı olmayaraq uyuşdurulacaq + ip_block: + severities: + no_access: Bütün resurslara erişimi əngəllə + sessions: + otp: 'Telefon tətbiqiniz tərəfindən yaradılmış iki faktorlu kodu daxil edin və ya geri qaytarma kodlarınızdan birini istifadə edin:' + user_role: + permissions_as_keys: Bu rola sahib istifadəçilər bunlara erişə biləcək... + labels: + defaults: + confirm_new_password: Yeni parolu təsdiqlə + confirm_password: Parolu təsdiqlə + current_password: Hazırkı parol + data: Veri + new_password: Yeni parol + password: Parol + setting_system_scrollbars_ui: Sistemin ilkin diyircəyini istifadə edin + ip_block: + severities: + no_access: Erişimi əngəllə + notification_emails: + appeal: Kimsə, bir moderasiya qərarına etiraz edir + username_block: + username: Uyuşacaq söz diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index e96f7d43db5..c9842f07ea0 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -56,6 +56,7 @@ fa: scopes: واسط‌های برنامه‌نویسی که این برنامه به آن دسترسی دارد. اگر بالاترین سطح دسترسی را انتخاب کنید، دیگر نیازی به انتخاب سطح‌های پایینی ندارید. setting_aggregate_reblogs: برای تقویت‌هایی که به تازگی برایتان نمایش داده شده‌اند، تقویت‌های بیشتر را نمایش نده (فقط روی تقویت‌های اخیر تأثیر می‌گذارد) setting_always_send_emails: در حالت عادی آگاهی‌های رایانامه‌ای هنگامی که فعّالانه از ماستودون استفاده می‌کنید فرستاده نمی‌شوند + setting_default_quote_policy: این تنظیمات تنها روی فرسته‌هایی ایجاد شده با نگارش بعدی Mastodon اعمال خواهد شد، اما می‌توانی ترجیحات خود را از قبل انتخاب کنید. setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند setting_display_media_default: تصویرهایی را که به عنوان حساس علامت زده شده‌اند پنهان کن setting_display_media_hide_all: همیشه همهٔ عکس‌ها و ویدیوها را پنهان کن @@ -324,6 +325,7 @@ fa: follow_request: شخصی خواست پیتان بگیرد mention: شخصی از شما نام برد pending_account: حساب تازهٔ نیازمند بررسی + quote: شخصی شما را نقل کرد reblog: شخصی فرسته‌تان را تقویت کرد report: گزارش جدیدی فرستاده شد software_updates: diff --git a/config/locales/simple_form.fo.yml b/config/locales/simple_form.fo.yml index f620d54280a..54e0e98c7de 100644 --- a/config/locales/simple_form.fo.yml +++ b/config/locales/simple_form.fo.yml @@ -329,6 +329,7 @@ fo: follow_request: Onkur biður um at fylgja tær mention: Onkur nevndi teg pending_account: Nýggj konta krevur viðgerð + quote: Onkur siteraði teg reblog: Onkur stimbraði postin hjá tær report: Nýggj melding er send inn software_updates: diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index 0f8c8c3a703..e9afcc8d427 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -332,6 +332,7 @@ ga: follow_request: D'iarr duine éigin tú a leanúint mention: Luaigh duine éigin tú pending_account: Ní mór athbhreithniú a dhéanamh ar chuntas nua + quote: Luaigh duine éigin thú reblog: Mhol duine éigin do phostáil report: Tá tuairisc nua curtha isteach software_updates: From 836a2bfee00de71e4943a99a7b8cfd2ce585f0c5 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 7 Aug 2025 14:52:29 +0200 Subject: [PATCH 243/660] Fix handling of inlined `instrument` in incoming `QuoteRequest` (#35714) --- app/lib/activitypub/activity/quote_request.rb | 25 ++++-- .../activity/quote_request_spec.rb | 80 +++++++++++++------ 2 files changed, 76 insertions(+), 29 deletions(-) diff --git a/app/lib/activitypub/activity/quote_request.rb b/app/lib/activitypub/activity/quote_request.rb index 9f18a1367b5..088360ff981 100644 --- a/app/lib/activitypub/activity/quote_request.rb +++ b/app/lib/activitypub/activity/quote_request.rb @@ -19,13 +19,13 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity private def accept_quote_request!(quoted_status) - status = status_from_uri(@json['instrument']) - # TODO: import inlined quote post if possible - status ||= ActivityPub::FetchRemoteStatusService.new.call(@json['instrument'], on_behalf_of: quoted_status.account, request_id: @options[:request_id]) + status = status_from_uri(instrument_uri) + status ||= import_instrument(quoted_status) + status ||= ActivityPub::FetchRemoteStatusService.new.call(instrument_uri, on_behalf_of: quoted_status.account, request_id: @options[:request_id]) # TODO: raise if status is nil # Sanity check - return unless status.quote.quoted_status == quoted_status + return unless status.quote.quoted_status == quoted_status && status.account == @account status.quote.ensure_quoted_access status.quote.update!(activity_uri: @json['id']) @@ -35,15 +35,30 @@ class ActivityPub::Activity::QuoteRequest < ActivityPub::Activity ActivityPub::DeliveryWorker.perform_async(json, quoted_status.account_id, @account.inbox_url) end + def import_instrument(quoted_status) + return unless @json['instrument'].is_a?(Hash) + + # NOTE: Replacing the object's context by that of the parent activity is + # not sound, but it's consistent with the rest of the codebase + instrument = @json['instrument'].merge({ '@context' => @json['@context'] }) + return if non_matching_uri_hosts?(instrument['id'], @account.uri) + + ActivityPub::FetchRemoteStatusService.new.call(instrument['id'], prefetched_body: instrument, on_behalf_of: quoted_status.account, request_id: @options[:request_id]) + end + def reject_quote_request!(quoted_status) quote = Quote.new( quoted_status: quoted_status, quoted_account: quoted_status.account, - status: Status.new(account: @account, uri: @json['instrument']), + status: Status.new(account: @account, uri: instrument_uri), account: @account, activity_uri: @json['id'] ) json = Oj.dump(serialize_payload(quote, ActivityPub::RejectQuoteRequestSerializer)) ActivityPub::DeliveryWorker.perform_async(json, quoted_status.account_id, @account.inbox_url) end + + def instrument_uri + value_or_id(@json['instrument']) + end end diff --git a/spec/lib/activitypub/activity/quote_request_spec.rb b/spec/lib/activitypub/activity/quote_request_spec.rb index 24a245c8981..64627cbdfbe 100644 --- a/spec/lib/activitypub/activity/quote_request_spec.rb +++ b/spec/lib/activitypub/activity/quote_request_spec.rb @@ -8,6 +8,7 @@ RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do let(:quoted_post) { Fabricate(:status, account: recipient) } let(:request_uri) { 'https://example.com/missing-ui' } let(:quoted_uri) { ActivityPub::TagManager.instance.uri_for(quoted_post) } + let(:instrument) { 'https://example.com/unknown-status' } let(:json) do { @@ -21,8 +22,30 @@ RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do type: 'QuoteRequest', actor: ActivityPub::TagManager.instance.uri_for(sender), object: quoted_uri, - instrument: 'https://example.com/unknown-status', - }.with_indifferent_access + instrument: instrument, + }.deep_stringify_keys + end + + let(:status_json) do + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + { + '@id': 'https://w3id.org/fep/044f#quote', + '@type': '@id', + }, + { + '@id': 'https://w3id.org/fep/044f#quoteAuthorization', + '@type': '@id', + }, + ], + id: 'https://example.com/unknown-status', + type: 'Note', + summary: 'Show more', + content: 'Hello universe', + quote: ActivityPub::TagManager.instance.uri_for(quoted_post), + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + }.deep_stringify_keys end describe '#perform' do @@ -48,29 +71,20 @@ RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do end end - context 'when trying to quote a quotable local status' do - let(:status_json) do - { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - { - '@id': 'https://w3id.org/fep/044f#quote', - '@type': '@id', - }, - { - '@id': 'https://w3id.org/fep/044f#quoteAuthorization', - '@type': '@id', - }, - ], - id: 'https://example.com/unknown-status', - type: 'Note', - summary: 'Show more', - content: 'Hello universe', - quote: ActivityPub::TagManager.instance.uri_for(quoted_post), - attributedTo: ActivityPub::TagManager.instance.uri_for(sender), - }.deep_stringify_keys - end + context 'when trying to quote an unquotable local status with an inlined instrument' do + let(:instrument) { status_json.without('@context') } + it 'sends a Reject activity' do + expect { subject.perform } + .to enqueue_sidekiq_job(ActivityPub::DeliveryWorker) + .with(satisfying do |body| + outgoing_json = Oj.load(body) + outgoing_json['type'] == 'Reject' && json['instrument']['id'] == outgoing_json['object']['instrument'] && %w(type id actor object).all? { |key| json[key] == outgoing_json['object'][key] } + end, recipient.id, sender.inbox_url) + end + end + + context 'when trying to quote a quotable local status' do before do stub_request(:get, 'https://example.com/unknown-status').to_return(status: 200, body: Oj.dump(status_json), headers: { 'Content-Type': 'application/activity+json' }) quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) @@ -86,5 +100,23 @@ RSpec.describe ActivityPub::Activity::QuoteRequest, feature: :outgoing_quotes do end, recipient.id, sender.inbox_url) end end + + context 'when trying to quote a quotable local status with an inlined instrument' do + let(:instrument) { status_json.without('@context') } + + before do + quoted_post.update(quote_approval_policy: Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + end + + it 'accepts the quote and sends an Accept activity' do + expect { subject.perform } + .to change { quoted_post.reload.quotes.accepted.count }.by(1) + .and enqueue_sidekiq_job(ActivityPub::DeliveryWorker) + .with(satisfying do |body| + outgoing_json = Oj.load(body) + outgoing_json['type'] == 'Accept' && json['instrument']['id'] == outgoing_json['object']['instrument'] && %w(type id actor object).all? { |key| json[key] == outgoing_json['object'][key] } + end, recipient.id, sender.inbox_url) + end + end end end From 496a5f423ec49b47741e14bf04bc42684c6cdcb6 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 7 Aug 2025 15:19:18 +0200 Subject: [PATCH 244/660] Inline `instrument` quote post in outgoing `QuoteRequest` activities (#35713) --- .../activitypub/quote_request_serializer.rb | 16 +++++++++++++--- app/workers/activitypub/quote_request_worker.rb | 2 +- .../activitypub/quote_request_worker_spec.rb | 4 +++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/serializers/activitypub/quote_request_serializer.rb b/app/serializers/activitypub/quote_request_serializer.rb index 2b3363fb576..e6e4bb9b036 100644 --- a/app/serializers/activitypub/quote_request_serializer.rb +++ b/app/serializers/activitypub/quote_request_serializer.rb @@ -1,11 +1,22 @@ # frozen_string_literal: true class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer + def self.serializer_for(model, options) + case model.class.name + when 'Status' + ActivityPub::NoteSerializer + else + super + end + end + context_extensions :quote_requests - attributes :id, :type, :actor, :instrument + attributes :id, :type, :actor attribute :virtual_object, key: :object + has_one :instrument + def id object.activity_uri end @@ -23,7 +34,6 @@ class ActivityPub::QuoteRequestSerializer < ActivityPub::Serializer end def instrument - # TODO: inline object? - ActivityPub::TagManager.instance.uri_for(object.status) + instance_options[:allow_post_inlining] && object.status.local? ? object.status : ActivityPub::TagManager.instance.uri_for(object.status) end end diff --git a/app/workers/activitypub/quote_request_worker.rb b/app/workers/activitypub/quote_request_worker.rb index de5e054f69f..0540492f863 100644 --- a/app/workers/activitypub/quote_request_worker.rb +++ b/app/workers/activitypub/quote_request_worker.rb @@ -17,6 +17,6 @@ class ActivityPub::QuoteRequestWorker < ActivityPub::RawDistributionWorker end def payload - @payload ||= Oj.dump(serialize_payload(@quote, ActivityPub::QuoteRequestSerializer, signer: @account)) + @payload ||= Oj.dump(serialize_payload(@quote, ActivityPub::QuoteRequestSerializer, signer: @account, allow_post_inlining: true)) end end diff --git a/spec/workers/activitypub/quote_request_worker_spec.rb b/spec/workers/activitypub/quote_request_worker_spec.rb index 3d0131baaad..b0e10aeffc3 100644 --- a/spec/workers/activitypub/quote_request_worker_spec.rb +++ b/spec/workers/activitypub/quote_request_worker_spec.rb @@ -23,7 +23,9 @@ RSpec.describe ActivityPub::QuoteRequestWorker do type: 'QuoteRequest', actor: ActivityPub::TagManager.instance.uri_for(quote.account), object: ActivityPub::TagManager.instance.uri_for(quoted_status), - instrument: anything # TODO: inline post in request? + instrument: a_hash_including( + id: ActivityPub::TagManager.instance.uri_for(status) + ) ) end end From a485f97d21c3fb7da927e8fd239b301a590ee9b8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 7 Aug 2025 09:47:47 -0400 Subject: [PATCH 245/660] Replace `EmailHelper` module with `normalizes` via model concern (#35702) --- app/helpers/email_helper.rb | 18 --------------- app/models/canonical_email_block.rb | 19 ++++++++++------ app/models/concerns/canonical_email.rb | 27 +++++++++++++++++++++++ spec/models/canonical_email_block_spec.rb | 8 +++++++ 4 files changed, 47 insertions(+), 25 deletions(-) delete mode 100644 app/helpers/email_helper.rb create mode 100644 app/models/concerns/canonical_email.rb diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb deleted file mode 100644 index 0800601f98b..00000000000 --- a/app/helpers/email_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module EmailHelper - def self.included(base) - base.extend(self) - end - - def email_to_canonical_email(str) - username, domain = str.downcase.split('@', 2) - username, = username.delete('.').split('+', 2) - - "#{username}@#{domain}" - end - - def email_to_canonical_email_hash(str) - Digest::SHA2.new(256).hexdigest(email_to_canonical_email(str)) - end -end diff --git a/app/models/canonical_email_block.rb b/app/models/canonical_email_block.rb index d09df6f5e2a..4ed160fc262 100644 --- a/app/models/canonical_email_block.rb +++ b/app/models/canonical_email_block.rb @@ -12,24 +12,29 @@ # class CanonicalEmailBlock < ApplicationRecord - include EmailHelper + include CanonicalEmail include Paginable belongs_to :reference_account, class_name: 'Account', optional: true validates :canonical_email_hash, presence: true, uniqueness: true - scope :matching_email, ->(email) { where(canonical_email_hash: email_to_canonical_email_hash(email)) } + scope :matching_email, ->(email) { where(canonical_email_hash: digest(normalize_value_for(:email, email))) } + + def self.block?(email) + matching_email(email).exists? + end + + def self.digest(value) + Digest::SHA256.hexdigest(value) + end def to_log_human_identifier canonical_email_hash end def email=(email) - self.canonical_email_hash = email_to_canonical_email_hash(email) - end - - def self.block?(email) - matching_email(email).exists? + super + self.canonical_email_hash = self.class.digest(self.email) end end diff --git a/app/models/concerns/canonical_email.rb b/app/models/concerns/canonical_email.rb new file mode 100644 index 00000000000..bbc529ff085 --- /dev/null +++ b/app/models/concerns/canonical_email.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module CanonicalEmail + extend ActiveSupport::Concern + + included do + normalizes :email, with: ->(value) { canonicalize_email(value) } + end + + class_methods do + def canonicalize_email(email) + email + .downcase + .split('@', 2) + .then { |local, domain| [canonical_username(local), domain] } + .join('@') + end + + def canonical_username(username) + username + .to_s + .delete('.') + .split('+', 2) + .first + end + end +end diff --git a/spec/models/canonical_email_block_spec.rb b/spec/models/canonical_email_block_spec.rb index 0b8dcfb9c19..f531fd214e0 100644 --- a/spec/models/canonical_email_block_spec.rb +++ b/spec/models/canonical_email_block_spec.rb @@ -7,6 +7,14 @@ RSpec.describe CanonicalEmailBlock do it { is_expected.to belong_to(:reference_account).class_name('Account').optional } end + describe 'Normalizations' do + describe 'email' do + it { is_expected.to normalize(:email).from('TEST@HOST.EXAMPLE').to('test@host.example') } + it { is_expected.to normalize(:email).from('test+more@host.example').to('test@host.example') } + it { is_expected.to normalize(:email).from('test.user@host.example').to('testuser@host.example') } + end + end + describe 'Scopes' do describe '.matching_email' do subject { described_class.matching_email(email) } From 8ee4b3f906c52bad2e2a899e17047235376a8d83 Mon Sep 17 00:00:00 2001 From: Echo Date: Fri, 8 Aug 2025 10:44:05 +0200 Subject: [PATCH 246/660] Update Redux to handle quote posts (#35715) --- app/javascript/mastodon/actions/compose.js | 2 + .../mastodon/actions/compose_typed.ts | 34 +++++- app/javascript/mastodon/api_types/quotes.ts | 23 ++++ app/javascript/mastodon/api_types/statuses.ts | 2 + app/javascript/mastodon/reducers/compose.js | 23 +++- .../mastodon/store/typed_functions.ts | 106 ++++++++++++++++-- app/models/concerns/user/has_settings.rb | 4 + app/serializers/initial_state_serializer.rb | 1 + 8 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 app/javascript/mastodon/api_types/quotes.ts diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index d70834cec65..28c90381e06 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -228,6 +228,8 @@ export function submitCompose() { visibility: getState().getIn(['compose', 'privacy']), poll: getState().getIn(['compose', 'poll'], null), language: getState().getIn(['compose', 'language']), + quoted_status_id: getState().getIn(['compose', 'quoted_status_id']), + quote_approval_policy: getState().getIn(['compose', 'quote_policy']), }, headers: { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts index 97f0d68c514..7b1f5e688c6 100644 --- a/app/javascript/mastodon/actions/compose_typed.ts +++ b/app/javascript/mastodon/actions/compose_typed.ts @@ -1,9 +1,18 @@ +import { createAction } from '@reduxjs/toolkit'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { apiUpdateMedia } from 'mastodon/api/compose'; import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; -import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; +import { + createDataLoadingThunk, + createAppThunk, +} from 'mastodon/store/typed_functions'; + +import type { ApiQuotePolicy } from '../api_types/quotes'; +import type { Status } from '../models/status'; + +import { ensureComposeIsVisible } from './compose'; type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & { unattached?: boolean; @@ -68,3 +77,26 @@ export const changeUploadCompose = createDataLoadingThunk( useLoadingBar: false, }, ); + +export const quoteComposeByStatus = createAppThunk( + 'compose/quoteComposeStatus', + (status: Status, { getState }) => { + ensureComposeIsVisible(getState); + return status; + }, +); + +export const quoteComposeById = createAppThunk( + (statusId: string, { dispatch, getState }) => { + const status = getState().statuses.get(statusId); + if (status) { + dispatch(quoteComposeByStatus(status)); + } + }, +); + +export const quoteComposeCancel = createAction('compose/quoteComposeCancel'); + +export const setQuotePolicy = createAction( + 'compose/setQuotePolicy', +); diff --git a/app/javascript/mastodon/api_types/quotes.ts b/app/javascript/mastodon/api_types/quotes.ts new file mode 100644 index 00000000000..8c0ea10fc3c --- /dev/null +++ b/app/javascript/mastodon/api_types/quotes.ts @@ -0,0 +1,23 @@ +import type { ApiStatusJSON } from './statuses'; + +export type ApiQuoteState = 'accepted' | 'pending' | 'revoked' | 'unauthorized'; +export type ApiQuotePolicy = 'public' | 'followers' | 'nobody'; + +interface ApiQuoteEmptyJSON { + state: Exclude; + quoted_status: null; +} + +interface ApiNestedQuoteJSON { + state: 'accepted'; + quoted_status_id: string; +} + +interface ApiQuoteAcceptedJSON { + state: 'accepted'; + quoted_status: Omit & { + quote: ApiNestedQuoteJSON | ApiQuoteEmptyJSON; + }; +} + +export type ApiQuoteJSON = ApiQuoteAcceptedJSON | ApiQuoteEmptyJSON; diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index 09bd2349b39..cd0b1001ac5 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -4,6 +4,7 @@ import type { ApiAccountJSON } from './accounts'; import type { ApiCustomEmojiJSON } from './custom_emoji'; import type { ApiMediaAttachmentJSON } from './media_attachments'; import type { ApiPollJSON } from './polls'; +import type { ApiQuoteJSON } from './quotes'; // See app/modals/status.rb export type StatusVisibility = @@ -118,6 +119,7 @@ export interface ApiStatusJSON { card?: ApiPreviewCardJSON; poll?: ApiPollJSON; + quote?: ApiQuoteJSON; } export interface ApiContextJSON { diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 6b799a46e80..c5b3c22ec14 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -1,6 +1,11 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; -import { changeUploadCompose } from 'mastodon/actions/compose_typed'; +import { + changeUploadCompose, + quoteComposeByStatus, + quoteComposeCancel, + setQuotePolicy, +} from 'mastodon/actions/compose_typed'; import { timelineDelete } from 'mastodon/actions/timelines_typed'; import { @@ -83,6 +88,11 @@ const initialState = ImmutableMap({ resetFileKey: Math.floor((Math.random() * 0x10000)), idempotencyKey: null, tagHistory: ImmutableList(), + + // Quotes + quoted_status_id: null, + quote_policy: 'public', + default_quote_policy: 'public', // Set in hydration. }); const initialPoll = ImmutableMap({ @@ -117,6 +127,8 @@ function clearAll(state) { map.set('progress', 0); map.set('poll', null); map.set('idempotencyKey', uuid()); + map.set('quoted_status_id', null); + map.set('quote_policy', state.get('default_quote_policy')); }); } @@ -317,6 +329,15 @@ export const composeReducer = (state = initialState, action) => { return state.set('is_changing_upload', true); } else if (changeUploadCompose.rejected.match(action)) { return state.set('is_changing_upload', false); + } else if (quoteComposeByStatus.match(action)) { + const status = action.payload; + if (status.getIn(['quote_approval', 'current_user']) === 'automatic') { + return state.set('quoted_status_id', status.get('id')); + } + } else if (quoteComposeCancel.match(action)) { + return state.set('quoted_status_id', null); + } else if (setQuotePolicy.match(action)) { + return state.set('quote_policy', action.payload); } switch(action.type) { diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index f0a18a0681a..69f6028be2e 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -1,5 +1,12 @@ -import type { GetThunkAPI } from '@reduxjs/toolkit'; -import { createAsyncThunk, createSelector } from '@reduxjs/toolkit'; +import type { + ActionCreatorWithPreparedPayload, + GetThunkAPI, +} from '@reduxjs/toolkit'; +import { + createAsyncThunk as rtkCreateAsyncThunk, + createSelector, + createAction, +} from '@reduxjs/toolkit'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch, useSelector } from 'react-redux'; @@ -18,7 +25,7 @@ interface AppMeta { useLoadingBar?: boolean; } -export const createAppAsyncThunk = createAsyncThunk.withTypes<{ +export const createAppAsyncThunk = rtkCreateAsyncThunk.withTypes<{ state: RootState; dispatch: AppDispatch; rejectValue: AsyncThunkRejectValue; @@ -43,9 +50,88 @@ interface AppThunkOptions { ) => boolean; } -const createBaseAsyncThunk = createAsyncThunk.withTypes(); +// Type definitions for the sync thunks. +type AppThunk = ( + arg: Arg, +) => (dispatch: AppDispatch, getState: () => RootState) => Returned; -export function createThunk( +type AppThunkCreator = ( + arg: Arg, + api: AppThunkApi, + extra?: ExtraArg, +) => Returned; + +type AppThunkActionCreator< + Arg = void, + Returned = void, +> = ActionCreatorWithPreparedPayload< + [Returned, Arg], + Returned, + string, + never, + { arg: Arg } +>; + +// Version that does not dispatch it's own action. +export function createAppThunk( + creator: AppThunkCreator, + extra?: ExtraArg, +): AppThunk; + +// Version that dispatches an named action with the result of the creator callback. +export function createAppThunk( + name: string, + creator: AppThunkCreator, + extra?: ExtraArg, +): AppThunk & AppThunkActionCreator; + +/** Creates a thunk that dispatches an action. */ +export function createAppThunk( + nameOrCreator: string | AppThunkCreator, + maybeCreatorOrExtra?: AppThunkCreator | ExtraArg, + maybeExtra?: ExtraArg, +) { + const isDispatcher = typeof nameOrCreator === 'string'; + const name = isDispatcher ? nameOrCreator : undefined; + const creator = isDispatcher + ? (maybeCreatorOrExtra as AppThunkCreator) + : nameOrCreator; + const extra = isDispatcher ? maybeExtra : (maybeCreatorOrExtra as ExtraArg); + let action: null | AppThunkActionCreator = null; + + // Creates a thunk that dispatches the action with the result of the creator. + const actionCreator: AppThunk = (arg) => { + return (dispatch, getState) => { + const result = creator(arg, { dispatch, getState }, extra); + if (action) { + // Dispatches the action with the result. + const actionObj = action(result, arg); + dispatch(actionObj); + } + return result; + }; + }; + + // No action name provided, return the thunk directly. + if (!name) { + return actionCreator; + } + + // Create the action and assign the action creator to it in order + // to have things like `toString` and `match` available. + action = createAction(name, (payload: Returned, arg: Arg) => ({ + payload, + meta: { + arg, + }, + })); + + return Object.assign({}, action, actionCreator); +} + +const createBaseAsyncThunk = rtkCreateAsyncThunk.withTypes(); + +export function createAsyncThunk( name: string, creator: (arg: Arg, api: AppThunkApi) => Returned | Promise, options: AppThunkOptions = {}, @@ -104,7 +190,7 @@ export function createDataLoadingThunk( name: string, loadData: (args: Args) => Promise, thunkOptions?: AppThunkOptions, -): ReturnType>; +): ReturnType>; // Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty export function createDataLoadingThunk( @@ -114,7 +200,7 @@ export function createDataLoadingThunk( | AppThunkOptions | OnData, thunkOptions?: AppThunkOptions, -): ReturnType>; +): ReturnType>; // Overload when the `onData` method returns nothing, then the mayload is the `onData` result export function createDataLoadingThunk( @@ -124,7 +210,7 @@ export function createDataLoadingThunk( | AppThunkOptions | OnData, thunkOptions?: AppThunkOptions, -): ReturnType>; +): ReturnType>; // Overload when there is an `onData` method returning something export function createDataLoadingThunk< @@ -138,7 +224,7 @@ export function createDataLoadingThunk< | AppThunkOptions | OnData, thunkOptions?: AppThunkOptions, -): ReturnType>; +): ReturnType>; /** * This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions. @@ -189,7 +275,7 @@ export function createDataLoadingThunk< thunkOptions = maybeThunkOptions; } - return createThunk( + return createAsyncThunk( name, async (arg, { getState, dispatch }) => { const data = await loadData(arg, { diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb index 14d2f22c24e..04ad524c5ac 100644 --- a/app/models/concerns/user/has_settings.rb +++ b/app/models/concerns/user/has_settings.rb @@ -107,6 +107,10 @@ module User::HasSettings settings['default_privacy'] || (account.locked? ? 'private' : 'public') end + def setting_default_quote_policy + settings['default_quote_policy'] || 'public' + end + def allows_report_emails? settings['notification_emails.report'] end diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index cc95d8e7543..7926cc54be9 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -54,6 +54,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:default_privacy] = object.visibility || object_account_user.setting_default_privacy store[:default_sensitive] = object_account_user.setting_default_sensitive store[:default_language] = object_account_user.preferred_posting_language + store[:default_quote_policy] = object_account_user.setting_default_quote_policy end store[:text] = object.text if object.text From 1fd147bf2b7ee9d996a04d94b52ed0842c604a94 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:00:51 +0200 Subject: [PATCH 247/660] New Crowdin Translations (automated) (#35720) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/be.json | 87 +++++++++++- app/javascript/mastodon/locales/da.json | 2 +- app/javascript/mastodon/locales/el.json | 4 + app/javascript/mastodon/locales/fi.json | 4 + app/javascript/mastodon/locales/ga.json | 4 + app/javascript/mastodon/locales/hu.json | 4 + app/javascript/mastodon/locales/kab.json | 20 ++- app/javascript/mastodon/locales/ko.json | 4 + app/javascript/mastodon/locales/si.json | 1 + config/locales/activerecord.be.yml | 10 ++ config/locales/activerecord.kab.yml | 2 + config/locales/be.yml | 173 ++++++++++++++++++++++- config/locales/doorkeeper.be.yml | 1 + config/locales/kab.yml | 4 + config/locales/simple_form.da.yml | 2 +- config/locales/simple_form.kab.yml | 13 +- 16 files changed, 322 insertions(+), 13 deletions(-) diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 706e746988f..2d0842727a1 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -43,7 +43,7 @@ "account.followers": "Падпісчыкі", "account.followers.empty": "Ніхто пакуль не падпісаны на гэтага карыстальніка.", "account.followers_counter": "{count, plural, one {{counter} падпісчык} few {{counter} падпісчыкі} many {{counter} падпісчыкаў} other {{counter} падпісчыка}}", - "account.followers_you_know_counter": "{count, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}", + "account.followers_you_know_counter": "{count, plural, one {{counter}, знаёмы вам} other {{counter}, знаёмых вам}}", "account.following": "Падпіскі", "account.following_counter": "{count, plural, one {{counter} падпіска} few {{counter} падпіскі} many {{counter} падпісак} other {{counter} падпіскі}}", "account.follows.empty": "Карыстальнік ні на каго не падпісаны.", @@ -62,6 +62,7 @@ "account.mute_notifications_short": "Не апавяшчаць", "account.mute_short": "Ігнараваць", "account.muted": "Ігнаруецца", + "account.muting": "Ігнараванне", "account.mutual": "Вы падпісаны адно на аднаго", "account.no_bio": "Апісанне адсутнічае.", "account.open_original_page": "Адкрыць арыгінальную старонку", @@ -103,6 +104,8 @@ "alt_text_modal.add_text_from_image": "Дадаць тэкст з відарыса", "alt_text_modal.cancel": "Скасаваць", "alt_text_modal.change_thumbnail": "Змяніць мініяцюру", + "alt_text_modal.describe_for_people_with_hearing_impairments": "Апішыце гэта людзям з праблемамі са слыхам…", + "alt_text_modal.describe_for_people_with_visual_impairments": "Апішыце гэта людзям з праблемамі са зрокам…", "alt_text_modal.done": "Гатова", "announcement.announcement": "Аб'ява", "annual_report.summary.archetype.booster": "Паляўнічы на трэнды", @@ -216,7 +219,13 @@ "confirmations.delete_list.confirm": "Выдаліць", "confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?", "confirmations.delete_list.title": "Выдаліць спіс?", + "confirmations.discard_draft.confirm": "Адмовіцца і працягнуць", "confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне", + "confirmations.discard_draft.edit.message": "Калі працягнуць, то ўсе змены, што Вы зрабілі ў гэтым допісе, будуць адмененыя.", + "confirmations.discard_draft.edit.title": "Адмовіцца ад змен у Вашым допісе?", + "confirmations.discard_draft.post.cancel": "Працягнуць чарнавік", + "confirmations.discard_draft.post.message": "Калі працягнуць, то допіс, які Вы зараз пішаце, не будзе апублікаваны.", + "confirmations.discard_draft.post.title": "Выдаліць чарнавік?", "confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", @@ -226,6 +235,7 @@ "confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?", "confirmations.logout.title": "Выйсці?", "confirmations.missing_alt_text.confirm": "Дадаць альтэрнатыўны тэкст", + "confirmations.missing_alt_text.message": "У Вашым допісе ёсць медыя без альтэрнатыўнага тэксту. Дадаванне апісання дапамагае зрабіць Ваш допіс даступным для большай колькасці людзей.", "confirmations.missing_alt_text.secondary": "Усё адно апублікаваць", "confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?", "confirmations.mute.confirm": "Ігнараваць", @@ -233,7 +243,11 @@ "confirmations.redraft.message": "Вы ўпэўнены, што хочаце выдаліць допіс і перапісаць яго? Упадабанні і пашырэнні згубяцца, а адказы да арыгінальнага допісу асірацеюць.", "confirmations.redraft.title": "Выдаліць і перапісаць допіс?", "confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка", + "confirmations.remove_from_followers.message": "Карыстальнік {name} больш не будзе падпісаны на Вас. Упэўненыя, што хочаце працягнуць?", "confirmations.remove_from_followers.title": "Выдаліць падпісчыка?", + "confirmations.revoke_quote.confirm": "Выдаліць допіс", + "confirmations.revoke_quote.message": "Гэтае дзеянне немагчыма адмяніць.", + "confirmations.revoke_quote.title": "Выдаліць допіс?", "confirmations.unfollow.confirm": "Адпісацца", "confirmations.unfollow.message": "Вы ўпэўненыя, што хочаце адпісацца ад {name}?", "confirmations.unfollow.title": "Адпісацца ад карыстальніка?", @@ -295,6 +309,9 @@ "emoji_button.search_results": "Вынікі пошуку", "emoji_button.symbols": "Сімвалы", "emoji_button.travel": "Падарожжы і месцы", + "empty_column.account_featured.me": "Вы яшчэ нічога не паказалі. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?", + "empty_column.account_featured.other": "{acct} яшчэ нічога не паказаў. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?", + "empty_column.account_featured_other.unknown": "Гэты профіль яшчэ нічога не паказаў.", "empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю", "empty_column.account_suspended": "Уліковы запіс прыпынены", "empty_column.account_timeline": "Тут няма допісаў!", @@ -327,6 +344,7 @@ "explore.trending_links": "Навіны", "explore.trending_statuses": "Допісы", "explore.trending_tags": "Хэштэгі", + "featured_carousel.header": "{count, plural,one {Замацаваны допіс} other {Замацаваныя допісы}}", "featured_carousel.next": "Далей", "featured_carousel.post": "Допіс", "featured_carousel.previous": "Назад", @@ -383,6 +401,8 @@ "generic.saved": "Захавана", "getting_started.heading": "Пачатак працы", "hashtag.admin_moderation": "Адкрыць інтэрфейс мадэратара для #{name}", + "hashtag.browse": "Праглядзець допісы ў #{hashtag}", + "hashtag.browse_from_account": "Праглядзець допісы ад @{name} у #{hashtag}", "hashtag.column_header.tag_mode.all": "і {additional}", "hashtag.column_header.tag_mode.any": "або {additional}", "hashtag.column_header.tag_mode.none": "без {additional}", @@ -395,7 +415,10 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} удзельнік} few {{counter} удзельніка} many {{counter} удзельнікаў} other {{counter} удзельніка}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}} за сёння", + "hashtag.feature": "Паказваць у профілі", "hashtag.follow": "Падпісацца на хэштэг", + "hashtag.mute": "Ігнараваць #{hashtag}", + "hashtag.unfeature": "Не паказваць у профілі", "hashtag.unfollow": "Адпісацца ад хэштэга", "hashtags.and_other": "…і яшчэ {count, plural, other {#}}", "hints.profiles.followers_may_be_missing": "Падпісчыкі гэтага профілю могуць адсутнічаць.", @@ -424,8 +447,12 @@ "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?", "ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?", "info_button.label": "Даведка", + "info_button.what_is_alt_text": "

Што такое альтэрнатыўны тэкст?

Альтэрнатыўны тэкст апісвае відарыс людзям з парушэннем зроку, павольным злучэннем або тым, каму патрэбны дадатковы кантэкст.

Вы можаце зрабіць відарыс больш дасяжным і зразумелым для ўсіх, напісаўшы зразумелы, сціслы і аб'ектыўны альтэрнатыўны тэкст.

  • Ахоплівайце важныя элементы
  • Тлумачце тэкст на відарысе
  • Карыстайцеся звычайнымі сказамі
  • Пазбягайце залішняй інфармацыі
  • Засяроджвайцеся на тэндэнцыях і ключавых высновах у цяжкіх для разумення візуальных матэрыялах (напрыклад, дыяграмах або картах)
", "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.", "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.", + "interaction_modal.action.reblog": "Каб працягнуць, Вам трэба пашырыць допіс са свайго профілю.", + "interaction_modal.action.reply": "Каб працягнуць, Вам трэба адказаць са свайго профілю.", + "interaction_modal.action.vote": "Каб працягнуць, Вам трэба прагаласаваць са свайго профілю.", "interaction_modal.go": "Перайсці", "interaction_modal.no_account_yet": "Не маеце ўліковага запісу?", "interaction_modal.on_another_server": "На іншым серверы", @@ -434,6 +461,7 @@ "interaction_modal.title.follow": "Падпісацца на {name}", "interaction_modal.title.reblog": "Пашырыць допіс ад {name}", "interaction_modal.title.reply": "Адказаць на допіс {name}", + "interaction_modal.title.vote": "Прагаласуйце ў апытанні {name}", "interaction_modal.username_prompt": "Напр., {example}", "intervals.full.days": "{number, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}", "intervals.full.hours": "{number, plural, one {# гадзіна} few {# гадзіны} many {# гадзін} other {# гадзіны}}", @@ -473,6 +501,8 @@ "keyboard_shortcuts.translate": "каб перакласці допіс", "keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок", "keyboard_shortcuts.up": "Перамясціцца ўверх па спісе", + "learn_more_link.got_it": "Зразумеў(-ла)", + "learn_more_link.learn_more": "Падрабязней", "lightbox.close": "Закрыць", "lightbox.next": "Далей", "lightbox.previous": "Назад", @@ -487,10 +517,15 @@ "lists.add_to_list": "Дадаць у спіс", "lists.add_to_lists": "Дадаць {name} у спісы", "lists.create": "Стварыць", + "lists.create_a_list_to_organize": "Стварыце новы спіс, каб арганізаваць сваю Галоўную старонку", "lists.create_list": "Стварыць спіс", "lists.delete": "Выдаліць спіс", "lists.done": "Гатова", "lists.edit": "Рэдагаваць спіс", + "lists.exclusive": "Схаваць карыстальнікаў на Галоўнай старонцы", + "lists.exclusive_hint": "Калі ў гэтым спісе нехта ёсць, схавайце яго на сваёй Галоўнай старонцы, каб не бачыць яго допісы двойчы.", + "lists.find_users_to_add": "Знайсці карыстальнікаў, каб дадаць", + "lists.list_members_count": "{count, plural,one {# карыстальнік}other {# карыстальнікі}}", "lists.list_name": "Назва спіса", "lists.new_list_name": "Назва новага спіса", "lists.no_lists_yet": "Пакуль няма спісаў.", @@ -502,6 +537,7 @@ "lists.replies_policy.none": "Нікога", "lists.save": "Захаваць", "lists.search": "Пошук", + "lists.show_replies_to": "Уключыць адказы ад карыстальнікаў са спіса", "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", "loading_indicator.label": "Ідзе загрузка…", "media_gallery.hide": "Схаваць", @@ -544,6 +580,8 @@ "navigation_bar.search_trends": "Пошук / Трэндавае", "navigation_panel.collapse_followed_tags": "Згарнуць меню падпісак на хэштэгі", "navigation_panel.collapse_lists": "Згарнуць меню спісаў", + "navigation_panel.expand_followed_tags": "Разгарнуць меню падпісак на хэштэгі", + "navigation_panel.expand_lists": "Разгарнуць меню спіса", "not_signed_in_indicator.not_signed_in": "Вам трэба ўвайсці каб атрымаць доступ да гэтага рэсурсу.", "notification.admin.report": "{name} паскардзіўся на {target}", "notification.admin.report_account": "{name} паскардзіўся на {count, plural, one {# допіс} many {# допісаў} other {# допіса}} ад {target} з прычыны {category}", @@ -551,16 +589,21 @@ "notification.admin.report_statuses": "{name} паскардзіўся на {target} з прычыны {category}", "notification.admin.report_statuses_other": "{name} паскардзіўся на {target}", "notification.admin.sign_up": "{name} зарэгістраваўся", + "notification.admin.sign_up.name_and_others": "{name} і {count, plural, one {# іншы} other {# іншых}} зарэгістраваліся", + "notification.annual_report.message": "Вас чакае Ваш #Wrapstodon нумар {year}! Падзяліцеся сваімі галоўнымі падзеямі і запамінальнымі момантамі ў Mastodon!", "notification.annual_report.view": "Перайсці да #Wrapstodon", - "notification.favourite": "Ваш допіс упадабаны {name}", + "notification.favourite": "Карыстальнік {name} упадабаў Ваш допіс", + "notification.favourite.name_and_others_with_link": "{name} і {count, plural, one {# іншы} other {# іншыя}} ўпадабалі Ваш допіс", "notification.favourite_pm": "Ваша асабістае згадванне ўпадабана {name}", "notification.favourite_pm.name_and_others_with_link": "{name} і {count, plural, one {# іншы} few {# іншыя} many {# іншых} other {# іншых}} ўпадабалі ваша асабістае згадванне", "notification.follow": "{name} падпісаўся на вас", + "notification.follow.name_and_others": "{name} і {count, plural, one {# іншы} other {# іншыя}} падпісаліся на Вас", "notification.follow_request": "{name} адправіў запыт на падпіску", "notification.follow_request.name_and_others": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} запыталіся падпісацца на вас", "notification.label.mention": "Згадванне", "notification.label.private_mention": "Асабістае згадванне", "notification.label.private_reply": "Асабісты адказ", + "notification.label.quote": "Карыстальнік {name} цытаваў Ваш допіс", "notification.label.reply": "Адказ", "notification.mention": "Згадванне", "notification.mentioned_you": "{name} згадаў вас", @@ -576,7 +619,7 @@ "notification.own_poll": "Ваша апытанне скончылася", "notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася", "notification.reblog": "{name} пашырыў ваш допіс", - "notification.reblog.name_and_others_with_link": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} абагулілі ваш допіс", + "notification.reblog.name_and_others_with_link": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} пашырылі ваш допіс", "notification.relationships_severance_event": "Страціў сувязь з {name}", "notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.", "notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.", @@ -585,11 +628,19 @@ "notification.status": "Новы допіс ад {name}", "notification.update": "Допіс {name} адрэдагаваны", "notification_requests.accept": "Прыняць", + "notification_requests.accept_multiple": "{count, plural,one {Прыняць # запыт…} other {Прыняць # запытаў…}}", + "notification_requests.confirm_accept_multiple.button": "{count, plural,one {Прыняць запыт} other {Прыняць запыты}}", + "notification_requests.confirm_accept_multiple.message": "Вы збіраецеся прыняць {count, plural, one {адзін запыт на апавяшчэнне} other {# запытаў на апавяшчэнне}}. Упэўненыя, што хочаце працягнуць?", "notification_requests.confirm_accept_multiple.title": "Прыняць запыты на апавяшчэнні?", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural,one {Адмовіцца ад запыту} other {Адмовіцца ад запытаў}}", + "notification_requests.confirm_dismiss_multiple.message": "Вы збіраецеся адмовіцца ад {count, plural, one {аднаго запыту на апавяшчэнне} other {# запытаў на апавяшчэнне}}. Вы не зможаце зноў лёгка атрымаць доступ да {count, plural, one {яго} other {іх}}. Упэўненыя, што хочаце працягнуць?", "notification_requests.confirm_dismiss_multiple.title": "Адхіліць запыты на апавяшчэнні?", "notification_requests.dismiss": "Адхіліць", + "notification_requests.dismiss_multiple": "{count, plural,one {Адмовіцца ад запыту…} other {Адмовіцца ад запытаў…}}", "notification_requests.edit_selection": "Рэдагаваць", "notification_requests.exit_selection": "Гатова", + "notification_requests.explainer_for_limited_account": "Апавяшчэнне з гэтага профілю было адфільтраванае, бо гэты профіль абмежаваў мадэратар.", + "notification_requests.explainer_for_limited_remote_account": "Апавяшчэнні з гэтага профілю былі адфільтраваныя, бо гэты профіль абмежаваў мадэратар.", "notification_requests.maximize": "Разгарнуць", "notification_requests.minimize_banner": "Згарнуць банер адфільтраваных апавяшчэнняў", "notification_requests.notifications_from": "Апавяшчэнні ад {name}", @@ -610,6 +661,7 @@ "notifications.column_settings.mention": "Згадванні:", "notifications.column_settings.poll": "Вынікі апытання:", "notifications.column_settings.push": "Push-апавяшчэнні", + "notifications.column_settings.quote": "Цытаваныя допісы:", "notifications.column_settings.reblog": "Пашырэнні:", "notifications.column_settings.show": "Паказваць у слупку", "notifications.column_settings.sound": "Прайграваць гук", @@ -633,7 +685,10 @@ "notifications.policy.accept": "Прыняць", "notifications.policy.accept_hint": "Паказваць у апавяшчэннях", "notifications.policy.drop": "Iгнараваць", + "notifications.policy.drop_hint": "Адправіць у бездань, адкуль больш ніколі не ўбачыце", "notifications.policy.filter": "Фільтраваць", + "notifications.policy.filter_hint": "Адправіць у скрыню адфільтраваных апавяшчэнняў", + "notifications.policy.filter_limited_accounts_hint": "Абмежавана мадэратарамі сервера", "notifications.policy.filter_limited_accounts_title": "Уліковыя запісы пад мадэрацыяй", "notifications.policy.filter_new_accounts.hint": "Створаныя на працягу {days, plural, one {апошняга # дня} few {апошніх # дзён} many {апошніх # дзён} other {апошняй # дня}}", "notifications.policy.filter_new_accounts_title": "Новыя ўліковыя запісы", @@ -755,6 +810,7 @@ "report_notification.categories.violation": "Парушэнне правілаў", "report_notification.categories.violation_sentence": "парушэнне правілаў", "report_notification.open": "Адкрыць скаргу", + "search.clear": "Ачысціць пошук", "search.no_recent_searches": "Гісторыя пошуку пустая", "search.placeholder": "Пошук", "search.quick_action.account_search": "Супадзенне профіляў {x}", @@ -796,6 +852,8 @@ "status.bookmark": "Дадаць закладку", "status.cancel_reblog_private": "Прыбраць", "status.cannot_reblog": "Гэты пост нельга пашырыць", + "status.context.load_new_replies": "Даступныя новыя адказы", + "status.context.loading": "Правяраюцца новыя адказы", "status.continued_thread": "Працяг ланцужка", "status.copy": "Скапіраваць спасылку на допіс", "status.delete": "Выдаліць", @@ -807,7 +865,7 @@ "status.edited_x_times": "Рэдагавана {count, plural, one {{count} раз} few {{count} разы} many {{count} разоў} other {{count} разу}}", "status.embed": "Атрымаць убудаваны код", "status.favourite": "Упадабанае", - "status.favourites": "{count, plural, one {# упадабанае} few {# упадабаныя} many {# упадабаных} other {# упадабанага}}", + "status.favourites": "{count, plural, one {упадабанне} few {упадабанні} other {упадабанняў}}", "status.filter": "Фільтраваць гэты допіс", "status.history.created": "Створана {name} {date}", "status.history.edited": "Адрэдагавана {name} {date}", @@ -821,19 +879,27 @@ "status.mute_conversation": "Ігнараваць размову", "status.open": "Разгарнуць гэты допіс", "status.pin": "Замацаваць у профілі", + "status.quote_error.filtered": "Схавана адным з Вашых фільтраў", + "status.quote_error.not_available": "Допіс недаступны", + "status.quote_error.pending_approval": "Допіс чакае пацвярджэння", + "status.quote_error.pending_approval_popout.body": "Допісы, якія былі цытаваныя паміж серверамі Fediverse, могуць доўга загружацца, паколькі розныя серверы маюць розныя пратаколы.", + "status.quote_error.pending_approval_popout.title": "Цытаваны допіс чакае пацвярджэння? Захоўвайце спакой", + "status.quote_post_author": "Цытаваў допіс @{name}", "status.read_more": "Чытаць болей", "status.reblog": "Пашырыць", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю", - "status.reblogged_by": "{name} пашырыў(-ла)", - "status.reblogs": "{count, plural, one {# пашырэнне} few {# пашырэнні} many {# пашырэнняў} other {# пашырэння}}", + "status.reblogged_by": "Карыстальнік {name} пашырыў", + "status.reblogs": "{count, plural, one {пашырэнне} few {пашырэнні} many {пашырэнняў} other {пашырэння}}", "status.reblogs.empty": "Гэты допіс яшчэ ніхто не пашырыў. Калі гэта адбудзецца, гэтых людзей будзе бачна тут.", - "status.redraft": "Выдаліць і паправіць", + "status.redraft": "Выдаліць і перапісаць", "status.remove_bookmark": "Выдаліць закладку", + "status.remove_favourite": "Выдаліць з упадабаных", "status.replied_in_thread": "Адказаў у ланцужку", "status.replied_to": "Адказаў {name}", "status.reply": "Адказаць", "status.replyAll": "Адказаць у ланцугу", "status.report": "Паскардзіцца на @{name}", + "status.revoke_quote": "Выдаліць мой допіс з допісу @{name}", "status.sensitive_warning": "Уражвальны змест", "status.share": "Абагуліць", "status.show_less_all": "Згарнуць усё", @@ -853,7 +919,9 @@ "tabs_bar.notifications": "Апавяшчэнні", "tabs_bar.publish": "Новы допіс", "tabs_bar.search": "Пошук", + "terms_of_service.effective_as_of": "Дзейнічае да {date}", "terms_of_service.title": "Умовы выкарыстання", + "terms_of_service.upcoming_changes_on": "Змены, якія адбудуцца {date}", "time_remaining.days": "{number, plural, one {застаўся # дзень} few {засталося # дні} many {засталося # дзён} other {засталося # дня}}", "time_remaining.hours": "{number, plural, one {засталася # гадзіна} few {засталося # гадзіны} many {засталося # гадзін} other {засталося # гадзіны}}", "time_remaining.minutes": "{number, plural, one {засталася # хвіліна} few {засталося # хвіліны} many {засталося # хвілін} other {засталося # хвіліны}}", @@ -869,6 +937,11 @@ "upload_button.label": "Дадаць выяву, відэа- ці аўдыяфайл", "upload_error.limit": "Перавышана колькасць файлаў.", "upload_error.poll": "Немагчыма прымацаваць файл да апытання.", + "upload_form.drag_and_drop.instructions": "Каб абраць медыя далучэнне, націсніце прабел ці Enter. Падчас перасоўвання выкарыстоўвайце кнопкі са стрэлкамі, каб пасунуць медыя далучэнне ў любым напрамку. Націсніце прабел ці Enter зноў, каб перасунуць медыя далучэнне ў новае месца, або Escape для адмены.", + "upload_form.drag_and_drop.on_drag_cancel": "Перасоўванне адмененае. Медыя ўлажэнне {item} на месцы.", + "upload_form.drag_and_drop.on_drag_end": "Медыя ўлажэнне {item} на месцы.", + "upload_form.drag_and_drop.on_drag_over": "Медыя ўлажэнне {item} перасунутае.", + "upload_form.drag_and_drop.on_drag_start": "Абранае медыя ўлажэнне {item}.", "upload_form.edit": "Рэдагаваць", "upload_progress.label": "Запампоўванне...", "upload_progress.processing": "Апрацоўка…", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 96a6eecf8c6..597990435b6 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -899,7 +899,7 @@ "status.reply": "Besvar", "status.replyAll": "Svar alle", "status.report": "Anmeld @{name}", - "status.revoke_quote": "Fjern mit indlæg fra @{name}'s indlæg", + "status.revoke_quote": "Fjern eget indlæg fra @{name}s indlæg", "status.sensitive_warning": "Følsomt indhold", "status.share": "Del", "status.show_less_all": "Vis mindre for alle", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 58de44bce39..5632def3eab 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Αφαίρεση ακολούθου", "confirmations.remove_from_followers.message": "Ο χρήστης {name} θα σταματήσει να σε ακολουθεί. Σίγουρα θες να συνεχίσεις;", "confirmations.remove_from_followers.title": "Αφαίρεση ακολούθου;", + "confirmations.revoke_quote.confirm": "Αφαίρεση ανάρτησης", + "confirmations.revoke_quote.message": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "confirmations.revoke_quote.title": "Αφαίρεση ανάρτησης;", "confirmations.unfollow.confirm": "Άρση ακολούθησης", "confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};", "confirmations.unfollow.title": "Άρση ακολούθησης;", @@ -896,6 +899,7 @@ "status.reply": "Απάντησε", "status.replyAll": "Απάντησε στο νήμα συζήτησης", "status.report": "Αναφορά @{name}", + "status.revoke_quote": "Αφαίρεση της ανάρτησης μου από την ανάρτηση του/της @{name}", "status.sensitive_warning": "Ευαίσθητο περιεχόμενο", "status.share": "Κοινοποίηση", "status.show_less_all": "Δείξε λιγότερο για όλες", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 2f7c13bfaf8..e1b8d49e112 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Poista seuraaja", "confirmations.remove_from_followers.message": "{name} lakkaa seuraamasta sinua. Haluatko varmasti jatkaa?", "confirmations.remove_from_followers.title": "Poistetaanko seuraaja?", + "confirmations.revoke_quote.confirm": "Poista julkaisu", + "confirmations.revoke_quote.message": "Tätä toimea ei voi peruuttaa.", + "confirmations.revoke_quote.title": "Poistetaanko julkaisu?", "confirmations.unfollow.confirm": "Lopeta seuraaminen", "confirmations.unfollow.message": "Haluatko varmasti lopettaa profiilin {name} seuraamisen?", "confirmations.unfollow.title": "Lopetetaanko käyttäjän seuraaminen?", @@ -896,6 +899,7 @@ "status.reply": "Vastaa", "status.replyAll": "Vastaa ketjuun", "status.report": "Raportoi @{name}", + "status.revoke_quote": "Poista julkaisuni käyttäjän @{name} julkaisusta", "status.sensitive_warning": "Arkaluonteista sisältöä", "status.share": "Jaa", "status.show_less_all": "Näytä kaikista vähemmän", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 1f7c3845e1c..4760b64626e 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Bain leantóir", "confirmations.remove_from_followers.message": "Scoirfidh {name} de bheith ag leanúint leat. An bhfuil tú cinnte gur mian leat leanúint ar aghaidh?", "confirmations.remove_from_followers.title": "Bain an leantóir?", + "confirmations.revoke_quote.confirm": "Bain postáil", + "confirmations.revoke_quote.message": "Ní féidir an gníomh seo a chealú.", + "confirmations.revoke_quote.title": "Bain postáil?", "confirmations.unfollow.confirm": "Ná lean", "confirmations.unfollow.message": "An bhfuil tú cinnte gur mhaith leat {name} a dhíleanúint?", "confirmations.unfollow.title": "Dílean ​​an t-úsáideoir?", @@ -896,6 +899,7 @@ "status.reply": "Freagair", "status.replyAll": "Freagair le snáithe", "status.report": "Tuairiscigh @{name}", + "status.revoke_quote": "Bain mo phost ó phost @{name}", "status.sensitive_warning": "Ábhar íogair", "status.share": "Comhroinn", "status.show_less_all": "Taispeáin níos lú d'uile", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 60740438e18..5ba44f9c758 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Követő eltávolítása", "confirmations.remove_from_followers.message": "{name} követ téged. Biztos, hogy folytatod?", "confirmations.remove_from_followers.title": "Követő eltávolítása?", + "confirmations.revoke_quote.confirm": "Bejegyzés eltávolítása", + "confirmations.revoke_quote.message": "Ez a művelet nem vonható vissza.", + "confirmations.revoke_quote.title": "Bejegyzés eltávolítása?", "confirmations.unfollow.confirm": "Követés visszavonása", "confirmations.unfollow.message": "Biztos, hogy vissza szeretnéd vonni {name} követését?", "confirmations.unfollow.title": "Megszünteted a felhasználó követését?", @@ -896,6 +899,7 @@ "status.reply": "Válasz", "status.replyAll": "Válasz a beszélgetésre", "status.report": "@{name} bejelentése", + "status.revoke_quote": "Saját bejegyzés eltávolítása @{name} bejegyzéséből", "status.sensitive_warning": "Kényes tartalom", "status.share": "Megosztás", "status.show_less_all": "Kevesebbet mindenhol", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index b0bee442cf3..e6e1bf565f0 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -85,9 +85,11 @@ "alt_text_modal.cancel": "Semmet", "alt_text_modal.done": "Immed", "announcement.announcement": "Ulɣu", + "annual_report.summary.followers.followers": "imeḍfaṛen", "annual_report.summary.most_used_app.most_used_app": "asnas yettwasqedcen s waṭas", "annual_report.summary.most_used_hashtag.none": "Ula yiwen", "annual_report.summary.new_posts.new_posts": "tisuffaɣ timaynutin", + "annual_report.summary.percentile.we_wont_tell_bernie": "Ur as-neqqar i yiwen.", "annual_report.summary.thanks": "Tanemmirt imi i tettekkiḍ deg Mastodon!", "audio.hide": "Ffer amesli", "block_modal.show_less": "Ssken-d drus", @@ -123,6 +125,7 @@ "column.firehose": "Isuddam usriden", "column.follow_requests": "Isuturen n teḍfeṛt", "column.home": "Agejdan", + "column.list_members": "Sefrek iεeggalen n tebdart", "column.lists": "Tibdarin", "column.mutes": "Imiḍanen yettwasgugmen", "column.notifications": "Ilɣa", @@ -160,6 +163,7 @@ "compose_form.save_changes": "Leqqem", "compose_form.spoiler.marked": "Kkes aḍris yettwaffren deffir n walɣu", "compose_form.spoiler.unmarked": "Rnu aḍris yettwaffren deffir n walɣu", + "compose_form.spoiler_placeholder": "Alɣu n ugbur (afrayan)", "confirmation_modal.cancel": "Sefsex", "confirmations.block.confirm": "Sewḥel", "confirmations.delete.confirm": "Kkes", @@ -168,8 +172,10 @@ "confirmations.delete_list.confirm": "Kkes", "confirmations.delete_list.message": "Tebɣiḍ s tidet ad tekkseḍ umuɣ-agi i lebda?", "confirmations.delete_list.title": "Tukksa n tebdart?", + "confirmations.discard_draft.confirm": "Ttu-t u kemmel", "confirmations.discard_edit_media.confirm": "Sefsex", "confirmations.follow_to_list.confirm": "Ḍfeṛ-it sakin rnu-t ɣer tebdart", + "confirmations.follow_to_list.title": "Ḍfer aseqdac?", "confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", @@ -178,6 +184,8 @@ "confirmations.missing_alt_text.title": "Rnu aḍris amlellay?", "confirmations.mute.confirm": "Sgugem", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", + "confirmations.remove_from_followers.confirm": "Kkes aneḍfar", + "confirmations.revoke_quote.confirm": "Kkes tasuffeɣt", "confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara", "confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?", "content_warning.hide": "Ffer tasuffeɣt", @@ -203,7 +211,12 @@ "domain_block_modal.you_wont_see_posts": "Ur tettuɣaleḍ ara ttwaliḍ tisuffaɣ neɣ ulɣuten n iseqdacen n uqeddac-a.", "domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeḍwan inmettiyen nniḍen.", "domain_pill.server": "Aqeddac", + "domain_pill.their_handle": "Asulay-is:", "domain_pill.username": "Isem n useqdac", + "domain_pill.whats_in_a_handle": "D acu i yellan deg usulay?", + "domain_pill.who_they_are": "Imi isulayen qqaren-d anwa i d yiwen d wanda yella, tzemreḍ ad temyigweḍ d yemdanen deg web anmetti yebnan s .", + "domain_pill.who_you_are": "Imi isulay-ik·im yeqqar-d anwa i d kečč·kemmi d wanda i telliḍ, zemren medden ad myigwen yid-k·m deg web anmetti yebnan s .", + "domain_pill.your_handle": "Asulay-ik·im:", "domain_pill.your_server": "D axxam-inek·inem umḍin, anda i zedɣent akk tsuffaɣ-ik·im. Ur k·m-yeεǧib ara wa? Ssenfel-d iqeddacen melmi i ak·m-yehwa, awi-d daɣen ineḍfaren-ik·im yid-k·m.", "embed.instructions": "Ẓẓu addad-agi deg usmel-inek·inem s wenɣal n tangalt yellan sdaw-agi.", "embed.preview": "Akka ara d-iban:", @@ -264,6 +277,7 @@ "firehose.remote": "Iqeddacen nniḍen", "follow_request.authorize": "Ssireg", "follow_request.reject": "Agi", + "follow_suggestions.curated_suggestion": "Yettwafren sɣur tarbaɛt", "follow_suggestions.dismiss": "Dayen ur t-id-skan ara", "follow_suggestions.featured_longer": "Yettwafraned s ufus sɣur agraw n {domain}", "follow_suggestions.friends_of_friends_longer": "D aɣeṛfan ar wid i teṭṭafareḍ", @@ -301,6 +315,7 @@ "hashtag.follow": "Ḍfeṛ ahacṭag", "hashtag.mute": "Sgugem #{hashtag}", "hashtags.and_other": "…d {count, plural, one {}other {# nniḍen}}", + "home.column_settings.show_quotes": "Sken-d tibdarin", "home.column_settings.show_reblogs": "Ssken-d beṭṭu", "home.column_settings.show_replies": "Ssken-d tiririyin", "home.hide_announcements": "Ffer ulɣuyen", @@ -354,6 +369,7 @@ "keyboard_shortcuts.toggle_hidden": "i uskan/tuffra n uḍris deffir CW", "keyboard_shortcuts.toggle_sensitivity": "i teskent/tuffra n yimidyaten", "keyboard_shortcuts.toot": "i wakken attebdud tajewwaqt tamaynut", + "keyboard_shortcuts.translate": "i usuqel n tsuffeɣt", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "i tulin ɣer d asawen n tebdart", "learn_more_link.got_it": "Gziɣ-t", @@ -458,6 +474,7 @@ "notifications.column_settings.mention": "Abdar:", "notifications.column_settings.poll": "Igemmaḍ n usenqed:", "notifications.column_settings.push": "Ilɣa yettudemmren", + "notifications.column_settings.quote": "Yebder-d:", "notifications.column_settings.reblog": "Seǧhed:", "notifications.column_settings.show": "Ssken-d tilɣa deg ujgu", "notifications.column_settings.sound": "Rmed imesli", @@ -631,6 +648,7 @@ "status.mute_conversation": "Sgugem adiwenni", "status.open": "Semɣeṛ tasuffeɣt-ayi", "status.pin": "Senteḍ-itt deg umaɣnu", + "status.quote_post_author": "Yebder-d tasuffeɣt sɣur @{name}", "status.read_more": "Issin ugar", "status.reblog": "Bḍu", "status.reblogged_by": "Yebḍa-tt {name}", @@ -650,7 +668,7 @@ "status.show_original": "Sken aɣbalu", "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", "status.translate": "Suqel", - "status.translated_from_with": "Yettwasuqel seg {lang} s {provider}", + "status.translated_from_with": "Tettwasuqel seg {lang} s {provider}", "status.uncached_media_warning": "Ulac taskant", "status.unmute_conversation": "Kkes asgugem n udiwenni", "status.unpin": "Kkes asenteḍ seg umaɣnu", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index e6cdc4e7800..92e3d7cafd1 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "팔로워 제거", "confirmations.remove_from_followers.message": "{name} 님이 나를 팔로우하지 않게 됩니다. 계속할까요?", "confirmations.remove_from_followers.title": "팔로워를 제거할까요?", + "confirmations.revoke_quote.confirm": "게시물 삭제", + "confirmations.revoke_quote.message": "이 작업은 되돌릴 수 없습니다.", + "confirmations.revoke_quote.title": "게시물을 지울까요?", "confirmations.unfollow.confirm": "팔로우 해제", "confirmations.unfollow.message": "정말로 {name} 님을 팔로우 해제하시겠습니까?", "confirmations.unfollow.title": "사용자를 언팔로우 할까요?", @@ -885,6 +888,7 @@ "status.reply": "답장", "status.replyAll": "글타래에 답장", "status.report": "@{name} 신고하기", + "status.revoke_quote": "내 게시물을 @{name}의 게시물에서 삭제", "status.sensitive_warning": "민감한 내용", "status.share": "공유", "status.show_less_all": "모두 접기", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 438ca0b735a..3de45108eee 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -1,6 +1,7 @@ { "about.blocks": "මැදිහත්කරණ සේවාදායක", "about.contact": "සබඳතාව:", + "about.default_locale": "Default", "about.disclaimer": "මාස්ටඩන් යනු නිදහස් විවෘත මූලාශ්‍ර මෘදුකාංගයකි. එය මාස්ටඩන් gGmbH හි වෙළඳ නාමයකි.", "about.domain_blocks.no_reason_available": "හේතුව ලබා ගත නොහැක.", "about.domain_blocks.preamble": "Mastodon සාමාන්‍යයෙන් ඔබට fediverse හි වෙනත් ඕනෑම සේවාදායකයකින් අන්තර්ගතයන් බැලීමට සහ පරිශීලකයින් සමඟ අන්තර් ක්‍රියා කිරීමට ඉඩ සලසයි. මෙම විශේෂිත සේවාදායකයේ සිදු කර ඇති ව්‍යතිරේක මේවාය.", diff --git a/config/locales/activerecord.be.yml b/config/locales/activerecord.be.yml index 73f6f67f01a..975bc1a7047 100644 --- a/config/locales/activerecord.be.yml +++ b/config/locales/activerecord.be.yml @@ -18,9 +18,13 @@ be: attributes: domain: invalid: не з’яўляецца сапраўдным даменным імем + messages: + invalid_domain_on_line: "%{value} не пасуе для карэктнай назвы сервера" models: account: attributes: + fields: + fields_with_values_missing_labels: утрымлівае значэнні без апісанняў username: invalid: павінна змяшчаць толькі літары, лічбы і ніжнія падкрэсліванні reserved: зарэзервавана @@ -45,8 +49,14 @@ be: attributes: reblog: taken: гэтага допісу ўжо існуе + terms_of_service: + attributes: + effective_date: + too_soon: занадта рана, мусіць быць пасля %{date} user: attributes: + date_of_birth: + below_limit: ніжэй за дазволены ўзрост email: blocked: выкарыстоўвае забароненую крыніцу электроннай пошты unreachable: не існуе diff --git a/config/locales/activerecord.kab.yml b/config/locales/activerecord.kab.yml index 8cdc6501cbb..8eb8ff24d02 100644 --- a/config/locales/activerecord.kab.yml +++ b/config/locales/activerecord.kab.yml @@ -39,6 +39,8 @@ kab: taken: n iddaden yellan yakan user: attributes: + date_of_birth: + below_limit: ddaw n talast n leɛmeṛ email: blocked: isseqdac asaǧǧaw n yimayl ur yettusirgen ara unreachable: ur d-ttban ara d akken yella diff --git a/config/locales/be.yml b/config/locales/be.yml index 2855230b156..3cd6effdc85 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -25,6 +25,7 @@ be: one: Допіс other: Допісы posts_tab_heading: Допісы + self_follow_error: Нельга падпісацца на свой профіль admin: account_actions: action: Выканаць дзеянне @@ -51,6 +52,7 @@ be: title: Змяніць адрас эл. пошты для %{username} change_role: changed_msg: Роля паспяхова зменена! + edit_roles: Наладзіць ролі карыстальнікаў label: Змяніць ролю no_role: Няма ролі title: Змяніць ролю для %{username} @@ -194,6 +196,7 @@ be: create_relay: Стварыць рэтранслятар create_unavailable_domain: Стварыць недаступны Дамен create_user_role: Стварыць ролю + create_username_block: Стварыць правіла імя карыстальніка demote_user: Панізіць карыстальніка destroy_announcement: Выдаліць аб'яву destroy_canonical_email_block: Выдаліць блакіроўку электроннай пошты @@ -207,6 +210,7 @@ be: destroy_status: Выдаліць допіс destroy_unavailable_domain: Выдаліць недаступны дамен destroy_user_role: Выдаліць ролю + destroy_username_block: Выдаліць правіла імя карыстальніка disable_2fa_user: Адключыць двухэтапнае спраўджанне disable_custom_emoji: Адключыць адвольныя эмодзі disable_relay: Выключыць рэтранслятар @@ -241,6 +245,7 @@ be: update_report: Абнавіць скаргу update_status: Абнавіць допіс update_user_role: Абнавіць ролю + update_username_block: Абнавіць правіла імя карыстальніка actions: approve_appeal_html: Карыстальнік %{name} ухваліў запыт на мадэрацыю %{target} approve_user_html: "%{name} пацвердзіў рэгістрацыю ад %{target}" @@ -259,6 +264,7 @@ be: create_relay_html: "%{name} стварыў(-ла) рэтранслятар %{target}" create_unavailable_domain_html: "%{name} прыпыніў дастаўку да дамена %{target}" create_user_role_html: "%{name} зрабіў ролю %{target}" + create_username_block_html: Адміністратар %{name} дадаў правіла для імён карыстальнікаў, у якіх %{target} demote_user_html: "%{name} прыбраў карыстальніка %{target}" destroy_announcement_html: "%{name} выдаліў аб'яву %{target}" destroy_canonical_email_block_html: "%{name} разблакіраваў эл. пошту з хэшам %{target}" @@ -272,6 +278,7 @@ be: destroy_status_html: "%{name} выдаліў допіс %{target}" destroy_unavailable_domain_html: "%{name} дазволіў працягнуць адпраўку на дамен %{target}" destroy_user_role_html: "%{name} выдаліў ролю %{target}" + destroy_username_block_html: Адміністратар %{name} прыбраў правіла для імён карыстальнікаў, у якіх %{target} disable_2fa_user_html: "%{name} амяніў абавязковую двухфактарную верыфікацыю для карыстальніка %{target}" disable_custom_emoji_html: "%{name} заблакіраваў эмодзі %{target}" disable_relay_html: "%{name} выключыў(-ла) рэтранслятар %{target}" @@ -306,6 +313,7 @@ be: update_report_html: "%{name} абнавіў скаргу %{target}" update_status_html: "%{name} абнавіў допіс %{target}" update_user_role_html: "%{name} змяніў ролю %{target}" + update_username_block_html: Адміністратар %{name} змяніў правіла для імён карыстальнікаў, у якіх %{target} deleted_account: выдалены ўліковы запіс empty: Логі не знойдзены. filter_by_action: Фільтраваць па дзеянню @@ -313,6 +321,7 @@ be: title: Аўдыт unavailable_instance: "(імя дамена недаступнае)" announcements: + back: Вярнуцца да аб'яў destroyed_msg: Аб’ява выдалена! edit: title: Рэдагаваць абвестку @@ -321,6 +330,10 @@ be: new: create: Стварыць аб'яву title: Новая аб'ява + preview: + disclaimer: Паколькі карыстальнікі не могуць адмовіцца ад іх, апавяшчэнні па электроннай пошце мусяць выкарыстоўвацца толькі для важных аб'яў, накшталт уцечкі асабістых дадзеных ці зачынення сервера. + explanation_html: 'Ліст будзе дасланы на электронную пошту %{display_count} карыстальнікам. У ім будзе наступнае:' + title: Перадпрагляд аб'явы апавяшчэння publish: Апублікаваць published_msg: Аб'ява паспяхова апублікавана! scheduled_for: Запланавана на %{time} @@ -492,22 +505,32 @@ be: fasp: debug: callbacks: + created_at: Створана delete: Выдаліць ip: IP-адрас + request_body: Запытаць цела + title: Зрабіць дэбаг зваротных выклікаў providers: active: Актыўны base_url: Базавы URL-адрас + callback: Зваротны выклік delete: Выдаліць edit: Рэдагаваць пастаўшчыка finish_registration: Завяршыць рэгістрацыю name: Назва providers: Пастаўшчыкі public_key_fingerprint: Лічбавы адбітак публічнага ключа + registration_requested: Патрабуюцца рэгістрацыя registrations: confirm: Пацвердзіць + description: Вы запыталі рэгістрацыю ад FASP. Адмоўцеся, калі Вы не рабілі гэтага. Калі ж Вы гэта зрабілі, то ўважліва параўнайце імя і ключ перад пацвярджэннем рэгістрацыі. reject: Адхіліць + title: Пацвердзіць рэгістрацыю ў FASP save: Захаваць + select_capabilities: Выбраць здольнасці sign_in: Увайсці + status: Допіс + title: Дапаможныя серверы Fediverse title: FASP follow_recommendations: description_html: "Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы." @@ -586,7 +609,9 @@ be: moderation_notes: create: Дадаць нататку мадэратара created_msg: Нататка мадэратара для экзэмпляра сервера створана! + description_html: Паглядзіце і пакіньце нататкі іншым мадэратарам або сабе ў будучыні destroyed_msg: Нататка мадэратара экзэмпляра сервера выдалена! + placeholder: Інфармацыя пра гэты выпадак, прынятыя меры ці нешта яшчэ, што дапаможа Вам разабрацца з гэтым у будучыні. title: Нататкі мадэратараў private_comment: Прыватны каментарый public_comment: Публічны каментарый @@ -807,15 +832,21 @@ be: description_html: Большасць сцвярджаюць, што прачыталі ўмовы абслугоўвання і згаджаюцца з імі, але звычайна людзі не чытаюць іх да канца, пакуль не ўзнікне праблема. Таму зрабіце правілы вашага сервера простымі з першага погляду, прадставіўшы іх у выглядзе маркіраванага спісу. Старайцеся рабіць правілы кароткімі і простымі, але не разбіваць іх на шмат асобных пунктаў. edit: Рэдагаваць правіла empty: Правілы сервера яшчэ не вызначаны. + move_down: Перасунуць уніз + move_up: Перасунуць уверх title: Правілы сервера translation: Пераклад translations: Пераклады + translations_explanation: Вы можаце па жаданні дадаваць пераклады Вашых правіл. Калі перакладу няма, то пакажацца арыгінальная версія. Калі ласка, заўсёды правярайце, каб пераклад быў такім жа актуальным, як і арыгінал. settings: about: manage_rules: Кіраваць правіламі сервера preamble: Дайце падрабязную інфармацыю аб тым, як сервер працуе, мадэруецца, фінансуецца. rules_hint: Існуе спецыяльная вобласць для правілаў, якіх вашы карыстальнікі павінны прытрымлівацца. title: Пра нас + allow_referrer_origin: + desc: Калі Вашыя карыстальнікі націскаюць спасылкі на знешнія сайты, іх браўзер можа дасылаць адрас Вашага сервера Mastodon як крыніцу спасылкі. Адключыце гэту функцыю, калі яна ўнікальна ідэнтыфікуе Вашых карыстальнікаў (напрыклад, калі гэта персанальны сервер Mastodon). + title: Дазволіць знешнім сайтам бачыць Ваш сервер Mastodon як крыніцу трафіка appearance: preamble: Наладзьце вэб-інтэрфейс Mastodon. title: Выгляд @@ -922,6 +953,8 @@ be: system_checks: database_schema_check: message_html: Ёсць незавершаныя міграцыі базы даных. Запусціце іх, каб пераканацца, што праграма паводзіць сябе належным чынам + elasticsearch_analysis_index_mismatch: + message_html: Налады аналізатара індэксаў Elasticsearch пратэрмінаваныя. Калі ласка, запусціце tootctl search deploy --only-mapping --only=%{value} elasticsearch_health_red: message_html: Кластар Elasticsearch нездаровы (чырвоны статус), функцыі пошуку недаступныя elasticsearch_health_yellow: @@ -990,9 +1023,25 @@ be: generate: Выкарыстаць шаблон generates: action: Згенерыраваць + chance_to_review_html: "Аўтаматычна згенераваныя ўмовы карыстання не будуць аўтаматычна апублікаваныя. У Вас будзе магчымасць паглядзець на вынікі. Калі ласка, дайце неабходныя дэталі, каб працягнуць." + explanation_html: Узор умоў карыстання прадстаўлены выключна з інфармацыйнай мэтай і не павінен успрымацца як юрыдычная кансультацыя ні ў якім пытанні. Калі ласка, правядзіце размову з Вашым уласным юрыдычным кансультантам па Вашай сітуацыі і Вашых канкрэтных юрыдычных пытаннях. + title: Стварэнне ўмоў карыстання + going_live_on_html: Уступяць у сілу %{date} history: Гісторыя live: Дзейнічае + no_history: Пакуль не заўважана ніякіх змен ва ўмовах пагаднення. + no_terms_of_service_html: У Вас пакуль няма ніякіх умоў карыстання. Умовы карыстання ствараюцца для яснасці і абароны ад патэнцыяльных абавязкаў у спрэчках з Вашымі карыстальнікамі. + notified_on_html: Карыстальнікам паведамяць %{date} notify_users: Апавясціць карыстальнікаў + preview: + explanation_html: 'Электронны ліст будзе дасланы %{display_count} карыстальнікам, якія зарэгістраваліся да %{date}. У лісце будзе наступны тэкст:' + send_preview: Адправіць на %{email} для перадпрагляду + send_to_all: + few: Адправіць %{display_count} электронныя лісты + many: Адправіць %{display_count} электронных лістоў + one: Адправіць %{display_count} электронны ліст + other: Адправіць %{display_count} электронных лістоў + title: Перадпрагляд апавяшчэння пра ўмовы карыстання publish: Апублікаваць published_on_html: Апублікавана %{date} save_draft: Захаваць чарнавік @@ -1002,10 +1051,15 @@ be: allow: Дазволіць approved: Пацверджаны confirm_allow: Вы ўпэўнены, што хочаце дазволіць выбраныя тэгі? + confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя хэштэгі? disallow: Забараніць links: allow: Дазволіць спасылка allow_provider: Дазволіць выдаўца + confirm_allow: Вы ўпэўненыя, што хочаце дазволіць абраныя спасылкі? + confirm_allow_provider: Вы ўпэўненыя, што хочаце дазволіць абраныя спасылкі? + confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя спасылкі? + confirm_disallow_provider: Вы ўпэўненыя, што хочаце забараніць абраныя серверы? description_html: Гэта спасылкі, якія зараз часта распаўсюджваюцца ўліковымі запісамі, з якіх ваш сервер бачыць паведамленні. Гэта можа дапамагчы вашым карыстальнікам даведацца, што адбываецца ў свеце. Ніякія спасылкі не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара. Вы таксама можаце дазволіць або адхіліць асобныя спасылкі. disallow: Забараніць спасылку disallow_provider: Дазволіць выдаўца @@ -1031,6 +1085,10 @@ be: statuses: allow: Дазволіць допіс allow_account: Дазволіць аўтара + confirm_allow: Вы ўпэўненыя, што хочаце дазволіць абраныя допісы? + confirm_allow_account: Вы ўпэўненыя, што хочаце дазволіць абраныя профілі? + confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя допісы? + confirm_disallow_account: Вы ўпэўненыя, што хочаце забараніць абраныя профілі? description_html: Гэта допісы, пра якія ведае ваш сервер, што на дадзены момант часта абагульваюцца і падабаюцца людзям. Гэта можа дапамагчы вашым новым і пастаянным карыстальнікам знайсці больш людзей, на якіх можна падпісацца. Ніякія допісы не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара, а аўтар не дазволіць прапанаваць свой уліковы запіс іншым. Вы таксама можаце дазволіць або адхіліць асобныя допісы. disallow: Забараніць допіс disallow_account: Забараніць аўтара @@ -1069,6 +1127,25 @@ be: other: Выкарысталі %{count} чалавек за апошні тыдзень title: Рэкамендацыі і трэнды trending: Трэндавае + username_blocks: + add_new: Дадаць новае + block_registrations: Заблакіраваць рэгістрацыю + comparison: + contains: Мае + equals: Такое ж, як + contains_html: Мае %{string} + created_msg: Правіла імя карыстальніка паспяхова створанае + delete: Выдаліць + edit: + title: Змяніць правіла імя карыстальніка + matches_exactly_html: Такое ж, як %{string} + new: + create: Стварыць правіла + title: Стварыць новае правіла імя карыстальніка + no_username_block_selected: Аніводнае з правіл імён карыстальніка не было змененае, бо аніводнае не было абранае + not_permitted: Забаронена + title: Правілы імені карыстальніка + updated_msg: Правіла імя карыстальніка паспяхова абноўленае warning_presets: add_new: Дадаць новы delete: Выдаліць @@ -1227,6 +1304,7 @@ be: set_new_password: Прызначыць новы пароль setup: email_below_hint_html: Праверце папку са спамам або зрабіце новы запыт. Вы можаце выправіць свой email, калі ён няправільны. + email_settings_hint_html: Націсніце на спасылку, якую мы адправілі на %{email}, каб пачаць карыстацца Mastodon. Мы пакуль пачакаем тут. link_not_received: Не атрымалі спасылку? new_confirmation_instructions_sent: Праз некалькі хвілін вы атрымаеце новы ліст на email са спасылкай для пацверджання! title: Праверце вашу пошту @@ -1235,6 +1313,7 @@ be: title: Уваход у %{domain} sign_up: manual_review: Рэгістрацыі на %{domain} праходзяць ручную праверку нашымі мадэратарамі. Каб дапамагчы нам апрацаваць вашу рэгістрацыю, напішыце крыху пра сябе і чаму вы хочаце мець уліковы запіс на %{domain}. + preamble: З профілем на серверы Mastodon Вы зможаце падпісацца на любога чалавека ў fediverse, незалежна ад таго, на якім серверы знаходзіцца іх профіль. title: Наладзьма вас на %{domain}. status: account_status: Стан уліковага запісу @@ -1246,9 +1325,15 @@ be: view_strikes: Праглядзець мінулыя папярэджанні для вашага ўліковага запісу too_fast: Форма адпраўлена занадта хутка, паспрабуйце яшчэ раз. use_security_key: Выкарыстаеце ключ бяспекі + user_agreement_html: Я прачытаў і згаджаюся з умовамі карыстання і палітыкай прыватнасці + user_privacy_agreement_html: Я прачытаў і згаджаюся з палітыкай прыватнасці author_attribution: example_title: Прыклад тэксту + hint_html: Вы пішаце навіны ці артыкулы ў блогу па-за Mastodon? Кантралюйце, як пазначаецца Вашае аўтарства, калі імі дзеляцца ў Mastodon. + instructions: 'Упэўніцеся, што гэты код прысутнічае ў HTML-кодзе Вашага артыкула:' + more_from_html: Больш ад %{name} s_blog: Блог %{name} + then_instructions: Пасля, дадайце назву сайта публікацыі ў полі знізу. title: Пазначэнне аўтарства challenge: confirm: Працягнуць @@ -1464,6 +1549,68 @@ be: merge_long: Захаваць існуючыя запісы і дадаць новыя overwrite: Перазапісаць overwrite_long: Замяніць бягучыя запісы на новыя + overwrite_preambles: + blocking_html: + few: Вы збіраецеся замяніць свой спіс заблакіраваных карыстальнікаў %{count} карыстальнікамі з %{filename}. + many: Вы збіраецеся замяніць свой спіс заблакіраваных карыстальнікаў %{count} карыстальнікамі з %{filename}. + one: Вы збіраецеся замяніць свой спіс заблакіраваных карыстальнікаў %{count} карыстальнікам з %{filename}. + other: Вы збіраецеся замяніць свой спіс заблакіраваных карыстальнікаў %{count} карыстальнікамі з %{filename}. + bookmarks_html: + few: Вы збіраецеся замяніць свае закладкі %{count} допісамі з %{filename}. + many: Вы збіраецеся замяніць свае закладкі %{count} допісамі з %{filename}. + one: Вы збіраецеся замяніць свае закладкі %{count} допісам з %{filename}. + other: Вы збіраецеся замяніць свае закладкі %{count} допісамі з %{filename}. + domain_blocking_html: + few: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. + many: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. + one: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверам з %{filename}. + other: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. + following_html: + few: Вы збіраецеся падпісацца на %{count} профілі з %{filename} і адпішацеся ад усіх астатніх. + many: Вы збіраецеся падпісацца на %{count} профіляў з %{filename} і адпішацеся ад усіх астатніх. + one: Вы збіраецеся падпісацца на %{count} профіль з %{filename} і адпішацеся ад усіх астатніх. + other: Вы збіраецеся падпісацца на %{count} профіляў з %{filename} і адпішацеся ад усіх астатніх. + lists_html: + few: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профілі будуць дададзеныя ў новыя спісы. + many: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіляў будуць дададзеныя ў новыя спісы. + one: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіль будзе дададзены ў новыя спісы. + other: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіляў будуць дададзеныя ў новыя спісы. + muting_html: + few: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. + many: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. + one: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілем з %{filename}. + other: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. + preambles: + blocking_html: + few: Вы збіраецеся заблакіраваць %{count} профілі з %{filename}. + many: Вы збіраецеся заблакіраваць %{count} профіляў з %{filename}. + one: Вы збіраецеся заблакіраваць %{count} профіль з %{filename}. + other: Вы збіраецеся заблакіраваць %{count} профіляў з %{filename}. + bookmarks_html: + few: Вы збіраецеся дадаць %{count} допісы з %{filename} у Вашыя закладкі. + many: Вы збіраецеся дадаць %{count} допісаў з %{filename} у Вашыя закладкі. + one: Вы збіраецеся дадаць %{count} допіс з %{filename} у Вашыя закладкі. + other: Вы збіраецеся дадаць %{count} допісаў з %{filename} у Вашыя закладкі. + domain_blocking_html: + few: Вы збіраецеся заблакіраваць %{count} серверы з %{filename}. + many: Вы збіраецеся заблакіраваць %{count} сервераў з %{filename}. + one: Вы збіраецеся заблакіраваць %{count} сервер з %{filename}. + other: Вы збіраецеся заблакіраваць %{count} сервераў з %{filename}. + following_html: + few: Вы збіраецеся падпісацца на %{count} профілі з %{filename}. + many: Вы збіраецеся падпісацца на %{count} профіляў з %{filename}. + one: Вы збіраецеся падпісацца на %{count} профіль з %{filename}. + other: Вы збіраецеся падпісацца на %{count} профіляў з %{filename}. + lists_html: + few: Вы збіраецеся дадаць %{count} профілі з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + many: Вы збіраецеся дадаць %{count} профіляў з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + one: Вы збіраецеся дадаць %{count} профіль з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + other: Вы збіраецеся дадаць %{count} профіляў з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + muting_html: + few: Вы збіраецеся пачаць ігнараваць %{count} профілі з %{filename}. + many: Вы збіраецеся пачаць ігнараваць %{count} профіляў з %{filename}. + one: Вы збіраецеся пачаць ігнараваць %{count} профіль з %{filename}. + other: Вы збіраецеся пачаць ігнараваць %{count} профіляў з %{filename}. preface: Вы можаце імпартаваць даныя, экспартаваныя вамі з іншага сервера, напрыклад, спіс людзей, на якіх вы падпісаны або якіх блакуеце. recent_imports: Нядаўнія імпарты states: @@ -1550,6 +1697,7 @@ be: media_attachments: validations: images_and_video: Немагчыма далучыць відэа да допісу, які ўжо змяшчае выявы + not_found: Файл %{ids} не знойдзены або ўжо далучаны да іншага допісу not_ready: Няможна далучыць файлы, апрацоўка якіх яшчэ не скончылася. Паспрабуйце яшчэ раз праз хвілінку! too_many: Немагчыма далучыць больш за 4 файлы migrations: @@ -1617,6 +1765,10 @@ be: title: Новае згадванне poll: subject: Апытанне ад %{name} скончылася + quote: + body: 'Ваш допіс працытаваў карыстальнік %{name}:' + subject: Карыстальнік %{name} працытаваў Ваш допіс + title: Цытаваць reblog: body: "%{name} пашырыў ваш пост:" subject: "%{name} пашырыў ваш допіс" @@ -1696,7 +1848,7 @@ be: follow_failure: Вы не можаце падпісацца на некаторыя акаўнты. follow_selected_followers: Падпісацца на выбраных падпісчыкаў followers: Падпісчыкі - following: Падпісаны + following: Падпіскі invited: Запрошаны last_active: Апошняя актыўнасць most_recent: Даўнасць @@ -1835,6 +1987,7 @@ be: edited_at_html: Адрэдагавана %{date} errors: in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе. + quoted_status_not_found: Выглядае, што допісу, які Вы спрабуеце цытаваць, не існуе. over_character_limit: перавышаная колькасць сімвалаў у %{max} pin_errors: direct: Допісы, бачныя толькі згаданым карыстальнікам, нельга замацаваць @@ -1842,6 +1995,8 @@ be: ownership: Немагчыма замацаваць чужы допіс reblog: Немагчыма замацаваць пашырэнне quote_policies: + followers: Толькі Вашыя падпісчыкі + nobody: Ніхто public: Усе title: '%{name}: "%{quote}"' visibilities: @@ -1896,6 +2051,11 @@ be: does_not_match_previous_name: не супадае з папярэднім імям terms_of_service: title: Умовы выкарыстання + terms_of_service_interstitial: + future_preamble_html: Мы ўносім праўкі ў нашыя ўмовы карыстання, якія пачнуць дзейнічаць %{date}. Мы раім Вам азнаёміцца з абноўленымі ўмовамі. + past_preamble_html: Пасля Вашага апошняга наведвання мы ўнеслі праўкі ў нашыя ўмовы карыстання. Мы раім Вам азнаёміцца з абноўленымі ўмовамі. + review_link: Прачытаць умовы карыстання + title: На %{domain} змяняюцца ўмовы карыстання themes: contrast: Mastodon (высокі кантраст) default: Mastodon (цёмная) @@ -1927,6 +2087,10 @@ be: recovery_instructions_html: Калі раптам вы страціце доступ да свайго тэлефона, вы можаце скарыстаць адзін з кодаў аднаўлення ніжэй каб аднавіць доступ да свайго ўліковага запісу. Захоўвайце іх у бяспечным месцы. Напрыклад, вы можаце раздрукаваць іх і захоўваць разам з іншымі важнымі дакументамі. webauthn: Ключы бяспекі user_mailer: + announcement_published: + description: 'Аб''ява ад адміністратараў %{domain}:' + subject: Аб'ява сэрвісу + title: Аб'ява сэрвісу %{domain} appeal_approved: action: Налады ўліковага запісу explanation: Апеляцыя на папярэджанне супраць вашага ўліковага запісу ад %{strike_date}, якую вы падалі %{appeal_date}, была ўхвалена. Ваш уліковы запіс зноў на добрым рахунку. @@ -1957,7 +2121,13 @@ be: subject: У вас уліковы запіс зайшлі з новага IP-адрасу title: Новы ўваход terms_of_service_changed: + agreement: Працягваючы карыстацца %{domain}, Вы пагаджаецеся з гэтымі ўмовамі. Калі Вы не згодныя з абноўленымі ўмовамі, то можаце ў любы момант адмовіцца ад пагаднення з %{domain}, выдаліўшы свой профіль. + changelog: 'Коратка пра тое, што значыць гэтае абнаўленне:' + description: 'Вы атрымалі дадзены ліст, бо мы ўносім праўкі ў нашыя ўмовы карыстання на %{domain}. Гэтыя абнаўленні ўступяць у сілу %{date}. Мы раім Вам цалкам азнаёміцца з абноўленымі ўмовамі тут:' + description_html: Вы атрымалі дадзены ліст, бо мы ўносім праўкі ў нашыя ўмовы карыстання на %{domain}. Гэтыя абнаўленні ўступяць у сілу %{date}. Мы раім Вам цалкам азнаёміцца з абноўленымі ўмовамі тут. sign_off: Каманда %{domain} + subject: Абнаўленні ў нашых умовах карыстання + subtitle: Змяняюцца ўмовы карыстання на %{domain} title: Важнае абнаўленне warning: appeal: Падаць апеляцыю @@ -2047,6 +2217,7 @@ be: instructions_html: Скапіруйце прыведзены ніжэй код і ўстаўце ў HTML вашага сайта. Затым дадайце адрас вашага сайта ў адно з дадатковых палёў вашага профілю на ўкладцы «рэдагаваць профіль» і захавайце змены. verification: Верыфікацыя verified_links: Вашыя правераныя спасылкі + website_verification: Пацвярджэнне сайта webauthn_credentials: add: Дадаць новы ключ бяспекі create: diff --git a/config/locales/doorkeeper.be.yml b/config/locales/doorkeeper.be.yml index b01f476a203..7f1dadc4cfb 100644 --- a/config/locales/doorkeeper.be.yml +++ b/config/locales/doorkeeper.be.yml @@ -60,6 +60,7 @@ be: error: title: Узнікла памылка new: + prompt_html: "%{client_name} хоча атрымаць дазвол на доступ да Вашага профілю. Ухваляйце гэты запыт толькі калі Вы ведаеце гэту крыніцу і давяраеце ёй." review_permissions: Прагледзець дазволы title: Патрабуецца аўтарызацыя show: diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 4685887a968..a50b2d108af 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -567,6 +567,7 @@ kab: accept: Qbel back: Tuɣalin invited_by: 'Tzemreḍ ad tkecmeḍ ɣer %{domain} s tanemmirt i tinnubga i d-teṭṭfeḍ sɣur :' + preamble: Tiyi ttwasemmant-d yerna ttwaḍemnent sɣur imḍebbren n %{domain}. preamble_invited: Uqbel ad tkemmleḍ, ttxil-k·m ẓer ilugan i d-sbedden yimkariyen n %{domain}. title: Kra n yilugan igejdanen. title_invited: Tettwaɛerḍeḍ. @@ -578,6 +579,7 @@ kab: preamble_html: Kcem ar %{domain} s inekcam-inek n tuqqna. Ma yella yezga-d umiḍan-ik deg uqeddac-nniḍen, ur tezmireḍ ara ad tkecmeḍ sya. title: Akeččum ɣer %{domain} sign_up: + preamble: S umiḍan yellan deg uqeddac-a n Mastodon, ad tizimreḍ ad t-ḍefreḍ yal yiwen nniḍen i yellan deg fediverse, akken yebɣu yili wanda i yella umiḍan-nsen. title: Iyya ad d-nessewjed tiɣawsiwin i %{domain}. status: account_status: Addad n umiḍan @@ -744,6 +746,8 @@ kab: action: Err body: 'Yuder-ik·ikem-id %{name} deg:' subject: Yuder-ik·ikem-id %{name} + quote: + title: Tabdert tamaynut reblog: subject: "%{name} yesselha addad-ik·im" title: Azuzer amaynut diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 296ce49131c..e85dd32556a 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -56,7 +56,7 @@ da: scopes: De API'er, som applikationen vil kunne tilgå. Vælges en topniveaudstrækning, vil detailvalg være unødvendige. setting_aggregate_reblogs: Vis ikke nye fremhævelser for nyligt fremhævede indlæg (påvirker kun nyligt modtagne fremhævelser) setting_always_send_emails: Normalt sendes ingen e-mailnotifikationer under aktivt brug af Mastodon - setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egne præference kan vælges som forberedelse. + setting_default_quote_policy: Denne indstilling træder kun i kraft for indlæg oprettet med den næste Mastodon-version, men egen præference kan vælges som forberedelse. setting_default_sensitive: Sensitive medier er som standard skjult og kan vises med et klik setting_display_media_default: Skjul medier med sensitiv-markering setting_display_media_hide_all: Skjul altid medier diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index d1a288e2139..16c425186bf 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -27,6 +27,8 @@ kab: username: Tzemreḍ ad tesqedceḍ isekkilen, uṭṭunen akked yijerriden n wadda featured_tag: name: 'Ha-t-an kra seg ihacṭagen i tesseqdaceḍ ussan-a ineggura maḍi :' + form_admin_settings: + min_age: Ad ttwasutren yiseqdacen ad sentemen azemz-nsen n tlalit deg ujerred form_challenge: current_password: Tkecmeḍ ɣer temnaḍt taɣellsant imports: @@ -37,15 +39,19 @@ kab: comment: D afrayan. Cfu ɣef wayɣer i terniḍ alugen-a. severities: no_access: Sewḥel anekcu ɣer akk tiɣbula + user: + date_of_birth: + one: Ilaq ad neḍmen belli tesɛiḍ ma ulac %{count} akken ad tesqedceḍ %{domain}. Ur neḥrez ara aya. + other: Ilaq ad neḍmen belli tesɛiḍ ma ulac %{count} akken ad tesqedceḍ %{domain}. Ur neḥrez ara aya. labels: account: fields: name: Tabzimt value: Agbur account_alias: - acct: Tansa n umiḍan aqbur + acct: Asulay n umiḍan aqbur account_migration: - acct: Tansa n umiḍan amaynut + acct: Asulay n umiḍan amaynut account_warning_preset: title: Azwel admin_account_action: @@ -90,12 +96,14 @@ kab: setting_always_send_emails: Dima ttazen-d ilɣa s yimayl setting_default_language: Tutlayt n usuffeɣ setting_default_privacy: Tabaḍnit n usuffeɣ + setting_default_quote_policy: Anwa i izemren ad d-yebder setting_display_media: Askanay n imidyaten setting_display_media_default: Akk-a kan setting_display_media_hide_all: Ffer-iten akk setting_display_media_show_all: Sken-iten-id akk setting_hide_network: Ffer azetta-k·m inmetti setting_theme: Asental n wesmel + setting_trends: Sken-d inezzaɣ n wass-a setting_use_pending_items: Askar aleɣwayan sign_in_token_attempt: Tangalt n tɣellist title: Azwel @@ -116,6 +124,7 @@ kab: status_page_url: URL n uusebter n waddaden theme: Asentel amezwer thumbnail: Tanfult n uqeddac + trends: Rmed inezzaɣ interactions: must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara From 868c46bc7687a8c361060078ad7d33f1e00941a6 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 8 Aug 2025 11:46:09 +0200 Subject: [PATCH 248/660] Add delivery failure handling to FASP jobs (#35723) --- app/models/fasp/provider.rb | 17 ++++ app/workers/fasp/account_search_worker.rb | 16 +-- ...announce_account_lifecycle_event_worker.rb | 10 +- ...announce_content_lifecycle_event_worker.rb | 10 +- app/workers/fasp/announce_trend_worker.rb | 10 +- app/workers/fasp/backfill_worker.rb | 12 +-- app/workers/fasp/base_worker.rb | 19 ++++ .../fasp/follow_recommendation_worker.rb | 20 ++-- ...livery_last_failed_at_to_fasp_providers.rb | 7 ++ db/schema.rb | 97 +++++++++--------- spec/models/fasp/provider_spec.rb | 98 +++++++++++++++++++ .../examples/workers/fasp/delivery_failure.rb | 57 +++++++++++ .../fasp/account_search_worker_spec.rb | 18 +++- ...nce_account_lifecycle_event_worker_spec.rb | 16 ++- ...nce_content_lifecycle_event_worker_spec.rb | 16 ++- .../fasp/announce_trend_worker_spec.rb | 18 +++- spec/workers/fasp/backfill_worker_spec.rb | 16 ++- .../fasp/follow_recommendation_worker_spec.rb | 22 +++-- 18 files changed, 373 insertions(+), 106 deletions(-) create mode 100644 app/workers/fasp/base_worker.rb create mode 100644 db/migrate/20250805075010_add_delivery_last_failed_at_to_fasp_providers.rb create mode 100644 spec/support/examples/workers/fasp/delivery_failure.rb diff --git a/app/models/fasp/provider.rb b/app/models/fasp/provider.rb index 9f7be482fed..9c9b187ccad 100644 --- a/app/models/fasp/provider.rb +++ b/app/models/fasp/provider.rb @@ -9,6 +9,7 @@ # capabilities :jsonb not null # confirmed :boolean default(FALSE), not null # contact_email :string +# delivery_last_failed_at :datetime # fediverse_account :string # name :string not null # privacy_policy :jsonb @@ -22,6 +23,8 @@ class Fasp::Provider < ApplicationRecord include DebugConcern + RETRY_INTERVAL = 1.hour + has_many :fasp_backfill_requests, inverse_of: :fasp_provider, class_name: 'Fasp::BackfillRequest', dependent: :delete_all has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all has_many :fasp_subscriptions, inverse_of: :fasp_provider, class_name: 'Fasp::Subscription', dependent: :delete_all @@ -122,6 +125,16 @@ class Fasp::Provider < ApplicationRecord @delivery_failure_tracker ||= DeliveryFailureTracker.new(base_url, resolution: :minutes) end + def available? + delivery_failure_tracker.available? || retry_worthwile? + end + + def update_availability! + self.delivery_last_failed_at = (Time.current unless delivery_failure_tracker.available?) + + save! + end + private def create_keypair @@ -148,4 +161,8 @@ class Fasp::Provider < ApplicationRecord Fasp::Request.new(self).delete(path) end end + + def retry_worthwile? + delivery_last_failed_at && delivery_last_failed_at < RETRY_INTERVAL.ago + end end diff --git a/app/workers/fasp/account_search_worker.rb b/app/workers/fasp/account_search_worker.rb index 745285c44de..12b000fa867 100644 --- a/app/workers/fasp/account_search_worker.rb +++ b/app/workers/fasp/account_search_worker.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -class Fasp::AccountSearchWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 0 +class Fasp::AccountSearchWorker < Fasp::BaseWorker + sidekiq_options retry: 0 def perform(query) return unless Mastodon::Feature.fasp_enabled? @@ -17,11 +15,13 @@ class Fasp::AccountSearchWorker fetch_service = ActivityPub::FetchRemoteActorService.new account_search_providers.each do |provider| - Fasp::Request.new(provider).get("/account_search/v0/search?#{params}").each do |uri| - next if Account.where(uri:).any? + with_provider(provider) do + Fasp::Request.new(provider).get("/account_search/v0/search?#{params}").each do |uri| + next if Account.where(uri:).any? - account = fetch_service.call(uri) - async_refresh.increment_result_count(by: 1) if account.present? + account = fetch_service.call(uri) + async_refresh.increment_result_count(by: 1) if account.present? + end end end ensure diff --git a/app/workers/fasp/announce_account_lifecycle_event_worker.rb b/app/workers/fasp/announce_account_lifecycle_event_worker.rb index ea8544c24da..fc7fb235ea4 100644 --- a/app/workers/fasp/announce_account_lifecycle_event_worker.rb +++ b/app/workers/fasp/announce_account_lifecycle_event_worker.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -class Fasp::AnnounceAccountLifecycleEventWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 5 +class Fasp::AnnounceAccountLifecycleEventWorker < Fasp::BaseWorker + sidekiq_options retry: 5 def perform(uri, event_type) Fasp::Subscription.includes(:fasp_provider).category_account.lifecycle.each do |subscription| - announce(subscription, uri, event_type) + with_provider(subscription.fasp_provider) do + announce(subscription, uri, event_type) + end end end diff --git a/app/workers/fasp/announce_content_lifecycle_event_worker.rb b/app/workers/fasp/announce_content_lifecycle_event_worker.rb index 744528f2d33..d4450a8aece 100644 --- a/app/workers/fasp/announce_content_lifecycle_event_worker.rb +++ b/app/workers/fasp/announce_content_lifecycle_event_worker.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -class Fasp::AnnounceContentLifecycleEventWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 5 +class Fasp::AnnounceContentLifecycleEventWorker < Fasp::BaseWorker + sidekiq_options retry: 5 def perform(uri, event_type) Fasp::Subscription.includes(:fasp_provider).category_content.lifecycle.each do |subscription| - announce(subscription, uri, event_type) + with_provider(subscription.fasp_provider) do + announce(subscription, uri, event_type) + end end end diff --git a/app/workers/fasp/announce_trend_worker.rb b/app/workers/fasp/announce_trend_worker.rb index ae93c3d9f60..dc1d94a2712 100644 --- a/app/workers/fasp/announce_trend_worker.rb +++ b/app/workers/fasp/announce_trend_worker.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -class Fasp::AnnounceTrendWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 5 +class Fasp::AnnounceTrendWorker < Fasp::BaseWorker + sidekiq_options retry: 5 def perform(status_id, trend_source) status = ::Status.includes(:account).find(status_id) return unless status.account.indexable? Fasp::Subscription.includes(:fasp_provider).category_content.trends.each do |subscription| - announce(subscription, status.uri) if trending?(subscription, status, trend_source) + with_provider(subscription.fasp_provider) do + announce(subscription, status.uri) if trending?(subscription, status, trend_source) + end end rescue ActiveRecord::RecordNotFound # status might not exist anymore, in which case there is nothing to do diff --git a/app/workers/fasp/backfill_worker.rb b/app/workers/fasp/backfill_worker.rb index 4e30b71a7dd..228dcbc1d22 100644 --- a/app/workers/fasp/backfill_worker.rb +++ b/app/workers/fasp/backfill_worker.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -class Fasp::BackfillWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 5 +class Fasp::BackfillWorker < Fasp::BaseWorker + sidekiq_options retry: 5 def perform(backfill_request_id) backfill_request = Fasp::BackfillRequest.find(backfill_request_id) - announce(backfill_request) + with_provider(backfill_request.fasp_provider) do + announce(backfill_request) - backfill_request.advance! + backfill_request.advance! + end rescue ActiveRecord::RecordNotFound # ignore missing backfill requests end diff --git a/app/workers/fasp/base_worker.rb b/app/workers/fasp/base_worker.rb new file mode 100644 index 00000000000..fe7f0b0c00c --- /dev/null +++ b/app/workers/fasp/base_worker.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Fasp::BaseWorker + include Sidekiq::Worker + + sidekiq_options queue: 'fasp' + + private + + def with_provider(provider) + return unless provider.available? + + yield + rescue *Mastodon::HTTP_CONNECTION_ERRORS + raise if provider.available? + ensure + provider.update_availability! + end +end diff --git a/app/workers/fasp/follow_recommendation_worker.rb b/app/workers/fasp/follow_recommendation_worker.rb index 5e760491bff..b0eb4e38bf4 100644 --- a/app/workers/fasp/follow_recommendation_worker.rb +++ b/app/workers/fasp/follow_recommendation_worker.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true -class Fasp::FollowRecommendationWorker - include Sidekiq::Worker - - sidekiq_options queue: 'fasp', retry: 0 +class Fasp::FollowRecommendationWorker < Fasp::BaseWorker + sidekiq_options retry: 0 def perform(account_id) return unless Mastodon::Feature.fasp_enabled? @@ -20,14 +18,16 @@ class Fasp::FollowRecommendationWorker fetch_service = ActivityPub::FetchRemoteActorService.new follow_recommendation_providers.each do |provider| - Fasp::Request.new(provider).get("/follow_recommendation/v0/accounts?#{params}").each do |uri| - next if Account.where(uri:).any? + with_provider(provider) do + Fasp::Request.new(provider).get("/follow_recommendation/v0/accounts?#{params}").each do |uri| + next if Account.where(uri:).any? - new_account = fetch_service.call(uri) + new_account = fetch_service.call(uri) - if new_account.present? - Fasp::FollowRecommendation.find_or_create_by(requesting_account: account, recommended_account: new_account) - async_refresh.increment_result_count(by: 1) + if new_account.present? + Fasp::FollowRecommendation.find_or_create_by(requesting_account: account, recommended_account: new_account) + async_refresh.increment_result_count(by: 1) + end end end end diff --git a/db/migrate/20250805075010_add_delivery_last_failed_at_to_fasp_providers.rb b/db/migrate/20250805075010_add_delivery_last_failed_at_to_fasp_providers.rb new file mode 100644 index 00000000000..f8af1f33377 --- /dev/null +++ b/db/migrate/20250805075010_add_delivery_last_failed_at_to_fasp_providers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddDeliveryLastFailedAtToFaspProviders < ActiveRecord::Migration[8.0] + def change + add_column :fasp_providers, :delivery_last_failed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 272d6fac182..cf8c74c8e2f 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: 2025_07_17_003848) do +ActiveRecord::Schema[8.0].define(version: 2025_08_05_075010) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -488,6 +488,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do t.string "fediverse_account" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "delivery_last_failed_at" t.index ["base_url"], name: "index_fasp_providers_on_base_url", unique: true end @@ -1483,53 +1484,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do add_foreign_key "web_settings", "users", name: "fk_11910667b2", on_delete: :cascade add_foreign_key "webauthn_credentials", "users", on_delete: :cascade - create_view "instances", materialized: true, sql_definition: <<-SQL - WITH domain_counts(domain, accounts_count) AS ( - SELECT accounts.domain, - count(*) AS accounts_count - FROM accounts - WHERE (accounts.domain IS NOT NULL) - GROUP BY accounts.domain - ) - SELECT domain_counts.domain, - domain_counts.accounts_count - FROM domain_counts - UNION - SELECT domain_blocks.domain, - COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count - FROM (domain_blocks - LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_blocks.domain)::text))) - UNION - SELECT domain_allows.domain, - COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count - FROM (domain_allows - LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_allows.domain)::text))); - SQL - add_index "instances", "reverse(('.'::text || (domain)::text)), domain", name: "index_instances_on_reverse_domain" - add_index "instances", ["domain"], name: "index_instances_on_domain", unique: true - - create_view "user_ips", sql_definition: <<-SQL - SELECT user_id, - ip, - max(used_at) AS used_at - FROM ( SELECT users.id AS user_id, - users.sign_up_ip AS ip, - users.created_at AS used_at - FROM users - WHERE (users.sign_up_ip IS NOT NULL) - UNION ALL - SELECT session_activations.user_id, - session_activations.ip, - session_activations.updated_at - FROM session_activations - UNION ALL - SELECT login_activities.user_id, - login_activities.ip, - login_activities.created_at - FROM login_activities - WHERE (login_activities.success = true)) t0 - GROUP BY user_id, ip; - SQL create_view "account_summaries", materialized: true, sql_definition: <<-SQL SELECT accounts.id AS account_id, mode() WITHIN GROUP (ORDER BY t0.language) AS language, @@ -1580,4 +1534,51 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_17_003848) do SQL add_index "global_follow_recommendations", ["account_id"], name: "index_global_follow_recommendations_on_account_id", unique: true + create_view "instances", materialized: true, sql_definition: <<-SQL + WITH domain_counts(domain, accounts_count) AS ( + SELECT accounts.domain, + count(*) AS accounts_count + FROM accounts + WHERE (accounts.domain IS NOT NULL) + GROUP BY accounts.domain + ) + SELECT domain_counts.domain, + domain_counts.accounts_count + FROM domain_counts + UNION + SELECT domain_blocks.domain, + COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count + FROM (domain_blocks + LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_blocks.domain)::text))) + UNION + SELECT domain_allows.domain, + COALESCE(domain_counts.accounts_count, (0)::bigint) AS accounts_count + FROM (domain_allows + LEFT JOIN domain_counts ON (((domain_counts.domain)::text = (domain_allows.domain)::text))); + SQL + add_index "instances", "reverse(('.'::text || (domain)::text)), domain", name: "index_instances_on_reverse_domain" + add_index "instances", ["domain"], name: "index_instances_on_domain", unique: true + + create_view "user_ips", sql_definition: <<-SQL + SELECT user_id, + ip, + max(used_at) AS used_at + FROM ( SELECT users.id AS user_id, + users.sign_up_ip AS ip, + users.created_at AS used_at + FROM users + WHERE (users.sign_up_ip IS NOT NULL) + UNION ALL + SELECT session_activations.user_id, + session_activations.ip, + session_activations.updated_at + FROM session_activations + UNION ALL + SELECT login_activities.user_id, + login_activities.ip, + login_activities.created_at + FROM login_activities + WHERE (login_activities.success = true)) t0 + GROUP BY user_id, ip; + SQL end diff --git a/spec/models/fasp/provider_spec.rb b/spec/models/fasp/provider_spec.rb index 9fd2c4c2348..c0e6ae255af 100644 --- a/spec/models/fasp/provider_spec.rb +++ b/spec/models/fasp/provider_spec.rb @@ -214,4 +214,102 @@ RSpec.describe Fasp::Provider do expect(subject.delivery_failure_tracker).to be_a(DeliveryFailureTracker) end end + + describe '#available?' do + subject { Fabricate(:fasp_provider, delivery_last_failed_at:) } + + let(:delivery_last_failed_at) { nil } + + before do + allow(subject.delivery_failure_tracker).to receive(:available?).and_return(available) + end + + context 'when the delivery failure tracker reports it is available' do + let(:available) { true } + + it 'returns true' do + expect(subject.available?).to be true + end + end + + context 'when the delivery failure tracker reports it is unavailable' do + let(:available) { false } + + context 'when the last failure was more than one hour ago' do + let(:delivery_last_failed_at) { 61.minutes.ago } + + it 'returns true' do + expect(subject.available?).to be true + end + end + + context 'when the last failure is very recent' do + let(:delivery_last_failed_at) { 5.minutes.ago } + + it 'returns false' do + expect(subject.available?).to be false + end + end + end + end + + describe '#update_availability!' do + subject { Fabricate(:fasp_provider, delivery_last_failed_at:) } + + before do + allow(subject.delivery_failure_tracker).to receive(:available?).and_return(available) + end + + context 'when `delivery_last_failed_at` is `nil`' do + let(:delivery_last_failed_at) { nil } + + context 'when the delivery failure tracker reports it is available' do + let(:available) { true } + + it 'does not update the provider' do + subject.update_availability! + + expect(subject.saved_changes?).to be false + end + end + + context 'when the delivery failure tracker reports it is unavailable' do + let(:available) { false } + + it 'sets `delivery_last_failed_at` to the current time' do + freeze_time + + subject.update_availability! + + expect(subject.delivery_last_failed_at).to eq Time.zone.now + end + end + end + + context 'when `delivery_last_failed_at` is present' do + context 'when the delivery failure tracker reports it is available' do + let(:available) { true } + let(:delivery_last_failed_at) { 5.minutes.ago } + + it 'sets `delivery_last_failed_at` to `nil`' do + subject.update_availability! + + expect(subject.delivery_last_failed_at).to be_nil + end + end + + context 'when the delivery failure tracker reports it is unavailable' do + let(:available) { false } + let(:delivery_last_failed_at) { 5.minutes.ago } + + it 'updates `delivery_last_failed_at` to the current time' do + freeze_time + + subject.update_availability! + + expect(subject.delivery_last_failed_at).to eq Time.zone.now + end + end + end + end end diff --git a/spec/support/examples/workers/fasp/delivery_failure.rb b/spec/support/examples/workers/fasp/delivery_failure.rb new file mode 100644 index 00000000000..006d93e80d8 --- /dev/null +++ b/spec/support/examples/workers/fasp/delivery_failure.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'worker handling fasp delivery failures' do + context 'when provider is not available' do + before do + provider.update(delivery_last_failed_at: 1.minute.ago) + domain = Addressable::URI.parse(provider.base_url).normalized_host + UnavailableDomain.create!(domain:) + end + + it 'does not attempt connecting and does not fail the job' do + expect { subject }.to_not raise_error + expect(stubbed_request).to_not have_been_made + end + end + + context 'when connection to provider fails' do + before do + base_stubbed_request + .to_raise(HTTP::ConnectionError) + end + + context 'when provider becomes unavailable' do + before do + travel_to 5.minutes.ago + 4.times do + provider.delivery_failure_tracker.track_failure! + travel_to 1.minute.since + end + end + + it 'updates the provider and does not fail the job, so it will not be retried' do + expect { subject }.to_not raise_error + expect(provider.reload.delivery_last_failed_at).to eq Time.current + end + end + + context 'when provider is still marked as available' do + it 'fails the job so it can be retried' do + expect { subject }.to raise_error(HTTP::ConnectionError) + end + end + end + + context 'when connection to a previously unavailable provider succeeds' do + before do + provider.update(delivery_last_failed_at: 2.hours.ago) + domain = Addressable::URI.parse(provider.base_url).normalized_host + UnavailableDomain.create!(domain:) + end + + it 'marks the provider as being available again' do + expect { subject }.to_not raise_error + expect(provider).to be_available + end + end +end diff --git a/spec/workers/fasp/account_search_worker_spec.rb b/spec/workers/fasp/account_search_worker_spec.rb index a96ba0c23b3..1c543b62ae0 100644 --- a/spec/workers/fasp/account_search_worker_spec.rb +++ b/spec/workers/fasp/account_search_worker_spec.rb @@ -5,12 +5,14 @@ require 'rails_helper' RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do include ProviderRequestHelper + subject { described_class.new.perform('cats') } + let(:provider) { Fabricate(:account_search_fasp) } let(:account) { Fabricate(:account) } let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService, call: true) } + let(:path) { '/account_search/v0/search?term=cats&limit=10' } let!(:stubbed_request) do - path = '/account_search/v0/search?term=cats&limit=10' stub_provider_request(provider, method: :get, path:, @@ -25,7 +27,7 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do end it 'requests search results and fetches received account uris' do - described_class.new.perform('cats') + subject expect(stubbed_request).to have_been_made expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/2') @@ -35,7 +37,7 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do it 'marks a running async refresh as finished' do async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true) - described_class.new.perform('cats') + subject expect(async_refresh.reload).to be_finished end @@ -43,8 +45,16 @@ RSpec.describe Fasp::AccountSearchWorker, feature: :fasp do it 'tracks the number of fetched accounts in the async refresh' do async_refresh = AsyncRefresh.create("fasp:account_search:#{Digest::MD5.base64digest('cats')}", count_results: true) - described_class.new.perform('cats') + subject expect(async_refresh.reload.result_count).to eq 2 end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:get, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end diff --git a/spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb b/spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb index 0d4a8708757..b89d4fe6587 100644 --- a/spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb +++ b/spec/workers/fasp/announce_account_lifecycle_event_worker_spec.rb @@ -5,15 +5,19 @@ require 'rails_helper' RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do include ProviderRequestHelper + subject { described_class.new.perform(account_uri, 'new') } + let(:account_uri) { 'https://masto.example.com/accounts/1' } let(:subscription) do Fabricate(:fasp_subscription, category: 'account') end let(:provider) { subscription.fasp_provider } + let(:path) { '/data_sharing/v0/announcements' } + let!(:stubbed_request) do stub_provider_request(provider, method: :post, - path: '/data_sharing/v0/announcements', + path:, response_body: { source: { subscription: { @@ -27,8 +31,16 @@ RSpec.describe Fasp::AnnounceAccountLifecycleEventWorker do end it 'sends the account uri to subscribed providers' do - described_class.new.perform(account_uri, 'new') + subject expect(stubbed_request).to have_been_made end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:post, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end diff --git a/spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb b/spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb index 60618607c95..6ff5a9f7712 100644 --- a/spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb +++ b/spec/workers/fasp/announce_content_lifecycle_event_worker_spec.rb @@ -5,15 +5,19 @@ require 'rails_helper' RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do include ProviderRequestHelper + subject { described_class.new.perform(status_uri, 'new') } + let(:status_uri) { 'https://masto.example.com/status/1' } let(:subscription) do Fabricate(:fasp_subscription) end let(:provider) { subscription.fasp_provider } + let(:path) { '/data_sharing/v0/announcements' } + let!(:stubbed_request) do stub_provider_request(provider, method: :post, - path: '/data_sharing/v0/announcements', + path:, response_body: { source: { subscription: { @@ -27,8 +31,16 @@ RSpec.describe Fasp::AnnounceContentLifecycleEventWorker do end it 'sends the status uri to subscribed providers' do - described_class.new.perform(status_uri, 'new') + subject expect(stubbed_request).to have_been_made end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:post, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end diff --git a/spec/workers/fasp/announce_trend_worker_spec.rb b/spec/workers/fasp/announce_trend_worker_spec.rb index 799d8a8f48a..369c2f12679 100644 --- a/spec/workers/fasp/announce_trend_worker_spec.rb +++ b/spec/workers/fasp/announce_trend_worker_spec.rb @@ -5,6 +5,8 @@ require 'rails_helper' RSpec.describe Fasp::AnnounceTrendWorker do include ProviderRequestHelper + subject { described_class.new.perform(status.id, 'favourite') } + let(:status) { Fabricate(:status) } let(:subscription) do Fabricate(:fasp_subscription, @@ -14,10 +16,12 @@ RSpec.describe Fasp::AnnounceTrendWorker do threshold_likes: 2) end let(:provider) { subscription.fasp_provider } + let(:path) { '/data_sharing/v0/announcements' } + let!(:stubbed_request) do stub_provider_request(provider, method: :post, - path: '/data_sharing/v0/announcements', + path:, response_body: { source: { subscription: { @@ -36,15 +40,23 @@ RSpec.describe Fasp::AnnounceTrendWorker do end it 'sends the account uri to subscribed providers' do - described_class.new.perform(status.id, 'favourite') + subject expect(stubbed_request).to have_been_made end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:post, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end context 'when the configured threshold is not met' do it 'does not notify any provider' do - described_class.new.perform(status.id, 'favourite') + subject expect(stubbed_request).to_not have_been_made end diff --git a/spec/workers/fasp/backfill_worker_spec.rb b/spec/workers/fasp/backfill_worker_spec.rb index 43734e02bac..e15493ea5d0 100644 --- a/spec/workers/fasp/backfill_worker_spec.rb +++ b/spec/workers/fasp/backfill_worker_spec.rb @@ -5,13 +5,17 @@ require 'rails_helper' RSpec.describe Fasp::BackfillWorker do include ProviderRequestHelper + subject { described_class.new.perform(backfill_request.id) } + let(:backfill_request) { Fabricate(:fasp_backfill_request) } let(:provider) { backfill_request.fasp_provider } let(:status) { Fabricate(:status) } + let(:path) { '/data_sharing/v0/announcements' } + let!(:stubbed_request) do stub_provider_request(provider, method: :post, - path: '/data_sharing/v0/announcements', + path:, response_body: { source: { backfillRequest: { @@ -25,8 +29,16 @@ RSpec.describe Fasp::BackfillWorker do end it 'sends status uri to provider that requested backfill' do - described_class.new.perform(backfill_request.id) + subject expect(stubbed_request).to have_been_made end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:post, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end diff --git a/spec/workers/fasp/follow_recommendation_worker_spec.rb b/spec/workers/fasp/follow_recommendation_worker_spec.rb index baa647aa06f..895175f3f3e 100644 --- a/spec/workers/fasp/follow_recommendation_worker_spec.rb +++ b/spec/workers/fasp/follow_recommendation_worker_spec.rb @@ -5,13 +5,15 @@ require 'rails_helper' RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do include ProviderRequestHelper + subject { described_class.new.perform(account.id) } + let(:provider) { Fabricate(:follow_recommendation_fasp) } let(:account) { Fabricate(:account) } + let(:account_uri) { ActivityPub::TagManager.instance.uri_for(account) } let(:fetch_service) { instance_double(ActivityPub::FetchRemoteActorService) } + let(:path) { "/follow_recommendation/v0/accounts?accountUri=#{URI.encode_uri_component(account_uri)}" } let!(:stubbed_request) do - account_uri = ActivityPub::TagManager.instance.uri_for(account) - path = "/follow_recommendation/v0/accounts?accountUri=#{URI.encode_uri_component(account_uri)}" stub_provider_request(provider, method: :get, path:, @@ -28,7 +30,7 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do end it "sends the requesting account's uri to provider and fetches received account uris" do - described_class.new.perform(account.id) + subject expect(stubbed_request).to have_been_made expect(fetch_service).to have_received(:call).with('https://fedi.example.com/accounts/1') @@ -38,7 +40,7 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do it 'marks a running async refresh as finished' do async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true) - described_class.new.perform(account.id) + subject expect(async_refresh.reload).to be_finished end @@ -46,14 +48,22 @@ RSpec.describe Fasp::FollowRecommendationWorker, feature: :fasp do it 'tracks the number of fetched accounts in the async refresh' do async_refresh = AsyncRefresh.create("fasp:follow_recommendation:#{account.id}", count_results: true) - described_class.new.perform(account.id) + subject expect(async_refresh.reload.result_count).to eq 2 end it 'persists the results' do expect do - described_class.new.perform(account.id) + subject end.to change(Fasp::FollowRecommendation, :count).by(2) end + + describe 'provider delivery failure handling' do + let(:base_stubbed_request) do + stub_request(:get, provider.url(path)) + end + + it_behaves_like('worker handling fasp delivery failures') + end end From 5d934c283525527959537be561cc0ba86955bf46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:06:23 +0200 Subject: [PATCH 249/660] Update dependency httplog to v1.7.3 (#35721) 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 943334b03d0..5f9259edead 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -315,7 +315,7 @@ GEM http_accept_language (2.1.1) httpclient (2.9.0) mutex_m - httplog (1.7.2) + httplog (1.7.3) rack (>= 2.0) rainbow (>= 2.0.0) i18n (1.14.7) From b8982cb881bf96ea5cb8f6ce48cf057f0ee4e520 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 8 Aug 2025 11:31:50 -0400 Subject: [PATCH 250/660] Use `around_action` to preserve stored location in `auth/sessions#destroy` (#35716) --- app/controllers/auth/sessions_controller.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index c52bda67b0a..182f242ae5b 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -12,6 +12,8 @@ class Auth::SessionsController < Devise::SessionsController skip_before_action :require_functional! skip_before_action :update_user_sign_in + around_action :preserve_stored_location, only: :destroy, if: :continue_after? + prepend_before_action :check_suspicious!, only: [:create] include Auth::TwoFactorAuthenticationConcern @@ -31,11 +33,9 @@ class Auth::SessionsController < Devise::SessionsController end def destroy - tmp_stored_location = stored_location_for(:user) super session.delete(:challenge_passed_at) flash.delete(:notice) - store_location_for(:user, tmp_stored_location) if continue_after? end def webauthn_options @@ -96,6 +96,12 @@ class Auth::SessionsController < Devise::SessionsController private + def preserve_stored_location + original_stored_location = stored_location_for(:user) + yield + store_location_for(:user, original_stored_location) + end + def check_suspicious! user = find_user @login_is_suspicious = suspicious_sign_in?(user) unless user.nil? From ce1680e6f979889ebd5c775353f3b82a8b2698bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:32:15 +0200 Subject: [PATCH 251/660] Update dependency core-js to v3.45.0 (#35667) 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 26f7cd409e9..b525ed6d67f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6015,9 +6015,9 @@ __metadata: linkType: hard "core-js@npm:^3.30.2, core-js@npm:^3.41.0": - version: 3.44.0 - resolution: "core-js@npm:3.44.0" - checksum: 10c0/759bf3dc5f75068e9425dddf895fd5531c38794a11ea1c2b65e5ef7c527fe3652d59e8c287e574a211af9bab3c057c5c3fa6f6a773f4e142af895106efad38a4 + version: 3.45.0 + resolution: "core-js@npm:3.45.0" + checksum: 10c0/118350f9f1d81f42a1276590d6c217dca04c789fdb8074c82e53056b1a784948769a62b16b98493fd73e8a988545432f302bca798571e56ad881b9c039a5a83c languageName: node linkType: hard From b827a0a6a8aa0c2a61b8d03ca617c58b629a61b9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 11 Aug 2025 03:33:36 -0400 Subject: [PATCH 252/660] Allow `CustomEmoji` to normalize its own domain (#35726) --- app/models/custom_emoji.rb | 2 +- app/models/custom_emoji_filter.rb | 2 +- spec/models/custom_emoji_spec.rb | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index f1423bc2fac..25ba3d921b4 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -42,7 +42,7 @@ class CustomEmoji < ApplicationRecord has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp', file_geometry_parser: FastGeometryParser } }, validate_media_type: false, processors: [:lazy_thumbnail] - normalizes :domain, with: ->(domain) { domain.downcase } + normalizes :domain, with: ->(domain) { domain.downcase.strip } validates_attachment :image, content_type: { content_type: IMAGE_MIME_TYPES }, presence: true, size: { less_than: LIMIT } validates :shortcode, uniqueness: { scope: :domain }, format: { with: SHORTCODE_ONLY_RE }, length: { minimum: MINIMUM_SHORTCODE_SIZE } diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb index 870cc71974e..070637fea07 100644 --- a/app/models/custom_emoji_filter.rb +++ b/app/models/custom_emoji_filter.rb @@ -35,7 +35,7 @@ class CustomEmojiFilter when 'remote' CustomEmoji.remote when 'by_domain' - CustomEmoji.where(domain: value.strip.downcase) + CustomEmoji.where(domain: value) when 'shortcode' CustomEmoji.search(value.strip) else diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index f812cf75847..244d0d126f1 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -80,6 +80,7 @@ RSpec.describe CustomEmoji, :attachment_processing do describe 'Normalizations' do describe 'domain' do + it { is_expected.to normalize(:domain).from(' www.mastodon.host ').to('www.mastodon.host') } it { is_expected.to normalize(:domain).from('wWw.MaStOdOn.CoM').to('www.mastodon.com') } it { is_expected.to normalize(:domain).from(nil).to(nil) } end From 3c6c9d650d40a8b9cea4bd46b0ed38a71d2a33e4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:08:33 +0200 Subject: [PATCH 253/660] New Crowdin Translations (automated) (#35729) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/az.json | 151 ++++++++++++++++++- app/javascript/mastodon/locales/be.json | 156 +++++++++---------- app/javascript/mastodon/locales/ko.json | 8 + app/javascript/mastodon/locales/nan.json | 6 + config/locales/activerecord.be.yml | 10 +- config/locales/az.yml | 182 +++++++++++++++++++++++ config/locales/be.yml | 114 +++++++------- config/locales/devise.az.yml | 8 +- config/locales/devise.be.yml | 46 +++--- config/locales/doorkeeper.az.yml | 16 ++ config/locales/doorkeeper.be.yml | 22 +-- config/locales/fi.yml | 4 +- config/locales/ko.yml | 26 ++++ config/locales/nan.yml | 30 ++++ config/locales/simple_form.az.yml | 54 ++++++- config/locales/simple_form.be.yml | 52 ++++++- config/locales/simple_form.ko.yml | 9 ++ 17 files changed, 702 insertions(+), 192 deletions(-) diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index b9371f10a78..7540c8fa4b6 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -49,12 +49,12 @@ "account.follows.empty": "Bu istifadəçi hələ ki, heç kimi izləmir.", "account.follows_you": "Sizi izləyir", "account.go_to_profile": "Profilə get", - "account.hide_reblogs": "@{name} istifadəçisindən olan gücləndirmələri gizlət", + "account.hide_reblogs": "@{name} - təkrar paylaşımlarını gizlət", "account.in_memoriam": "Xatirə.", "account.joined_short": "Qoşulub", "account.languages": "Abunə olunmuş dilləri dəyiş", "account.link_verified_on": "Bu linkin dəqiqliyi {date} tarixində yoxlanılıb", - "account.locked_info": "Bu hesabın məxfilik statusu kilidlənib. Hesabın sahibi onu kimin izləyə biləcəyini manual olaraq təyin edir.", + "account.locked_info": "Bu hesabın məxfilik statusu kilidlənib. Sahibi, onu kimin izləyə biləcəyini manual olaraq incələyir.", "account.media": "Media", "account.mention": "@{name} istifadəçisini teq et", "account.moved_to": "{name} onun yeni hesabının artıq bu olduğunu bildirdi:", @@ -74,7 +74,7 @@ "account.requested_follow": "{name} sizi izləmək sorğusu göndərib", "account.requests_to_follow_you": "Sizi izləmək istəyir", "account.share": "@{name} profilini paylaş", - "account.show_reblogs": "@{name} istifadəçisindən olan gücləndirmələri göstər", + "account.show_reblogs": "@{name} - təkrar paylaşımlarını göstər", "account.statuses_counter": "{count, plural, one {{counter} paylaşım} other {{counter} paylaşım}}", "account.unblock": "@{name} blokunu aç", "account.unblock_domain": "{domain} domeninin blokunu aç", @@ -117,7 +117,7 @@ "annual_report.summary.followers.total": "Cəmi {count}", "annual_report.summary.here_it_is": "{year} icmalınız:", "annual_report.summary.highlighted_post.by_favourites": "ən çox sevilən postu", - "annual_report.summary.highlighted_post.by_reblogs": "ən çox gücləndirilən paylaşımı", + "annual_report.summary.highlighted_post.by_reblogs": "ən çox təkrar paylaşılan göndəriş", "annual_report.summary.highlighted_post.by_replies": "ən çox cavabı olan paylaşımı", "annual_report.summary.highlighted_post.possessive": "{name} istifadəçisinin", "annual_report.summary.most_used_app.most_used_app": "ən çox istifadə etdiyi tətbiq", @@ -129,7 +129,7 @@ "annual_report.summary.thanks": "Mastodonun bir parçası olduğunuz üçün təşəkkür edirik!", "attachments_list.unprocessed": "(emal edilməyib)", "audio.hide": "Audionu gizlət", - "block_modal.remote_users_caveat": "Biz {domain} serverindən qərarınıza hörmət etməsini xahiş edəcəyik. Bununla belə, bəzi serverlər blokları fərqli şəkildə idarə edə bildiyi üçün uyğunluğa zəmanət verilmir. İctimai paylaşımlar hələ də daxil olmayan istifadəçilərə görünə bilər.", + "block_modal.remote_users_caveat": "{domain} serverindən qərarınıza hörmət etməsini xahiş edəcəyik. Ancaq, bəzi serverlər əngəlləmələri fərqli şəkildə idarə edə bilər deyə, qərarınıza uymağına zəmanət verilmir. Hər kəsə açıq göndərişlər, hələ də sistemə giriş etməmiş istifadəçilərə görünə bilər.", "block_modal.show_less": "Daha az göstər", "block_modal.show_more": "Daha çox göstər", "block_modal.they_cant_mention": "O səni teq edə bilməz və ya izləyə bilməz.", @@ -138,8 +138,8 @@ "block_modal.title": "İstifadəçi bloklansın?", "block_modal.you_wont_see_mentions": "Onu teq edən postları görməyəcəksən.", "boost_modal.combo": "Növbəti dəfə bunu atlamaq üçün {combo} klikləyə bilərsən", - "boost_modal.reblog": "Paylaşım gücləndirilsin?", - "boost_modal.undo_reblog": "Paylaşımın gücləndirilməsi ləğv edilsin?", + "boost_modal.reblog": "Paylaşım təkrar paylaşılsın?", + "boost_modal.undo_reblog": "Təkrar paylaşım ləğv edilsin?", "bundle_column_error.copy_stacktrace": "Xəta hesabatını kopyala", "bundle_column_error.error.body": "Tələb olunan səhifəni göstərmək mümkün olmadı. Bu, kodumuzdakı səhv və ya brauzer uyğunluğu problemi ilə bağlı ola bilər.", "bundle_column_error.error.title": "Ah, yox!", @@ -240,7 +240,7 @@ "confirmations.missing_alt_text.title": "Alternativ mətn əlavə edilsin?", "confirmations.mute.confirm": "Səssizləşdir", "confirmations.redraft.confirm": "Sil və qaralamaya köçür", - "confirmations.redraft.message": "Bu paylaşımı silmək və qaralamaya köçürmək istədiyinizə əminsiniz? Bəyənmələr və gücləndirmələr itəcək və orijinal paylaşıma olan cavablar tənha qalacaq.", + "confirmations.redraft.message": "Bu göndərişi silib yenidən qaralama kimi saxlamaq istədiyinizə əminsiniz? Sevimlilər və təkrar paylaşımlar silinəcək, orijinal göndərişə verilən cavablar isə əlaqəsiz qalacaq.", "confirmations.redraft.title": "Paylaşım silinsin & qaralamaya köçürülsün?", "confirmations.remove_from_followers.confirm": "İzləyicini çıxart", "confirmations.remove_from_followers.message": "{name} sizi izləməyəcək. Davam etmək istədiyinizə əminsiniz?", @@ -325,6 +325,12 @@ "empty_column.followed_tags": "Heç bir heşteq izləmirsiniz. İzlədikdə burada görünəcək.", "empty_column.hashtag": "Bu heşteqdə hələ ki, heç nə yoxdur.", "empty_column.notification_requests": "Hamısı hazırdır! Burada heç nə yoxdur. Yeni bildiriş aldığınız zaman, ayarlarınıza görə burada görünəcək.", + "empty_column.notifications": "Hələ heç bir bildirişiniz yoxdur. Başqaları sizinlə qarşılıqlı əlaqə qurduğu zaman, onu burada görəcəksiniz.", + "empty_column.public": "Burada hələ heç nə yoxdur! Buranı doldurmaq üçün hər kəsə açıq bir şey yazın və ya digər serverlərdəki istifadəçiləri izləyin.", + "error.unexpected_crash.explanation": "Kodumuzdakı bir xətaya, ya da brauzer uyumluluq probleminə görə, bu səhifə düzgün nümayiş etdirilə bilmədi.", + "error.unexpected_crash.explanation_addons": "Bu səhifə düzgün nümayiş etdirilə bilmədi. Bu xəta, yəqin ki, brauzer əlavəsi və ya avtomatik tərcümə alətlərindən qaynaqlanır.", + "error.unexpected_crash.next_steps": "Səhifəni təzələməyi sınayın. Bu kömək etməzsə, Mastodon-u başqa bir brauzer və ya yerli tətbiq vasitəsilə istifadə edə bilərsiniz.", + "error.unexpected_crash.next_steps_addons": "Onları sıradan çıxartmağı və səhifəni təzələməyi sınayın. Bu kömək etməzsə, Mastodon-u başqa bir brauzer və ya yerli tətbiq vasitəsilə istifadə edə bilərsiniz.", "errors.unexpected_crash.report_issue": "Problemi bildir", "explore.suggested_follows": "İnsanlar", "explore.title": "Trendlər", @@ -345,9 +351,43 @@ "filter_modal.added.settings_link": "ayarlar səhifəsi", "filter_modal.added.short_explanation": "Bu göndəriş, aşağıdakı filtr kateqoriyasına əlavə edilib: {title}.", "filter_modal.added.title": "Filtr əlavə edilib!", + "filter_modal.select_filter.expired": "müddəti bitib", + "filter_modal.select_filter.prompt_new": "Yeni kateqoriya: {name}", + "filter_modal.select_filter.search": "Axtar və ya yarat", + "filter_modal.select_filter.subtitle": "Mövcud bir kateqoriyanı istifadə et, ya da yenisini yarat", + "filter_modal.select_filter.title": "Bu göndərişi filtrlə", + "filter_modal.title.status": "Bir göndərişi filtrlə", "filter_warning.matches_filter": "“{title}” filtri ilə uyuşur", + "filtered_notifications_banner.title": "Filtrlənmiş bildirişlər", + "firehose.all": "Hamısı", + "firehose.local": "Bu server", + "firehose.remote": "Digər serverlər", + "follow_request.authorize": "Səlahiyyət ver", + "follow_request.reject": "Rədd et", + "follow_requests.unlocked_explanation": "Hesabınız kilidli olmasa da, {domain} heyəti bu hesabların izləmə tələblərini manual olaraq incələmək istəyə biləcəyinizi düşündü.", + "follow_suggestions.curated_suggestion": "Heyətin seçimi", + "follow_suggestions.dismiss": "Təkrar göstərmə", + "follow_suggestions.featured_longer": "{domain} komandası tərəfindən əllə seçildi", + "follow_suggestions.friends_of_friends_longer": "İzlədiyiniz insanlar arasında məşhur", + "follow_suggestions.hints.featured": "Bu profil {domain} komandası tərəfindən əllə seçilib.", "follow_suggestions.hints.friends_of_friends": "Bu profil izlədiyiniz insanlar arasında populyardır.", "follow_suggestions.hints.most_followed": "Bu profil {domain} serverində ən çox izlənilənlərdən biridir.", + "follow_suggestions.hints.most_interactions": "Bu profil son zamanlar {domain} üzərində çox diqqət çəkir.", + "follow_suggestions.hints.similar_to_recently_followed": "Bu profil, son vaxtlar izlədiyiniz profillərə bənzəyir.", + "follow_suggestions.personalized_suggestion": "Fərdiləşdirilmiş təklif", + "follow_suggestions.popular_suggestion": "Məşhur təklif", + "follow_suggestions.popular_suggestion_longer": "{domain} üzərində məşhur", + "follow_suggestions.similar_to_recently_followed_longer": "Son zaman izlədiyiniz profillərə oxşar", + "follow_suggestions.view_all": "Hamısına bax", + "followed_tags": "İzlənilən mövzu etiketləri", + "footer.about": "Haqqında", + "footer.directory": "Profil kataloqu", + "footer.get_app": "Tətbiqi əldə et", + "footer.keyboard_shortcuts": "Klaviatura qısayolları", + "footer.privacy_policy": "Gizlilik siyasəti", + "footer.source_code": "Mənbə koduna bax", + "footer.status": "Status", + "footer.terms_of_service": "Xidmət şərtləri", "generic.saved": "Saxlanıldı", "getting_started.heading": "Başlayaq", "hashtag.admin_moderation": "#{name} üçün moderasiya interfeysini aç", @@ -365,19 +405,31 @@ "hashtag.counter_by_accounts": "{count, plural, one {{counter} iştirakçı} other {{counter} iştirakçı}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}", "hashtag.counter_by_uses_today": "Bu gün {count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}", + "hashtag.feature": "Profildə önə çıxart", "hashtag.follow": "Mövzu etiketini izlə", "hashtag.mute": "#{hashtag} - səssizə al", + "hashtag.unfeature": "Profildə önə çıxarılmasın", "hashtag.unfollow": "Mövzu etiketini izləmə", "hashtags.and_other": "…və daha {count, plural, one {}other {# ədəd}}", "hints.profiles.followers_may_be_missing": "Bu profilin izləyiciləri əskik ola bilər.", "hints.profiles.follows_may_be_missing": "Bu profilin izləyənləri əskik ola bilər.", + "hints.profiles.posts_may_be_missing": "Bu profilin bəzi göndərişləri əskik ola bilər.", + "hints.profiles.see_more_followers": "{domain} üzərində daha çox izləyici gör", + "hints.profiles.see_more_follows": "{domain} üzərində izlənilən gör", + "hints.profiles.see_more_posts": "{domain} üzərində daha çox göndəriş gör", "home.column_settings.show_quotes": "Sitatları göstər", + "home.column_settings.show_reblogs": "Təkrar paylaşmaları göstər", "home.column_settings.show_replies": "Cavabları göstər", "home.hide_announcements": "Elanları gizlət", "home.pending_critical_update.body": "Lütfən Mastodon serverinizi mümkün olan ən qısa müddətdə güncəlləyin!", "home.pending_critical_update.link": "Güncəlləmələrə bax", "home.pending_critical_update.title": "Kritik güvənlik güncəlləməsi mövcuddur!", "home.show_announcements": "Elanları göstər", + "ignore_notifications_modal.disclaimer": "Mastodon, bildirişlərini yox saydığınız istifadəçiləri məlumatlandırmır. Bildirişləri yox saymaq, mesajların göndərilməsini dayandırmayacaq.", + "ignore_notifications_modal.filter_instead": "Əvəzinə filtrlə", + "ignore_notifications_modal.filter_to_act_users": "Hələ də istifadəçiləri qəbul edə, rədd edə və ya bildirə bilərsiniz", + "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrləmə, mümkün çaşqınlığın qarşısını almağa kömək edir.", + "ignore_notifications_modal.filter_to_review_separately": "Filtrlənmiş bildirişləri ayrı-ayrı incələyə bilərsiniz", "ignore_notifications_modal.ignore": "Bildirişləri yox say", "ignore_notifications_modal.limited_accounts_title": "Moderasiya edilmiş hesabların bildirişləri yox sayılsın?", "ignore_notifications_modal.new_accounts_title": "Yeni hesabların bildirişləri yox sayılsın?", @@ -390,17 +442,71 @@ "interaction_modal.action.reblog": "Davam etmək üçün hesabınızdan təkrar göndərməlisiniz.", "interaction_modal.action.reply": "Davam etmək üçün hesabınızdan cavab verməlisiniz.", "interaction_modal.action.vote": "Davam etmək üçün hesabınızdan səs verməlisiniz.", + "interaction_modal.title.reblog": "{name} - göndərişini təkrar paylaş", + "keyboard_shortcuts.boost": "Göndərişi təkrar paylaş", "keyboard_shortcuts.profile": "Müəllifin profilini aç", "keyboard_shortcuts.reply": "Göndərişə cavab ver", + "keyboard_shortcuts.toot": "Yeni bir göndəriş başlat", + "keyboard_shortcuts.translate": "bir göndərişi tərcümə etmək üçün", + "keyboard_shortcuts.unfocus": "Fokusu göndəriş yazma xanasından/axtarışdan götür", + "keyboard_shortcuts.up": "Siyahıda yuxarı daşı", "learn_more_link.got_it": "Anladım", "learn_more_link.learn_more": "Daha ətraflı", "lightbox.close": "Bağla", "lightbox.next": "Növbəti", "lightbox.previous": "Əvvəlki", "lightbox.zoom_in": "Həqiqi ölçüyə qayıt", + "lightbox.zoom_out": "Sığacaq şəkildə yaxınlaşdır", "limited_account_hint.action": "Yenə də profili göstər", "limited_account_hint.title": "Bu profil, {domain} moderatorları tərəfindən gizlədildi.", + "link_preview.author": "Müəllif: {name}", + "link_preview.more_from_author": "{name} - daha çox", + "link_preview.shares": "{count, plural, one {{counter} göndəriş} other {{counter} göndəriş}}", + "lists.add_member": "Əlavə et", + "lists.add_to_list": "Siyahıya əlavə et", + "lists.add_to_lists": "{name} - siyahılara əlavə et", + "lists.create": "Yarat", + "lists.create_a_list_to_organize": "Əsas ekran lentinizi təşkil etmək üçün yeni bir siyahı yaradın", + "lists.create_list": "Siyahı yarat", + "lists.delete": "Siyahını sil", + "lists.done": "Hazırdır", + "lists.edit": "Siyahıya düzəliş et", + "lists.exclusive": "Əsas ekranda üzvləri gizlət", + "lists.exclusive_hint": "Kimsə bu siyahıdadırsa, onun göndərişləri bir dəfədən çox görünməməsi üçün Əsas ekran lentində gizlədilir.", + "lists.find_users_to_add": "Əlavə ediləcək istifadəçiləri tap", + "lists.list_members_count": "{count, plural, one {# üzv} other {# üzv}}", + "lists.list_name": "Siyahı adı", + "lists.new_list_name": "Yeni siyahı adı", + "lists.no_lists_yet": "Hələ siyahı yoxdur.", + "lists.no_members_yet": "Hələ üzv yoxdur.", + "lists.no_results_found": "Heç bir nəticə tapılmadı.", + "lists.remove_member": "Çıxart", + "lists.replies_policy.followed": "İstənilən izlənilən istifadəçi", + "lists.replies_policy.list": "Siyahıdakı üzvlər", + "lists.replies_policy.none": "Heç kim", + "lists.save": "Saxla", + "lists.search": "Axtar", + "lists.show_replies_to": "Siyahı üzvlərinin cavablarını daxil et", + "load_pending": "{count, plural, one {# yeni element} other {# yeni element}}", + "loading_indicator.label": "Yüklənir…", + "media_gallery.hide": "Gizlət", + "moved_to_account_banner.text": "{disabledAccount} hesabınız, {movedToAccount} hesabına keçdiyiniz üçün hazırda sıradan çıxarılıb.", + "mute_modal.hide_from_notifications": "Bildirişlərdə gizlət", + "mute_modal.hide_options": "Seçimləri gizlət", + "mute_modal.indefinite": "Mən səsini açana qədər", + "mute_modal.show_options": "Seçimləri göstər", + "mute_modal.they_can_mention_and_follow": "Sizin adınızı çəkə və sizi izləyə bilər, ancaq siz onları görə bilməzsiniz.", + "mute_modal.they_wont_know": "Onlar, səssizə alındıqlarını bilməyəcəklər.", + "mute_modal.title": "İstifadəçi səssizə alınsın?", + "mute_modal.you_wont_see_mentions": "Onların adı çəkilən göndərişləri görməyəcəksiniz.", + "mute_modal.you_wont_see_posts": "Onlar hələ də göndərişlərinizi görə biləcək, ancaq onların göndərişlərini görməyəcəksiniz.", + "navigation_bar.about": "Haqqında", "navigation_bar.account_settings": "Parol və təhlükəsizlik", + "navigation_bar.advanced_interface": "Qabaqcıl veb interfeysində aç", + "navigation_bar.automated_deletion": "Göndərişin avtomatik silinməsi", + "navigation_bar.blocks": "Əngəllənmiş istifadəçilər", + "navigation_bar.follows_and_followers": "İzlənilənlər və izləyicilər", + "navigation_bar.import_export": "Daxilə və xaricə köçürmə", "navigation_bar.moderation": "Moderasiya", "not_signed_in_indicator.not_signed_in": "Bu resursa erişmək üçün giriş etməlisiniz.", "notification.moderation-warning.learn_more": "Daha ətraflı", @@ -412,14 +518,36 @@ "notification.moderation_warning.action_sensitive": "Göndərişləriniz artıq həssas olaraq işarələnəcək.", "notification.moderation_warning.action_silence": "Hesabınız məhdudlaşdırılıb.", "notification.moderation_warning.action_suspend": "Hesabınızın fəaliyyəti dayandırılıb.", + "notification.reblog": "{name} göndərişinizi təkrar paylaşdı", + "notification.reblog.name_and_others_with_link": "{name} və {count, plural, one {digər # nəfər} other {digər # nəfər}} göndərişinizi təkrar paylaşdı", + "notification.relationships_severance_event.account_suspension": "{from} admini {target} fəaliyyətini dayandırıb, bu da o deməkdir ki, artıq onlardan güncəlləmələr ala və ya onlarla qarşılıqlı əlaqə qura bilməyəcəyiniz.", + "notification.relationships_severance_event.domain_block": "{target}, {from} admini tərəfindən əngəllənib, buna {followersCount} izləyiciniz və izlədiyiniz {followingCount, plural, one {# hesab} other {# hesab}} daxildir.", + "notification.relationships_severance_event.learn_more": "Daha ətraflı", + "notification.relationships_severance_event.user_domain_block": "{target} əngəlləmisiniz, bununla {followersCount} izləyiciniz və izlədiyiniz {followingCount, plural, one {# hesab} other {# hesab}} silinib.", + "notification.status": "{name} indicə paylaşdı", + "notification.update": "{name} bir göndərişə düzəliş etdi", + "notification_requests.accept": "Qəbul et", "notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {bir bildiriş sorğusunu} other {# bildiriş sorğusunu}} bağlamaq üzrəsiniz. {count, plural, one {Ona} other {Onlara}} yenidən asanlıqla erişə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", + "notification_requests.edit_selection": "Düzəliş et", + "notification_requests.exit_selection": "Hazırdır", "notification_requests.explainer_for_limited_account": "Hesab, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", "notification_requests.explainer_for_limited_remote_account": "Hesab və ya onun serveri, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", + "notifications.column_settings.follow": "Yeni izləyicilər:", + "notifications.column_settings.reblog": "Təkrar paylaşmalar:", + "notifications.filter.boosts": "Təkrar paylaşmalar", + "notifications.filter.follows": "İzlənilənlər", "notifications.filter.statuses": "İzlədiyiniz şəxslərdən güncəlləmələr", "notifications.policy.filter_limited_accounts_hint": "Server moderatorları tərəfindən məhdudlaşdırılıb", "notifications.policy.filter_limited_accounts_title": "Moderasiya edilmiş hesablar", + "onboarding.profile.discoverable": "Profilimi kəşf edilə bilən et", + "onboarding.profile.discoverable_hint": "Mastodon-da kəşf edilə bilməni aktivləşdirsəniz, göndərişləriniz axtarış nəticələrində və trendlərdə görünə bilər və profiliniz sizinlə oxşar maraqlara sahib şəxslərə təklif edilə bilər.", + "onboarding.profile.display_name": "Ekran adı", + "onboarding.profile.note_hint": "Digər insanların @adını_çəkə və ya #mövzu_etiketləri istifadə edə bilərsiniz…", + "onboarding.profile.title": "Profili ayarla", "password_confirmation.exceeds_maxlength": "Parol təsdiqi, maksimum parol uzunluğunu aşır", "password_confirmation.mismatching": "Parol təsdiqi uyuşmur", + "privacy.private.long": "Yalnız izləyiciləriniz", + "privacy.private.short": "İzləyicilər", "privacy_policy.last_updated": "Son güncəlləmə {date}", "report.category.subtitle": "Ən çox uyuşanı seçin", "report_notification.categories.spam_sentence": "spam", @@ -465,6 +593,8 @@ "status.admin_status": "Moderasiya interfeysində bu göndərişi aç", "status.block": "Əngəllə: @{name}", "status.bookmark": "Əlfəcin", + "status.cancel_reblog_private": "Təkrar paylaşımı geri al", + "status.cannot_reblog": "Bu göndəriş təkrar paylaşıla bilməz", "status.context.load_new_replies": "Yeni cavablar mövcuddur", "status.context.loading": "Daha çox cavab yoxlanılır", "status.delete": "Sil", @@ -491,6 +621,11 @@ "status.quote_error.not_available": "Göndəriş əlçatmazdır", "status.quote_error.pending_approval": "Göndəriş gözləmədədir", "status.read_more": "Daha çoxunu oxu", + "status.reblog": "Təkrar paylaş", + "status.reblog_private": "Orijinal görünmə ilə təkrar paylaş", + "status.reblogged_by": "{name} təkrar paylaşdı", + "status.reblogs": "{count, plural, one {təkrar paylaşma} other {təkrar paylaşma}}", + "status.reblogs.empty": "Hələ heç kim bu göndərişi təkrar paylaşmayıb. Kimsə paylaşdığı zaman, burada görünəcək.", "status.remove_bookmark": "Əlfəcini sil", "status.remove_favourite": "Sevimlilərdən sil", "status.replied_to": "Cavab verildi: {name}", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 2d0842727a1..a3caa5a0235 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -115,25 +115,25 @@ "annual_report.summary.archetype.replier": "Душа кампаніі", "annual_report.summary.followers.followers": "падпісчыкі", "annual_report.summary.followers.total": "Агулам {count}", - "annual_report.summary.here_it_is": "Вось вашы вынікі {year} за год:", + "annual_report.summary.here_it_is": "Вось Вашы вынікі {year} за год:", "annual_report.summary.highlighted_post.by_favourites": "самы ўпадабаны допіс", "annual_report.summary.highlighted_post.by_reblogs": "самы пашыраны допіс", "annual_report.summary.highlighted_post.by_replies": "самы каментаваны допіс", "annual_report.summary.highlighted_post.possessive": "{name}", - "annual_report.summary.most_used_app.most_used_app": "праграма, якой карысталіся часцей", - "annual_report.summary.most_used_hashtag.most_used_hashtag": "хэштэг, якім карысталіся часцей", + "annual_report.summary.most_used_app.most_used_app": "праграма, якой карысталіся найчасцей", + "annual_report.summary.most_used_hashtag.most_used_hashtag": "хэштэг, якім карысталіся найчасцей", "annual_report.summary.most_used_hashtag.none": "Няма", "annual_report.summary.new_posts.new_posts": "новыя допісы", - "annual_report.summary.percentile.text": "З-за гэтага, вы знаходзіцеся ў топе карыстальнікаў {domain}.", + "annual_report.summary.percentile.text": "Гэта падымае Вас у топ карыстальнікаў {domain}.", "annual_report.summary.percentile.we_wont_tell_bernie": "КДБ пра гэта не даведаецца.", "annual_report.summary.thanks": "Дзякуй за ўдзел у Mastodon!", "attachments_list.unprocessed": "(неапрацаваны)", "audio.hide": "Схаваць аўдыя", - "block_modal.remote_users_caveat": "Мы папросім сервер {domain} паважаць ваш выбар. Аднак гэта не гарантуецца, паколькі некаторыя серверы могуць апрацоўваць блакіроўкі іншым чынам. Публічныя паведамленні могуць заставацца бачнымі для ананімных карыстальнікаў.", + "block_modal.remote_users_caveat": "Мы папросім сервер {domain} паважаць Ваш выбар. Аднак гэта не гарантуецца, паколькі некаторыя серверы могуць апрацоўваць блакіроўкі іншым чынам. Публічныя паведамленні могуць заставацца бачнымі для ананімных карыстальнікаў.", "block_modal.show_less": "Паказаць меньш", "block_modal.show_more": "Паказаць больш", "block_modal.they_cant_mention": "Карыстальнік не зможа згадваць або сачыць за вамі.", - "block_modal.they_cant_see_posts": "Карыстальнік не будзе бачыць вашых допісаў, а вы — карыстальніка.", + "block_modal.they_cant_see_posts": "Карыстальнік не будзе бачыць Вашых допісаў, а Вы — ягоных.", "block_modal.they_will_know": "Карыстальнік убачыць, што адбылася блакіроўка.", "block_modal.title": "Заблакіраваць карыстальніка?", "block_modal.you_wont_see_mentions": "Вы не ўбачыце паведамленняў са згадваннем карыстальніка.", @@ -162,7 +162,7 @@ "column.bookmarks": "Закладкі", "column.community": "Лакальная стужка", "column.create_list": "Стварыць спіс", - "column.direct": "Асабістыя згадванні", + "column.direct": "Прыватныя згадванні", "column.directory": "Праглядзець профілі", "column.domain_blocks": "Заблакіраваныя дамены", "column.edit_list": "Рэдагаваць спіс", @@ -217,7 +217,7 @@ "confirmations.delete.message": "Вы ўпэўненыя, што хочаце выдаліць гэты допіс?", "confirmations.delete.title": "Выдаліць допіс?", "confirmations.delete_list.confirm": "Выдаліць", - "confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты чарнавік?", + "confirmations.delete_list.message": "Вы ўпэўненыя, што хочаце беззваротна выдаліць гэты спіс?", "confirmations.delete_list.title": "Выдаліць спіс?", "confirmations.discard_draft.confirm": "Адмовіцца і працягнуць", "confirmations.discard_draft.edit.cancel": "Працягнуць рэдагаванне", @@ -225,7 +225,7 @@ "confirmations.discard_draft.edit.title": "Адмовіцца ад змен у Вашым допісе?", "confirmations.discard_draft.post.cancel": "Працягнуць чарнавік", "confirmations.discard_draft.post.message": "Калі працягнуць, то допіс, які Вы зараз пішаце, не будзе апублікаваны.", - "confirmations.discard_draft.post.title": "Выдаліць чарнавік?", + "confirmations.discard_draft.post.title": "Скасаваць чарнавік?", "confirmations.discard_edit_media.confirm": "Адмяніць", "confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?", "confirmations.follow_to_list.confirm": "Падпісацца і дадаць у спіс", @@ -261,7 +261,7 @@ "copy_icon_button.copied": "Скапіявана ў буфер абмену", "copypaste.copied": "Скапіравана", "copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену", - "directory.federated": "З вядомага федэсвету", + "directory.federated": "З вядомага федэральнага сусвету", "directory.local": "Толькі з {domain}", "directory.new_arrivals": "Новыя карыстальнікі", "directory.recently_active": "Нядаўна актыўныя", @@ -269,7 +269,7 @@ "disabled_account_banner.text": "Ваш уліковы запіс {disabledAccount} часова адключаны.", "dismissable_banner.community_timeline": "Гэта самыя апошнія допісы ад людзей, уліковыя запісы якіх размяшчаюцца на {domain}.", "dismissable_banner.dismiss": "Адхіліць", - "dismissable_banner.public_timeline": "Вось апошнія публічныя допісы ад карыстальнікаў fediverse на якіх падпісаны карыстальнікі {domain}.", + "dismissable_banner.public_timeline": "Вось апошнія публічныя допісы ад карыстальнікаў федэральнага сусвету, на якіх падпісаныя карыстальнікі {domain}.", "domain_block_modal.block": "Заблакіраваць сервер", "domain_block_modal.block_account_instead": "Заблакіраваць @{name} замест гэтага", "domain_block_modal.they_can_interact_with_old_posts": "Людзі з гэтага сервера змогуць узаемадзейнічаць з вашымі старымі допісамі.", @@ -287,12 +287,12 @@ "domain_pill.their_username": "Унікальны ідэнтыфікатар карыстальніка на серверы. Можна знайсці карыстальнікаў з аднолькавым іменем карыстальніка на розных серверах.", "domain_pill.username": "Імя карыстальніка", "domain_pill.whats_in_a_handle": "Што такое ідэнтыфікатар карыстальніка?", - "domain_pill.who_they_are": "Паколькі ідэнтыфікатары кажуць аб тым, хто гэты чалавек і якім серверам ён карыстаецца, вы можаце ўзаемадзейнічаць з карыстальнікамі .", - "domain_pill.who_you_are": "Паколькі ваш ідэнтыфікатар кажа аб тым, хто вы і дзе знаходзіцеся, людзі могуць узаемадзейнічаць з вамі ў сацыяльнай сетцы .", + "domain_pill.who_they_are": "Паколькі ідэнтыфікатары кажуць аб тым, хто гэты чалавек і якім серверам ён карыстаецца, Вы можаце ўзаемадзейнічаць з карыстальнікамі .", + "domain_pill.who_you_are": "Паколькі Ваш ідэнтыфікатар кажа аб тым, хто Вы і дзе знаходзіцеся, людзі могуць узаемадзейнічаць з вамі ў сацыяльнай сетцы .", "domain_pill.your_handle": "Ваш ідэнтыфікатар:", "domain_pill.your_server": "Ваш лічбавы дом, дзе захоўваюцца ўсе вашыя допісы. Не падабаецца гэты сервер? Змяніце сервер у любы час з захаваннем сваіх падпісчыкаў.", "domain_pill.your_username": "Ваш унікальны ідэнтыфікатар на гэтым серверы. Можна знайсці карыстальнікаў з аднолькавым іменем карыстальніка на розных серверах.", - "embed.instructions": "Убудуйце гэты пост на свой сайт, скапіраваўшы прыведзены ніжэй код", + "embed.instructions": "Убудуйце гэты допіс на свой сайт, скапіраваўшы прыведзены ніжэй код.", "embed.preview": "Вось як гэта будзе выглядаць:", "emoji_button.activity": "Актыўнасць", "emoji_button.clear": "Ачысціць", @@ -304,39 +304,39 @@ "emoji_button.not_found": "Адпаведныя эмодзі не знойдзены", "emoji_button.objects": "Прадметы", "emoji_button.people": "Людзі", - "emoji_button.recent": "Чата выкарыстаныя", + "emoji_button.recent": "Часта выкарыстоўваемыя", "emoji_button.search": "Пошук...", "emoji_button.search_results": "Вынікі пошуку", "emoji_button.symbols": "Сімвалы", "emoji_button.travel": "Падарожжы і месцы", - "empty_column.account_featured.me": "Вы яшчэ нічога не паказалі. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?", - "empty_column.account_featured.other": "{acct} яшчэ нічога не паказаў. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якім найбольш карыстаецеся, і нават профілі сваіх сяброў?", - "empty_column.account_featured_other.unknown": "Гэты профіль яшчэ нічога не паказаў.", + "empty_column.account_featured.me": "Вы яшчэ нічога не паказалі. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якімі найбольш карыстаецеся, і нават профілі сваіх сяброў?", + "empty_column.account_featured.other": "{acct} яшчэ нічога не паказаў. Ці ведалі Вы, што ў сваім профілі Вы можаце паказаць свае хэштэгі, якімі найбольш карыстаецеся, і нават профілі сваіх сяброў?", + "empty_column.account_featured_other.unknown": "Гэты ўліковы запіс яшчэ нічога не паказаў.", "empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю", "empty_column.account_suspended": "Уліковы запіс прыпынены", "empty_column.account_timeline": "Тут няма допісаў!", "empty_column.account_unavailable": "Профіль недаступны", "empty_column.blocks": "Вы яшчэ нікога не заблакіравалі.", - "empty_column.bookmarked_statuses": "У вашых закладках яшчэ няма допісаў. Калі вы дадасце закладку, яна з’явіцца тут.", + "empty_column.bookmarked_statuses": "У Вашых закладках яшчэ няма допісаў. Калі Вы дадасце закладку, яна з’явіцца тут.", "empty_column.community": "Мясцовая стужка пустая. Напішыце нешта публічнае, каб разварушыць справу!", - "empty_column.direct": "Пакуль у вас няма асабістых згадванняў. Калі вы дашляце або атрымаеце штосьці, яно з’явіцца тут.", + "empty_column.direct": "Пакуль у Вас няма асабістых згадванняў. Калі Вы дашляце або атрымаеце штосьці, яно з’явіцца тут.", "empty_column.domain_blocks": "Заблакіраваных даменаў пакуль няма.", "empty_column.explore_statuses": "Зараз не ў трэндзе. Праверце пазней", - "empty_column.favourited_statuses": "Вы яшчэ не ўпадабалі ніводны допіс. Калі гэта адбудзецца, вы ўбачыце яго тут.", - "empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, вы ўбачыце гэтых людзей тут.", - "empty_column.follow_requests": "У вас яшчэ няма запытаў на падпіску. Калі вы атрымаеце запыт, ён з’явіцца тут.", + "empty_column.favourited_statuses": "Вы яшчэ не ўпадабалі ніводны допіс. Калі гэта адбудзецца, Вы ўбачыце яго тут.", + "empty_column.favourites": "Ніхто яшчэ не ўпадабаў гэты допіс. Калі гэта адбудзецца, Вы ўбачыце гэтых людзей тут.", + "empty_column.follow_requests": "У Вас яшчэ няма запытаў на падпіску. Калі Вы атрымаеце запыт, ён з’явіцца тут.", "empty_column.followed_tags": "Вы пакуль не падпісаны ні на адзін хэштэг. Калі падпішацеся, яны з’явяцца тут.", "empty_column.hashtag": "Па гэтаму хэштэгу пакуль што нічога няма.", "empty_column.home": "Галоўная стужка пустая! Падпішыцеся на іншых людзей, каб запоўніць яе. {suggestions}", - "empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены лісту апублікуюць новыя запісы, яны з'явяцца тут.", + "empty_column.list": "У гэтым спісе пакуль што нічога няма. Калі члены спіса апублікуюць новыя запісы, яны з'явяцца тут.", "empty_column.mutes": "Вы яшчэ нікога не ігнаруеце.", - "empty_column.notification_requests": "Чысціня! Тут нічога няма. Калі вы будзеце атрымліваць новыя апавяшчэння, яны будуць з'яўляцца тут у адпаведнасці з вашымі наладамі.", - "empty_column.notifications": "У вас няма ніякіх апавяшчэнняў. Калі іншыя людзі ўзаемадзейнічаюць з вамі, вы ўбачыце гэта тут.", + "empty_column.notification_requests": "Чысціня! Тут нічога няма. Калі Вы будзеце атрымліваць новыя апавяшчэнні, яны будуць з'яўляцца тут у адпаведнасці з Вашымі наладамі.", + "empty_column.notifications": "У Вас няма ніякіх апавяшчэнняў. Калі іншыя людзі захочуць узаемадзейнічаць з Вамі, Вы ўбачыце гэта тут.", "empty_column.public": "Тут нічога няма! Апублікуйце што-небудзь, або падпішыцеся на карыстальнікаў з другіх сервераў", "error.unexpected_crash.explanation": "Гэта старонка не можа быць адлюстравана карэктна з-за памылкі ў нашым кодзе, або праблемы з сумяшчальнасцю браўзера.", - "error.unexpected_crash.explanation_addons": "Гэтая старонка не можа быць адлюстравана карэктна. Верагодна, гэтая памылка выклікана дадатковым кампанентам браўзера або інструментамі аўтаматычнага перакладу", - "error.unexpected_crash.next_steps": "Паспрабуйце абнавіць старонку. Калі гэта не дапаможа, вы можаце паспрабаваць іншы браўзер, альбо выкарыстаць усталяваную праграму.", - "error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і абнавіць старонку. Калі гэта не дапамагае, вы ўсё яшчэ можаце карыстацца Mastodon праз іншы браўзер ці натыўную праграму.", + "error.unexpected_crash.explanation_addons": "Гэтая старонка не можа быць адлюстравана карэктна. Верагодна, гэтая памылка выкліканая дадатковым кампанентам браўзера або інструментамі аўтаматычнага перакладу.", + "error.unexpected_crash.next_steps": "Паспрабуйце абнавіць старонку. Калі гэта не дапаможа, Вы можаце паспрабаваць іншы браўзер, альбо выкарыстаць усталяваную праграму.", + "error.unexpected_crash.next_steps_addons": "Паспрабуйце выключыць іх і абнавіць старонку. Калі гэта не дапамагае, Вы ўсё яшчэ можаце карыстацца Mastodon праз іншы браўзер ці асобную праграму.", "errors.unexpected_crash.copy_stacktrace": "Дадаць дыягнастычны стэк у буфер абмену", "errors.unexpected_crash.report_issue": "Паведаміць аб праблеме", "explore.suggested_follows": "Людзі", @@ -349,14 +349,14 @@ "featured_carousel.post": "Допіс", "featured_carousel.previous": "Назад", "featured_carousel.slide": "{index} з {total}", - "filter_modal.added.context_mismatch_explanation": "Гэтая катэгорыя фільтра не прымяняецца да кантэксту, у якім вы адкрылі гэты пост. Калі вы хочаце, каб паведамленне таксама было адфільтравана ў гэтым кантэксце, вам трэба будзе адрэдагаваць фільтр", + "filter_modal.added.context_mismatch_explanation": "Гэтая катэгорыя фільтра не прымяняецца да кантэксту, у якім Вы адкрылі гэты допіс. Калі Вы хочаце, каб паведамленне таксама было адфільтраванае ў гэтым кантэксце, Вам трэба будзе адрэдагаваць фільтр.", "filter_modal.added.context_mismatch_title": "Неадпаведны кантэкст!", "filter_modal.added.expired_explanation": "Тэрмін дзеяння гэтай катэгорыі фільтраў скончыўся, вам трэба будзе змяніць дату заканчэння тэрміну дзеяння, каб яна прымянялася", "filter_modal.added.expired_title": "Пратэрмінаваны фільтр!", "filter_modal.added.review_and_configure": "Для прагляду і наступнай канфігурацыі фільтра катэгорый, перайдзіце на {settings_link}.", "filter_modal.added.review_and_configure_title": "Налады фільтра", "filter_modal.added.settings_link": "старонка наладаў", - "filter_modal.added.short_explanation": "Гэты пост быў дабаўлены ў катэгорыю з наступным фільтрам: {title}.", + "filter_modal.added.short_explanation": "Гэты допіс быў дададзены ў катэгорыю з наступным фільтрам: {title}.", "filter_modal.added.title": "Фільтр дабаўлены!", "filter_modal.select_filter.context_mismatch": "Не мае дачынення да кантэксту ", "filter_modal.select_filter.expired": "пратэрмінавана", @@ -366,27 +366,27 @@ "filter_modal.select_filter.title": "Фільтраваць гэты допіс", "filter_modal.title.status": "Фільтраваць допіс", "filter_warning.matches_filter": "Адпавядае фільтру \"{title}\"", - "filtered_notifications_banner.pending_requests": "Ад {count, plural, =0 {# людзей якіх} one {# чалавека якіх} few {# чалавек якіх} many {# людзей якіх} other {# чалавека якіх}} вы магчыма ведаеце", + "filtered_notifications_banner.pending_requests": "Ад {count, plural, =0 {# людзей якіх} one {# чалавека якіх} few {# чалавек якіх} many {# людзей якіх} other {# чалавека якіх}} Вы магчыма ведаеце", "filtered_notifications_banner.title": "Адфільтраваныя апавяшчэнні", "firehose.all": "Усе", "firehose.local": "Гэты сервер", "firehose.remote": "Іншыя серверы", "follow_request.authorize": "Аўтарызацыя", "follow_request.reject": "Адхіліць", - "follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.", + "follow_requests.unlocked_explanation": "Ваш уліковы запіс не схаваны, аднак прадстаўнікі {domain} палічылі, што Вы можаце захацець праглядзець запыты на падпіску з гэтых уліковых запісаў уручную.", "follow_suggestions.curated_suggestion": "Выбар адміністрацыі", "follow_suggestions.dismiss": "Не паказваць зноў", "follow_suggestions.featured_longer": "Адабраныя камандай {domain} уручную", - "follow_suggestions.friends_of_friends_longer": "Папулярнае сярод людзей, на якіх Вы падпісаны", + "follow_suggestions.friends_of_friends_longer": "Папулярнае сярод людзей, на якіх Вы падпісаныя", "follow_suggestions.hints.featured": "Гэты профіль быў выбраны ўручную камандай {domain}.", - "follow_suggestions.hints.friends_of_friends": "Гэты профіль папулярны сярод людзей, на якіх вы падпісаліся.", + "follow_suggestions.hints.friends_of_friends": "Гэты профіль папулярны сярод людзей, на якіх Вы падпісаліся.", "follow_suggestions.hints.most_followed": "Гэты профіль - адзін з профіляў з самай вялікай колькасцю падпісак на {domain}.", "follow_suggestions.hints.most_interactions": "У апошні час гэты профіль прыцягвае шмат увагі на {domain}.", - "follow_suggestions.hints.similar_to_recently_followed": "Гэты профіль падобны на профілі, на якія вы нядаўна падпісаліся.", + "follow_suggestions.hints.similar_to_recently_followed": "Гэты профіль падобны на профілі, на якія Вы нядаўна падпісаліся.", "follow_suggestions.personalized_suggestion": "Персаналізаваная прапанова", "follow_suggestions.popular_suggestion": "Папулярная прапанова", "follow_suggestions.popular_suggestion_longer": "Папулярнае на {domain}", - "follow_suggestions.similar_to_recently_followed_longer": "Падобныя профілі, за якімі вы нядаўна сачылі", + "follow_suggestions.similar_to_recently_followed_longer": "Падобныя профілі, за якімі Вы нядаўна сачылі", "follow_suggestions.view_all": "Праглядзець усё", "follow_suggestions.who_to_follow": "На каго падпісацца", "followed_tags": "Падпіскі", @@ -435,7 +435,7 @@ "home.pending_critical_update.link": "Прагледзець абнаўленні", "home.pending_critical_update.title": "Даступна крытычнае абнаўленне бяспекі!", "home.show_announcements": "Паказаць аб'явы", - "ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.", + "ignore_notifications_modal.disclaimer": "Mastodon не можа паведамляць карыстальнікам, што Вы праігнаравалі апавяшчэнні ад іх. Ігнараванне апавяшчэнняў не спыніць адпраўку саміх паведамленняў.", "ignore_notifications_modal.filter_instead": "Замест гэтага адфільтраваць", "ignore_notifications_modal.filter_to_act_users": "Вы па-ранейшаму зможаце прымаць, адхіляць ці скардзіцца на карыстальнікаў", "ignore_notifications_modal.filter_to_avoid_confusion": "Выкарыстанне фільтраў дапамагае пазбягаць патэнцыйнай блытаніны", @@ -444,15 +444,15 @@ "ignore_notifications_modal.limited_accounts_title": "Ігнараваць апавяшчэнні ад уліковых запісаў пад мадэрацыяй?", "ignore_notifications_modal.new_accounts_title": "Ігнараваць апавяшчэнні ад новых уліковых запісаў?", "ignore_notifications_modal.not_followers_title": "Ігнараваць апавяшчэнні ад людзей, якія не падпісаныя на вас?", - "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей на якіх вы не падпісаны?", - "ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя асабістыя згадванні?", + "ignore_notifications_modal.not_following_title": "Ігнараваць апавяшчэнні ад людзей, на якіх Вы не падпісаныя?", + "ignore_notifications_modal.private_mentions_title": "Ігнараваць апавяшчэнні пра непажаданыя прыватныя згадванні?", "info_button.label": "Даведка", "info_button.what_is_alt_text": "

Што такое альтэрнатыўны тэкст?

Альтэрнатыўны тэкст апісвае відарыс людзям з парушэннем зроку, павольным злучэннем або тым, каму патрэбны дадатковы кантэкст.

Вы можаце зрабіць відарыс больш дасяжным і зразумелым для ўсіх, напісаўшы зразумелы, сціслы і аб'ектыўны альтэрнатыўны тэкст.

  • Ахоплівайце важныя элементы
  • Тлумачце тэкст на відарысе
  • Карыстайцеся звычайнымі сказамі
  • Пазбягайце залішняй інфармацыі
  • Засяроджвайцеся на тэндэнцыях і ключавых высновах у цяжкіх для разумення візуальных матэрыялах (напрыклад, дыяграмах або картах)
", - "interaction_modal.action.favourite": "Каб працягнуць, вы мусіце ўпадабаць нешта са свайго ўліковага запісу.", - "interaction_modal.action.follow": "Каб працягнуць, вы мусіце падпісацца на некага са свайго ўліковага запісу.", - "interaction_modal.action.reblog": "Каб працягнуць, Вам трэба пашырыць допіс са свайго профілю.", - "interaction_modal.action.reply": "Каб працягнуць, Вам трэба адказаць са свайго профілю.", - "interaction_modal.action.vote": "Каб працягнуць, Вам трэба прагаласаваць са свайго профілю.", + "interaction_modal.action.favourite": "Каб працягнуць, Вы мусіце ўпадабаць нешта са свайго ўліковага запісу.", + "interaction_modal.action.follow": "Каб працягнуць, Вы мусіце падпісацца на некага са свайго ўліковага запісу.", + "interaction_modal.action.reblog": "Каб працягнуць, Вам трэба пашырыць допіс са свайго ўліковага запісу.", + "interaction_modal.action.reply": "Каб працягнуць, Вам трэба адказаць са свайго ўліковага запісу.", + "interaction_modal.action.vote": "Каб працягнуць, Вам трэба прагаласаваць са свайго ўліковага запісу.", "interaction_modal.go": "Перайсці", "interaction_modal.no_account_yet": "Не маеце ўліковага запісу?", "interaction_modal.on_another_server": "На іншым серверы", @@ -499,7 +499,7 @@ "keyboard_shortcuts.toggle_sensitivity": "Паказаць/схаваць медыя", "keyboard_shortcuts.toot": "Стварыць новы допіс", "keyboard_shortcuts.translate": "каб перакласці допіс", - "keyboard_shortcuts.unfocus": "Расфакусаваць тэкставую вобласць/пошукавы радок", + "keyboard_shortcuts.unfocus": "Расфакусіраваць тэкставую вобласць/пошукавы радок", "keyboard_shortcuts.up": "Перамясціцца ўверх па спісе", "learn_more_link.got_it": "Зразумеў(-ла)", "learn_more_link.learn_more": "Падрабязней", @@ -509,7 +509,7 @@ "lightbox.zoom_in": "Маштабаваць да фактычнага памеру", "lightbox.zoom_out": "Дапасаваць усё змесціва пад памеры экрана", "limited_account_hint.action": "Усе роўна паказваць профіль", - "limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі", + "limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі {domain}.", "link_preview.author": "Ад {name}", "link_preview.more_from_author": "Больш ад {name}", "link_preview.shares": "{count, plural, one {{counter} допіс} few {{counter} допісы} many {{counter} допісаў} other {{counter} допісу}}", @@ -532,7 +532,7 @@ "lists.no_members_yet": "Пакуль няма ўдзельнікаў.", "lists.no_results_found": "Нічога не знойдзена.", "lists.remove_member": "Выдаліць", - "lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся", + "lists.replies_policy.followed": "Любы карыстальнік, на якога Вы падпісаліся", "lists.replies_policy.list": "Удзельнікі гэтага спісу", "lists.replies_policy.none": "Нікога", "lists.save": "Захаваць", @@ -541,16 +541,16 @@ "load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}", "loading_indicator.label": "Ідзе загрузка…", "media_gallery.hide": "Схаваць", - "moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны таму што вы перанесены на {movedToAccount}.", + "moved_to_account_banner.text": "Ваш уліковы запіс {disabledAccount} зараз адключаны, таму што Вы перайшлі на {movedToAccount}.", "mute_modal.hide_from_notifications": "Схаваць з апавяшчэнняў", "mute_modal.hide_options": "Схаваць опцыі", "mute_modal.indefinite": "Пакуль я не прыбяру ігнараванне", "mute_modal.show_options": "Паказаць опцыі", - "mute_modal.they_can_mention_and_follow": "Карыстальнік зможа згадваць вас і падпісацца на вас, але вы гэтага не ўбачыце.", + "mute_modal.they_can_mention_and_follow": "Карыстальнік зможа згадваць Вас і падпісацца на Вас, але Вы гэтага не ўбачыце.", "mute_modal.they_wont_know": "Карыстальнік не будзе ведаць пра ігнараванне.", "mute_modal.title": "Ігнараваць карыстальніка?", "mute_modal.you_wont_see_mentions": "Вы не ўбачыце паведамленняў са згадваннем карыстальніка.", - "mute_modal.you_wont_see_posts": "Карыстальнік па-ранейшаму будзе бачыць вашыя паведамленні, але вы не будзеце паведамленні карыстальніка.", + "mute_modal.you_wont_see_posts": "Карыстальнік па-ранейшаму будзе бачыць Вашыя допісы, але Вы не будзеце бачыць ягоныя.", "navigation_bar.about": "Пра нас", "navigation_bar.account_settings": "Пароль і бяспека", "navigation_bar.administration": "Адміністрацыя", @@ -558,7 +558,7 @@ "navigation_bar.automated_deletion": "Аўтаматычнае выдаленне допісаў", "navigation_bar.blocks": "Заблакіраваныя карыстальнікі", "navigation_bar.bookmarks": "Закладкі", - "navigation_bar.direct": "Асабістыя згадванні", + "navigation_bar.direct": "Прыватныя згадванні", "navigation_bar.domain_blocks": "Заблакіраваныя дамены", "navigation_bar.favourites": "Упадабанае", "navigation_bar.filters": "Ігнараваныя словы", @@ -609,22 +609,22 @@ "notification.mentioned_you": "{name} згадаў вас", "notification.moderation-warning.learn_more": "Даведацца больш", "notification.moderation_warning": "Вы атрымалі папярэджанне ад мадэратараў", - "notification.moderation_warning.action_delete_statuses": "Некаторыя вашыя допісы былі выдаленыя.", + "notification.moderation_warning.action_delete_statuses": "Некаторыя Вашыя допісы былі выдаленыя.", "notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.", "notification.moderation_warning.action_none": "Ваш уліковы запіс атрымаў папярэджанне ад мадэратараў.", "notification.moderation_warning.action_sensitive": "З гэтага моманту вашыя допісы будуць пазначаныя як далікатныя.", "notification.moderation_warning.action_silence": "Ваш уліковы запіс быў абмежаваны.", - "notification.moderation_warning.action_suspend": "Ваш уліковы запіс быў прыпынены.", + "notification.moderation_warning.action_suspend": "Ваш уліковы запіс быў заблакіраваны.", "notification.own_poll": "Ваша апытанне скончылася", - "notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася", + "notification.poll": "Апытанне, дзе Вы прынялі ўдзел, скончылася", "notification.reblog": "{name} пашырыў ваш допіс", "notification.reblog.name_and_others_with_link": "{name} і {count, plural, one {# іншы} many {# іншых} other {# іншых}} пашырылі ваш допіс", "notification.relationships_severance_event": "Страціў сувязь з {name}", - "notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.", + "notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу ўліковага запісу {target}, што азначае, што Вы больш не можаце атрымліваць ад іх абнаўленні ці ўзаемадзейнічаць з імі.", "notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.", "notification.relationships_severance_event.learn_more": "Даведацца больш", - "notification.relationships_severance_event.user_domain_block": "Вы заблакіравалі {target} выдаліўшы {followersCount} сваіх падпісчыкаў і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}, за якімі вы сочыце.", + "notification.relationships_severance_event.user_domain_block": "Вы заблакіравалі {target} выдаліўшы {followersCount} сваіх падпісчыкаў і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}, за якімі Вы сочыце.", "notification.status": "Новы допіс ад {name}", "notification.update": "Допіс {name} адрэдагаваны", "notification_requests.accept": "Прыняць", @@ -639,8 +639,8 @@ "notification_requests.dismiss_multiple": "{count, plural,one {Адмовіцца ад запыту…} other {Адмовіцца ад запытаў…}}", "notification_requests.edit_selection": "Рэдагаваць", "notification_requests.exit_selection": "Гатова", - "notification_requests.explainer_for_limited_account": "Апавяшчэнне з гэтага профілю было адфільтраванае, бо гэты профіль абмежаваў мадэратар.", - "notification_requests.explainer_for_limited_remote_account": "Апавяшчэнні з гэтага профілю былі адфільтраваныя, бо гэты профіль абмежаваў мадэратар.", + "notification_requests.explainer_for_limited_account": "Апавяшчэнне з гэтага ўліковага запісу было адфільтраванае, бо гэты ўліковы запіс абмежаваў мадэратар.", + "notification_requests.explainer_for_limited_remote_account": "Апавяшчэнні з гэтага ўліковага запісу былі адфільтраваныя, бо гэты ўліковы запіс абмежаваў мадэратар.", "notification_requests.maximize": "Разгарнуць", "notification_requests.minimize_banner": "Згарнуць банер адфільтраваных апавяшчэнняў", "notification_requests.notifications_from": "Апавяшчэнні ад {name}", @@ -675,7 +675,7 @@ "notifications.filter.follows": "Падпісаны на", "notifications.filter.mentions": "Згадванні", "notifications.filter.polls": "Вынікі апытання", - "notifications.filter.statuses": "Навіны ад людзей, на якіх вы падпісаны", + "notifications.filter.statuses": "Навіны ад людзей, на якіх Вы падпісаныя", "notifications.grant_permission": "Дазволіць.", "notifications.group": "{count} Апавяшчэнняў", "notifications.mark_as_read": "Пазначыць усе апавяшчэнні як прачытаныя", @@ -695,20 +695,20 @@ "notifications.policy.filter_not_followers_hint": "Уключаючы людзей, якія падпісаны на вас менш, чым {days, plural, one {# дзень} few {# дні} many {# дзён} other {# дня}}", "notifications.policy.filter_not_followers_title": "Людзі, якія не падпісаны на вас", "notifications.policy.filter_not_following_hint": "Пакуль вы не пацвердзіце іх уручную", - "notifications.policy.filter_not_following_title": "Людзі, на якіх вы не падпісаны", - "notifications.policy.filter_private_mentions_hint": "Фільтруецца за выключэннем адказу на вашае згадванне ці калі вы падпісаны на адпраўніка", - "notifications.policy.filter_private_mentions_title": "Непажаданыя асаблівыя згадванні", + "notifications.policy.filter_not_following_title": "Людзі, на якіх Вы не падпісаныя", + "notifications.policy.filter_private_mentions_hint": "Фільтруецца, за выключэннем адказу на Вашае згадванне ці калі Вы падпісаныя на адпраўніка", + "notifications.policy.filter_private_mentions_title": "Непажаданыя прыватныя згадванні", "notifications.policy.title": "Наладзіць апавяшчэнні ад…", "notifications_permission_banner.enable": "Уключыць апавяшчэнні на працоўным стале", "notifications_permission_banner.how_to_control": "Каб атрымліваць апавяшчэнні, калі Mastodon не адкрыты, уключыце апавяшчэнні працоўнага стала. Вы зможаце дакладна кантраляваць, якія падзеі будуць ствараць апавяшчэнні з дапамогай {icon} кнопкі, як толькі яны будуць уключаны.", "notifications_permission_banner.title": "Не прапусціце нічога", "onboarding.follows.back": "Назад", "onboarding.follows.done": "Гатова", - "onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыце спробу пазней.", + "onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыць спробу пазней.", "onboarding.follows.search": "Пошук", "onboarding.follows.title": "Падпішыцеся на некага, каб пачаць", "onboarding.profile.discoverable": "Зрабіць мой профіль бачным", - "onboarding.profile.discoverable_hint": "Калі вы звяртаецеся да адкрытасці на Mastodon, вашы паведамленні могуць з'яўляцца ў выніках пошуку і тэндэнцый, а ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.", + "onboarding.profile.discoverable_hint": "Калі Вы звяртаецеся да адкрытасці на Mastodon, Вашы допісы могуць з'яўляцца ў выніках пошуку і трэндах, а Ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.", "onboarding.profile.display_name": "Бачнае імя", "onboarding.profile.display_name_hint": "Ваша поўнае імя або ваш псеўданім…", "onboarding.profile.note": "Біяграфія", @@ -717,7 +717,7 @@ "onboarding.profile.title": "Налады профілю", "onboarding.profile.upload_avatar": "Загрузіць фота профілю", "onboarding.profile.upload_header": "Загрузіць шапку профілю", - "password_confirmation.exceeds_maxlength": "Пароль пацьверджання перавышае максімальна дапушчальную даўжыню", + "password_confirmation.exceeds_maxlength": "Пароль пацвярджэння перавышае максімальна дапушчальную даўжыню", "password_confirmation.mismatching": "Пароль пацьверджання не супадае", "picture_in_picture.restore": "Вярніце назад", "poll.closed": "Закрыта", @@ -737,7 +737,7 @@ "privacy.private.short": "Падпісчыкі", "privacy.public.long": "Усе, хто ёсць і каго няма ў Mastodon", "privacy.public.short": "Публічны", - "privacy.unlisted.additional": "Паводзіць сябе гэтак жа, як і публічны, за выключэннем таго, што пост не будзе адлюстроўвацца ў жывой стужцы, хэштэгах, аглядзе або ў пошуку Mastodon, нават калі вы ўключылі бачнасць у пошуку ў наладах.", + "privacy.unlisted.additional": "Паводзіць сябе гэтак жа, як і публічны, за выключэннем таго, што допіс не будзе адлюстроўвацца ў жывой стужцы, хэштэгах, аглядзе або ў пошуку Mastodon, нават калі Вы ўключылі бачнасць у пошуку ў наладах.", "privacy.unlisted.long": "Менш фанфар ад алгарытмаў", "privacy.unlisted.short": "Ціхі публічны", "privacy_policy.last_updated": "Адноўлена {date}", @@ -761,7 +761,7 @@ "reply_indicator.cancel": "Скасаваць", "reply_indicator.poll": "Апытанне", "report.block": "Заблакіраваць", - "report.block_explanation": "Вы перастанеце бачыць допісы гэтага карыстальніка. Ён не зможа сачыць за вамі і бачыць вашы допісы. Ён зможа зразумець, што яго заблакіравалі.", + "report.block_explanation": "Вы перастанеце бачыць допісы гэтага карыстальніка. Ён не зможа сачыць за Вамі і бачыць Вашы допісы. Ён зможа зразумець, што яго заблакіравалі.", "report.categories.legal": "Звязанае з правам", "report.categories.other": "Іншае", "report.categories.spam": "Спам", @@ -775,9 +775,9 @@ "report.forward": "Пераслаць на {target}", "report.forward_hint": "Гэты ўліковы запіс з іншага сервера. Даслаць ананімную копію скаргі і туды?", "report.mute": "Ігнараваць", - "report.mute_explanation": "Вы не будзеце бачыць допісы гэтага карыстальніка. Ён усё яшчэ зможа сачыць за вамі і бачыць вашы допісы, не ведаючы, што яго ігнаруюць.", + "report.mute_explanation": "Вы не будзеце бачыць допісы гэтага карыстальніка. Ён усё яшчэ зможа сачыць за Вамі і бачыць Вашы допісы, не ведаючы, што яго ігнаруюць.", "report.next": "Далей", - "report.placeholder": "Дадатковы каментар", + "report.placeholder": "Дадатковыя каментарыі", "report.reasons.dislike": "Мне ён не падабаецца", "report.reasons.dislike_description": "Гэта тое, што Вы не хочаце бачыць", "report.reasons.legal": "Гэта незаконна", @@ -794,9 +794,9 @@ "report.statuses.title": "Ці ёсць допісы, каб падмацаваць гэтую скаргу?", "report.submit": "Адправіць", "report.target": "Скарга на {target}", - "report.thanks.take_action": "Вось вашыя варыянты кантролю над тым, што вы бачыце в Mastodon:", - "report.thanks.take_action_actionable": "Пакуль мы разглядаем яе, вы можаце распачаць дзеянні супраць @{name}:", - "report.thanks.title": "Ці хочаце вы бачыць гэта?", + "report.thanks.take_action": "Вось Вашыя варыянты кантролю над тым, што Вы бачыце в Mastodon:", + "report.thanks.take_action_actionable": "Пакуль мы разглядаем яе, Вы можаце распачаць дзеянні супраць @{name}:", + "report.thanks.title": "Ці хочаце Вы бачыць гэта?", "report.thanks.title_actionable": "Дзякуем за зварот, мы разбяромся з гэтым.", "report.unfollow": "Адпісацца ад @{name}", "report.unfollow_explanation": "Вы падпісаныя на гэты ўліковы запіс. Каб не бачыць допісы з яго ў вашай стужцы, адпішыцеся.", @@ -838,10 +838,10 @@ "server_banner.about_active_users": "Людзі, якія карыстаюцца гэтым сервера на працягу апошніх 30 дзён (Штомесячна Актыўныя Карыстальнікі)", "server_banner.active_users": "актыўныя карыстальнікі", "server_banner.administered_by": "Адміністратар:", - "server_banner.is_one_of_many": "{domain} - гэта адзін з многіх незалежных сервераў Mastodon, якія вы можаце выкарыстоўваць для ўдзелу ў fediverse.", + "server_banner.is_one_of_many": "{domain} - гэта адзін з многіх незалежных сервераў Mastodon, які Вы можаце выкарыстоўваць для ўдзелу ў федэральным сусвеце.", "server_banner.server_stats": "Статыстыка сервера:", "sign_in_banner.create_account": "Стварыць уліковы запіс", - "sign_in_banner.follow_anyone": "Сачыце за кім заўгодна ва ўсім fediverse і глядзіце ўсё ў храналагічным парадку. Ніякіх алгарытмаў, рэкламы або клікбэйту.", + "sign_in_banner.follow_anyone": "Падпісвайцеся на каго захочаце ва ўсім федэральным сусвеце і глядзіце ўсё ў храналагічным парадку. Ніякіх алгарытмаў, рэкламы або клікбэйту.", "sign_in_banner.mastodon_is": "Mastodon - лепшы спосаб быць у курсе ўсяго, што адбываецца.", "sign_in_banner.sign_in": "Увайсці", "sign_in_banner.sso_redirect": "Уваход ці рэгістрацыя", @@ -851,7 +851,7 @@ "status.block": "Заблакаваць @{name}", "status.bookmark": "Дадаць закладку", "status.cancel_reblog_private": "Прыбраць", - "status.cannot_reblog": "Гэты пост нельга пашырыць", + "status.cannot_reblog": "Гэты допіс нельга пашырыць", "status.context.load_new_replies": "Даступныя новыя адказы", "status.context.loading": "Правяраюцца новыя адказы", "status.continued_thread": "Працяг ланцужка", @@ -929,7 +929,7 @@ "time_remaining.seconds": "{number, plural, one {засталася # секунда} few {засталося # секунды} many {засталося # секунд} other {засталося # секунды}}", "trends.counter_by_accounts": "{count, plural, one {{counter} чалавек} few {{counter} чалавекі} many {{counter} людзей} other {{counter} чалавек}} за {days, plural, one {{days} апошні дзень} few {{days} апошнія дні} many {{days} апошніх дзён} other {{days} апошніх дзён}}", "trends.trending_now": "Актуальнае", - "ui.beforeunload": "Ваш чарнавік знішчыцца калі вы пакінеце Mastodon.", + "ui.beforeunload": "Ваш чарнавік будзе страчаны, калі Вы пакінеце Mastodon.", "units.short.billion": "{count} млрд.", "units.short.million": "{count} міл.", "units.short.thousand": "{count} тыс.", @@ -937,7 +937,7 @@ "upload_button.label": "Дадаць выяву, відэа- ці аўдыяфайл", "upload_error.limit": "Перавышана колькасць файлаў.", "upload_error.poll": "Немагчыма прымацаваць файл да апытання.", - "upload_form.drag_and_drop.instructions": "Каб абраць медыя далучэнне, націсніце прабел ці Enter. Падчас перасоўвання выкарыстоўвайце кнопкі са стрэлкамі, каб пасунуць медыя далучэнне ў любым напрамку. Націсніце прабел ці Enter зноў, каб перасунуць медыя далучэнне ў новае месца, або Escape для адмены.", + "upload_form.drag_and_drop.instructions": "Каб абраць медыя ўлажэнне, націсніце прабел ці Enter. Падчас перасоўвання выкарыстоўвайце кнопкі са стрэлкамі, каб пасунуць медыя далучэнне ў любым напрамку. Націсніце прабел ці Enter зноў, каб перасунуць медыя далучэнне ў новае месца, або Escape для адмены.", "upload_form.drag_and_drop.on_drag_cancel": "Перасоўванне адмененае. Медыя ўлажэнне {item} на месцы.", "upload_form.drag_and_drop.on_drag_end": "Медыя ўлажэнне {item} на месцы.", "upload_form.drag_and_drop.on_drag_over": "Медыя ўлажэнне {item} перасунутае.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 92e3d7cafd1..310c701c71b 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -501,6 +501,8 @@ "keyboard_shortcuts.translate": "게시물 번역", "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제", "keyboard_shortcuts.up": "리스트에서 위로 이동", + "learn_more_link.got_it": "확인", + "learn_more_link.learn_more": "더 알아보기", "lightbox.close": "닫기", "lightbox.next": "다음", "lightbox.previous": "이전", @@ -601,6 +603,7 @@ "notification.label.mention": "멘션", "notification.label.private_mention": "개인 멘션", "notification.label.private_reply": "개인 답글", + "notification.label.quote": "{name} 님이 내 게시물을 인용했습니다", "notification.label.reply": "답글", "notification.mention": "멘션", "notification.mentioned_you": "{name} 님의 멘션", @@ -848,6 +851,8 @@ "status.bookmark": "북마크", "status.cancel_reblog_private": "부스트 취소", "status.cannot_reblog": "이 게시물은 부스트 할 수 없습니다", + "status.context.load_new_replies": "새 답글 보기", + "status.context.loading": "추가 답글 확인중", "status.continued_thread": "이어지는 글타래", "status.copy": "게시물 링크 복사", "status.delete": "삭제", @@ -874,6 +879,9 @@ "status.open": "상세 정보 표시", "status.pin": "고정", "status.quote_error.filtered": "필터에 의해 가려짐", + "status.quote_error.not_available": "게시물 사용 불가", + "status.quote_error.pending_approval": "게시물 대기중", + "status.quote_post_author": "인용된 @{name} 님의 게시물", "status.read_more": "더 보기", "status.reblog": "부스트", "status.reblog_private": "원래의 수신자들에게 부스트", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 40b3c06281c..33cb8cac4bf 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Suá掉跟tuè lí ê", "confirmations.remove_from_followers.message": "{name} ē停止跟tuè lí。Lí kám確定beh繼續?", "confirmations.remove_from_followers.title": "Kám beh suá掉跟tuè lí ê?", + "confirmations.revoke_quote.confirm": "Thâi掉PO文", + "confirmations.revoke_quote.message": "Tsit ê動作bē當復原。", + "confirmations.revoke_quote.title": "Kám beh thâi掉PO文?", "confirmations.unfollow.confirm": "取消跟tuè", "confirmations.unfollow.message": "Lí kám確定無愛跟tuè {name}?", "confirmations.unfollow.title": "Kám beh取消跟tuè tsit ê用者?", @@ -600,6 +603,7 @@ "notification.label.mention": "提起", "notification.label.private_mention": "私人ê提起", "notification.label.private_reply": "私人ê回應", + "notification.label.quote": "{name} 引用lí ê PO文", "notification.label.reply": "回應", "notification.mention": "提起", "notification.mentioned_you": "{name}kā lí提起", @@ -657,6 +661,7 @@ "notifications.column_settings.mention": "提起:", "notifications.column_settings.poll": "投票ê結果:", "notifications.column_settings.push": "Sak通知", + "notifications.column_settings.quote": "引用:", "notifications.column_settings.reblog": "轉送:", "notifications.column_settings.show": "佇欄內底顯示", "notifications.column_settings.sound": "播放聲音", @@ -893,6 +898,7 @@ "status.reply": "回應", "status.replyAll": "應討論線", "status.report": "檢舉 @{name}", + "status.revoke_quote": "Kā 我ê PO文tuì @{name} ê thâi掉", "status.sensitive_warning": "敏感ê內容", "status.share": "分享", "status.show_less_all": "Lóng收起來", diff --git a/config/locales/activerecord.be.yml b/config/locales/activerecord.be.yml index 975bc1a7047..3ddb3e328ed 100644 --- a/config/locales/activerecord.be.yml +++ b/config/locales/activerecord.be.yml @@ -3,7 +3,7 @@ be: activerecord: attributes: poll: - expires_at: Дэдлайн + expires_at: Скончыцца options: Выбар user: agreement: Пагадненне аб абслугоўванні @@ -19,7 +19,7 @@ be: domain: invalid: не з’яўляецца сапраўдным даменным імем messages: - invalid_domain_on_line: "%{value} не пасуе для карэктнай назвы сервера" + invalid_domain_on_line: "%{value} не пасуе для карэктнай назвы дамена" models: account: attributes: @@ -44,7 +44,7 @@ be: attributes: account_id: taken: ужо ў спісе - must_be_following: мусіць быць карыстальнікам у вашых падпісках + must_be_following: мусіць быць карыстальнікам у Вашых падпісках status: attributes: reblog: @@ -61,7 +61,7 @@ be: blocked: выкарыстоўвае забароненую крыніцу электроннай пошты unreachable: не існуе role_id: - elevated: не можа быць вышэй за вашу бягучую ролю + elevated: не можа быць вышэй за Вашу бягучую ролю user_role: attributes: permissions_as_keys: @@ -69,7 +69,7 @@ be: elevated: не можа ўключыць дазволы, якімі не валодае ваша бягучая роля own_role: не можа быць зменены з вашай бягучай роляй position: - elevated: не можа быць вышэй за вашу бягучую ролю + elevated: не можа быць вышэй за Вашу бягучую ролю own_role: не можа быць зменены з вашай бягучай роляй webhook: attributes: diff --git a/config/locales/az.yml b/config/locales/az.yml index 516f03f6040..1e66e71bed3 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -11,26 +11,49 @@ az: accounts: delete: Veriləri sil destroyed_msg: "%{username} - verilərinin tezliklə silinməsi növbədədir" + display_name: Ekran adı + followers: İzləyicilər + follows: İzlənilənlər moderation: title: Moderasiya moderation_notes: Moderasiya notları + most_recent_activity: Ən son fəaliyyət + most_recent_ip: Ən son IP remote_suspension_irreversible: Bu hesabın veriləri geri qaytarılmayacaq şəkildə silinib. remote_suspension_reversible_hint_html: Hesabın fəaliyyəti öz serverində dayandırılıb və verilər %{date} tarixində tamamilə silinəcək. O vaxta qədər, uzaq server hər hansısa mənfi təsir olmadan bu hesabı bərpa edə bilər. Hesabın bütün verilərini dərhal silmək istəyirsinizsə, bunu aşağıdan edə bilərsiniz. + resend_confirmation: + send: Təsdiq keçidini təkrar göndər reset_password: Parolu sıfırla + security: Təhlükəsizlik security_measures: only_password: Yalnız parol password_and_2fa: Parol və 2FA suspension_irreversible: Bu hesabın veriləri geri qaytarılmayacaq şəkildə silinib. Hesabı istifadəyə yararlı etmək üçün hesab fəaliyyətinin dayandırılma prosesini ləğv edə bilərsiniz, ancaq daha əvvəl sahib olduğunuz heç bir veri geri qaytarılmayacaq. action_logs: action_types: + resend_user: Təsdiq poçtunu təkrar göndər reset_password_user: Parolu sıfırla actions: approve_appeal_html: "%{name}, moderasiya qərarına %{target} tərəfindən verilən etirazı təsdiqlədi" reject_appeal_html: "%{name}, moderasiya qərarına %{target} tərəfindən verilən etirazı rədd etdi" reset_password_user_html: "%{name}, %{target} istifadəçisinin parolunu sıfırladı" + deleted_account: silinmiş hesab dashboard: + media_storage: Media anbarı software: Yazılım + domain_allows: + export: Xaricə köçür + domain_blocks: + export: Xaricə köçür + export_domain_blocks: + import: + title: Əngəllənən domenləri daxilə köçür + new: + title: Əngəllənən domenləri daxilə köçür instances: + dashboard: + instance_followers_measure: oradakı izləyicilərimiz + instance_follows_measure: buradakı izləyiciləri moderation: title: Moderasiya moderation_notes: @@ -61,18 +84,29 @@ az: view_dashboard_description: İstifadəçilərin idarəetmə lövhəsinə və müxtəlif metriklərə erişməsinə icazə verir view_devops_description: İstifadəçilərin Sidekiq və pgHero idarəetmə lövhələrinə erişməsinə icazə verir settings: + appearance: + title: Görünüş + discovery: + public_timelines: Ümumi zaman xətləri + title: Kəşf et registrations: moderation_recommandation: Hər kəs üçün qeydiyyatı açmazdan əvvəl lütfən əmin olun ki, adekvat və reaktiv moderasiya komandanız var! registrations_mode: warning_hint: Moderasiya komandanızın spam və zərərli qeydiyyatları vaxtında idarə edə biləcəyinə əmin deyilsinizsə, “Qeydiyyat üçün təsdiq tələb olunur”dan istifadə etməyi tövsiyə edirik. title: Server ayarları statuses: + application: Tətbiq metadata: Meta veri system_checks: elasticsearch_version_check: message_html: 'Uyumlu olmayan Elasticsearch versiyası: %{value}' tags: updated_msg: Mövzu etiketi ayarları uğurla güncəlləndi + trends: + tags: + used_by_over_week: + one: Keçən həftə bir nəfər istifadə etdi + other: Keçən həftə %{count} nəfər istifadə etdi admin_mailer: auto_close_registrations: body: Son vaxtlarda moderator fəaliyyətinin olmamasına görə, %{instance} üzərindəki qeydiyyatlar avtomatik olaraq manual yoxlanış tələb edəcək şəkildə dəyişdirilib, beləliklə %{instance} potensial zərərli aktyorlar tərəfindən istifadə edilən platforma çevrilməyəcək. İstənilən vaxt açıq qeydiyyat rejiminə qaytara bilərsiniz. @@ -81,23 +115,52 @@ az: next_steps: Moderasiya qərarını geri almaq üçün etirazı təsdiqləyə, ya da etirazı yox saya bilərsiniz. subject: "%{username}, %{instance} üzərindəki bir moderasiya qərarına etiraz edir" appearance: + advanced_web_interface: Qabaqcıl veb interfeys + advanced_web_interface_hint: 'Tam ekran enini istifadə etmək istəyirsinizsə, qabaqcıl veb interfeys, istədiyiniz qədər məlumatı eyni anda görə bilməyiniz üçün bir çox fərqli sütunu konfiqurasiya etməyinizə imkan verir: Əsas ekran, bildirişlər, birləşmiş zaman xətti, istənilən sayda siyahı və mövzu etiketləri.' animations_and_accessibility: Animasiyalar və erişiləbilənlik + confirmation_dialogs: Təsdiq dialoq pəncərələri + discovery: Kəşf et + localization: + guide_link_text: Hər kəs töhfə verə bilər. + sensitive_content: Həssas məzmun applications: + created: Tətbiq uğurla yaradıldı + destroyed: Tətbiq uğurla silindi regenerate_token: Erişim tokenini təkrar yarat token_regenerated: Erişim tokeni uğurla yaradıldı your_token: Erişim tokeniniz auth: + captcha_confirmation: + help_html: CAPTCHA-nı həll etməklə bağlı probleminiz varsa, %{email} üzərindən əlaqə saxlaya bilərsiniz, sizə kömək edə bilərik. + hint_html: Sadəcə bir şey qaldı! Sizin insan olduğunuzu təsdiqləməyimiz lazımdır (bunu spam-ları əngəlləmək üçün edirik). Aşağıdakı CAPTCHA-nı həll edib "Davam et"ə klikləyin. + title: Təhlükəsizlik yoxlanışı confirmations: wrong_email_hint: Əgər bu e-poçt ünvanı doğru deyilsə, hesab ayarlarında onu dəyişdirə bilərsiniz. + delete_account: Hesabı sil + delete_account_html: Hesabınızı silmək istəyirsinizsə, buradan davam edə bilərsiniz. Sizdən təsdiq etməyiniz soruşulacaq. forgot_password: Parolunuzu unutmusunuz? invalid_reset_password_token: Parol sıfırlama tokeni yararsızdır və ya vaxtı bitib. Lütfən yenisini tələb edin. link_to_otp: Telefonunuzdan iki faktorlu kodu və ya bir geri qaytarma kodunu daxil edin + migrate_account: Fərqli bir hesaba daşı + migrate_account_html: Bu hesabı başqa bir hesaba yönləndirmək istəyirsinizsə, burada konfiqurasiya edə bilərsiniz. + resend_confirmation: Təsdiq keçidini təkrar göndər reset_password: Parolu sıfırla rules: preamble: Bunlar, %{domain} moderatorları tərəfindən təyin edilib və tətbiq edilib. preamble_invited: Davam etməzdən əvvəl, lütfən %{domain} moderatorları tərəfindən təyin edilmiş qaydaları nəzərdən keçirin. + title: Bəzi təməl qaydalar. set_new_password: Yeni parol təyin et + setup: + email_below_hint_html: Spam qovluğunuzu yoxlayın, ya da başqasını tələb edin. Əgər yanlışdırsa, e-poçt ünvanınızı düzəldə bilərsiniz. + email_settings_hint_html: Mastodon istifadə etməyə başlamaq üçün %{email} ünvanına göndərdiyimiz keçidə klikləyin. Biz burada gözləyirik. + link_not_received: Keçidi almamısınız? + title: Gələn qutunuzu yoxlayın + sign_up: + preamble: Bu Mastodon serveri üzərindəki hesabla, hesabın yarada yerləşməsindən asılı olmayaraq fediverse-dəki istənilən şəxsi izləyə biləcəksiniz. + title: "%{domain} üzərində qeydiyyatınızı başladaq." status: + account_status: Hesab statusu + functional: Hesabınız tamamilə işləkdir. self_destruct: "%{domain} bağlandığı üçün, hesabınıza yalnız məhdud erişiminiz olacaq." challenge: hint_html: "İpucu: Sonrakı bir saat ərzində sizdən parolu soruşmayacağıq." @@ -105,45 +168,157 @@ az: prompt: Davam etmək üçün parolu təsdiqlə deletes: confirm_password: Kimliyinizi doğrulamaq üçün hazırkı parolunuzu daxil edin + proceed: Hesabı sil + success_msg: Hesabınız uğurla silindi + edit_profile: + basic_information: Təməl məlumatlar + hint_html: "İnsanların hər kəsə açıq profilinizdə və göndərişlərinizin yanında nə göstərmək istədiyinizi özəlləşdirin. Doldurulmuş bir profilə və bir profil şəklinə sahib olduğunuz zaman digər şəxslərin sizi izləmə və sizinlə əlaqə qurma ehtimalı yüksəkdir." exports: archive_takeout: hint_html: "Göndərişlərinizin və yüklədiyiniz medianın bir arxivini tələb edə bilərsiniz. Xaricə köçürülmüş verilər, istənilən uyumlu yazılım tərəfindən oxuna bilən ActivityPub formatında olacaq. Hər 7 gündə bir dəfə arxiv tələb edə bilərsiniz." + domain_blocks: Domen əngəlləmələri + storage: Media anbarı filters: + contexts: + public: Ümumi zaman xətləri edit: statuses_hint_html: Bu filtr, aşağıdakı açar sözləri ilə uyuşmasından asılı olmayaraq fərdi göndərişləri seçmək üçün tətbiq olunur. Göndərişləri incələyin və ya filtrdən silin. + index: + contexts: "%{contexts} üzrə filtrlər" + empty: Filtriniz yoxdur. + title: Filtrlər generic: all_matching_items_selected_html: one: Axtarışınızla uyuşan %{count} element seçilib. other: Axtarışınızla uyuşan %{count} element seçilib. + order_by: Sırala select_all_matching_items: one: Axtarışınızla uyuşan <0>%{count} elementi seçin. other: Axtarışınızla uyuşan <0>%{count} elementi seçin. imports: errors: incompatible_type: Seçilmiş daxilə köçürmə növü ilə uyumlu deyil + preface: Başqa serverdən xaricə köçürdüyünüz veriləri (məsələn, izlədiyiniz və ya əngəllədiyiniz insanların siyahısını) daxilə köçürə bilərsiniz. + type_groups: + constructive: İzlənilənlər və Əlfəcinlər invites: + max_uses_prompt: Limitsiz prompt: Bu serverə erişim icazəsi vermək üçün keçid yaradın və başqaları ilə paylaşın + table: + uses: İstifadə login_activities: authentication_methods: + otp: iki faktorlu kimlik doğrulama tətbiqi password: parol description_html: Əgər tanımadığınız bir fəaliyyəti görsəniz, parolunuzu dəyişdirməyi və iki faktorlu kimlik doğrulamanı fəallaşdırmağı düşünə bilərsiniz + mail_subscriptions: + unsubscribe: + emails: + notification_emails: + reblog: təkrar paylaşma bildirişi e-poçtları migrations: + incoming_migrations: Fərqli bir hesabdan daşı + incoming_migrations_html: Başqa bir hesabdan bu hesaba daşımaq üçün əvvəlcə bir hesab alias-ı yaratmalısınız. + proceed_with_move: İzləyiciləri daşı warning: disabled_account: Hazırkı hesabınız daha sonra istifadəyə yararsız olacaq. Ancaq, verilərin xaricə köçürülməsinə, həmçinin təkrar aktivləşdirmə prosesinə erişə biləcəksiniz. moderation: title: Moderasiya + notification_mailer: + reblog: + body: "%{name} göndərişinizi təkrar paylaşdı:" + subject: "%{name} göndərişinizi təkrar paylaşdı" + title: Yeni təkrar paylaşma + notifications: + administration_emails: Admin e-poçt bildirişləri + email_events: Fəaliyyətlər üçün e-poçt bildirişləri + email_events_hint: 'Bildiriş almaq istədiyiniz fəaliyyətləri seçin:' + otp_authentication: + description_html: Bir kimlik doğrulayıcı tətbiq istifadə edərək iki addımlı kimlik doğrulamanı fəallaşdırsanız, giriş etmək üçün telefonunuzun yanınızda olması tələb olunacaq, çünki daxil olmağınız üçün kodlar yaradılacaq. + preferences: + posting_defaults: Göndəriş ilkin ayarları + public_timelines: Ümumi zaman xətləri + relationships: + activity: Hesab fəaliyyəti + confirm_follow_selected_followers: Seçilmiş izləyicləri izləmək istədiyinizə əminsiniz? + confirm_remove_selected_followers: Seçilmiş izləyiciləri çıxartmaq istədiyinizə əminsiniz? + confirm_remove_selected_follows: Seçilmiş izləmələri çıxartmaq istədiyinizə əminsiniz? + dormant: Fəaliyyətsiz + follow_failure: Seçilmiş hesablardan bəziləri izlənilə bilmədi. + follow_selected_followers: Seçilmiş izləyiciləri izlə + followers: İzləyicilər + most_recent: Ən son + mutual: Ortaq + relationship: Münasibət + remove_selected_domains: Seçilmiş domenlərin bütün izləyicilərini çıxart + remove_selected_followers: Seçilmiş izləyiciləri çıxart + remove_selected_follows: Seçilmiş istifadəçiləri izləmədən çıxart + status: Hesab statusu sessions: browsers: edge: Microsoft Edge + explanation: Bunlar, hazırda Mastodon hesabınıza giriş etmiş veb brauzerlərdir. + title: Seanslar + view_authentication_history: Hesabınızın kimlik doğrulama tarixçəsinə baxın settings: account_settings: Hesab ayarları + appearance: Görünüş development: Gəlişdirmə + export: Xaricə köçür + import_and_export: Daxilə və xaricə köçürmə + notifications: E-poçt bildirişləri + relationships: İzlənilənlər və izləyicilər + severed_relationships: Kəsilmiş münasibətlər + statuses_cleanup: Göndərişin avtomatik silinməsi strikes: Moderasiya pozuntuları + two_factor_authentication: İki faktorlu kimlik doğrulama + severed_relationships: + lost_followers: İtirilən izləyicilər + preamble: Bir domeni əngəllədiyiniz zaman və ya moderatorlarınız uzaq bir serverin fəaliyyətini dayandırmağa qərar verdiyi zaman izlədiklərinizi və izləyicilərinizi itirə bilərsiniz. Bu baş verdiyi zaman, kəsilmiş münasibətlərin bir siyahısını endirə, nəzərdən keçirə və bəlkə də başqa bir serverə köçürə biləcəksiniz. + statuses: + boosted_from_html: "%{acct_link} üzərindən təkrar paylaşdı" + default_language: İnterfeys dili ilə eyni + pin_errors: + reblog: Təkrar paylaşım, sancıla bilməz + quote_policies: + followers: Yalnız izləyiciləriniz + public: Hər kəs + visibilities: + private: Yalnız izləyicilər + private_long: Yalnız izləyicilər görə bilər + public_long: Hər kəs görə bilər + unlisted_long: Hər kəs görə bilər, ancaq hər kəsə açıq zaman xətlərində sadalanmır statuses_cleanup: + enabled: Köhnə göndərişləri avtomatik sil enabled_hint: Aşağıdakı istisnalardan heç birinə uyuşmadığı müddətcə, göndərişləriniz qeyd edilmiş yaş həddinə çatdıqda avtomatik silinir + exceptions: İstisnalar + explanation: Göndərişlərin silinməsi bahalı əməliyyat olduğu üçün, server çox məşğul olmadığı zaman bu proses yavaş-yavaş icra edilir. Bu səbəbdən, bəzi göndərişləriniz yaş həddinə çatdıqdan bir müddət sonra silinə bilər. + ignore_reblogs: Təkrar paylaşımları yox say + interaction_exceptions: Qarşılıqlı əlaqələrə əsaslanan istisnalar + interaction_exceptions_explanation: Nəzərə alın ki, bir göndəriş sevimlilərə əlavə edilmə və ya təkrar paylaşma həddini keçdikdən sonra həmin həddin altına düşsə belə, onun silinəcəyinə zəmanət verilmir. + keep_direct: Birbaşa mesajları saxla + keep_direct_hint: Birbaşa yazdığınız mesajların heç biri silinməyəcək + keep_media: Media qoşmaları olan göndərişləri saxla + keep_media_hint: Media qoşması olan göndərişlərinizdən heç biri silinməyəcək + keep_pinned: Sancaqlanmış göndərişləri saxla + keep_pinned_hint: Sancaqlanmış göndərişlərinizdən heç biri silinməyəcək + keep_polls: Anketləri saxla + keep_polls_hint: Anketlərinizdən heç biri silinməyəcək + keep_self_bookmark: Əlfəcinlərə əlavə etdiyiniz göndərişləri saxla + keep_self_bookmark_hint: Öz göndərişlərinizi əlfəcinlərə əlavə etmisinizsə silinməyəcək + keep_self_fav: Sevimlilərə əlavə etdiyiniz göndərişləri saxla + keep_self_fav_hint: Öz göndərişlərinizi sevimlilərə əlavə etmisinizsə silinməyəcək + min_age_label: Yaş həddi + min_reblogs: Bu həddən çox təkrar paylaşılan göndərişləri saxla + min_reblogs_hint: Bu sayda və ya daha çox təkrar paylaşma alan göndərişləriniz silinməyəcək. Təkrar paylaşılma sayından asılı olmayaraq göndərişlərin silinməsi üçün boş buraxın. + stream_entries: + sensitive_content: Həssas məzmun tags: does_not_match_previous_name: əvvəlki adla uyuşmur two_factor_authentication: + disabled_success: İki faktorlu kimlik doğrulama uğurla sıradan çıxarıldı + enabled: İki faktorlu kimlik doğrulama fəallaşdırıldı + enabled_success: İki faktorlu kimlik doğrulama uğurla fəallaşdırıldı generate_recovery_codes: Geri qaytarma kodlarını yarat lost_recovery_codes: Geri qaytarma kodları, telefonunuzu itirdiyiniz halda hesabınıza yenidən erişməyinizə imkan verir. Geri qaytarma kodlarınızı itirsəniz, onları təkrar yarada bilərsiniz. Köhnə geri qaytarma kodlarınız yararsız sayılacaq. recovery_codes: Geri qaytarma kodlarını nüsxələ @@ -152,14 +327,21 @@ az: user_mailer: appeal_approved: action: Hesab ayarları + explanation: "%{strike_date} tarixində hesabınıza qarşı tətbiq edilən cəza ilə bağlı %{appeal_date} tarixində göndərdiyiniz etirazınız təsdiqlənib. Hesabınız yenidən normal vəziyyətdədir." + subject: "%{date} tarixli etirazınız təsdiqlənib" + subtitle: Hesabınız yenidən normal vəziyyətdədir. + title: Etiraz təsdiqlənib suspicious_sign_in: change_password: parolu dəyişdir subject: Hesabınıza yeni bir IP ünvanından erişildi + terms_of_service_changed: + title: Vacib güncəlləmə warning: explanation: disable: Artıq hesabınızı istifadə edə bilməzsiniz, ancaq profiliniz və digər veriləriniz olduğu kimi qalacaq. Verilərinizin bir nüsxəsini tələb edə, hesab ayarlarınızı dəyişdirə və ya hesabınızı silə bilərsiniz. suspend: Hesabınızı artıq istifadə edə bilməzsiniz, profiliniz və digər veriləriniz artıq əlçatmazdır. Təxminən 30 gün ərzində verilər tamamilə silinənə qədər verilərinizin bir nüsxəsini tələb etmək üçün hələ də hesabınıza giriş edə bilərsiniz, ancaq hesab fəaliyyətinin dayandırılması prosesini ləğv etməyinizi önləmək üçün bəzi təməl veriləri saxlayacağıq. welcome: + edit_profile_step: Profilinizi tam dolduraraq qarşılıqlı əlaqələrinizi artırın. feature_creativity: Mastodon özünüzü onlayn mühitdə ifadə etməyinizə kömək edəcək səs, video və şəkil göndərişləri, erişiləbilənlik açıqlamaları, anketlər, məzmun xəbərdarlıqları, animasiyalı avatarlar, özəl emojilər, kiçik şəkli kəsmə nəzarəti və daha çoxunu dəstəkləyir. Öz sənətinizi, musiqinizi və ya podkastınızı dərc edirsinizsə, Mastodon sizin üçün buradadır. feature_moderation_title: Olmalı olduğu şəkildə moderasiya users: diff --git a/config/locales/be.yml b/config/locales/be.yml index 3cd6effdc85..7bf031bf88e 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -12,7 +12,7 @@ be: many: Падпісчыкаў one: Падпісчык other: Падпісчыкі - following: Падпісаны + following: Падпіскі instance_actor_flash: Гэты ўліковы запіс - лічбавы аватар, неабходны для рэпрэзентацыі самога сервера, а не якой-небудзь асобы. Ён выкарыстоўваецца для федэралізацыі і не можа быць замарожаны. last_active: апошняя актыўнасць link_verified_on: Права ўласнасці на гэтую спасылку праверана %{date} @@ -25,12 +25,12 @@ be: one: Допіс other: Допісы posts_tab_heading: Допісы - self_follow_error: Нельга падпісацца на свой профіль + self_follow_error: Нельга падпісацца на свой уліковы запіс admin: account_actions: action: Выканаць дзеянне already_silenced: Гэты ўліковы запіс ужо абмежаваны. - already_suspended: Гэты ўліковы запіс ужо прыпынены. + already_suspended: Гэты ўліковы запіс ужо прыпыніў працу. title: Мадэраваць %{acct} account_moderation_notes: create: Пакінуць нататку @@ -91,9 +91,9 @@ be: remote: Адлеглы title: Месцазнаходжанне login_status: Стан уваходу - media_attachments: Медыя далучэнні + media_attachments: Медыя ўлажэнні memorialize: Даданае да памяці - memorialized: Запомненае + memorialized: Увекавечаны memorialized_msg: Уліковы запіс %{username} ператвораны ў мемарыяльны moderation: active: Актыўны @@ -445,7 +445,7 @@ be: create: Стварыць блакіроўку hint: Блакіроўка дамена не будзе перашкаджаць стварэнню запісаў уліковых запісаў у базе даных, але будзе заднім лікам і аўтаматычна прымяняць да гэтых уліковых запісаў пэўныя метады мадэрацыі. severity: - desc_html: "Абмежаванне зробіць допісы людзей з гэтага дамену нябачнымі для тых, хто на іх не падпісаны. Выключэнне выдаліць усё змесціва, медыя і даныя профіляў дамену з вашага серверу. «Нічога» проста адхіліць медыя файлы." + desc_html: "Абмежаванне зробіць допісы людзей з гэтага дамену нябачнымі для тых, хто на іх не падпісаны. Выключэнне выдаліць усё змесціва, медыя і даныя ўліковых запісаў дамену з вашага серверу. «Нічога» проста адхіліць медыя файлы." noop: Пуста silence: Абмежаваць suspend: Прыпыніць @@ -530,7 +530,7 @@ be: select_capabilities: Выбраць здольнасці sign_in: Увайсці status: Допіс - title: Дапаможныя серверы Fediverse + title: Дапаможныя серверы федэральнага сусвету title: FASP follow_recommendations: description_html: "Рэкамендацыі падпісак, дапамогаюць новым карыстальнікам хутка знайсці цікавы кантэнт. Калі карыстальнік недастаткова ўзаемадзейнічаў з іншымі, каб сфарміраваць персанальныя рэкамендацыі прытрымлівацца, замест гэтага рэкамендуюцца гэтыя ўліковыя запісы. Яны штодзённа пераразлічваюцца з сумесі ўліковых запісаў з самымі апошнімі ўзаемадзеяннямі і найбольшай колькасцю мясцовых падпісчыкаў для дадзенай мовы." @@ -569,7 +569,7 @@ be: description_html: Вы можаце вызначыць палітыку кантэнту, якая будзе прымяняцца да ўсіх уліковых запісаў гэтага дамена і любога з яго субдаменаў. limited_federation_mode_description_html: Вы можаце выбраць ці дазволіць уваходзіць у федэрацыю з гэтым даменам. policies: - reject_media: Адхіліць мультымедыя + reject_media: Адхіліць медыя reject_reports: Адхіліць справаздачы silence: Ліміт suspend: Прыпыніць @@ -582,7 +582,7 @@ be: instance_followers_measure: нашых падпісчыкаў там instance_follows_measure: іх падпісчыкаў тут instance_languages_dimension: Папулярныя мовы - instance_media_attachments_measure: захаваныя медыя-далучэнні + instance_media_attachments_measure: захаваныя медыя ўлажэнні instance_reports_measure: справаздач пра іх instance_statuses_measure: захаваных паведамленняў delivery: @@ -622,7 +622,7 @@ be: total_followed_by_them: Іхнія падпіскі total_followed_by_us: Нашыя падпіскі total_reported: Скаргі на іх - total_storage: Медыя далучэнні + total_storage: Медыя ўлажэнні totals_time_period_hint_html: Паказаныя агульныя значэнні ніжэй уключаюць даныя за ўвесь час. unknown_instance: На дадзены момант няма запісаў аб гэтым дамене на гэтым серверы. invites: @@ -653,7 +653,7 @@ be: relays: add_new: Дадаць новы рэтранслятар delete: Выдаліць - description_html: "Федэрацыйны рэтранслятар - гэта прамежкавы сервер, які абменьваецца вялікімі аб’ёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і робяць публікацыі на ім. Гэта можа дапамагчы малым і сярэднім серверам выяўляць змесціва з fediverse, бо ў іншым выпадку лакальным карыстальнікам трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах." + description_html: "Федэрацыйны рэтранслятар - гэта прамежкавы сервер, які абменьваецца вялікімі аб’ёмамі публічных паведамленняў паміж серверамі, якія падпісваюцца і робяць публікацыі на ім. Гэта можа дапамагчы малым і сярэднім серверам выяўляць змесціва з федэральнага сусвету, бо ў іншым выпадку лакальным карыстальнікам трэба было б уручную сачыць за іншымі людзьмі на аддаленых серверах." disable: Адключыць disabled: Адключана enable: Уключыць @@ -1086,9 +1086,9 @@ be: allow: Дазволіць допіс allow_account: Дазволіць аўтара confirm_allow: Вы ўпэўненыя, што хочаце дазволіць абраныя допісы? - confirm_allow_account: Вы ўпэўненыя, што хочаце дазволіць абраныя профілі? + confirm_allow_account: Вы ўпэўненыя, што хочаце дазволіць абраныя ўліковыя запісы? confirm_disallow: Вы ўпэўненыя, што хочаце забараніць абраныя допісы? - confirm_disallow_account: Вы ўпэўненыя, што хочаце забараніць абраныя профілі? + confirm_disallow_account: Вы ўпэўненыя, што хочаце забараніць абраныя ўліковыя запісы? description_html: Гэта допісы, пра якія ведае ваш сервер, што на дадзены момант часта абагульваюцца і падабаюцца людзям. Гэта можа дапамагчы вашым новым і пастаянным карыстальнікам знайсці больш людзей, на якіх можна падпісацца. Ніякія допісы не будуць паказвацца публічна, пакуль вы не зацвердзіце аўтара, а аўтар не дазволіць прапанаваць свой уліковы запіс іншым. Вы таксама можаце дазволіць або адхіліць асобныя допісы. disallow: Забараніць допіс disallow_account: Забараніць аўтара @@ -1313,7 +1313,7 @@ be: title: Уваход у %{domain} sign_up: manual_review: Рэгістрацыі на %{domain} праходзяць ручную праверку нашымі мадэратарамі. Каб дапамагчы нам апрацаваць вашу рэгістрацыю, напішыце крыху пра сябе і чаму вы хочаце мець уліковы запіс на %{domain}. - preamble: З профілем на серверы Mastodon Вы зможаце падпісацца на любога чалавека ў fediverse, незалежна ад таго, на якім серверы знаходзіцца іх профіль. + preamble: З уліковым запісам на гэтым серверы Mastodon Вы зможаце падпісацца на любога чалавека ў федэральным сусвеце, незалежна ад таго, на якім серверы знаходзіцца яго ўліковы запіс. title: Наладзьма вас на %{domain}. status: account_status: Стан уліковага запісу @@ -1561,56 +1561,56 @@ be: one: Вы збіраецеся замяніць свае закладкі %{count} допісам з %{filename}. other: Вы збіраецеся замяніць свае закладкі %{count} допісамі з %{filename}. domain_blocking_html: - few: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. - many: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. - one: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверам з %{filename}. - other: Вы збіраецеся замяніць свой спіс заблакіраваных сервераў %{count} серверамі з %{filename}. + few: Вы збіраецеся замяніць свой спіс заблакіраваных даменаў %{count} даменамі з %{filename}. + many: Вы збіраецеся замяніць свой спіс заблакіраваных даменаў %{count} даменамі з %{filename}. + one: Вы збіраецеся замяніць свой спіс заблакіраваных даменаў %{count} даменам з %{filename}. + other: Вы збіраецеся замяніць свой спіс заблакіраваных даменаў %{count} дамены з %{filename}. following_html: - few: Вы збіраецеся падпісацца на %{count} профілі з %{filename} і адпішацеся ад усіх астатніх. - many: Вы збіраецеся падпісацца на %{count} профіляў з %{filename} і адпішацеся ад усіх астатніх. - one: Вы збіраецеся падпісацца на %{count} профіль з %{filename} і адпішацеся ад усіх астатніх. - other: Вы збіраецеся падпісацца на %{count} профіляў з %{filename} і адпішацеся ад усіх астатніх. + few: Вы збіраецеся падпісацца на %{count} уліковыя запісы з %{filename} і адпішацеся ад усіх астатніх. + many: Вы збіраецеся падпісацца на %{count} уліковых запісаў з %{filename} і адпішацеся ад усіх астатніх. + one: Вы збіраецеся падпісацца на %{count} уліковы запіс з %{filename} і адпішацеся ад усіх астатніх. + other: Вы збіраецеся падпісацца на %{count} уліковыя запісы з %{filename} і адпішацеся ад усіх астатніх. lists_html: - few: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профілі будуць дададзеныя ў новыя спісы. - many: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіляў будуць дададзеныя ў новыя спісы. - one: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіль будзе дададзены ў новыя спісы. - other: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} профіляў будуць дададзеныя ў новыя спісы. + few: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} уліковыя запісы будуць дададзеныя ў новыя спісы. + many: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} уліковых запісаў будуць дададзеныя ў новыя спісы. + one: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} уліковы запіс будзе дададзены ў новыя спісы. + other: Вы збіраецеся замяніць свае спісы змесцівам з %{filename}. %{count} уліковыя запісы будуць дададзеныя ў новыя спісы. muting_html: - few: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. - many: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. - one: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілем з %{filename}. - other: Вы збіраецеся замяніць свой спіс профіляў, якія Вы ігнаруеце, %{count} профілямі з %{filename}. + few: Вы збіраецеся замяніць свой спіс уліковых запісаў, якія Вы ігнаруеце, %{count} уліковымі запісамі з %{filename}. + many: Вы збіраецеся замяніць свой спіс уліковых запісаў, якія Вы ігнаруеце, %{count} уліковымі запісамі з %{filename}. + one: Вы збіраецеся замяніць свой спіс уліковых запісаў, якія Вы ігнаруеце, %{count} уліковым запісам з %{filename}. + other: Вы збіраецеся замяніць свой спіс уліковых запісаў, якія Вы ігнаруеце, %{count} уліковыя запісы з %{filename}. preambles: blocking_html: - few: Вы збіраецеся заблакіраваць %{count} профілі з %{filename}. - many: Вы збіраецеся заблакіраваць %{count} профіляў з %{filename}. - one: Вы збіраецеся заблакіраваць %{count} профіль з %{filename}. - other: Вы збіраецеся заблакіраваць %{count} профіляў з %{filename}. + few: Вы збіраецеся заблакіраваць %{count} уліковыя запісы з %{filename}. + many: Вы збіраецеся заблакіраваць %{count} уліковых запісаў з %{filename}. + one: Вы збіраецеся заблакіраваць %{count} уліковы запіс з %{filename}. + other: Вы збіраецеся заблакіраваць %{count} уліковыя запісы з %{filename}. bookmarks_html: few: Вы збіраецеся дадаць %{count} допісы з %{filename} у Вашыя закладкі. many: Вы збіраецеся дадаць %{count} допісаў з %{filename} у Вашыя закладкі. one: Вы збіраецеся дадаць %{count} допіс з %{filename} у Вашыя закладкі. other: Вы збіраецеся дадаць %{count} допісаў з %{filename} у Вашыя закладкі. domain_blocking_html: - few: Вы збіраецеся заблакіраваць %{count} серверы з %{filename}. - many: Вы збіраецеся заблакіраваць %{count} сервераў з %{filename}. - one: Вы збіраецеся заблакіраваць %{count} сервер з %{filename}. - other: Вы збіраецеся заблакіраваць %{count} сервераў з %{filename}. + few: Вы збіраецеся заблакіраваць %{count} дамены з %{filename}. + many: Вы збіраецеся заблакіраваць %{count} даменаў з %{filename}. + one: Вы збіраецеся заблакіраваць %{count} дамен з %{filename}. + other: Вы збіраецеся заблакіраваць %{count} дамены з %{filename}. following_html: - few: Вы збіраецеся падпісацца на %{count} профілі з %{filename}. - many: Вы збіраецеся падпісацца на %{count} профіляў з %{filename}. - one: Вы збіраецеся падпісацца на %{count} профіль з %{filename}. - other: Вы збіраецеся падпісацца на %{count} профіляў з %{filename}. + few: Вы збіраецеся падпісацца на %{count} уліковыя запісы з %{filename}. + many: Вы збіраецеся падпісацца на %{count} уліковых запісаў з %{filename}. + one: Вы збіраецеся падпісацца на %{count} уліковы запіс з %{filename}. + other: Вы збіраецеся падпісацца на %{count} уліковыя запісы з %{filename}. lists_html: - few: Вы збіраецеся дадаць %{count} профілі з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. - many: Вы збіраецеся дадаць %{count} профіляў з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. - one: Вы збіраецеся дадаць %{count} профіль з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. - other: Вы збіраецеся дадаць %{count} профіляў з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + few: Вы збіраецеся дадаць %{count} уліковыя запісы з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + many: Вы збіраецеся дадаць %{count} уліковых запісаў з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + one: Вы збіраецеся дадаць %{count} уліковы запіс з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. + other: Вы збіраецеся дадаць %{count} уліковыя запісы з %{filename} у Вашыя спісы. Калі спісаў няма, то будуць створаны новыя. muting_html: - few: Вы збіраецеся пачаць ігнараваць %{count} профілі з %{filename}. - many: Вы збіраецеся пачаць ігнараваць %{count} профіляў з %{filename}. - one: Вы збіраецеся пачаць ігнараваць %{count} профіль з %{filename}. - other: Вы збіраецеся пачаць ігнараваць %{count} профіляў з %{filename}. + few: Вы збіраецеся пачаць ігнараваць %{count} уліковыя запісы з %{filename}. + many: Вы збіраецеся пачаць ігнараваць %{count} уліковых запісаў з %{filename}. + one: Вы збіраецеся пачаць ігнараваць %{count} уліковы запіс з %{filename}. + other: Вы збіраецеся пачаць ігнараваць %{count} уліковыя запісы з %{filename}. preface: Вы можаце імпартаваць даныя, экспартаваныя вамі з іншага сервера, напрыклад, спіс людзей, на якіх вы падпісаны або якіх блакуеце. recent_imports: Нядаўнія імпарты states: @@ -1698,7 +1698,7 @@ be: validations: images_and_video: Немагчыма далучыць відэа да допісу, які ўжо змяшчае выявы not_found: Файл %{ids} не знойдзены або ўжо далучаны да іншага допісу - not_ready: Няможна далучыць файлы, апрацоўка якіх яшчэ не скончылася. Паспрабуйце яшчэ раз праз хвілінку! + not_ready: Немагчыма далучыць файлы, апрацоўка якіх яшчэ не скончылася. Паспрабуйце яшчэ раз праз хвілінку! too_many: Немагчыма далучыць больш за 4 файлы migrations: acct: Перамешчана ў @@ -1746,7 +1746,7 @@ be: sign_up: subject: "%{name} зарэгістраваўся" favourite: - body: "%{name} упадабаў ваш пост:" + body: "%{name} упадабаў(-ла) Ваш допіс:" subject: "%{name} упадабаў ваш допіс" title: Новае ўпадабанае follow: @@ -1770,7 +1770,7 @@ be: subject: Карыстальнік %{name} працытаваў Ваш допіс title: Цытаваць reblog: - body: "%{name} пашырыў ваш пост:" + body: "%{name} пашырыў(-ла) Ваш допіс:" subject: "%{name} пашырыў ваш допіс" title: Новае пашырэнне status: @@ -2017,9 +2017,9 @@ be: interaction_exceptions: Выключэнні, заснаваныя на ўзаемадзеянні interaction_exceptions_explanation: Звярніце ўвагу, што няма гарантыі выдалення пастоў, калі колькасць іх упадабанняў ці пашырэннняў упадзе ніжэй за ліміт, хаця некалі гэтая колькасць перавышала яго. keep_direct: Захаваць асабістыя паведамленні - keep_direct_hint: Не выдаляць асабістыя паведамленні - keep_media: Захоўваць допісы з далучаным медыя - keep_media_hint: Не выдаляць вашыя допісы, якія ўтрымліваюць медыя + keep_direct_hint: Не выдаляе асабістыя паведамленні + keep_media: Захоўваць допісы з медыя ўлажэннямі + keep_media_hint: Не выдаляць вашыя допісы, якія ўтрымліваюць медыя ўлажэнні keep_pinned: Захаваць замацаваныя допісы keep_pinned_hint: Не выдаляць вашыя замацаваныя допісы keep_polls: Працягнуць апытанне @@ -2151,7 +2151,7 @@ be: none: Папярэджанне для %{acct} sensitive: З гэтага моманту вашыя допісы на %{acct} будуць пазначаныя як далікатныя silence: Ваш уліковы запіс %{acct} быў абмежаваны - suspend: Ваш уліковы запіс %{acct} быў выключаны + suspend: Ваш уліковы запіс %{acct} быў прыпынены title: delete_statuses: Выдаленыя допісы disable: Уліковы запіс замарожаны diff --git a/config/locales/devise.az.yml b/config/locales/devise.az.yml index d472634f0a1..1df8c31cd69 100644 --- a/config/locales/devise.az.yml +++ b/config/locales/devise.az.yml @@ -19,13 +19,13 @@ az: unconfirmed: Davam etmək üçün e-poçt ünvanınızı təsdiqləməlisiniz. mailer: confirmation_instructions: - action: E-poçt ünvanını təsdiqlə + action: E-poçt ünvanını doğrula action_with_app: Təsdiqlə və %{app}-a geri qayıt - explanation: Siz %{host} saytında bu e-poçt ilə hesab yaratmısınız. Onu aktivləşdirməkdən bir klik uzaqlıqdasınız. Əgər bu siz olmamısınızsa, zəhmət olmasa, bu e-məktuba məhəl qoymayın. + explanation: Bu e-poçt ünvanı ilə %{host} üzərində bir hesab yaratmısınız. Onu aktivləşdirməkdən bir klik uzaqlıqdasınız. Əgər bunu siz etməmisinizsə, lütfən e-poçtu yox sayın. explanation_when_pending: Bu e-poçt ünvanı ilə %{host} ünvanına dəvət üçün müraciət etmisiniz. E-poçt ünvanınızı təsdiqlədikdən sonra müraciətinizi nəzərdən keçirəcəyik. Hesab məlumatlarını dəyişdirmək və ya hesabınızı silmək üçün giriş edə bilərsiniz, ancaq hesabınız təsdiqlənənə qədər əksər funksiyalara erişə bilməyəcəksiniz. Müraciətinizə rədd cavabı gəlsə, veriləriniz silinəcək, sizdən heç bir əməliyyat etməyiniz istənilməyəcək. Əgər bu siz deyilsinizsə, lütfən bu e-poçtu yox sayın. extra_html: Həmçinin zəhmət olmasa, serverin qaydalarınıistifadə şərtlərini oxuyun. subject: 'Mastodon: %{instance} üçün təsdiqlənmə təlimatları' - title: E-poçt ünvanını təsdiqlə + title: E-poçt ünvanını doğrula email_changed: explanation: 'Hesabınız üçün e-poçt ünvanı buna dəyişdirilir:' extra: E-poçtunuzu dəyişməmisinizsə, çox güman ki, kimsə hesabınıza erişib. Hesabınıza giriş edə bilmirsinizsə, lütfən parolunuzu dərhal dəyişdirin və ya server admini ilə əlaqə saxlayın. @@ -40,7 +40,7 @@ az: explanation: E-poçtunuzu dəyişdirmək üçün yeni ünvanı təsdiqləyin. extra: Bu dəyişikliyi siz etməmisinizsə, lütfən bu e-poçtu yox sayın. Yuxarıdakı keçidə erişənə qədər Mastodon hesabının e-poçt ünvanı dəyişməyəcək. subject: 'Mastodon: %{instance} üçün e-poçtu təsdiqlə' - title: E-poçt ünvanını təsdiqlə + title: E-poçt ünvanını doğrula reset_password_instructions: action: Parolu dəyiş explanation: Siz hesabınız üçün yeni parol tələb etmisiniz. diff --git a/config/locales/devise.be.yml b/config/locales/devise.be.yml index b4498f7ee85..8aebd2b198e 100644 --- a/config/locales/devise.be.yml +++ b/config/locales/devise.be.yml @@ -3,20 +3,20 @@ be: devise: confirmations: confirmed: Адрас вашай электроннай пошты паспяхова пацверджаны. - send_instructions: Цягам некалькіх хвілін вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашую электронную пошту. Калі ласка, зазірніце ў папку са спамам, калі не знойдзеце ліст. - send_paranoid_instructions: Калі адрас вашай электроннай пошты існуе ў нашай базе даных, на працягу некалькіх хвілін вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашу электронную пошту. Калі вы не знойдзеце ліст, праверце папку са спамам. + send_instructions: Цягам некалькіх хвілін Вы атрымаеце ліст з інструкцыямі, каб пацвердзіць вашую электронную пошту. Калі ласка, зазірніце ў папку са спамам, калі не знойдзеце ліст. + send_paranoid_instructions: Калі адрас Вашай электроннай пошты існуе ў нашай базе даных, на працягу некалькіх хвілін Вы атрымаеце ліст з інструкцыямі, каб пацвердзіць Вашу электронную пошту. Калі Вы не знойдзеце ліст, праверце папку са спамам. failure: already_authenticated: Вы ўжо ўвайшлі. inactive: Ваш уліковы запіс яшчэ не актываваны. invalid: Няправільны %{authentication_keys} або пароль. - last_attempt: У вас ёсць яшчэ адна спроба, перш чым ваш рахунак будзе заблакаваны + last_attempt: У вас ёсць яшчэ адна спроба, перш чым Ваш уліковы запіс будзе заблакіраваны. locked: Ваш уліковы запіс заблакіраваны. not_found_in_database: Няправільны %{authentication_keys} або пароль. omniauth_user_creation_failure: Памылка пры стварэнні ўліковага запісу для гэтай асобы. pending: Ваш уліковы запіс яшчэ разглядаецца. timeout: Ваш сеанс скончыўся. Каб працягнуць, увайдзіце яшчэ раз. unauthenticated: Каб працягнуць, вам трэба ўвайсці або зарэгістравацца. - unconfirmed: Вы павінны пацвердзіць свой адрас электроннай пошты, перш чым працягнуць + unconfirmed: Вы мусіце пацвердзіць свой адрас электроннай пошты, перш чым працягнуць. mailer: confirmation_instructions: action: Пацвердзіць адрас электроннай пошты @@ -27,24 +27,24 @@ be: subject: 'Mastodon: Інструкцыі па пацвярджэнні для %{instance}' title: Праверце адрас электроннай пошты email_changed: - explanation: Адрас электроннай пошты для вашага ўліковага запісу будзе зменены на - extra: Калі вы не змянялі сваю электронную пошту, хутчэй за ўсё, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакіраваны са свайго ўліковага запісу. + explanation: 'Адрас электроннай пошты для вашага ўліковага запісу будзе зменены на:' + extra: Калі Вы не змянялі сваю электронную пошту, хутчэй за ўсё, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі Вы заблакіраваны са свайго ўліковага запісу. subject: 'Mastodon: адрас электроннай пошты зменены' title: Новы адрас электроннай пошты password_change: - explanation: Пароль для вашага ўліковага запісу быў зменены - extra: Калі вы не змянялі свой пароль, імаверна, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі вы заблакіраваны са свайго ўліковага запісу. + explanation: Пароль для вашага ўліковага запісу быў зменены. + extra: Калі Вы не змянялі свой пароль, імаверна, нехта атрымаў доступ да вашага ўліковага запісу. Неадкладна змяніце свой пароль або звярніцеся да адміністратара сервера, калі Вы заблакіраваны са свайго ўліковага запісу. subject: 'Mastodon: пароль зменены' title: Пароль зменены reconfirmation_instructions: explanation: Пацвердзіце, каб змяніць адрас вашай электроннай пошты. - extra: Калі гэтыя змены не былі зроблены вамі, ігнаруйце гэты ліст. Адрас электроннай пошты для акаўнту Mastodon не будзе зменены, пакуль вы не пяройдзеце па спасылцы вышэй. + extra: Калі гэтыя змены не былі зроблены Вамі, праігнаруйце гэты ліст. Адрас электроннай пошты для ўліковага запісу Mastodon не будзе зменены, пакуль вы не пяройдзеце па спасылцы вышэй. subject: 'Mastodon: пацвердзіце электронную пошту для %{instance}' title: Пацвердзіце адрас электроннай пошты reset_password_instructions: action: Змяніць пароль explanation: Вы запыталі новы пароль для свайго ўліковага запісу. - extra: Калі вы не рабілі такога запыту, калі ласка, ігнаруйце гэты ліст. Ваш пароль не будзе зменены, пакуль вы не пяройдзеце па спасылцы вышэй і не створыце новы. + extra: Калі Вы не рабілі такога запыту, калі ласка, праігнаруйце гэты ліст. Ваш пароль не будзе зменены, пакуль Вы не пяройдзеце па спасылцы вышэй і не створыце новы. subject: 'Mastodon: Інструкцыі па скіданню пароля' title: Скіданне пароля two_factor_disabled: @@ -58,21 +58,21 @@ be: subtitle: Для вашага ўліковага запісу была ўключаная двухфактарная аўтэнтыфікацыя. title: двухэтапнае спраўджанне уключана two_factor_recovery_codes_changed: - explanation: Папярэднія коды аднаўлення былі ануляваны і створаны новыя. - subject: 'Mastodon: створаны новыя коды аднаўлення' - subtitle: Папярэднія коды аднаўлення былі ануляваны і замест іх створаны новыя. + explanation: Папярэднія коды аднаўлення былі ануляваны і былі створаныя новыя. + subject: 'Mastodon: створаныя новыя коды аднаўлення' + subtitle: Папярэднія коды аднаўлення былі ануляваны і замест іх былі створаныя новыя. title: 2FA коды аднаўлення былі зменены unlock_instructions: subject: 'Mastodon: інструкцыя па разблакаванні' webauthn_credential: added: - explanation: Наступны ключ бяспекі быў дададзены ў ваш уліковы запіс + explanation: Наступны ключ бяспекі быў дададзены ў Ваш уліковы запіс subject: 'Mastodon: новы ключ бяспекі' title: Быў дададзены новы ключ бяспекі deleted: - explanation: Наступны ключ бяспекі быў выдалены з вашага ўліковага запісу + explanation: Наступны ключ бяспекі быў выдалены з Вашага ўліковага запісу subject: 'Mastodon: ключ бяспекі выдалены' - title: Адзін з вашых ключоў бяспекі быў выдалены + title: Адзін з Вашых ключоў бяспекі быў выдалены webauthn_disabled: explanation: Аўтэнтыфікацыя з дапамогай ключоў бяспекі была адключаная для вашага ўліковага запісу. extra: Зараз уваход у сістэму магчымы толькі з выкарыстаннем токена, згенераванага спалучанай праграмай TOTP. @@ -80,29 +80,29 @@ be: title: Ключы бяспекі адключаны webauthn_enabled: explanation: Для вашага ўліковага запісу ўключана аўтэнтыфікацыя па ключу бяспекі. - extra: Цяпер ваш ключ бяспекі можна выкарыстоўваць для ўваходу ў сістэму. + extra: Цяпер Ваш ключ бяспекі можна выкарыстоўваць для ўваходу ў сістэму. subject: 'Mastodon: Аўтэнтыфікацыя праз ключ бяспекі была ўключана' title: Ключы бяспекі ўключаны omniauth_callbacks: failure: Немагчыма аўтэнтыфікаваць вас з %{kind}, таму што “%{reason}”. success: Паспяховая аўтэнтыфікацыя з %{kind} уліковага запісу. passwords: - no_token: Вы не можаце атрымаць доступ да гэтай старонкі не з ліста аднаўлення пароля. Калі вы ўсе ж такі перайшлі па спасылцы ў лісце аднаўлення пароля, упэўніцеся, што яна поўная. - send_instructions: Калі ваш электроны адрас існуе ў нашай базе даных, вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі вы не атрымалі гэты ліст, праверце папку са спамам. - send_paranoid_instructions: Калі ваш электроны адрас існуе ў нашай базе даных, вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі вы не атрымалі гэты ліст, праверце папку са спамам. + no_token: Вы не можаце атрымаць доступ да гэтай старонкі не з ліста аднаўлення пароля. Калі Вы ўсе ж такі перайшлі па спасылцы ў лісце аднаўлення пароля, упэўніцеся, што яна поўная. + send_instructions: Калі Ваш электроны адрас існуе ў нашай базе даных, Вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі Вы не атрымалі гэты ліст, праверце папку са спамам. + send_paranoid_instructions: Калі Ваш электроны адрас існуе ў нашай базе даных, Вы атрымаеце спасылку для аднаўлення пароля на сваю электронную пошту праз пару хвілін. Калі Вы не атрымалі гэты ліст, праверце папку са спамам. updated: Ваш пароль быў паспяхова зменены. Вы ўвайшлі ў сістэму. updated_not_active: Ваш пароль быў паспяхова зменены. registrations: destroyed: Пакуль! Ваш уліковы запіс быў паспяхова выдалены. Мы спадзяваемся хутка ўбачыць вас зноў. - update_needs_confirmation: Вы паспяхова абнавілі свой уліковы запіс, аднак, нам неабходна пацвердзіць ваш новы адрас электроннай пошты. Праверце вашу пошту і перайдзіце па спасылцы для пацвярджэння свайго новага адраса электроннай пошты. Калі вы не атрымалі гэты ліст, праверце папку са спамам. + update_needs_confirmation: Вы паспяхова абнавілі свой уліковы запіс, аднак, нам неабходна пацвердзіць Ваш новы адрас электроннай пошты. Праверце Вашу пошту і перайдзіце па спасылцы для пацвярджэння свайго новага адраса электроннай пошты. Калі Вы не атрымалі гэты ліст, праверце папку са спамам. updated: Ваш уліковы запіс быў паспяхова абноўлены. sessions: already_signed_out: Выхад паспяховы. signed_in: Уваход паспяховы. signed_out: Выхад паспяховы. unlocks: - send_instructions: Вы атрымаеце ліст з інструкцыямі па разблакаванні вашага ўліковага запісу цягам некалькіх хвілін. Праверце тэчку са спамам, калі вы не атрымалі такі ліст. - send_paranoid_instructions: Калі ваш уліковы запіс існуе, вы атрымаеце ліст з інструкцыямі па яго разблакаванні цягам некалькіх хвілін. Праверце тэчку са спамам, калі вы не атрымалі такі ліст. + send_instructions: Вы атрымаеце ліст з інструкцыямі па разблакіроўцы вашага ўліковага запісу цягам некалькіх хвілін. Праверце тэчку са спамам, калі Вы не атрымалі такі ліст. + send_paranoid_instructions: Калі Ваш уліковы запіс існуе, вы атрымаеце ліст з інструкцыямі па яго разблакіроўцы цягам некалькіх хвілін. Праверце тэчку са спамам, калі Вы не атрымалі такі ліст. unlocked: Ваш уліковы запіс быў разблакіраваны. Увайдзіце, каб працягнуць. errors: messages: diff --git a/config/locales/doorkeeper.az.yml b/config/locales/doorkeeper.az.yml index aac8511a1cc..210506ffeb4 100644 --- a/config/locales/doorkeeper.az.yml +++ b/config/locales/doorkeeper.az.yml @@ -1,6 +1,12 @@ --- az: doorkeeper: + authorized_applications: + index: + authorized_at: "%{date} tarixində səlahiyyət verilib" + description_html: Bunlar, API istifadə edərək hesabınıza erişəbilən tətbiqlərdir. Əgər burada tanımadığınız tətbiqlər və ya yanlış davranan bir tətbiq varsa, erişimini ləğv edə bilərsiniz. + last_used_at: Ən son %{date} istifadə edilib + title: Səlahiyyətli tətbiqləriniz errors: messages: invalid_token: @@ -14,13 +20,23 @@ az: write: Yalnız yazma erişimi title: all: Mastodon hesabınıza tam erişim + filters: Filtrlər + follow: İzləmələr, səsi kəsmələr və əngəlləmələr + follows: İzlənilənlər scopes: admin:read: serverdəki bütün veriləri oxuma + admin:read:domain_blocks: əngəllənən bütün domenlərin həssas məlumatlarını oxuma + admin:read:email_domain_blocks: əngəllənən bütün e-poçt domenlərinin həssas məlumatlarını oxuma admin:write: serverdəki bütün veriləri dəyişdirmə admin:write:accounts: hesablarda moderasiya əməliyyatlarını icra et admin:write:canonical_email_blocks: admin:write:domain_allows: domen icazələri üzərində moderasiya əməliyyatlarını icra et + admin:write:domain_blocks: əngəllənən domenlər üzərində moderasiya fəaliyyətlərini icra et + admin:write:email_domain_blocks: əngəllənən e-poçt domenləri üzərində moderasiya fəaliyyətlərini icra et admin:write:ip_blocks: IP əngəlləmələri üzrə moderasiya əməliyyatlarını icra et admin:write:reports: hesabatlarda moderasiya əməliyyatlarını icra et read: hesabınızın bütün verilərini oxuma + read:filters: filtrlərinizə baxın + read:follows: izlədiklərinizə baxın write: hesabınızın bütün verilərini dəyişdirmə + write:filters: filtrlər yarat diff --git a/config/locales/doorkeeper.be.yml b/config/locales/doorkeeper.be.yml index 7f1dadc4cfb..45fa4ba1beb 100644 --- a/config/locales/doorkeeper.be.yml +++ b/config/locales/doorkeeper.be.yml @@ -4,7 +4,7 @@ be: attributes: doorkeeper/application: name: Назва праграмы - redirect_uri: перанакіравць URI + redirect_uri: Перанакіравць URI scopes: Дазволы website: Вэб-сайт праграмы errors: @@ -12,8 +12,8 @@ be: doorkeeper/application: attributes: redirect_uri: - fragment_present: не можа ўтрымліваць фрагмент - invalid_uri: URI павінен быць сапраўдным + fragment_present: не можа ўтрымліваць фрагмент. + invalid_uri: URI павінен быць сапраўдным. relative_uri: павінен быць абсалютным URI. secured_uri: павінен быць HTTPS/SSL URI. doorkeeper: @@ -60,7 +60,7 @@ be: error: title: Узнікла памылка new: - prompt_html: "%{client_name} хоча атрымаць дазвол на доступ да Вашага профілю. Ухваляйце гэты запыт толькі калі Вы ведаеце гэту крыніцу і давяраеце ёй." + prompt_html: "%{client_name} хоча атрымаць дазвол на доступ да Вашага ўліковага запісу. Ухваляйце гэты запыт толькі калі Вы ведаеце гэту крыніцу і давяраеце ёй." review_permissions: Прагледзець дазволы title: Патрабуецца аўтарызацыя show: @@ -72,7 +72,7 @@ be: revoke: Вы ўпэўнены? index: authorized_at: Аўтарызавана %{date} - description_html: Гэта прыкладанні якія могуць мець доступ да вашага акаунта з дапамогай API. Калі вы бачыце тут прыкладанні, якія вы не пазнаеце, або прыкладанне блага сябе паводзіць вы можаце прыбраць ягонны доступ. + description_html: Гэта прыкладанні якія могуць мець доступ да вашага ўліковага запісу з дапамогай API. Калі вы бачыце тут прыкладанні, якія вы не пазнаеце, або прыкладанне блага сябе паводзіць вы можаце прыбраць ягоны доступ. last_used_at: Апошні раз скарыстана %{date} never_used: Не выкарыстоўвалася scopes: Дазволы @@ -120,10 +120,10 @@ be: write: Доступ толькі для запісу title: accounts: Уліковыя запісы - admin/accounts: Кіраванне акаўнтамі + admin/accounts: Кіраванне ўліковымі запісамі admin/all: Усе кіравальныя функцыі admin/reports: Кіраванне скардамі - all: Поўны доступ да акаўнта Mastodon + all: Поўны доступ да ўліковага запісу Mastodon blocks: Блакаванні bookmarks: Закладкі conversations: Размовы @@ -150,15 +150,15 @@ be: title: Патрабуецца аўтарызацыя OAuth scopes: admin:read: чытаць усе даныя на серверы - admin:read:accounts: чытаць канфідэнцыйную інфармацыю ўсіх акаўнтаў + admin:read:accounts: чытаць канфідэнцыяльную інфармацыю ўсіх уліковых запісаў admin:read:canonical_email_blocks: чытаць канфідэнцыйную інфармацыю ўсіх кананічных блокаў электроннай пошты admin:read:domain_allows: чытаць канфідэнцыйную інфармацыю ўсіх дазволеных даменаў admin:read:domain_blocks: чытаць канфідэнцыйную інфармацыю ўсіх блакіраваных даменаў admin:read:email_domain_blocks: чытаць канфідэнцыйную інфармацыю ўсіх блакіраваных даменаў эл. пошты admin:read:ip_blocks: чытаць канфідэнцыяльную інфармацыю ўсіх блакіраваных IP - admin:read:reports: чытаць канфідэнцыйную інфармацыю ўсіх справаздач і справаздачных уліковых запісаў + admin:read:reports: чытаць канфідэнцыяльную інфармацыю ўсіх скаргаў і абскарджаных уліковых запісаў admin:write: змяняць усе даныя на серверы - admin:write:accounts: выконваць дзеянні па мадэрацыі акаўнтаў + admin:write:accounts: выконваць дзеянні па мадэрацыі ўліковых запісаў admin:write:canonical_email_blocks: выконваць дзеянні па мадэрацыі кананічных блокаў электроннай пошты admin:write:domain_allows: дазваляе праводзіць мадэрацыю ў дамене admin:write:domain_blocks: мадэраваць блакіраваныя дамены @@ -189,7 +189,7 @@ be: write:conversations: ігнараваць і выдаляць размовы write:favourites: упадабаныя допісы write:filters: ствараць фільтры - write:follows: Сачыць за людзьмі + write:follows: сачыць за людзьмі write:lists: ствараць спiсы write:media: запампоўваць медыяфайлы write:mutes: ігнараваць людзей і размовы diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 190a58b8ee0..64752d55556 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -19,7 +19,7 @@ fi: following: Sinun täytyy seurata käyttäjää, jota haluat tukea posts: one: Julkaisu - other: viestiä + other: Julkaisua posts_tab_heading: Julkaisut self_follow_error: Oman tilisi seuraaminen ei ole sallittua admin: @@ -507,12 +507,14 @@ fi: registration_requested: Rekisteröintiä pyydetty registrations: confirm: Vahvista + description: Sait rekisteröinnin FASP:lta. Hylkää se, jos et aloittanut sitä. Jos aloitat tämän, vertaile huolellisesti nimeä ja sormenjälkeä ennen rekisteröinnin vahvistamista. reject: Hylkää title: Vahvista FASP-rekisteröinti save: Tallenna select_capabilities: Valitse kyvykkyydet sign_in: Kirjaudu sisään status: Tila + title: Fediversumin Tukitoimintojen Tarjoajat title: FASP follow_recommendations: description_html: "Seurantasuositukset auttavat uusia käyttäjiä löytämään nopeasti kiinnostavaa sisältöä. Kun käyttäjä ei ole ollut tarpeeksi vuorovaikutuksessa muiden kanssa, jotta hänelle olisi muodostunut henkilökohtaisia seuraamissuosituksia, suositellaan niiden sijaan näitä tilejä. Ne lasketaan päivittäin uudelleen yhdistelmästä tilejä, jotka ovat viime aikoina olleet aktiivisimmin sitoutuneita ja joilla on suurimmat paikalliset seuraajamäärät tietyllä kielellä." diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 52825685013..e0393583997 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -187,6 +187,7 @@ ko: create_relay: 릴레이 생성 create_unavailable_domain: 사용 불가능한 도메인 생성 create_user_role: 역할 생성 + create_username_block: 새 사용자명 규칙 만들기 demote_user: 사용자 강등 destroy_announcement: 공지사항 삭제 destroy_canonical_email_block: 이메일 차단 삭제 @@ -200,6 +201,7 @@ ko: destroy_status: 게시물 삭제 destroy_unavailable_domain: 사용 불가능한 도메인 제거 destroy_user_role: 역할 삭제 + destroy_username_block: 사용자명 규칙 삭제 disable_2fa_user: 2단계 인증 비활성화 disable_custom_emoji: 커스텀 에모지 비활성화 disable_relay: 릴레이 비활성화 @@ -234,6 +236,7 @@ ko: update_report: 신고 업데이트 update_status: 게시물 수정 update_user_role: 역할 수정 + update_username_block: 사용자명 규칙 업데이트 actions: approve_appeal_html: "%{name} 님이 %{target}의 중재 결정에 대한 이의 제기를 승인했습니다" approve_user_html: "%{name} 님이 %{target} 님의 가입을 승인했습니다" @@ -252,6 +255,7 @@ ko: create_relay_html: "%{name} 님이 릴레이 %{target}를 생성했습니다" create_unavailable_domain_html: "%{name} 님이 도메인 %{target}에 대한 전달을 중지했습니다" create_user_role_html: "%{name} 님이 %{target} 역할을 생성했습니다" + create_username_block_html: "%{name} 님이 %{target}를 포함하는 사용자명에 대한 규칙을 생성했습니다" demote_user_html: "%{name} 님이 사용자 %{target} 님을 강등했습니다" destroy_announcement_html: "%{name} 님이 공지 %{target}을 삭제했습니다" destroy_canonical_email_block_html: "%{name} 님이 %{target} 해시를 가진 이메일을 차단 해제했습니다" @@ -265,6 +269,7 @@ ko: destroy_status_html: "%{name} 님이 %{target} 님의 게시물을 삭제했습니다" destroy_unavailable_domain_html: "%{name} 님이 도메인 %{target}에 대한 전달을 재개" destroy_user_role_html: "%{name} 님이 %{target} 역할을 삭제했습니다" + destroy_username_block_html: "%{name} 님이 %{target}를 포함하는 사용자명에 대한 규칙을 삭제했습니다" disable_2fa_user_html: "%{name} 님이 사용자 %{target} 님의 2단계 인증을 비활성화 했습니다" disable_custom_emoji_html: "%{name} 님이 에모지 %{target}를 비활성화했습니다" disable_relay_html: "%{name} 님이 릴레이 %{target}를 비활성화했습니다" @@ -299,6 +304,7 @@ ko: update_report_html: "%{name} 님이 신고 %{target}를 업데이트 했습니다" update_status_html: "%{name} 님이 %{target}의 게시물을 업데이트했습니다" update_user_role_html: "%{name} 님이 %{target} 역할을 수정했습니다" + update_username_block_html: "%{name} 님이 %{target}를 포함하는 사용자명에 대한 규칙을 수정했습니다" deleted_account: 계정을 삭제했습니다 empty: 로그를 찾을 수 없습니다 filter_by_action: 동작 별 필터 @@ -1071,10 +1077,23 @@ ko: trending: 유행 중 username_blocks: add_new: 새로 추가 + block_registrations: 가입 차단 + comparison: + contains: 포함 + equals: 일치 + contains_html: "%{string} 포함" + created_msg: 사용자명 규칙을 생성했습니다 + delete: 삭제 + edit: + title: 사용자명 규칙 편집 + matches_exactly_html: "%{string}과 일치" new: create: 규칙 만들기 title: 새 유저네임 규칙 만들기 + no_username_block_selected: 아무 것도 선택 되지 않아 어떤 사용자명 규칙도 변경 되지 않았습니다 not_permitted: 허용하지 않음 + title: 사용자명 규칙 + updated_msg: 사용자명 규칙을 변경했습니다 warning_presets: add_new: 새로 추가 delete: 삭제 @@ -1631,6 +1650,10 @@ ko: title: 새 답글 poll: subject: "%{name}의 설문이 종료됨" + quote: + body: '당신의 게시물을 %{name} 님이 인용했습니다:' + subject: "%{name} 님이 내 게시물을 인용했습니다" + title: 새 인용 reblog: body: '당신의 게시물을 %{name} 님이 부스트 했습니다:' subject: "%{name} 님이 내 게시물을 부스트 했습니다" @@ -1837,6 +1860,7 @@ ko: edited_at_html: "%{date}에 편집됨" errors: in_reply_not_found: 답장하려는 게시물이 존재하지 않습니다. + quoted_status_not_found: 인용하려는 게시물이 존재하지 않습니다. over_character_limit: 최대 %{max}자까지 입력할 수 있습니다 pin_errors: direct: 멘션된 사용자들에게만 보이는 게시물은 고정될 수 없습니다 @@ -1844,6 +1868,8 @@ ko: ownership: 다른 사람의 게시물은 고정될 수 없습니다 reblog: 부스트는 고정될 수 없습니다 quote_policies: + followers: 내 팔로워만 + nobody: 없음 public: 모두 title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/nan.yml b/config/locales/nan.yml index a6203584284..f6b11afc930 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -690,6 +690,36 @@ nan: remote_user_placeholder: tuì %{instance} 來ê遠距離用者 reopen: 重頭phah開檢舉 report: '檢舉 #%{id}' + reported_account: 受檢舉ê口座 + reported_by: 檢舉人 + reported_with_application: 用應用程式檢舉 + resolved: 解決ah + resolved_msg: 檢舉成功解決ah! + skip_to_actions: 跳kàu行動 + status: 狀態 + statuses: 受檢舉ê內容 + statuses_description_html: 冒犯ê內容ē引用tī kap受檢舉口座ê聯絡 + summary: + action_preambles: + delete_html: Lí teh-beh thâi掉 @%{acct} ê tsi̍t-kuá PO文。Tse ē: + mark_as_sensitive_html: Lí teh-beh @%{acct} ê tsi̍t-kuá PO文標做敏感。Tse ē: + silence_html: Lí teh-beh 限制 @%{acct} ê口座。Tse ē: + suspend_html: Lí teh-beh 停止 @%{acct} ê口座權限。Tse ē: + actions: + delete_html: Thâi掉冒犯ê PO文 + mark_as_sensitive_html: Kā冒犯êPO文ê媒體標做敏感 + silence_html: Kā in ê個人資料kap內容標做kan-ta有跟tuè ê,á是手動tshiau伊ê個人檔案ê,通看見,來嚴嚴限制 @%{acct} ê傳播範圍 + suspend_html: Kā @%{acct} 停止權限,koh kā in ê 個人資料kap內容標做bē當用,kap bē當hām in互動 + close_report: 'Kā 檢舉報告 #%{id} 標做解決ah' + close_reports_html: Kā ta̍k êtuì @%{acct} ê檢舉標做解決ah + delete_data_html: Tuì tann起30kang以後,thâi掉 @%{acct} ê個人資料kap內容,除非佇tsit ê期限進前,取消停止in ê權限 + preview_preamble_html: "@%{acct} ē收著警告,包含下kha ê內容:" + record_strike_html: 記錄tuì @%{acct}ê警告,來幫tsān lí提升佇tsit ê口座ê未來違規 + send_email_html: Kā警告電子phue寄hōo @%{acct} + warning_placeholder: 通選ê,管理行動ê補充理由 + target_origin: 受檢舉ê口座ê來源 + title: 檢舉 + unassign: 取消分配 roles: privileges: manage_announcements: 管理公告 diff --git a/config/locales/simple_form.az.yml b/config/locales/simple_form.az.yml index b03505358f8..97c8eb60151 100644 --- a/config/locales/simple_form.az.yml +++ b/config/locales/simple_form.az.yml @@ -7,26 +7,78 @@ az: defaults: current_password: Təhlükəsizlik səbəblərinə görə lütfən hazırkı hesabın parolunu daxil edin phrase: Mətndəki böyük-kiçik hərfdən və ya göndərişin məzmun xəbərdarlığından asılı olmayaraq uyuşdurulacaq + setting_aggregate_reblogs: Təzəlikcə təkrar paylaşılmış göndərişlər üçün yeni təkrar paylaşımlar göstərilməsin (yalnız yeni alınan təkrar paylaşımlara təsir edir). + setting_always_send_emails: Normalda, Mastodon-u aktiv olaraq istifadə etdiyiniz zaman e-poçt bildirişləri göndərilməyəcək + setting_default_quote_policy: Bu ayar, yalnız növbəti Mastodon versiyası ilə yaradılmış göndərişlər üçün qüvvəyə minəcək, ancaq hazırlıq mərhələsində tərcihinizi edə bilərsiniz. + setting_default_sensitive: Həssas media, ilkin olaraq gizlədilir və bir kliklə göstərilə bilər + setting_display_media_default: Həssas olaraq işarələnmiş medianı gizlət + setting_display_media_hide_all: Medianı həmişə gizlət + setting_display_media_show_all: Medianı həmişə göstər + setting_system_scrollbars_ui: Yalnız Safari və Chrome əaslı masaüstü brauzerlərinə tətbiq olunur + setting_use_blurhash: Meyillər, gizli vizualların rənglərinə əsaslanır, ancaq detalları gizlədir + setting_use_pending_items: Lenti avtomatik diyirləmək əvəzinə, zaman xətti güncəlləmələrini tək bir kliklə gizlət + featured_tag: + name: 'Budur, təzəlikcə istifadə etdiyiniz mövzu etiketlərindən bəziləri:' + form_admin_settings: + content_cache_retention_period: Digər serverlərdən olan bütün göndərişlər (təkrar paylaşımlar və cavablar daxil olmaqla) göstərilən gün sayından sonra, lokal istifadəçilərin həmin göndərişlərlə qarşılıqlı əlaqəsinə baxılmadan silinəcək. Buna, lokal istifadəçinin həmin göndərişi əlfəcinlərə və ya sevimlilərə əlavə etdiyi hallar da daxildir. Fərqli instansiyalardakı istifadəçilər arasında olan şəxsi adçəkmələr də itəcək və bərpası mümkün olmayacaq. Bu ayarın istifadəsi xüsusi məqsədli instansiyalar üçün nəzərdə tutulub və ümumi məqsədli istifadəyə tətbiq edildikdə bir çox istifadəçi gözləntilərini qarşılamaya bilər. + imports: + data: Digər Mastodon serverindən xaricə köçürdüyünüz CSV faylı ip_block: severities: no_access: Bütün resurslara erişimi əngəllə sessions: otp: 'Telefon tətbiqiniz tərəfindən yaradılmış iki faktorlu kodu daxil edin və ya geri qaytarma kodlarınızdan birini istifadə edin:' + user: + chosen_languages: İşarələnsə, yalnız seçilmiş dillərdəki göndərişlər ümumi zaman xətlərində nümayiş etdiriləcək user_role: permissions_as_keys: Bu rola sahib istifadəçilər bunlara erişə biləcək... labels: + account: + show_collections: Profildə izlənilənləri və izləyiciləri göstər defaults: + autofollow: Hesabınızı izləməyə dəvət edin confirm_new_password: Yeni parolu təsdiqlə confirm_password: Parolu təsdiqlə current_password: Hazırkı parol data: Veri + display_name: Ekran adı + header: Örtük şəkli + locale: İnterfeys dili + max_uses: Maksimum istifadə sayı new_password: Yeni parol password: Parol - setting_system_scrollbars_ui: Sistemin ilkin diyircəyini istifadə edin + setting_advanced_layout: Qabaqcıl veb interfeysini fəallaşdır + setting_aggregate_reblogs: Zaman xəttindəki TP-ları qruplaşdır + setting_always_send_emails: E-poçt göndərişlərini həmişə göndər + setting_auto_play_gif: Animasiyalı GIF-ləri avto-oxut + setting_boost_modal: Bir təkrar paylaşımı silməzdən əvvəl təsdiq dialoq pəncərəsini göstər + setting_default_privacy: Göndəriş məxfiliyi + setting_default_sensitive: Medianı həmişə həssas olaraq işarələ + setting_delete_modal: Bir göndərişi silməzdən əvvəl təsdiq dialoq pəncərəsini göstər + setting_disable_hover_cards: Üzərinə gəldikdə profil önizləməsini sıradan çıxart + setting_disable_swiping: Sürüşdürmə hərəkətlərini sıradan çıxart + setting_display_media: Medianın nümayişi + setting_expand_spoilers: Məzmun xəbərdarlığı ilə işarələnmiş göndərişləri həmişə genişləndir + setting_missing_alt_text_modal: Alternativ mətni olmayan medianı göndərməzdən əvvəl təsdiq dialoq pəncərəsini göstər + setting_reduce_motion: Animasiyalarda hərəkəti azalt + setting_system_font_ui: Sistemin ilkin şriftini istifadə et + setting_system_scrollbars_ui: Sistemin ilkin sürüşdürmə çubuğunu istifadə et + setting_trends: Bugünün trendlərini göstər + setting_use_blurhash: Gizli media üçün rəngli meyilləri göstər + setting_use_pending_items: Yavaş rejim + form_admin_settings: + show_domain_blocks: Əngəllənən domenləri göstər + timeline_preview: Ümumi zaman xətlərinə səlahiyyətsiz erişimə icazə ver ip_block: severities: no_access: Erişimi əngəllə notification_emails: appeal: Kimsə, bir moderasiya qərarına etiraz edir + favourite: Kimsə göndərişinizi sevimlilərinə əlavə etdi + follow: Kimsə sizi izləyir + follow_request: Kimsə sizi izləmək üçün sorğu göndərdi + mention: Kimsə adınızı çəkdi + quote: Kimsə sizdən sitat gətirdi + reblog: Kimsə göndərişinizi təkrar paylaşdı username_block: username: Uyuşacaq söz diff --git a/config/locales/simple_form.be.yml b/config/locales/simple_form.be.yml index 8a5c912e20c..da411075d64 100644 --- a/config/locales/simple_form.be.yml +++ b/config/locales/simple_form.be.yml @@ -3,12 +3,14 @@ be: simple_form: hints: account: + attribution_domains: Адзін на радок. Абараняе ад ілжывых значанняў аўтарства. discoverable: Вашы публічныя паведамленні і профіль могуць быць паказаны або рэкамендаваны ў розных раздзелах Mastodon, і ваш профіль можа быць прапанаваны іншым карыстальнікам. display_name: Ваша поўнае імя або ваш псеўданім. fields: Ваша хатняя старонка, займеннікі, узрост, усё, што заўгодна. indexable: Вашыя публічныя допісы могуць з'яўляцца ў рэзультатах пошуку Mastodon. Людзі, якія ўзаемадзейнічалі з вашымі допісамі, усё роўна маюць магчымасць іх знаходзіць. note: 'Вы можаце @згадваць іншых людзей або выкарыстоўваць #хэштэгі.' show_collections: Людзі змогуць праглядаць спіс вашых падпісак і падпісчыкаў. Людзі, на якіх вы падпісаны ў любым выпадку будуць бачыць, што вы іх чытаеце. + unlocked: Людзі змогуць падпісвацца на Вас без запыту на ўхваленне. Зніміце птушку, калі хочаце разглядаць запыты на падпіску і выбіраць, ці хочаце Вы прымаць або адмаўляць новым падпісчыкам. account_alias: acct: Прызначце карыстальнік@дамен уліковага запісу з якога вы хочаце пераехаць account_migration: @@ -43,7 +45,7 @@ be: context: Адзін ці некалькі кантэкстаў, да якіх трэба прымяніць фільтр current_password: У мэтах бяспекі, увядзіце пароль бягучага ўліковага запісу current_username: Каб пацвердзіць, увядзіце, калі ласка імя карыстальніка бягучага ўліковага запісу - digest: Будзе даслана толькі пасля доўгага перыяду неактыўнасці і толькі калі вы атрымалі асабістыя паведамленні падчас вашай адсутнасці + digest: Будзе даслана толькі пасля доўгага перыяду неактыўнасці і толькі, калі Вы атрымалі асабістыя паведамленні падчас Вашай адсутнасці email: Пацвярджэнне будзе выслана па электроннай пошце header: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions}} пікселяў inbox_url: Капіраваць URL са старонкі рэтранслятара, якім вы хочаце карыстацца @@ -54,11 +56,13 @@ be: scopes: Абярыце, якімі API праграма зможа карыстацца. Выбар дазволу найвышэйшага ўзроўню ўключае ў сябе дазволу астатніх узроўняў. setting_aggregate_reblogs: Не паказваць новыя пашырэнні для допісаў, якія пашырылі нядаўна (закранае толькі нядаўнія пашырэнні) setting_always_send_emails: Звычайна лісты з апавяшчэннямі не будуць дасылацца, калі вы актыўна карыстаецеся Mastodon + setting_default_quote_policy: Гэтая налада будзе працаваць толькі з допісамі, створанымі ў наступнай версіі Mastodon, але Вы можаце падрыхтавацца і задаць яе. setting_default_sensitive: Далікатныя медыя прадвызначана схаваныя. Іх можна адкрыць адзіным клікам setting_display_media_default: Хаваць медыя пазначаныя як далікатныя setting_display_media_hide_all: Заўсёды хаваць медыя setting_display_media_show_all: Заўсёды паказваць медыя setting_emoji_style: Як паказваць эмодзі. "Аўтаматычны" будзе намагацца выкарыстоўваць мясцовыя эмодзі, але для састарэлых браўзераў — Twemoji. + setting_system_scrollbars_ui: Працуе толькі ў камп'ютарных браўзерах на аснове Safari і Chrome setting_use_blurhash: Градыенты заснаваны на колерах схаваных выяў, але размываюць дэталі setting_use_pending_items: Схаваць абнаўленні стужкі за клікам замест аўтаматычнага пракручвання стужкі username: Вы можаце выкарыстоўваць літары, лічбы і падкрэсліванне @@ -71,8 +75,9 @@ be: featured_tag: name: 'Вось некаторыя з хэштэгаў, якімі вы нядаўна карысталіся:' filters: - action: Абярыце, што зрабіць, калі пост падпадае пад умовы фільтру + action: Абярыце, што зрабіць, калі допіс падпадае пад умовы фільтру actions: + blur: Схавайце медыя за знакам папярэджання, не хаваючы пры гэтым тэкст hide: Поўнасцю схаваць адфільтраванае змесціва, дзейнічаць, нібы яго не існуе warn: Схаваць адфільтраваны кантэнт за папярэджаннем з назвай фільтру form_admin_settings: @@ -86,7 +91,8 @@ be: favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок. mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе. media_cache_retention_period: Медыяфайлы з допісаў, зробленых выдаленымі карыстальнікамі, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыяданыя будуць запытаны пасля выдалення, яны будуць спампаваны зноў, калі зыходнае змесціва усё яшчэ даступнае. У сувязі з абмежаваннямі на частату абнаўлення картак перадпрагляду іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш гэтыя карткі не будуць абнаўляцца па запыце раней за гэты тэрмін. - peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у fediverse. Даныя пра тое, ці знаходзіцеся вы з дадзеным серверам у федэрацыі, не ўключаны. Уключаны толькі даныя пра тое, што ваш сервер ведае пра іншыя серверы. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. + min_age: Карыстальнікі будуць атрымліваць запыт на пацвярджэнне даты нараджэння падчас рэгістрацыі + peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у федэральным сусвеце. Даныя пра тое, ці знаходзіцеся вы з дадзеным серверам у федэрацыі, не ўключаны. Уключаны толькі даныя пра тое, што ваш сервер ведае пра іншыя серверы. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. profile_directory: Дырэкторыя профіляў змяшчае спіс усіх карыстальнікаў, якія вырашылі быць бачнымі. require_invite_text: Калі рэгістрацыя патрабуе ручнога пацвержання, зрабіце поле "Чаму вы хочаце далучыцца?" абавязковым site_contact_email: Як людзі могуць звязацца з вамі па юрыдычных запытах або пытаннях падтрымкі. @@ -129,21 +135,44 @@ be: tag: name: Вы можаце змяняць толькі рэгістр літар, напрыклад для таго, каб падвысіць чытабельнасць terms_of_service: + changelog: Можна карыстацца сінтаксісам Markdown, каб структураваць тэкст. + effective_date: Дапушчальны перыяд часу можа вар'іравацца ад 10 да 30 дзён з моманту, як Вы апавясціце сваіх карыстальнікаў. text: Тэкст можна структураваць з дапамогай сінтаксісу Markdown. + terms_of_service_generator: + admin_email: Юрыдычныя абвесткі ўключаюць сустрэчныя абвесткі, судзейскія пастановы, запыты на выдаленне і запыты праваахоўных органаў. + arbitration_address: Можа быць такім жа, што і фізічны адрас вышэй, або “N/A”, калі карыстаецеся электроннай поштай. + arbitration_website: Можа быць у выглядзе электроннай формы або “N/A”, калі карыстаецеся электроннай поштай. + choice_of_law: шашашаГорад, вобласць, тэрыторыя штата (Зша), унутраныя законы якой рэгулююць усе прэтэнзіі. + dmca_address: 'Для ўладальнікаў з ЗША: выкарыстоўвайце адрас, які зарэгістраваны ў даведніку ўпаўнаважаных агентаў DMCA. Спіс паштовых адрасоў даступны па прамым запыце, выкарыстоўвайце запыт на адмову ад правоў на пошту ўпаўнаважаных агентаў DMCA, каб адправіць электронны ліст у Офіс па Аўтарскіх Правах і апішыце, што Вы мадэратар, які працуе з уласнага дому, які баіцца помсты ці пакарання за свае дзеянні і якому патрэбны выкарыстаць паштовы адрас, каб прыбраць дамашні адрас з публічнага доступу.' + dmca_email: Можа быць такім жа, што і "Электронны адрас для юрыдычных абвестак" вышэй. + domain: Унікальны ідэнтыфікатар анлайн сэрвісу, які Вы прадстаўляеце. + jurisdiction: Дадайце краіну, дзе жыве той, хто плаціць за рахункі. Калі гэта кампанія ці іншая інстанцыя, дадайце краіну, дзе яна знаходзіцца, а таксама (па меры неабходнасці) горад, вобласць, тэрыторыю штата (ЗША). + min_age: Не павінен быць ніжэй за мінімальны ўзрост, які патрабуюць законы Вашай юрысдыкцыі. user: chosen_languages: У публічных стужках будуць паказвацца допісы толькі на тых мовах, якія вы пазначыце + date_of_birth: + few: Нам трэба ўпэўніцца, што Вы як мінімум %{count} чалавекі з тых, хто карыстаецца %{domain}. Мы не будзем захоўваць гэту інфармацыю. + many: Нам трэба ўпэўніцца, што Вы як мінімум %{count} людзей з тых, хто карыстаецца %{domain}. Мы не будзем захоўваць гэту інфармацыю. + one: Нам трэба ўпэўніцца, што Вы як мінімум %{count} чалавек з тых, хто карыстаецца %{domain}. Мы не будзем захоўваць гэту інфармацыю. + other: Нам трэба ўпэўніцца, што Вы як мінімум %{count} чалавекі з тых, хто карыстаецца %{domain}. Мы не будзем захоўваць гэту інфармацыю. + role: Роля кантралюе тое, якія дазволы мае карыстальнік. user_role: color: Колер, які будзе выкарыстоўвацца для гэтай ролі па ўсім UI, у фармаце RGB ці hex highlighted: Гэта робіць ролю публічна бачнай name: Публічная назва ролі, калі роля дэманструецца як значок у профілю permissions_as_keys: Карыстальнікі з гэтай роляй будуць мець доступ да... position: Ролі вышэйшага рангу займаюцца вырашэннем канфліктаў у пэўных сітуацыях. Некаторыя дзеянні можна выкананаць толькі над ролямі з ніжэйшым рангам + username_block: + allow_with_approval: Рэгістрацыя не будзе цалкам забараняцца. Замест гэтага на адпаведныя рэгістрацыі спатрэбіцца Ваша ўхваленне + comparison: Калі ласка, бярыце пад увагу Сканторпскую Праблему, калі блакіруеце частковыя супадзенні + username: Супадзе пры любым рэгістры літар і часта ўжывальных амогліфах, накшталт "4" у якасці "а" ці "3" у якасці "е" webhook: events: Выберыце падзеі для адпраўкі template: Стварыце сваю ўласную карысную нагрузку JSON з дапамогай інтэрпаляцыі зменных. Пакіньце пустым для стандартнага змесціва JSON. url: Куды падзеі будуць адпраўляцца labels: account: + attribution_domains: Сайтам дазволена пазначаць ваша аўтарства discoverable: Уключыць профіль і допісы ў алгарытмы рэкамендацый fields: name: Пазнака @@ -222,8 +251,10 @@ be: setting_emoji_style: Стыль эмодзі setting_expand_spoilers: Заўжды разгортваць допісы з папярэджаннем аб змесціве setting_hide_network: Схаваць вашы сувязі + setting_missing_alt_text_modal: Паказваць акно пацвярджэння перад публікацыяй медыя без альтэрнатыўнага тэксту setting_reduce_motion: Памяншэнне руху ў анімацыях setting_system_font_ui: Выкарыстоўваць прадвызначаны сістэмны шрыфт + setting_system_scrollbars_ui: Паказваць паласу пракручвання па змаўчанні setting_theme: Тэма сайта setting_trends: Паказваць трэнды дня setting_unfollow_modal: Паказваць акно пацвярджэння перад адпісваннем @@ -242,6 +273,7 @@ be: name: Хэштэг filters: actions: + blur: Хаваць медыя з папярэджаннем hide: Схаваць цалкам warn: Схаваць з папярэджаннем form_admin_settings: @@ -255,6 +287,7 @@ be: favicon: Значок сайта mascot: Уласны маскот(спадчына) media_cache_retention_period: Працягласць захавання кэшу для медыя + min_age: Патрабаванне мінімальнага ўзросту peers_api_enabled: Апублікаваць спіс знойдзеных сервераў у API profile_directory: Уключыць каталог профіляў registrations_mode: Хто можа зарэгістравацца @@ -277,7 +310,7 @@ be: interactions: must_be_follower: Заблакіраваць апавяшчэнні ад непадпісаных людзей must_be_following: Заблакіраваць апавяшчэнні ад людзей на якіх вы не падпісаны - must_be_following_dm: Заблакіраваць асабістыя паведамленні ад людзей на якіх вы не падпісаны + must_be_following_dm: Заблакіраваць асабістыя паведамленні ад людзей, на якіх Вы не падпісаныя invite: comment: Каментар invite_request: @@ -298,6 +331,7 @@ be: follow_request: Нехта даслаў вам запыт на падпіску mention: Нехта згадаў вас pending_account: Новы акаўнт патрабуе разгляду + quote: Хтосьці цытаваў Вас reblog: Нехта пашырыў ваш допіс report: Новая скарга даслана software_updates: @@ -320,9 +354,15 @@ be: usable: Дазволіць допісам выкарыстоўваць гэты хэштэг лакальна terms_of_service: changelog: Што змянілася? + effective_date: Дата пачатку дзеяння text: Умовы выкарыстання terms_of_service_generator: + admin_email: Адрас электроннай пошты для юрыдычных абвестак + arbitration_address: Фізічны адрас для арбітражных абвестак + arbitration_website: Сайт для дасылання арбітражных абвестак choice_of_law: Выбар заканадаўства + dmca_address: Фізічны адрас для абвестак DMCA/аўтарскага права + dmca_email: Адрас электроннай пошты для абвестак DMCA/аўтарскага права domain: Дамен jurisdiction: Юрысдыкцыя min_age: Мінімальны ўзрост @@ -338,6 +378,10 @@ be: name: Назва permissions_as_keys: Дазволы position: Прыярытэт + username_block: + allow_with_approval: Дазваляць рэгістрацыі з ухваленнем + comparison: Метад параўнання + username: Словы, з якімі трэба параўноўваць webhook: events: Актыўныя падзеі template: Шаблон карыснай нагрузкі diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index da8f90d189e..e60ad24d78c 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -56,6 +56,7 @@ ko: scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다. setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다) setting_always_send_emails: 기본적으로 마스토돈을 활동적으로 사용하고 있을 때에는 이메일 알림이 보내지지 않습니다 + setting_default_quote_policy: 이 설정은 다음 마스토돈 버전부터 효과가 적용되지만 미리 준비할 수 있도록 설정을 제공합니다. setting_default_sensitive: 민감한 미디어는 기본적으로 가려져 있으며 클릭해서 볼 수 있습니다 setting_display_media_default: 민감함으로 표시된 미디어 가리기 setting_display_media_hide_all: 모든 미디어를 가리기 @@ -158,6 +159,9 @@ ko: name: 역할이 배지로 표시될 경우, 그 역할에 대한 공개적인 이름입니다 permissions_as_keys: 이 역할을 가진 사용자는 다음에 접근할 수 있게 됩니다... position: 특정 상황에서 충돌이 발생할 경우 더 높은 역할이 충돌을 해결합니다. 특정 작업은 우선순위가 낮은 역할에 대해서만 수행될 수 있습니다 + username_block: + allow_with_approval: 바로 가입을 막는 대신, 일치하는 가입에 승인을 요구합니다 + username: 대소문자와 관계 없고 "4"를 "a"로, "3"을 "e"로 사용하는 등의 보편적으로 사용되는 비슷한 문자까지 매치됩니다 webhook: events: 전송할 이벤트를 선택하세요 template: 원하는 JSON 페이로드를 변수와 함께 작성하거나, 그대로 두어 기본 JSON을 사용할 수 있습니다. @@ -323,6 +327,7 @@ ko: follow_request: 누군가 나를 팔로우 하길 요청할 때 mention: 누군가 나를 언급했을 때 pending_account: 새 계정이 심사가 필요할 때 + quote: 누군가 나를 인용했을 때 reblog: 누군가 내 게시물을 부스트 했을 때 report: 새 신고가 접수되었을 때 software_updates: @@ -369,6 +374,10 @@ ko: name: 이름 permissions_as_keys: 권한 position: 우선순위 + username_block: + allow_with_approval: 승인을 통한 가입 허용 + comparison: 비교 방식 + username: 일치할 단어 webhook: events: 활성화된 이벤트 template: 페이로드 템플릿 From dbab3912bd471ab78232cf5640921fce0f7f3bf9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:08:40 +0200 Subject: [PATCH 254/660] Update dependency hiredis-client to v0.25.2 (#35732) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5f9259edead..0c1dfc20f34 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -300,8 +300,8 @@ GEM highline (3.1.2) reline hiredis (0.6.3) - hiredis-client (0.25.1) - redis-client (= 0.25.1) + hiredis-client (0.25.2) + redis-client (= 0.25.2) hkdf (0.3.0) htmlentities (4.3.4) http (5.3.1) @@ -717,7 +717,7 @@ GEM reline redcarpet (3.6.1) redis (4.8.1) - redis-client (0.25.1) + redis-client (0.25.2) connection_pool redlock (1.3.2) redis (>= 3.0.0, < 6.0) From 0e99d428b2d61f1a2e90d5d2cd2d8bb062e39024 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 11 Aug 2025 10:55:18 +0200 Subject: [PATCH 255/660] Change serialization of `Delete` activities for `QuoteAuthorization` to inline the latter (#35725) --- .../activitypub/delete_quote_authorization_serializer.rb | 5 ++--- .../delete_quote_authorization_serializer_spec.rb | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/serializers/activitypub/delete_quote_authorization_serializer.rb b/app/serializers/activitypub/delete_quote_authorization_serializer.rb index 150f2a4554b..4d09bb4649b 100644 --- a/app/serializers/activitypub/delete_quote_authorization_serializer.rb +++ b/app/serializers/activitypub/delete_quote_authorization_serializer.rb @@ -3,15 +3,14 @@ class ActivityPub::DeleteQuoteAuthorizationSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to - # TODO: change the `object` to a `QuoteAuthorization` object instead of just the URI? - attribute :virtual_object, key: :object + has_one :virtual_object, key: :object, serializer: ActivityPub::QuoteAuthorizationSerializer def id [ActivityPub::TagManager.instance.approval_uri_for(object, check_approval: false), '#delete'].join end def virtual_object - ActivityPub::TagManager.instance.approval_uri_for(object, check_approval: false) + object end def type diff --git a/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb index 48e3a4ddf73..9fef61e9d72 100644 --- a/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb +++ b/spec/serializers/activitypub/delete_quote_authorization_serializer_spec.rb @@ -13,7 +13,10 @@ RSpec.describe ActivityPub::DeleteQuoteAuthorizationSerializer do expect(subject.deep_symbolize_keys) .to include( actor: eq(ActivityPub::TagManager.instance.uri_for(status.account)), - object: ActivityPub::TagManager.instance.approval_uri_for(quote, check_approval: false), + object: a_hash_including( + type: 'QuoteAuthorization', + id: ActivityPub::TagManager.instance.approval_uri_for(quote, check_approval: false) + ), type: 'Delete' ) end From 8a7e84a475ba7f707153691abc1adbc692ea1cc2 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 11 Aug 2025 10:55:23 +0200 Subject: [PATCH 256/660] Add test for `Delete` of inlined `QuoteAuthorization` (#35724) --- spec/lib/activitypub/activity/delete_spec.rb | 84 ++++++++------------ 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index 849c7ada908..48d2946b941 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -12,27 +12,22 @@ RSpec.describe ActivityPub::Activity::Delete do id: 'foo', type: 'Delete', actor: ActivityPub::TagManager.instance.uri_for(sender), - object: ActivityPub::TagManager.instance.uri_for(status), + object: object_json, signature: 'foo', - }.with_indifferent_access + }.deep_stringify_keys end + let(:object_json) { ActivityPub::TagManager.instance.uri_for(status) } + describe '#perform' do subject { described_class.new(json, sender) } - before do - subject.perform - end - it 'deletes sender\'s status' do + subject.perform expect(Status.find_by(id: status.id)).to be_nil end - end - - context 'when the status has been reblogged' do - describe '#perform' do - subject { described_class.new(json, sender) } + context 'when the status has been reblogged' do let!(:reblogger) { Fabricate(:account) } let!(:follower) { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } let!(:reblog) { Fabricate(:status, account: reblogger, reblog: status) } @@ -55,12 +50,8 @@ RSpec.describe ActivityPub::Activity::Delete do expect { reblog.reload }.to raise_error(ActiveRecord::RecordNotFound) end end - end - - context 'when the status has been reported' do - describe '#perform' do - subject { described_class.new(json, sender) } + context 'when the status has been reported' do let!(:reporter) { Fabricate(:account) } before do @@ -76,23 +67,9 @@ RSpec.describe ActivityPub::Activity::Delete do expect(Status.with_discarded.find_by(id: status.id)).to_not be_nil end end - end - - context 'when the deleted object is an account' do - let(:json) do - { - '@context': 'https://www.w3.org/ns/activitystreams', - id: 'foo', - type: 'Delete', - actor: ActivityPub::TagManager.instance.uri_for(sender), - object: ActivityPub::TagManager.instance.uri_for(sender), - signature: 'foo', - }.with_indifferent_access - end - - describe '#perform' do - subject { described_class.new(json, sender) } + context 'when the deleted object is an account' do + let(:object_json) { ActivityPub::TagManager.instance.uri_for(sender) } let(:service) { instance_double(DeleteAccountService, call: true) } before do @@ -106,27 +83,36 @@ RSpec.describe ActivityPub::Activity::Delete do .to have_received(:call).with(sender, { reserve_username: false, skip_activitypub: true }) end end - end - context 'when the deleted object is a quote authorization' do - let(:quoter) { Fabricate(:account, domain: 'b.example.com') } - let(:status) { Fabricate(:status, account: quoter) } - let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') } - let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) } + context 'when the deleted object is a quote authorization' do + let(:quoter) { Fabricate(:account, domain: 'b.example.com') } + let(:status) { Fabricate(:status, account: quoter) } + let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') } + let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) } - let(:json) do - { - '@context': 'https://www.w3.org/ns/activitystreams', - id: 'foo', - type: 'Delete', - actor: ActivityPub::TagManager.instance.uri_for(sender), - object: quote.approval_uri, - signature: 'foo', - }.with_indifferent_access + let(:object_json) { quote.approval_uri } + + it 'revokes the authorization' do + expect { subject.perform } + .to change { quote.reload.state }.to('revoked') + end end - describe '#perform' do - subject { described_class.new(json, sender) } + context 'when the deleted object is an inlined quote authorization' do + let(:quoter) { Fabricate(:account, domain: 'b.example.com') } + let(:status) { Fabricate(:status, account: quoter) } + let(:quoted_status) { Fabricate(:status, account: sender, uri: 'https://example.com/statuses/1234') } + let!(:quote) { Fabricate(:quote, approval_uri: 'https://example.com/approvals/1234', state: :accepted, status: status, quoted_status: quoted_status) } + + let(:object_json) do + { + type: 'QuoteAuthorization', + id: quote.approval_uri, + attributedTo: ActivityPub::TagManager.instance.uri_for(quoted_status.account), + interactionTarget: ActivityPub::TagManager.instance.uri_for(quoted_status), + interactingObject: ActivityPub::TagManager.instance.uri_for(status), + }.deep_stringify_keys + end it 'revokes the authorization' do expect { subject.perform } From 9c5b4b263905947b7f7569a76462cce4c465f279 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 10:57:42 +0200 Subject: [PATCH 257/660] Update dependency sass to v1.90.0 (#35695) 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 b525ed6d67f..e7139b681fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11989,8 +11989,8 @@ __metadata: linkType: hard "sass@npm:^1.62.1": - version: 1.89.2 - resolution: "sass@npm:1.89.2" + version: 1.90.0 + resolution: "sass@npm:1.90.0" dependencies: "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" @@ -12001,7 +12001,7 @@ __metadata: optional: true bin: sass: sass.js - checksum: 10c0/752ccc7581b0c6395f63918116c20924e99943a86d79e94f5c4a0d41b1e981fe1f0ecd1ee82fff21496f81dbc91f68fb35a498166562ec8ec53e7aad7c3dbd9d + checksum: 10c0/cd882a61811447c079cdc78b41f3be8164f2a2ced56e0b256b33da2de383a5f10e11ed15f8080bd832e6df302238e11649b07eb5f6e7d257d9b6982a8325d269 languageName: node linkType: hard From 4cabc031e6e33f6f0f6eb26585a85cf9e6564ae2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:12:14 +0200 Subject: [PATCH 258/660] Update dependency rubyzip to v3 (#35576) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index ee2369921de..57ea00cc835 100644 --- a/Gemfile +++ b/Gemfile @@ -223,7 +223,7 @@ gem 'connection_pool', require: false gem 'xorcist', '~> 1.1' gem 'net-http', '~> 0.6.0' -gem 'rubyzip', '~> 2.3' +gem 'rubyzip', '~> 3.0' gem 'hcaptcha', '~> 7.1' diff --git a/Gemfile.lock b/Gemfile.lock index 0c1dfc20f34..111837de739 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -811,7 +811,7 @@ GEM ruby-vips (2.2.4) ffi (~> 1.12) logger - rubyzip (2.4.1) + rubyzip (3.0.1) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) safety_net_attestation (0.4.0) @@ -1075,7 +1075,7 @@ DEPENDENCIES ruby-prof ruby-progressbar (~> 1.13) ruby-vips (~> 2.2) - rubyzip (~> 2.3) + rubyzip (~> 3.0) sanitize (~> 7.0) scenic (~> 1.7) shoulda-matchers From bf15b1d65dc9f5391edbd57c1c26c303148cc92d Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 11 Aug 2025 14:00:35 +0200 Subject: [PATCH 259/660] Prevent props being added as HTML attributes (#35739) --- app/javascript/mastodon/features/emoji/emoji_html.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/emoji/emoji_html.tsx b/app/javascript/mastodon/features/emoji/emoji_html.tsx index ed6f7a20465..60a27306ddb 100644 --- a/app/javascript/mastodon/features/emoji/emoji_html.tsx +++ b/app/javascript/mastodon/features/emoji/emoji_html.tsx @@ -38,11 +38,7 @@ export const EmojiHTML = ( if (isModernEmojiEnabled()) { return ; } - const Wrapper = props.as ?? 'div'; - return ( - - ); + const { as: asElement, htmlString, extraEmojis, ...rest } = props; + const Wrapper = asElement ?? 'div'; + return ; }; From 379f12ea0083e073eab10cecb32a8d21126adcf1 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 11 Aug 2025 14:35:36 +0200 Subject: [PATCH 260/660] Update to Vite 7 (#35598) --- package.json | 47 ++- vite.config.mts | 1 + yarn.lock | 1049 ++++++++++++++++++++++++----------------------- 3 files changed, 552 insertions(+), 545 deletions(-) diff --git a/package.json b/package.json index 6109b42875b..de656d33c1d 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ "@react-spring/web": "^9.7.5", "@reduxjs/toolkit": "^2.0.1", "@use-gesture/react": "^10.3.1", - "@vitejs/plugin-legacy": "^6.1.1", - "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-legacy": "^7.2.1", + "@vitejs/plugin-react": "^5.0.0", "arrow-key-navigation": "^1.2.0", "async-mutex": "^0.5.0", "axios": "^1.4.0", @@ -108,7 +108,7 @@ "regenerator-runtime": "^0.14.0", "requestidlecallback": "^0.3.0", "rollup-plugin-gzip": "^4.1.1", - "rollup-plugin-visualizer": "^6.0.0", + "rollup-plugin-visualizer": "^6.0.3", "sass": "^1.62.1", "stacktrace-js": "^2.0.2", "stringz": "^2.1.0", @@ -117,27 +117,27 @@ "tiny-queue": "^0.2.1", "twitter-text": "3.1.0", "use-debounce": "^10.0.0", - "vite": "^6.3.5", + "vite": "^7.1.1", "vite-plugin-manifest-sri": "^0.2.0", - "vite-plugin-pwa": "^1.0.0", - "vite-plugin-static-copy": "^3.1.0", + "vite-plugin-pwa": "^1.0.2", + "vite-plugin-static-copy": "^3.1.1", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^5.1.4", "wicg-inert": "^3.1.2", - "workbox-expiration": "^7.0.0", - "workbox-routing": "^7.0.0", - "workbox-strategies": "^7.0.0", - "workbox-window": "^7.0.0" + "workbox-expiration": "^7.3.0", + "workbox-routing": "^7.3.0", + "workbox-strategies": "^7.3.0", + "workbox-window": "^7.3.0" }, "devDependencies": { "@eslint/js": "^9.23.0", "@formatjs/cli": "^6.1.1", - "@storybook/addon-a11y": "^9.0.4", - "@storybook/addon-docs": "^9.0.4", - "@storybook/addon-vitest": "^9.0.4", - "@storybook/react-vite": "^9.0.4", - "@testing-library/dom": "^10.2.0", - "@testing-library/react": "^16.0.0", + "@storybook/addon-a11y": "^9.1.1", + "@storybook/addon-docs": "^9.1.1", + "@storybook/addon-vitest": "^9.1.1", + "@storybook/react-vite": "^9.1.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.0", "@types/debug": "^4", "@types/emoji-mart": "3.0.14", "@types/escape-html": "^1.0.2", @@ -162,10 +162,10 @@ "@types/react-toggle": "^4.0.3", "@types/redux-immutable": "^4.0.3", "@types/requestidlecallback": "^0.3.5", - "@vitest/browser": "^3.2.1", - "@vitest/coverage-v8": "^3.2.0", - "@vitest/ui": "^3.2.1", - "chromatic": "^13.0.0", + "@vitest/browser": "^3.2.4", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", + "chromatic": "^13.1.3", "eslint": "^9.23.0", "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", @@ -185,19 +185,18 @@ "playwright": "^1.54.1", "prettier": "^3.3.3", "react-test-renderer": "^18.2.0", - "storybook": "^9.0.4", + "storybook": "^9.1.1", "stylelint": "^16.19.1", "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-standard-scss": "^15.0.1", "typescript": "~5.7.3", "typescript-eslint": "^8.29.1", - "vitest": "^3.2.1" + "vitest": "^3.2.4" }, "resolutions": { "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", - "kind-of": "^6.0.3", - "vite": "^6.3.5" + "kind-of": "^6.0.3" }, "peerDependenciesMeta": { "react": { diff --git a/vite.config.mts b/vite.config.mts index 30c0741aaa6..6876302dd90 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -73,6 +73,7 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { port: 3036, }, build: { + target: 'modules', commonjsOptions: { transformMixedEsModules: true }, chunkSizeWarningLimit: 1 * 1024 * 1024, // 1MB sourcemap: true, diff --git a/yarn.lock b/yarn.lock index e7139b681fe..2795331617a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -66,10 +66,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/compat-data@npm:7.27.2" - checksum: 10c0/077c9e01af3b90decee384a6a44dcf353898e980cee22ec7941f9074655dbbe97ec317345536cdc7ef7391521e1497930c522a3816af473076dd524be7fccd32 +"@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/compat-data@npm:7.28.0" + checksum: 10c0/c4e527302bcd61052423f757355a71c3bc62362bac13f7f130de16e439716f66091ff5bdecda418e8fa0271d4c725f860f0ee23ab7bf6e769f7a8bb16dfcb531 languageName: node linkType: hard @@ -109,16 +109,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-annotate-as-pure@npm:7.27.1" +"@babel/helper-annotate-as-pure@npm:^7.27.1, @babel/helper-annotate-as-pure@npm:^7.27.3": + version: 7.27.3 + resolution: "@babel/helper-annotate-as-pure@npm:7.27.3" dependencies: - "@babel/types": "npm:^7.27.1" - checksum: 10c0/fc4751b59c8f5417e1acb0455d6ffce53fa5e79b3aca690299fbbf73b1b65bfaef3d4a18abceb190024c5836bb6cfbc3711e83888648df93df54e18152a1196c + "@babel/types": "npm:^7.27.3" + checksum: 10c0/94996ce0a05b7229f956033e6dcd69393db2b0886d0db6aff41e704390402b8cdcca11f61449cb4f86cfd9e61b5ad3a73e4fa661eeed7846b125bd1c33dbc633 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": +"@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" dependencies: @@ -161,18 +161,18 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.3, @babel/helper-define-polyfill-provider@npm:^0.6.4": - version: 0.6.4 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.4" +"@babel/helper-define-polyfill-provider@npm:^0.6.5": + version: 0.6.5 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.5" dependencies: - "@babel/helper-compilation-targets": "npm:^7.22.6" - "@babel/helper-plugin-utils": "npm:^7.22.5" - debug: "npm:^4.1.1" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-plugin-utils": "npm:^7.27.1" + debug: "npm:^4.4.1" lodash.debounce: "npm:^4.0.8" - resolve: "npm:^1.14.2" + resolve: "npm:^1.22.10" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/b74f2b46e233a178618d19432bdae16e0137d0a603497ee901155e083c4a61f26fe01d79fb95d5f4c22131ade9d958d8f587088d412cca1302633587f070919d + checksum: 10c0/4886a068d9ca1e70af395340656a9dda33c50502c67eed39ff6451785f370bdfc6e57095b90cb92678adcd4a111ca60909af53d3a741120719c5604346ae409e languageName: node linkType: hard @@ -225,7 +225,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.27.1": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-plugin-utils@npm:7.27.1" checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b @@ -445,16 +445,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.27.1" +"@babel/plugin-transform-async-generator-functions@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.28.0" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-remap-async-to-generator": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/772e449c69ee42a466443acefb07083bd89efb1a1d95679a4dc99ea3be9d8a3c43a2b74d2da95d7c818e9dd9e0b72bfa7c03217a1feaf108f21b7e542f0943c0 + checksum: 10c0/739d577e649d7d7b9845dc309e132964327ab3eaea43ad04d04a7dcb977c63f9aa9a423d1ca39baf10939128d02f52e6fda39c834fb9f1753785b1497e72c4dc languageName: node linkType: hard @@ -482,14 +482,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-block-scoping@npm:7.27.1" +"@babel/plugin-transform-block-scoping@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-block-scoping@npm:7.28.0" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/d3f357beeb92fbdf3045aea2ba286a60dafc9c2d2a9f89065bb3c4bea9cc48934ee6689df3db0439d9ec518eda5e684f3156cab792b7c38c33ece2f8204ddee8 + checksum: 10c0/787d85e72a92917e735aa54e23062fa777031f8a07046e67f5026eff3d91e64eb535575dd1df917b0011bee014ae51287478af14c1d4ba60bc81e326bc044cfc languageName: node linkType: hard @@ -517,19 +517,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-classes@npm:7.27.1" +"@babel/plugin-transform-classes@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-classes@npm:7.28.0" dependencies: - "@babel/helper-annotate-as-pure": "npm:^7.27.1" - "@babel/helper-compilation-targets": "npm:^7.27.1" + "@babel/helper-annotate-as-pure": "npm:^7.27.3" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-globals": "npm:^7.28.0" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-replace-supers": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.1" - globals: "npm:^11.1.0" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/1071f4cb1ed5deb5e6f8d0442f2293a540cac5caa5ab3c25ad0571aadcbf961f61e26d367a67894976165a543e02f3a19e40b63b909afbed6e710801a590635c + checksum: 10c0/3b213b43104fe99dd7e79401a86d09e545836e057a70ffe77e8196a87bf67ae167e502ae90afdf0d1a2be683be5652514aaeda743bd984e583523dd8ecfef887 languageName: node linkType: hard @@ -545,14 +545,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-destructuring@npm:7.27.1" +"@babel/plugin-transform-destructuring@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-destructuring@npm:7.28.0" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/56afda7a0b205f8d1af727daef4c529fc2e756887408affd39033ae4476e54d586d3d9dc1e72cfb15c74a2a5ca0653ab13dbaa8cbf79fbb2a3a746d0f107cb86 + checksum: 10c0/cc7ccafa952b3ff7888544d5688cfafaba78c69ce1e2f04f3233f4f78c9de5e46e9695f5ea42c085b0c0cfa39b10f366d362a2be245b6d35b66d3eb1d427ccb2 languageName: node linkType: hard @@ -602,6 +603,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-explicit-resource-management@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.0" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/3baa706af3112adf2ae0c7ec0dc61b63dd02695eb5582f3c3a2b2d05399c6aa7756f55e7bbbd5412e613a6ba1dd6b6736904074b4d7ebd6b45a1e3f9145e4094 + languageName: node + linkType: hard + "@babel/plugin-transform-exponentiation-operator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.27.1" @@ -788,17 +801,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.27.2" +"@babel/plugin-transform-object-rest-spread@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.0" dependencies: "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-plugin-utils": "npm:^7.27.1" - "@babel/plugin-transform-destructuring": "npm:^7.27.1" - "@babel/plugin-transform-parameters": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" + "@babel/plugin-transform-parameters": "npm:^7.27.7" + "@babel/traverse": "npm:^7.28.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/5e255b262dd65c8700078d9f6ed87bd45f951a905dda6b3414be28d7b2781b18e6b812e9d71421e61360c9cf51e1e619c1d48348593bb7399496f61f5f221446 + checksum: 10c0/360dc6fd5285ee5e1d3be8a1fb0decd120b2a1726800317b4ab48b7c91616247030239b7fa06ceaa1a8a586fde1e143c24d45f8d41956876099d97d664f8ef1e languageName: node linkType: hard @@ -837,14 +851,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-parameters@npm:7.27.1" +"@babel/plugin-transform-parameters@npm:^7.27.7": + version: 7.27.7 + resolution: "@babel/plugin-transform-parameters@npm:7.27.7" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/453a9618735eeff5551d4c7f02c250606586fe1dd210ec9f69a4f15629ace180cd944339ebff2b0f11e1a40567d83a229ba1c567620e70b2ebedea576e12196a + checksum: 10c0/f2da3804e047d9f1cfb27be6c014e2c7f6cf5e1e38290d1cb3cb2607859e3d6facb4ee8c8c1e336e9fbb440091a174ce95ce156582d7e8bf9c0e735d11681f0f languageName: node linkType: hard @@ -906,14 +920,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-regenerator@npm:7.27.1" +"@babel/plugin-transform-regenerator@npm:^7.28.0": + version: 7.28.1 + resolution: "@babel/plugin-transform-regenerator@npm:7.28.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/42395908899310bb107d9ca31ebd4c302e14c582e3ad3ebfe1498fabafc43155c8f10850265c1e686a2afcf50d1f402cc5c5218fba72e167852607a4d8d6492e + checksum: 10c0/6c9e6eb80ce9c0bde0876c80979e078fbc85dc802272cba4ee72b5b1c858472e38167c418917e4f0d4384ce888706d95544a8d266880c0e199e167e078168b67 languageName: node linkType: hard @@ -1043,11 +1057,11 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.26.9": - version: 7.27.2 - resolution: "@babel/preset-env@npm:7.27.2" +"@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/preset-env@npm:7.28.0" dependencies: - "@babel/compat-data": "npm:^7.27.2" + "@babel/compat-data": "npm:^7.28.0" "@babel/helper-compilation-targets": "npm:^7.27.2" "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" @@ -1061,19 +1075,20 @@ __metadata: "@babel/plugin-syntax-import-attributes": "npm:^7.27.1" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.27.1" - "@babel/plugin-transform-async-generator-functions": "npm:^7.27.1" + "@babel/plugin-transform-async-generator-functions": "npm:^7.28.0" "@babel/plugin-transform-async-to-generator": "npm:^7.27.1" "@babel/plugin-transform-block-scoped-functions": "npm:^7.27.1" - "@babel/plugin-transform-block-scoping": "npm:^7.27.1" + "@babel/plugin-transform-block-scoping": "npm:^7.28.0" "@babel/plugin-transform-class-properties": "npm:^7.27.1" "@babel/plugin-transform-class-static-block": "npm:^7.27.1" - "@babel/plugin-transform-classes": "npm:^7.27.1" + "@babel/plugin-transform-classes": "npm:^7.28.0" "@babel/plugin-transform-computed-properties": "npm:^7.27.1" - "@babel/plugin-transform-destructuring": "npm:^7.27.1" + "@babel/plugin-transform-destructuring": "npm:^7.28.0" "@babel/plugin-transform-dotall-regex": "npm:^7.27.1" "@babel/plugin-transform-duplicate-keys": "npm:^7.27.1" "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.27.1" "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" + "@babel/plugin-transform-explicit-resource-management": "npm:^7.28.0" "@babel/plugin-transform-exponentiation-operator": "npm:^7.27.1" "@babel/plugin-transform-export-namespace-from": "npm:^7.27.1" "@babel/plugin-transform-for-of": "npm:^7.27.1" @@ -1090,15 +1105,15 @@ __metadata: "@babel/plugin-transform-new-target": "npm:^7.27.1" "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.27.1" "@babel/plugin-transform-numeric-separator": "npm:^7.27.1" - "@babel/plugin-transform-object-rest-spread": "npm:^7.27.2" + "@babel/plugin-transform-object-rest-spread": "npm:^7.28.0" "@babel/plugin-transform-object-super": "npm:^7.27.1" "@babel/plugin-transform-optional-catch-binding": "npm:^7.27.1" "@babel/plugin-transform-optional-chaining": "npm:^7.27.1" - "@babel/plugin-transform-parameters": "npm:^7.27.1" + "@babel/plugin-transform-parameters": "npm:^7.27.7" "@babel/plugin-transform-private-methods": "npm:^7.27.1" "@babel/plugin-transform-private-property-in-object": "npm:^7.27.1" "@babel/plugin-transform-property-literals": "npm:^7.27.1" - "@babel/plugin-transform-regenerator": "npm:^7.27.1" + "@babel/plugin-transform-regenerator": "npm:^7.28.0" "@babel/plugin-transform-regexp-modifiers": "npm:^7.27.1" "@babel/plugin-transform-reserved-words": "npm:^7.27.1" "@babel/plugin-transform-shorthand-properties": "npm:^7.27.1" @@ -1111,14 +1126,14 @@ __metadata: "@babel/plugin-transform-unicode-regex": "npm:^7.27.1" "@babel/plugin-transform-unicode-sets-regex": "npm:^7.27.1" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.11.0" - babel-plugin-polyfill-regenerator: "npm:^0.6.1" - core-js-compat: "npm:^3.40.0" + babel-plugin-polyfill-corejs2: "npm:^0.4.14" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10c0/fd7ec310832a9ff26ed8d56bc0832cdbdb3a188e022050b74790796650649fb8373568af05b320b58b3ff922507979bad50ff95a4d504ab0081134480103504e + checksum: 10c0/f343103b8f0e8da5be4ae031aff8bf35da4764997af4af78ae9506f421b785dd45da1bc09f845b1fc308c8b7d134aead4a1f89e7fb6e213cd2f9fe1d2aa78bc9 languageName: node linkType: hard @@ -1179,13 +1194,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.6, @babel/types@npm:^7.28.0, @babel/types@npm:^7.4.4": - version: 7.28.1 - resolution: "@babel/types@npm:7.28.1" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.25.4, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6, @babel/types@npm:^7.28.0, @babel/types@npm:^7.4.4": + version: 7.28.2 + resolution: "@babel/types@npm:7.28.2" dependencies: "@babel/helper-string-parser": "npm:^7.27.1" "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/5e99b346c11ee42ffb0cadc28159fe0b184d865a2cc1593df79b199772a534f6453969b4942aa5e4a55a3081863096e1cc3fc1c724d826926dc787cf229b845d + checksum: 10c0/24b11c9368e7e2c291fe3c1bcd1ed66f6593a3975f479cbb9dd7b8c8d8eab8a962b0d2fca616c043396ce82500ac7d23d594fbbbd013828182c01596370a0b10 languageName: node linkType: hard @@ -2519,20 +2534,20 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.0": - version: 0.6.0 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.0" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1": + version: 0.6.1 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1" dependencies: glob: "npm:^10.0.0" magic-string: "npm:^0.30.0" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/cbb76545214929e628de661985f69f9b79f324ad8db0aa19b2937c52730be57eb37848a7b7d5986ccc00f09d8bc0623ec16f83c9c13aaca3ef5afd0bc322da2e + checksum: 10c0/0bcc2adbb49158018102bd9d84cd8572c770daee3d46733157933ef0330953bd5b9e102c26f2338ee7dfb8f21a7bb937134d23f8a7935d5dc88525a253557467 languageName: node linkType: hard @@ -2606,12 +2621,12 @@ __metadata: "@rails/ujs": "npm:7.1.501" "@react-spring/web": "npm:^9.7.5" "@reduxjs/toolkit": "npm:^2.0.1" - "@storybook/addon-a11y": "npm:^9.0.4" - "@storybook/addon-docs": "npm:^9.0.4" - "@storybook/addon-vitest": "npm:^9.0.4" - "@storybook/react-vite": "npm:^9.0.4" - "@testing-library/dom": "npm:^10.2.0" - "@testing-library/react": "npm:^16.0.0" + "@storybook/addon-a11y": "npm:^9.1.1" + "@storybook/addon-docs": "npm:^9.1.1" + "@storybook/addon-vitest": "npm:^9.1.1" + "@storybook/react-vite": "npm:^9.1.1" + "@testing-library/dom": "npm:^10.4.1" + "@testing-library/react": "npm:^16.3.0" "@types/debug": "npm:^4" "@types/emoji-mart": "npm:3.0.14" "@types/escape-html": "npm:^1.0.2" @@ -2637,18 +2652,18 @@ __metadata: "@types/redux-immutable": "npm:^4.0.3" "@types/requestidlecallback": "npm:^0.3.5" "@use-gesture/react": "npm:^10.3.1" - "@vitejs/plugin-legacy": "npm:^6.1.1" - "@vitejs/plugin-react": "npm:^4.2.1" - "@vitest/browser": "npm:^3.2.1" - "@vitest/coverage-v8": "npm:^3.2.0" - "@vitest/ui": "npm:^3.2.1" + "@vitejs/plugin-legacy": "npm:^7.2.1" + "@vitejs/plugin-react": "npm:^5.0.0" + "@vitest/browser": "npm:^3.2.4" + "@vitest/coverage-v8": "npm:^3.2.4" + "@vitest/ui": "npm:^3.2.4" arrow-key-navigation: "npm:^1.2.0" async-mutex: "npm:^0.5.0" axios: "npm:^1.4.0" babel-plugin-formatjs: "npm:^10.5.37" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - chromatic: "npm:^13.0.0" + chromatic: "npm:^13.1.3" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" @@ -2717,10 +2732,10 @@ __metadata: regenerator-runtime: "npm:^0.14.0" requestidlecallback: "npm:^0.3.0" rollup-plugin-gzip: "npm:^4.1.1" - rollup-plugin-visualizer: "npm:^6.0.0" + rollup-plugin-visualizer: "npm:^6.0.3" sass: "npm:^1.62.1" stacktrace-js: "npm:^2.0.2" - storybook: "npm:^9.0.4" + storybook: "npm:^9.1.1" stringz: "npm:^2.1.0" stylelint: "npm:^16.19.1" stylelint-config-prettier-scss: "npm:^1.0.0" @@ -2732,18 +2747,18 @@ __metadata: typescript: "npm:~5.7.3" typescript-eslint: "npm:^8.29.1" use-debounce: "npm:^10.0.0" - vite: "npm:^6.3.5" + vite: "npm:^7.1.1" vite-plugin-manifest-sri: "npm:^0.2.0" - vite-plugin-pwa: "npm:^1.0.0" - vite-plugin-static-copy: "npm:^3.1.0" + vite-plugin-pwa: "npm:^1.0.2" + vite-plugin-static-copy: "npm:^3.1.1" vite-plugin-svgr: "npm:^4.3.0" vite-tsconfig-paths: "npm:^5.1.4" - vitest: "npm:^3.2.1" + vitest: "npm:^3.2.4" wicg-inert: "npm:^3.1.2" - workbox-expiration: "npm:^7.0.0" - workbox-routing: "npm:^7.0.0" - workbox-strategies: "npm:^7.0.0" - workbox-window: "npm:^7.0.0" + workbox-expiration: "npm:^7.3.0" + workbox-routing: "npm:^7.3.0" + workbox-strategies: "npm:^7.3.0" + workbox-window: "npm:^7.3.0" peerDependenciesMeta: react: optional: true @@ -3200,10 +3215,10 @@ __metadata: languageName: node linkType: hard -"@rolldown/pluginutils@npm:1.0.0-beta.27": - version: 1.0.0-beta.27 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" - checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c +"@rolldown/pluginutils@npm:1.0.0-beta.30": + version: 1.0.0-beta.30 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.30" + checksum: 10c0/aff8b532cb9d82d94c9a4101fa12ecb10620ad47d52dbb9135a5c65bde1ad19895b41026b821f4d607083699239a5d0010198401b6a6a54ab6a10d0015302768 languageName: node linkType: hard @@ -3299,142 +3314,142 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.40.2" +"@rollup/rollup-android-arm-eabi@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.46.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-android-arm64@npm:4.40.2" +"@rollup/rollup-android-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-android-arm64@npm:4.46.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-darwin-arm64@npm:4.40.2" +"@rollup/rollup-darwin-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.46.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-darwin-x64@npm:4.40.2" +"@rollup/rollup-darwin-x64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.46.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.40.2" +"@rollup/rollup-freebsd-arm64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.46.2" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-freebsd-x64@npm:4.40.2" +"@rollup/rollup-freebsd-x64@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-freebsd-x64@npm:4.46.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.40.2" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.46.2" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.40.2" +"@rollup/rollup-linux-arm-musleabihf@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.46.2" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.40.2" +"@rollup/rollup-linux-arm64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.46.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.40.2" +"@rollup/rollup-linux-arm64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.46.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.40.2" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.46.2" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.40.2" +"@rollup/rollup-linux-ppc64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.46.2" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.40.2" +"@rollup/rollup-linux-riscv64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.46.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.40.2" +"@rollup/rollup-linux-riscv64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.46.2" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.40.2" +"@rollup/rollup-linux-s390x-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.46.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.40.2" +"@rollup/rollup-linux-x64-gnu@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.46.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.40.2" +"@rollup/rollup-linux-x64-musl@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.46.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.40.2" +"@rollup/rollup-win32-arm64-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.46.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.40.2" +"@rollup/rollup-win32-ia32-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.46.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.40.2": - version: 4.40.2 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.40.2" +"@rollup/rollup-win32-x64-msvc@npm:4.46.2": + version: 4.46.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.46.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3460,38 +3475,38 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-a11y@npm:^9.0.4": - version: 9.0.4 - resolution: "@storybook/addon-a11y@npm:9.0.4" +"@storybook/addon-a11y@npm:^9.1.1": + version: 9.1.1 + resolution: "@storybook/addon-a11y@npm:9.1.1" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^9.0.4 - checksum: 10c0/558a71244ce6eb18eb08e95c47d94fa81d29f70226f37b92d1cc52a86fe6389ce978f0ad3a2e90d90dfedb6476d5c7bc7218b66201d3f94627b789783d53e808 + storybook: ^9.1.1 + checksum: 10c0/bf5eba0a51ffec20c8c4432985494295115bcf48e0807e4ca21314845d4aaaaaae9122d4be4f78a2fc4c15caa5e1207c01e118724c2cbecbd80aa8a5f6826924 languageName: node linkType: hard -"@storybook/addon-docs@npm:^9.0.4": - version: 9.0.4 - resolution: "@storybook/addon-docs@npm:9.0.4" +"@storybook/addon-docs@npm:^9.1.1": + version: 9.1.1 + resolution: "@storybook/addon-docs@npm:9.1.1" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:9.0.4" - "@storybook/icons": "npm:^1.2.12" - "@storybook/react-dom-shim": "npm:9.0.4" + "@storybook/csf-plugin": "npm:9.1.1" + "@storybook/icons": "npm:^1.4.0" + "@storybook/react-dom-shim": "npm:9.1.1" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.0.4 - checksum: 10c0/c70937abe73ec77e80017f14f459224fcefae457999be7be25cd198d49bdff31d181a99d3ec7b72fa494063e4f229c7c4e324173b416a2710208c8a12882e2bd + storybook: ^9.1.1 + checksum: 10c0/92b3ac089a38b892319de5ec02ca4ae477e38d88aef8561f6cbb1f15dd69ad856c194fca4514c783983baba063f428994a7d7f2aed98204b931f8e684e681194 languageName: node linkType: hard -"@storybook/addon-vitest@npm:^9.0.4": - version: 9.0.4 - resolution: "@storybook/addon-vitest@npm:9.0.4" +"@storybook/addon-vitest@npm:^9.1.1": + version: 9.1.1 + resolution: "@storybook/addon-vitest@npm:9.1.1" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.4.0" @@ -3500,7 +3515,7 @@ __metadata: peerDependencies: "@vitest/browser": ^3.0.0 "@vitest/runner": ^3.0.0 - storybook: ^9.0.4 + storybook: ^9.1.1 vitest: ^3.0.0 peerDependenciesMeta: "@vitest/browser": @@ -3509,31 +3524,31 @@ __metadata: optional: true vitest: optional: true - checksum: 10c0/7379826120c7d2aa4161abd978038161d0ffe2670d40315144ef8b8b1efa9afc793457fe9439928fc039565de2d4da4a109936bd7275518ee7f2fccfce66df21 + checksum: 10c0/a4770aad2f3e4ae10e3d7ae7083354e98287d095644aae87af62c59c9a97ec7e57cf25620c32e2e5f9261a3686a6efabea0a830061500f63e78a49a2bac6f130 languageName: node linkType: hard -"@storybook/builder-vite@npm:9.0.4": - version: 9.0.4 - resolution: "@storybook/builder-vite@npm:9.0.4" +"@storybook/builder-vite@npm:9.1.1": + version: 9.1.1 + resolution: "@storybook/builder-vite@npm:9.1.1" dependencies: - "@storybook/csf-plugin": "npm:9.0.4" + "@storybook/csf-plugin": "npm:9.1.1" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.0.4 - vite: ^5.0.0 || ^6.0.0 - checksum: 10c0/137c1b114d96f1e12f0d76b38d7d4ec10842b8d8284ead906e56d91d61dfbd9a82b84e7643be7cb226040d5829a72d580b441cb26cfa8d6634a7ebe4eff14071 + storybook: ^9.1.1 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/334235a64e05d6fb1e1cdf23f41ac211d1e55429e425e1aea33d9b4469503aa328eb5d9cad24e6328b392098afb2c884943d2c304c1eb3cf55ce25dcc3ad4414 languageName: node linkType: hard -"@storybook/csf-plugin@npm:9.0.4": - version: 9.0.4 - resolution: "@storybook/csf-plugin@npm:9.0.4" +"@storybook/csf-plugin@npm:9.1.1": + version: 9.1.1 + resolution: "@storybook/csf-plugin@npm:9.1.1" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^9.0.4 - checksum: 10c0/3988920c425b5ea8c2fd6d7148ae8d009ec4556fab2e14b6223ea6ae7422a0242a16c2e333f28218f85b7c0781c3540ecefbeaa94a492aaf9132e98a5785b254 + storybook: ^9.1.1 + checksum: 10c0/d29b5685ef79eacbcd891977f95a58238f104004b014f88ee59eab6e5995df31010435aa222a27cdf54056accc43239c44f7e8e461c263c60b09d7d2383be8b8 languageName: node linkType: hard @@ -3544,7 +3559,7 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^1.2.12, @storybook/icons@npm:^1.4.0": +"@storybook/icons@npm:^1.4.0": version: 1.4.0 resolution: "@storybook/icons@npm:1.4.0" peerDependencies: @@ -3554,26 +3569,26 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:9.0.4": - version: 9.0.4 - resolution: "@storybook/react-dom-shim@npm:9.0.4" +"@storybook/react-dom-shim@npm:9.1.1": + version: 9.1.1 + resolution: "@storybook/react-dom-shim@npm:9.1.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.0.4 - checksum: 10c0/abee05aa500c90b15a850163965f6631c1cd95f688eb12f5c629de0b2ce47f340b540e1f76b15bb84219a8961c0389316f39f5cc10cf807f76ae7e8d0c33a1c7 + storybook: ^9.1.1 + checksum: 10c0/ea2725719e04871b56c0a3755a7791034abd1d8ebb51392ed5f1eb2d21aee873444080509efe69c919e43121c299e06e2266d603478d059efe38762b0ae96ae5 languageName: node linkType: hard -"@storybook/react-vite@npm:^9.0.4": - version: 9.0.4 - resolution: "@storybook/react-vite@npm:9.0.4" +"@storybook/react-vite@npm:^9.1.1": + version: 9.1.1 + resolution: "@storybook/react-vite@npm:9.1.1" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.0" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:9.0.4" - "@storybook/react": "npm:9.0.4" - find-up: "npm:^5.0.0" + "@storybook/builder-vite": "npm:9.1.1" + "@storybook/react": "npm:9.1.1" + find-up: "npm:^7.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" resolve: "npm:^1.22.8" @@ -3581,27 +3596,27 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.0.4 - vite: ^5.0.0 || ^6.0.0 - checksum: 10c0/79031f1d6f07c5830fbc3d21e6483f090b75b20cbfe9ee4d26efa3c6346c70c1c462751ce1d110b100ace383a0adc0e159535d5de0d399980c07be95c21d170a + storybook: ^9.1.1 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/fa79e5a9be38f229a8cc0db85452e651081740cd3f9e6951c0fc3e195437124f8a792494b90a613c673ef9410d565d5f459276e157f073367ea28f0293d10167 languageName: node linkType: hard -"@storybook/react@npm:9.0.4": - version: 9.0.4 - resolution: "@storybook/react@npm:9.0.4" +"@storybook/react@npm:9.1.1": + version: 9.1.1 + resolution: "@storybook/react@npm:9.1.1" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:9.0.4" + "@storybook/react-dom-shim": "npm:9.1.1" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.0.4 + storybook: ^9.1.1 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d1b7f3c2ccd9d8eeb7fd029a74a0011fb4ddaf7b46ea8d6bbd51a4dedecf9959aed5db44725169e134fb931a3c2d7edfc29d3c6e63a0d1f1535317609bed1a97 + checksum: 10c0/343598e9c1bb2e53b7d6eaaa50b5c024c109fb825f6f4869a82da0ccd428400e5a93eba494e810f6bfbf6bff9b73810595ab8ffdd1530df8b4f2b20e617623e3 languageName: node linkType: hard @@ -3744,19 +3759,19 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^10.2.0, @testing-library/dom@npm:^10.4.0": - version: 10.4.0 - resolution: "@testing-library/dom@npm:10.4.0" +"@testing-library/dom@npm:^10.4.0, @testing-library/dom@npm:^10.4.1": + version: 10.4.1 + resolution: "@testing-library/dom@npm:10.4.1" dependencies: "@babel/code-frame": "npm:^7.10.4" "@babel/runtime": "npm:^7.12.5" "@types/aria-query": "npm:^5.0.1" aria-query: "npm:5.3.0" - chalk: "npm:^4.1.0" dom-accessibility-api: "npm:^0.5.9" lz-string: "npm:^1.5.0" + picocolors: "npm:1.1.1" pretty-format: "npm:^27.0.2" - checksum: 10c0/0352487720ecd433400671e773df0b84b8268fb3fe8e527cdfd7c11b1365b398b4e0eddba6e7e0c85e8d615f48257753283fccec41f6b986fd6c85f15eb5f84f + checksum: 10c0/19ce048012d395ad0468b0dbcc4d0911f6f9e39464d7a8464a587b29707eed5482000dad728f5acc4ed314d2f4d54f34982999a114d2404f36d048278db815b1 languageName: node linkType: hard @@ -3775,9 +3790,9 @@ __metadata: languageName: node linkType: hard -"@testing-library/react@npm:^16.0.0": - version: 16.1.0 - resolution: "@testing-library/react@npm:16.1.0" +"@testing-library/react@npm:^16.3.0": + version: 16.3.0 + resolution: "@testing-library/react@npm:16.3.0" dependencies: "@babel/runtime": "npm:^7.12.5" peerDependencies: @@ -3791,7 +3806,7 @@ __metadata: optional: true "@types/react-dom": optional: true - checksum: 10c0/8451dcc76ba0d4f3504af78f2a4aacc13117691f4b7a3c279f3e047d5ea817ff686496ad53e7f65f6183112aef2be3f318af609b1f5d666eed42b1014d1c68d5 + checksum: 10c0/3a2cb1f87c9a67e1ebbbcfd99b94b01e496fc35147be8bc5d8bf07a699c7d523a09d57ef2f7b1d91afccd1a28e21eda3b00d80187fbb51b1de01e422592d845e languageName: node linkType: hard @@ -3963,7 +3978,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": +"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 @@ -3977,13 +3992,6 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.7": - version: 1.0.7 - resolution: "@types/estree@npm:1.0.7" - checksum: 10c0/be815254316882f7c40847336cd484c3bc1c3e34f710d197160d455dc9d6d050ffbf4c3bc76585dba86f737f020ab20bdb137ebe0e9116b0c86c7c0342221b8c - languageName: node - linkType: hard - "@types/express-serve-static-core@npm:^4.17.33": version: 4.17.41 resolution: "@types/express-serve-static-core@npm:4.17.41" @@ -4685,56 +4693,60 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-legacy@npm:^6.1.1": - version: 6.1.1 - resolution: "@vitejs/plugin-legacy@npm:6.1.1" +"@vitejs/plugin-legacy@npm:^7.2.1": + version: 7.2.1 + resolution: "@vitejs/plugin-legacy@npm:7.2.1" dependencies: - "@babel/core": "npm:^7.26.10" - "@babel/preset-env": "npm:^7.26.9" - browserslist: "npm:^4.24.4" + "@babel/core": "npm:^7.28.0" + "@babel/plugin-transform-dynamic-import": "npm:^7.27.1" + "@babel/plugin-transform-modules-systemjs": "npm:^7.27.1" + "@babel/preset-env": "npm:^7.28.0" + babel-plugin-polyfill-corejs3: "npm:^0.13.0" + babel-plugin-polyfill-regenerator: "npm:^0.6.5" + browserslist: "npm:^4.25.1" browserslist-to-esbuild: "npm:^2.1.1" - core-js: "npm:^3.41.0" + core-js: "npm:^3.45.0" magic-string: "npm:^0.30.17" regenerator-runtime: "npm:^0.14.1" systemjs: "npm:^6.15.1" peerDependencies: terser: ^5.16.0 - vite: ^6.0.0 - checksum: 10c0/3a41098e422246d7ddbb6add678c1317474679a5e82e011947898cea5336fa8da9bb8fa625ab1284265f6ba123278fc2bf7c5b0b9b87febaaf865f14ac8e6ece + vite: ^7.0.0 + checksum: 10c0/bb9c14793c304ab84202a27218df4a71472ef8998c984121dcbc268b7dad8141d3c153f899794d5d62fed0e422ca756d24780e7da3f053e0ff87a37429808737 languageName: node linkType: hard -"@vitejs/plugin-react@npm:^4.2.1": - version: 4.7.0 - resolution: "@vitejs/plugin-react@npm:4.7.0" +"@vitejs/plugin-react@npm:^5.0.0": + version: 5.0.0 + resolution: "@vitejs/plugin-react@npm:5.0.0" dependencies: "@babel/core": "npm:^7.28.0" "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.27" + "@rolldown/pluginutils": "npm:1.0.0-beta.30" "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.17.0" peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 + checksum: 10c0/e5813839d319ab5dc1b90cab40b6c08388f26e456166ba9df10ffc3c3f4ecc594cec06715b5c93390bba56140ca5f68a18f2233f7d275d77e5bbfeb979e4fd9b languageName: node linkType: hard -"@vitest/browser@npm:^3.2.1": - version: 3.2.1 - resolution: "@vitest/browser@npm:3.2.1" +"@vitest/browser@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/browser@npm:3.2.4" dependencies: "@testing-library/dom": "npm:^10.4.0" "@testing-library/user-event": "npm:^14.6.1" - "@vitest/mocker": "npm:3.2.1" - "@vitest/utils": "npm:3.2.1" + "@vitest/mocker": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" magic-string: "npm:^0.30.17" sirv: "npm:^3.0.1" tinyrainbow: "npm:^2.0.0" ws: "npm:^8.18.2" peerDependencies: playwright: "*" - vitest: 3.2.1 + vitest: 3.2.4 webdriverio: ^7.0.0 || ^8.0.0 || ^9.0.0 peerDependenciesMeta: playwright: @@ -4743,13 +4755,13 @@ __metadata: optional: true webdriverio: optional: true - checksum: 10c0/304ae5107113230cc80b0b4eedcb3167ae776036797c77d97315ce4022e6c94553c557a0db6aff6de596fe77a0808b1e79b2576bd8e01386c010ea1c4a97a52b + checksum: 10c0/0db39daad675aad187eff27d5a7f17a9f533d7abc7476ee1a0b83a9c62a7227b24395f4814e034ecb2ebe39f1a2dec0a8c6a7f79b8d5680c3ac79e408727d742 languageName: node linkType: hard -"@vitest/coverage-v8@npm:^3.2.0": - version: 3.2.2 - resolution: "@vitest/coverage-v8@npm:3.2.2" +"@vitest/coverage-v8@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/coverage-v8@npm:3.2.4" dependencies: "@ampproject/remapping": "npm:^2.3.0" "@bcoe/v8-coverage": "npm:^1.0.2" @@ -4765,45 +4777,33 @@ __metadata: test-exclude: "npm:^7.0.1" tinyrainbow: "npm:^2.0.0" peerDependencies: - "@vitest/browser": 3.2.2 - vitest: 3.2.2 + "@vitest/browser": 3.2.4 + vitest: 3.2.4 peerDependenciesMeta: "@vitest/browser": optional: true - checksum: 10c0/d807f006ab9d4d3fb78c34586ab057a8e588746430b2d3ab07cfb972b5fabe65fde7033a9718ee598d0e3001085fa83edee76ff3f03a05e50a8879beee3ae37a + checksum: 10c0/cae3e58d81d56e7e1cdecd7b5baab7edd0ad9dee8dec9353c52796e390e452377d3f04174d40b6986b17c73241a5e773e422931eaa8102dcba0605ff24b25193 languageName: node linkType: hard -"@vitest/expect@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/expect@npm:3.0.9" - dependencies: - "@vitest/spy": "npm:3.0.9" - "@vitest/utils": "npm:3.0.9" - chai: "npm:^5.2.0" - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/4e5eef8fbc9c3e47f3fb69dbbd5b51aabdf1b6de2f781556d37d79731678fc83cf4a01d146226b12a27df051a4110153a6172506c9c74ae08e5b924a9c947f08 - languageName: node - linkType: hard - -"@vitest/expect@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/expect@npm:3.2.1" +"@vitest/expect@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/expect@npm:3.2.4" dependencies: "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:3.2.1" - "@vitest/utils": "npm:3.2.1" + "@vitest/spy": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" chai: "npm:^5.2.0" tinyrainbow: "npm:^2.0.0" - checksum: 10c0/ea02306fff2e657412ac36169621d742898d95cb2a4922f0a81e1fcfc81d755f337f176ddc2a2ed9281e0f2c1648bb6b08b09d4fd523d203d1238e62344c0385 + checksum: 10c0/7586104e3fd31dbe1e6ecaafb9a70131e4197dce2940f727b6a84131eee3decac7b10f9c7c72fa5edbdb68b6f854353bd4c0fa84779e274207fb7379563b10db languageName: node linkType: hard -"@vitest/mocker@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/mocker@npm:3.2.1" +"@vitest/mocker@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/mocker@npm:3.2.4" dependencies: - "@vitest/spy": "npm:3.2.1" + "@vitest/spy": "npm:3.2.4" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.17" peerDependencies: @@ -4814,81 +4814,55 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/bcd8865e8e8f45fdf59bb817b788bebe13c509e0220eee723bc6b8ee139352b30e074e674e8f9092ae75db0a66c1ca3887ee078df27ea2d5d7889c9d45cfb675 + checksum: 10c0/f7a4aea19bbbf8f15905847ee9143b6298b2c110f8b64789224cb0ffdc2e96f9802876aa2ca83f1ec1b6e1ff45e822abb34f0054c24d57b29ab18add06536ccd languageName: node linkType: hard -"@vitest/pretty-format@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/pretty-format@npm:3.0.9" +"@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/pretty-format@npm:3.2.4" dependencies: tinyrainbow: "npm:^2.0.0" - checksum: 10c0/56ae7b1f14df2905b3205d4e121727631c4938ec44f76c1e9fa49923919010378f0dad70b1d277672f3ef45ddf6372140c8d1da95e45df8282f70b74328fce47 + checksum: 10c0/5ad7d4278e067390d7d633e307fee8103958806a419ca380aec0e33fae71b44a64415f7a9b4bc11635d3c13d4a9186111c581d3cef9c65cc317e68f077456887 languageName: node linkType: hard -"@vitest/pretty-format@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/pretty-format@npm:3.2.1" +"@vitest/runner@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/runner@npm:3.2.4" dependencies: - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/24c9d380900d0e2c2296f7a0a86b9efdd02034f1b84a93c0fc01a17ff6aa3b7e80d6bc4fe07e8e78404e6b66d1b8dc5a7d4199e7ed4f89f1874c3f74b731e48c - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:3.2.2, @vitest/pretty-format@npm:^3.2.1": - version: 3.2.2 - resolution: "@vitest/pretty-format@npm:3.2.2" - dependencies: - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/bc74488e6d56b1f86a0066cc7ea53bdd9c062886bb060c37cd4c312d674c1bc3ba9d1350b748a07d2ae9558223f817735edf920496cafadf98a214b683ea0d0e - languageName: node - linkType: hard - -"@vitest/runner@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/runner@npm:3.2.1" - dependencies: - "@vitest/utils": "npm:3.2.1" + "@vitest/utils": "npm:3.2.4" pathe: "npm:^2.0.3" - checksum: 10c0/b0c4b75627852c56a67aef10176880def0e6e785e96f6e7a1ad632d799e202528de62cab6c8c0c8e1d2afc8255c40225f2cb8ab6fa99925db8c8aca28b6bba3c + strip-literal: "npm:^3.0.0" + checksum: 10c0/e8be51666c72b3668ae3ea348b0196656a4a5adb836cb5e270720885d9517421815b0d6c98bfdf1795ed02b994b7bfb2b21566ee356a40021f5bf4f6ed4e418a languageName: node linkType: hard -"@vitest/snapshot@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/snapshot@npm:3.2.1" +"@vitest/snapshot@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/snapshot@npm:3.2.4" dependencies: - "@vitest/pretty-format": "npm:3.2.1" + "@vitest/pretty-format": "npm:3.2.4" magic-string: "npm:^0.30.17" pathe: "npm:^2.0.3" - checksum: 10c0/7428cfe239c40a146a5e6c73fdefa9167496524aeefd01db28f55dea945ec14f00c982ff4ccf47208e870b020e0edd0f17416cd8db9ae80d2332fb925d4bac94 + checksum: 10c0/f8301a3d7d1559fd3d59ed51176dd52e1ed5c2d23aa6d8d6aa18787ef46e295056bc726a021698d8454c16ed825ecba163362f42fa90258bb4a98cfd2c9424fc languageName: node linkType: hard -"@vitest/spy@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/spy@npm:3.0.9" - dependencies: - tinyspy: "npm:^3.0.2" - checksum: 10c0/993085dbaf9e651ca9516f88e440424d29279def998186628a1ebcab5558a3045fee8562630608f58303507135f6f3bf9970f65639f3b9baa8bf86cab3eb4742 - languageName: node - linkType: hard - -"@vitest/spy@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/spy@npm:3.2.1" +"@vitest/spy@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/spy@npm:3.2.4" dependencies: tinyspy: "npm:^4.0.3" - checksum: 10c0/5b6e36c5e21cb8ed4b5f8e95c24379846168a719125cc189e53ccd9717bae8a6a63e16a04a57fb8736b6523b1b870df691a27a04e86c487f388d66af669672ca + checksum: 10c0/6ebf0b4697dc238476d6b6a60c76ba9eb1dd8167a307e30f08f64149612fd50227682b876420e4c2e09a76334e73f72e3ebf0e350714dc22474258292e202024 languageName: node linkType: hard -"@vitest/ui@npm:^3.2.1": - version: 3.2.2 - resolution: "@vitest/ui@npm:3.2.2" +"@vitest/ui@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/ui@npm:3.2.4" dependencies: - "@vitest/utils": "npm:3.2.2" + "@vitest/utils": "npm:3.2.4" fflate: "npm:^0.8.2" flatted: "npm:^3.3.3" pathe: "npm:^2.0.3" @@ -4896,41 +4870,19 @@ __metadata: tinyglobby: "npm:^0.2.14" tinyrainbow: "npm:^2.0.0" peerDependencies: - vitest: 3.2.2 - checksum: 10c0/343ccf49f4ef449d7bde80eb2f815e6548e5fab25bba1923d9fc8483fae9469b477782fe1eaee61d613aca2eef2718ca880d8e6d2135c6f4b09fe653921bf119 + vitest: 3.2.4 + checksum: 10c0/c3de1b757905d050706c7ab0199185dd8c7e115f2f348b8d5a7468528c6bf90c2c46096e8901602349ac04f5ba83ac23cd98c38827b104d5151cf8ba21739a0c languageName: node linkType: hard -"@vitest/utils@npm:3.0.9": - version: 3.0.9 - resolution: "@vitest/utils@npm:3.0.9" +"@vitest/utils@npm:3.2.4": + version: 3.2.4 + resolution: "@vitest/utils@npm:3.2.4" dependencies: - "@vitest/pretty-format": "npm:3.0.9" - loupe: "npm:^3.1.3" + "@vitest/pretty-format": "npm:3.2.4" + loupe: "npm:^3.1.4" tinyrainbow: "npm:^2.0.0" - checksum: 10c0/b966dfb3b926ee9bea59c1fb297abc67adaa23a8a582453ee81167b238446394693617a5e0523eb2791d6983173ef1c07bf28a76bd5a63b49a100610ed6b6a6c - languageName: node - linkType: hard - -"@vitest/utils@npm:3.2.1": - version: 3.2.1 - resolution: "@vitest/utils@npm:3.2.1" - dependencies: - "@vitest/pretty-format": "npm:3.2.1" - loupe: "npm:^3.1.3" - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/a1fbdf1f16f7df2aabda9a96516481f5ef52eff38b69cbf3d11725fb30351dd1c3d480678c040cf25d4a01238f8f8d5650b554c5790078f8770f54acbc54411a - languageName: node - linkType: hard - -"@vitest/utils@npm:3.2.2": - version: 3.2.2 - resolution: "@vitest/utils@npm:3.2.2" - dependencies: - "@vitest/pretty-format": "npm:3.2.2" - loupe: "npm:^3.1.3" - tinyrainbow: "npm:^2.0.0" - checksum: 10c0/0274a1f060006616a8dad7ef11fb13d81ca00df8104eb015d4832cc6fc2217c37d0eec083afa2eb5118a8879942c6e055aec7f23325ce8f791b3b4b2c7fa16c3 + checksum: 10c0/024a9b8c8bcc12cf40183c246c244b52ecff861c6deb3477cbf487ac8781ad44c68a9c5fd69f8c1361878e55b97c10d99d511f2597f1f7244b5e5101d028ba64 languageName: node linkType: hard @@ -5393,39 +5345,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.4.10": - version: 0.4.13 - resolution: "babel-plugin-polyfill-corejs2@npm:0.4.13" +"babel-plugin-polyfill-corejs2@npm:^0.4.14": + version: 0.4.14 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.14" dependencies: - "@babel/compat-data": "npm:^7.22.6" - "@babel/helper-define-polyfill-provider": "npm:^0.6.4" + "@babel/compat-data": "npm:^7.27.7" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/b4a54561606d388e6f9499f39f03171af4be7f9ce2355e737135e40afa7086cf6790fdd706c2e59f488c8fa1f76123d28783708e07ddc84647dca8ed8fb98e06 + checksum: 10c0/d74cba0600a6508e86d220bde7164eb528755d91be58020e5ea92ea7fbb12c9d8d2c29246525485adfe7f68ae02618ec428f9a589cac6cbedf53cc3972ad7fbe languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.11.0": - version: 0.11.1 - resolution: "babel-plugin-polyfill-corejs3@npm:0.11.1" +"babel-plugin-polyfill-corejs3@npm:^0.13.0": + version: 0.13.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.3" - core-js-compat: "npm:^3.40.0" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" + core-js-compat: "npm:^3.43.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/025f754b6296d84b20200aff63a3c1acdd85e8c621781f2bd27fe2512d0060526192d02329326947c6b29c27cf475fbcfaaff8c51eab1d2bfc7b79086bb64229 + checksum: 10c0/5d8e228da425edc040d8c868486fd01ba10b0440f841156a30d9f8986f330f723e2ee61553c180929519563ef5b64acce2caac36a5a847f095d708dda5d8206d languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.6.1": - version: 0.6.4 - resolution: "babel-plugin-polyfill-regenerator@npm:0.6.4" +"babel-plugin-polyfill-regenerator@npm:^0.6.5": + version: 0.6.5 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.5" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.4" + "@babel/helper-define-polyfill-provider": "npm:^0.6.5" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10c0/ebaaf9e4e53201c02f496d3f686d815e94177b3e55b35f11223b99c60d197a29f907a2e87bbcccced8b7aff22a807fccc1adaf04722864a8e1862c8845ab830a + checksum: 10c0/63aa8ed716df6a9277c6ab42b887858fa9f57a70cc1d0ae2b91bdf081e45d4502848cba306fb60b02f59f99b32fd02ff4753b373cac48ccdac9b7d19dd56f06d languageName: node linkType: hard @@ -5555,17 +5507,17 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.24.0, browserslist@npm:^4.24.4, browserslist@npm:^4.25.0": - version: 4.25.0 - resolution: "browserslist@npm:4.25.0" +"browserslist@npm:^4.24.0, browserslist@npm:^4.24.4, browserslist@npm:^4.25.0, browserslist@npm:^4.25.1": + version: 4.25.1 + resolution: "browserslist@npm:4.25.1" dependencies: - caniuse-lite: "npm:^1.0.30001718" - electron-to-chromium: "npm:^1.5.160" + caniuse-lite: "npm:^1.0.30001726" + electron-to-chromium: "npm:^1.5.173" node-releases: "npm:^2.0.19" update-browserslist-db: "npm:^1.1.3" bin: browserslist: cli.js - checksum: 10c0/cc16c55b4468b18684a0e1ca303592b38635b1155d6724f172407192737a2f405b8030d87a05813729592793445b3d15e737b0055f901cdecccb29b1e580a1c5 + checksum: 10c0/acba5f0bdbd5e72dafae1e6ec79235b7bad305ed104e082ed07c34c38c7cb8ea1bc0f6be1496958c40482e40166084458fc3aee15111f15faa79212ad9081b2a languageName: node linkType: hard @@ -5686,10 +5638,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001718": - version: 1.0.30001721 - resolution: "caniuse-lite@npm:1.0.30001721" - checksum: 10c0/fa3a8926899824b385279f1f886fe34c5efb1321c9ece1b9df25c8d567a2706db8450cc5b4d969e769e641593e08ea644909324aba93636a43e4949a75f81c4c +"caniuse-lite@npm:^1.0.30001702, caniuse-lite@npm:^1.0.30001726": + version: 1.0.30001731 + resolution: "caniuse-lite@npm:1.0.30001731" + checksum: 10c0/d8cddf817d5bec8e7c2106affdbf1bfc3923463ca16697c992b2efeb043e6a5d9dcb70cda913bc6acf9112fd66f9e80279316c08e7800359116925066a63fdfa languageName: node linkType: hard @@ -5716,7 +5668,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -5782,9 +5734,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^13.0.0": - version: 13.0.0 - resolution: "chromatic@npm:13.0.0" +"chromatic@npm:^13.1.3": + version: 13.1.3 + resolution: "chromatic@npm:13.1.3" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -5797,7 +5749,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/30c697eb84d5b3b8cdab989df0e4fed0bf51f4bfefb616873f68fc00337978b9b38b84e52af22861769176181bd98525d467baeb22daa712a0f7a58bd61bf336 + checksum: 10c0/5fa2d381e06d1b089ecb790247844cfb510b063c4d8f8c0d2a3d0620ff94864003158e34338246bb1d07504d554e73dc8d5b639dc3e176ce3c88816fdc853285 languageName: node linkType: hard @@ -5998,12 +5950,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.40.0": - version: 3.41.0 - resolution: "core-js-compat@npm:3.41.0" +"core-js-compat@npm:^3.43.0": + version: 3.44.0 + resolution: "core-js-compat@npm:3.44.0" dependencies: - browserslist: "npm:^4.24.4" - checksum: 10c0/92d2c748d3dd1c4e3b6cee6b6683b9212db9bc0a6574d933781210daf3baaeb76334ed4636eb8935b45802aa8d9235ab604c9a262694e02a2fa17ad0f6976829 + browserslist: "npm:^4.25.1" + checksum: 10c0/5de4b042b8bb232b8390be3079030de5c7354610f136ed3eb91310a44455a78df02cfcf49b2fd05d5a5aa2695460620abf1b400784715f7482ed4770d40a68b2 languageName: node linkType: hard @@ -6014,7 +5966,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.30.2, core-js@npm:^3.41.0": +"core-js@npm:^3.30.2, core-js@npm:^3.45.0": version: 3.45.0 resolution: "core-js@npm:3.45.0" checksum: 10c0/118350f9f1d81f42a1276590d6c217dca04c789fdb8074c82e53056b1a784948769a62b16b98493fd73e8a988545432f302bca798571e56ad881b9c039a5a83c @@ -6516,10 +6468,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.5.160": - version: 1.5.165 - resolution: "electron-to-chromium@npm:1.5.165" - checksum: 10c0/20b91e67e7a8829a358c4a488e9b59b0e5f8d4cb075a70b9757bb21acf0fc751ca58ca7d9c6018bec74ac4bd42f7859e4ef37421c252a2275f642e12a32271d6 +"electron-to-chromium@npm:^1.5.173": + version: 1.5.192 + resolution: "electron-to-chromium@npm:1.5.192" + checksum: 10c0/7993350fdd3c12d9667a42370ce3202bf3012fd6fed13ac1393eeb3fdda51347e805f340ae06939192f37b00a3d0856034b69b1bf6696ba96848fd42267a6f8b languageName: node linkType: hard @@ -7441,15 +7393,15 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.4.4": - version: 6.4.4 - resolution: "fdir@npm:6.4.4" +"fdir@npm:^6.4.4, fdir@npm:^6.4.6": + version: 6.4.6 + resolution: "fdir@npm:6.4.6" peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - checksum: 10c0/6ccc33be16945ee7bc841e1b4178c0b4cf18d3804894cb482aa514651c962a162f96da7ffc6ebfaf0df311689fb70091b04dd6caffe28d56b9ebdc0e7ccadfdd + checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9 languageName: node linkType: hard @@ -7528,6 +7480,17 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^7.0.0": + version: 7.0.0 + resolution: "find-up@npm:7.0.0" + dependencies: + locate-path: "npm:^7.2.0" + path-exists: "npm:^5.0.0" + unicorn-magic: "npm:^0.1.0" + checksum: 10c0/e6ee3e6154560bc0ab3bc3b7d1348b31513f9bdf49a5dd2e952495427d559fa48cdf33953e85a309a323898b43fa1bfbc8b80c880dfc16068384783034030008 + languageName: node + linkType: hard + "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -7884,13 +7847,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 - languageName: node - linkType: hard - "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -9159,6 +9115,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^7.2.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: "npm:^6.0.0" + checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -9232,10 +9197,10 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^3.1.0, loupe@npm:^3.1.3": - version: 3.1.3 - resolution: "loupe@npm:3.1.3" - checksum: 10c0/f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 +"loupe@npm:^3.1.0, loupe@npm:^3.1.4": + version: 3.2.0 + resolution: "loupe@npm:3.2.0" + checksum: 10c0/f572fd9e38db8d36ae9eede305480686e310d69bc40394b6842838ebc6c3860a0e35ab30182f33606ab2d8a685d9ff6436649269f8218a1c3385ca329973cb2c languageName: node linkType: hard @@ -9669,12 +9634,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.8": - version: 3.3.8 - resolution: "nanoid@npm:3.3.8" +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" bin: nanoid: bin/nanoid.cjs - checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b languageName: node linkType: hard @@ -9981,6 +9946,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad + languageName: node + linkType: hard + "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -9990,6 +9964,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: "npm:^4.0.0" + checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -10080,6 +10063,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -10236,17 +10226,17 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.1.1": +"picocolors@npm:1.1.1, picocolors@npm:^1.1.1": version: 1.1.1 resolution: "picocolors@npm:1.1.1" checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 languageName: node linkType: hard -"picomatch@npm:2 || 3 || 4, picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc +"picomatch@npm:2 || 3 || 4, picomatch@npm:^4.0.2, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 languageName: node linkType: hard @@ -10782,14 +10772,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.3": - version: 8.5.3 - resolution: "postcss@npm:8.5.3" +"postcss@npm:^8.5.3, postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" dependencies: - nanoid: "npm:^3.3.8" + nanoid: "npm:^3.3.11" picocolors: "npm:^1.1.1" source-map-js: "npm:^1.2.1" - checksum: 10c0/b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3 + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 languageName: node linkType: hard @@ -11714,7 +11704,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:^1.19.0, resolve@npm:^1.22.1, resolve@npm:^1.22.10, resolve@npm:^1.22.4, resolve@npm:^1.22.8": version: 1.22.10 resolution: "resolve@npm:1.22.10" dependencies: @@ -11740,7 +11730,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.10#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": version: 1.22.10 resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" dependencies: @@ -11806,7 +11796,7 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:^6.0.0": +"rollup-plugin-visualizer@npm:^6.0.3": version: 6.0.3 resolution: "rollup-plugin-visualizer@npm:6.0.3" dependencies: @@ -11842,31 +11832,31 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.34.9": - version: 4.40.2 - resolution: "rollup@npm:4.40.2" +"rollup@npm:^4.43.0": + version: 4.46.2 + resolution: "rollup@npm:4.46.2" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.40.2" - "@rollup/rollup-android-arm64": "npm:4.40.2" - "@rollup/rollup-darwin-arm64": "npm:4.40.2" - "@rollup/rollup-darwin-x64": "npm:4.40.2" - "@rollup/rollup-freebsd-arm64": "npm:4.40.2" - "@rollup/rollup-freebsd-x64": "npm:4.40.2" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.40.2" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.40.2" - "@rollup/rollup-linux-arm64-gnu": "npm:4.40.2" - "@rollup/rollup-linux-arm64-musl": "npm:4.40.2" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.40.2" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.40.2" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.40.2" - "@rollup/rollup-linux-riscv64-musl": "npm:4.40.2" - "@rollup/rollup-linux-s390x-gnu": "npm:4.40.2" - "@rollup/rollup-linux-x64-gnu": "npm:4.40.2" - "@rollup/rollup-linux-x64-musl": "npm:4.40.2" - "@rollup/rollup-win32-arm64-msvc": "npm:4.40.2" - "@rollup/rollup-win32-ia32-msvc": "npm:4.40.2" - "@rollup/rollup-win32-x64-msvc": "npm:4.40.2" - "@types/estree": "npm:1.0.7" + "@rollup/rollup-android-arm-eabi": "npm:4.46.2" + "@rollup/rollup-android-arm64": "npm:4.46.2" + "@rollup/rollup-darwin-arm64": "npm:4.46.2" + "@rollup/rollup-darwin-x64": "npm:4.46.2" + "@rollup/rollup-freebsd-arm64": "npm:4.46.2" + "@rollup/rollup-freebsd-x64": "npm:4.46.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.46.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.46.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.46.2" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-riscv64-musl": "npm:4.46.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.46.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.46.2" + "@rollup/rollup-linux-x64-musl": "npm:4.46.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.46.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.46.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.46.2" + "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: "@rollup/rollup-android-arm-eabi": @@ -11891,7 +11881,7 @@ __metadata: optional: true "@rollup/rollup-linux-loongarch64-gnu": optional: true - "@rollup/rollup-linux-powerpc64le-gnu": + "@rollup/rollup-linux-ppc64-gnu": optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true @@ -11913,7 +11903,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/cbe9b766891da74fbf7c3b50420bb75102e5c59afc0ea45751f7e43a581d2cd93367763f521f820b72e341cf1f6b9951fbdcd3be67a1b0aa774b754525a8b9c7 + checksum: 10c0/f428497fe119fe7c4e34f1020d45ba13e99b94c9aa36958d88823d932b155c9df3d84f53166f3ee913ff68ea6c7599a9ab34861d88562ad9d8420f64ca5dad4c languageName: node linkType: hard @@ -12536,15 +12526,16 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^9.0.4": - version: 9.0.4 - resolution: "storybook@npm:9.0.4" +"storybook@npm:^9.1.1": + version: 9.1.1 + resolution: "storybook@npm:9.1.1" dependencies: "@storybook/global": "npm:^5.0.0" "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/user-event": "npm:^14.6.1" - "@vitest/expect": "npm:3.0.9" - "@vitest/spy": "npm:3.0.9" + "@vitest/expect": "npm:3.2.4" + "@vitest/mocker": "npm:3.2.4" + "@vitest/spy": "npm:3.2.4" better-opn: "npm:^3.0.2" 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" esbuild-register: "npm:^3.5.0" @@ -12558,7 +12549,7 @@ __metadata: optional: true bin: storybook: ./bin/index.cjs - checksum: 10c0/52aa44ac9ba73bf3ab2718669faa02c515ef91243f7d39bcac815efc1b72ac2f2c3b1ea4586ec37beb8f6044c8c5b1c9e2d46405a8832974f3cee37f4b3c3821 + checksum: 10c0/efd2665547dc6bfd4cabd00ee1a6368c7cec27a3e4c16bc875ae06bb5d9ee74120c11102699165530f31fe8becb47212d80632b1fefcafa60fef94b3cd0bf50f languageName: node linkType: hard @@ -12766,6 +12757,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-literal@npm:3.0.0" + dependencies: + js-tokens: "npm:^9.0.1" + checksum: 10c0/d81657f84aba42d4bbaf2a677f7e7f34c1f3de5a6726db8bc1797f9c0b303ba54d4660383a74bde43df401cf37cce1dff2c842c55b077a4ceee11f9e31fba828 + 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" @@ -13113,7 +13113,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.14": +"tinyglobby@npm:^0.2.10, tinyglobby@npm:^0.2.14": version: 0.2.14 resolution: "tinyglobby@npm:0.2.14" dependencies: @@ -13123,10 +13123,10 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.1.0": - version: 1.1.0 - resolution: "tinypool@npm:1.1.0" - checksum: 10c0/deb6bde5e3d85d4ba043806c66f43fb5b649716312a47b52761a83668ffc71cd0ea4e24254c1b02a3702e5c27e02605f0189a1460f6284a5930a08bd0c06435c +"tinypool@npm:^1.1.1": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b languageName: node linkType: hard @@ -13137,13 +13137,6 @@ __metadata: languageName: node linkType: hard -"tinyspy@npm:^3.0.2": - version: 3.0.2 - resolution: "tinyspy@npm:3.0.2" - checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 - languageName: node - linkType: hard - "tinyspy@npm:^4.0.3": version: 4.0.3 resolution: "tinyspy@npm:4.0.3" @@ -13545,6 +13538,13 @@ __metadata: languageName: node linkType: hard +"unicorn-magic@npm:^0.1.0": + version: 0.1.0 + resolution: "unicorn-magic@npm:0.1.0" + checksum: 10c0/e4ed0de05b0a05e735c7d8a2930881e5efcfc3ec897204d5d33e7e6247f4c31eac92e383a15d9a6bccb7319b4271ee4bea946e211bf14951fec6ff2cbbb66a92 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -13810,9 +13810,9 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:3.2.1": - version: 3.2.1 - resolution: "vite-node@npm:3.2.1" +"vite-node@npm:3.2.4": + version: 3.2.4 + resolution: "vite-node@npm:3.2.4" dependencies: cac: "npm:^6.7.14" debug: "npm:^4.4.1" @@ -13821,7 +13821,7 @@ __metadata: vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" bin: vite-node: vite-node.mjs - checksum: 10c0/e196bc4660baed4f18530b43ce896017adbe480c329f03ac72d2237788ddeaca50904e9e9a4fb8e65300d088ff2737227a00c0e3bae697067acebcd8f08f7faa + checksum: 10c0/6ceca67c002f8ef6397d58b9539f80f2b5d79e103a18367288b3f00a8ab55affa3d711d86d9112fce5a7fa658a212a087a005a045eb8f4758947dd99af2a6c6b languageName: node linkType: hard @@ -13832,7 +13832,7 @@ __metadata: languageName: node linkType: hard -"vite-plugin-pwa@npm:^1.0.0": +"vite-plugin-pwa@npm:^1.0.2": version: 1.0.2 resolution: "vite-plugin-pwa@npm:1.0.2" dependencies: @@ -13853,7 +13853,7 @@ __metadata: languageName: node linkType: hard -"vite-plugin-static-copy@npm:^3.1.0": +"vite-plugin-static-copy@npm:^3.1.1": version: 3.1.1 resolution: "vite-plugin-static-copy@npm:3.1.1" dependencies: @@ -13897,26 +13897,26 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.3.5": - version: 6.3.5 - resolution: "vite@npm:6.3.5" +"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.1.1": + version: 7.1.1 + resolution: "vite@npm:7.1.1" dependencies: esbuild: "npm:^0.25.0" - fdir: "npm:^6.4.4" + fdir: "npm:^6.4.6" fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.2" - postcss: "npm:^8.5.3" - rollup: "npm:^4.34.9" - tinyglobby: "npm:^0.2.13" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.14" peerDependencies: - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@types/node": ^20.19.0 || >=22.12.0 jiti: ">=1.21.0" - less: "*" + less: ^4.0.0 lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 @@ -13948,22 +13948,22 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/df70201659085133abffc6b88dcdb8a57ef35f742a01311fc56a4cfcda6a404202860729cc65a2c401a724f6e25f9ab40ce4339ed4946f550541531ced6fe41c + checksum: 10c0/391a5c8b8f287b7b1653dedbe952fb4cb93bf7c99b19dab915cf63497892427198fef637e943a3391eacfecf7f2e8f55c40d0fa065fabdd885641430d0b74af7 languageName: node linkType: hard -"vitest@npm:^3.2.1": - version: 3.2.1 - resolution: "vitest@npm:3.2.1" +"vitest@npm:^3.2.4": + version: 3.2.4 + resolution: "vitest@npm:3.2.4" dependencies: "@types/chai": "npm:^5.2.2" - "@vitest/expect": "npm:3.2.1" - "@vitest/mocker": "npm:3.2.1" - "@vitest/pretty-format": "npm:^3.2.1" - "@vitest/runner": "npm:3.2.1" - "@vitest/snapshot": "npm:3.2.1" - "@vitest/spy": "npm:3.2.1" - "@vitest/utils": "npm:3.2.1" + "@vitest/expect": "npm:3.2.4" + "@vitest/mocker": "npm:3.2.4" + "@vitest/pretty-format": "npm:^3.2.4" + "@vitest/runner": "npm:3.2.4" + "@vitest/snapshot": "npm:3.2.4" + "@vitest/spy": "npm:3.2.4" + "@vitest/utils": "npm:3.2.4" chai: "npm:^5.2.0" debug: "npm:^4.4.1" expect-type: "npm:^1.2.1" @@ -13974,17 +13974,17 @@ __metadata: tinybench: "npm:^2.9.0" tinyexec: "npm:^0.3.2" tinyglobby: "npm:^0.2.14" - tinypool: "npm:^1.1.0" + tinypool: "npm:^1.1.1" tinyrainbow: "npm:^2.0.0" vite: "npm:^5.0.0 || ^6.0.0 || ^7.0.0-0" - vite-node: "npm:3.2.1" + vite-node: "npm:3.2.4" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@types/debug": ^4.1.12 "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.2.1 - "@vitest/ui": 3.2.1 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -14004,7 +14004,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/1f4128f93fff708fa5bd7d1547a0877c4266466f0f91f5e1dd5d7f09267a0c171cf87c83acd86ebd53e561aa2bcef1311e984b8205370c7f596e6ff5a9c8cd6b + checksum: 10c0/5bf53ede3ae6a0e08956d72dab279ae90503f6b5a05298a6a5e6ef47d2fd1ab386aaf48fafa61ed07a0ebfe9e371772f1ccbe5c258dd765206a8218bf2eb79eb languageName: node linkType: hard @@ -14310,7 +14310,7 @@ __metadata: languageName: node linkType: hard -"workbox-expiration@npm:7.3.0, workbox-expiration@npm:^7.0.0": +"workbox-expiration@npm:7.3.0, workbox-expiration@npm:^7.3.0": version: 7.3.0 resolution: "workbox-expiration@npm:7.3.0" dependencies: @@ -14375,7 +14375,7 @@ __metadata: languageName: node linkType: hard -"workbox-routing@npm:7.3.0, workbox-routing@npm:^7.0.0": +"workbox-routing@npm:7.3.0, workbox-routing@npm:^7.3.0": version: 7.3.0 resolution: "workbox-routing@npm:7.3.0" dependencies: @@ -14384,7 +14384,7 @@ __metadata: languageName: node linkType: hard -"workbox-strategies@npm:7.3.0, workbox-strategies@npm:^7.0.0": +"workbox-strategies@npm:7.3.0, workbox-strategies@npm:^7.3.0": version: 7.3.0 resolution: "workbox-strategies@npm:7.3.0" dependencies: @@ -14410,7 +14410,7 @@ __metadata: languageName: node linkType: hard -"workbox-window@npm:7.3.0, workbox-window@npm:^7.0.0, workbox-window@npm:^7.3.0": +"workbox-window@npm:7.3.0, workbox-window@npm:^7.3.0": version: 7.3.0 resolution: "workbox-window@npm:7.3.0" dependencies: @@ -14583,6 +14583,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.2.1 + resolution: "yocto-queue@npm:1.2.1" + checksum: 10c0/5762caa3d0b421f4bdb7a1926b2ae2189fc6e4a14469258f183600028eb16db3e9e0306f46e8ebf5a52ff4b81a881f22637afefbef5399d6ad440824e9b27f9f + languageName: node + linkType: hard + "yoctocolors-cjs@npm:^2.1.2": version: 2.1.2 resolution: "yoctocolors-cjs@npm:2.1.2" From 61f0ce654f5e0a3f5b61e5dd350d41e64d5582e0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 12 Aug 2025 03:29:02 -0400 Subject: [PATCH 261/660] Update `rubocop-rails` to version 2.33.0 (#35741) --- Gemfile.lock | 2 +- app/controllers/auth/registrations_controller.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 111837de739..db58971b6e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -789,7 +789,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.32.0) + rubocop-rails (2.33.0) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 3b42dc48ba9..fc430544fbe 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -23,11 +23,11 @@ class Auth::RegistrationsController < Devise::RegistrationsController super(&:build_invite_request) end - def edit # rubocop:disable Lint/UselessMethodDefinition + def edit super end - def create # rubocop:disable Lint/UselessMethodDefinition + def create super end From ca3d67e88d3dc46eb59122dc7c24dba3194fa279 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 07:40:37 +0000 Subject: [PATCH 262/660] New Crowdin Translations (automated) (#35745) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/pl.json | 14 +++++++ app/javascript/mastodon/locales/ru.json | 8 ++++ config/locales/pl.yml | 8 ++++ config/locales/ru.yml | 49 +++++++++++++++++++++++++ config/locales/simple_form.pl.yml | 15 ++++++++ config/locales/simple_form.ru.yml | 5 +++ 6 files changed, 99 insertions(+) diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 58c3f1fa574..26417dc0eb8 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -224,6 +224,7 @@ "confirmations.discard_draft.edit.message": "Kontynuowanie spowoduje utratę wszystkich zmian wprowadzonych przez Ciebie w aktualnie edytowanym poście.", "confirmations.discard_draft.edit.title": "Odrzucić zmiany w poście?", "confirmations.discard_draft.post.cancel": "Wznów wersję roboczą", + "confirmations.discard_draft.post.message": "Kontynuacja odrzuci aktualnie tworzony post.", "confirmations.discard_draft.post.title": "Anulować wersję roboczą?", "confirmations.discard_edit_media.confirm": "Odrzuć", "confirmations.discard_edit_media.message": "Masz niezapisane zmiany w opisie lub podglądzie, odrzucić je mimo to?", @@ -244,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Usuń obserwującego", "confirmations.remove_from_followers.message": "{name} przestanie Cię obserwować. Czy na pewno chcesz kontynuować?", "confirmations.remove_from_followers.title": "Usunąć obserwującego?", + "confirmations.revoke_quote.confirm": "Usuń post", + "confirmations.revoke_quote.message": "Tej akcji nie można cofnąć.", + "confirmations.revoke_quote.title": "Usuń post?", "confirmations.unfollow.confirm": "Nie obserwuj", "confirmations.unfollow.message": "Czy na pewno nie chcesz obserwować {name}?", "confirmations.unfollow.title": "Cofnąć obserwację?", @@ -305,6 +309,9 @@ "emoji_button.search_results": "Wyniki wyszukiwania", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Podróże i miejsca", + "empty_column.account_featured.me": "Niczego jeszcze nie poleciłeś. Czy wiesz, że możesz wyświetlać swoje hashtagi, z których korzystasz najbardziej, a nawet konta znajomego na swoim profilu?", + "empty_column.account_featured.other": "{acct} nie wyróżnił jeszcze nic. Czy wiesz, że możesz wyświetlać swoje hashtagi, z których korzystasz najbardziej, a nawet konta znajomego na swoim profilu?", + "empty_column.account_featured_other.unknown": "To konto nie zostało jeszcze wyróżnione.", "empty_column.account_hides_collections": "Ta osoba postanowiła nie udostępniać tych informacji", "empty_column.account_suspended": "Konto zawieszone", "empty_column.account_timeline": "Brak wpisów!", @@ -560,6 +567,8 @@ "navigation_bar.follows_and_followers": "Obserwowani i obserwujący", "navigation_bar.import_export": "Import i eksport", "navigation_bar.lists": "Listy", + "navigation_bar.live_feed_local": "Kanał na żywo (lokalny)", + "navigation_bar.live_feed_public": "Kanał na żywo (publiczny)", "navigation_bar.logout": "Wyloguj", "navigation_bar.moderation": "Moderacja", "navigation_bar.more": "Więcej", @@ -594,6 +603,7 @@ "notification.label.mention": "Wzmianka", "notification.label.private_mention": "Wzmianka bezpośrednia", "notification.label.private_reply": "Komentarz bezpośredni", + "notification.label.quote": "{name} cytował twój post", "notification.label.reply": "Komentarz", "notification.mention": "Wzmianka", "notification.mentioned_you": "{name} wzmiankuje cię", @@ -651,6 +661,7 @@ "notifications.column_settings.mention": "Wzmianki:", "notifications.column_settings.poll": "Wyniki ankiety:", "notifications.column_settings.push": "Powiadomienia push", + "notifications.column_settings.quote": "Cytaty:", "notifications.column_settings.reblog": "Podbicia:", "notifications.column_settings.show": "Pokaż w kolumnie", "notifications.column_settings.sound": "Odtwarzaj dźwięk", @@ -871,6 +882,8 @@ "status.quote_error.filtered": "Ukryte z powodu jednego z Twoich filtrów", "status.quote_error.not_available": "Post niedostępny", "status.quote_error.pending_approval": "Post oczekujący", + "status.quote_error.pending_approval_popout.body": "Oferty współdzielone przez Fediverse mogą wymagać czasu, ponieważ różne serwery mają różne protokoły.", + "status.quote_error.pending_approval_popout.title": "Oczekujący cytat? Spokojnie, bądź cierpliwy :)", "status.quote_post_author": "Zacytowano post @{name}", "status.read_more": "Czytaj dalej", "status.reblog": "Podbij", @@ -886,6 +899,7 @@ "status.reply": "Odpowiedz", "status.replyAll": "Odpowiedz na wątek", "status.report": "Zgłoś @{name}", + "status.revoke_quote": "Usuń mój wpis z postu @{name}", "status.sensitive_warning": "Wrażliwa zawartość", "status.share": "Udostępnij", "status.show_less_all": "Zwiń wszystkie", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index ada27cea5fd..219149239a6 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Убрать подписчика", "confirmations.remove_from_followers.message": "Пользователь {name} перестанет быть подписан на вас. Продолжить?", "confirmations.remove_from_followers.title": "Убрать подписчика?", + "confirmations.revoke_quote.confirm": "Убрать пост", + "confirmations.revoke_quote.message": "Это действие невозможно отменить.", + "confirmations.revoke_quote.title": "Убрать пост?", "confirmations.unfollow.confirm": "Отписаться", "confirmations.unfollow.message": "Вы уверены, что хотите отписаться от {name}?", "confirmations.unfollow.title": "Отписаться?", @@ -498,6 +501,8 @@ "keyboard_shortcuts.translate": "перевести пост", "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска", "keyboard_shortcuts.up": "вверх по списку", + "learn_more_link.got_it": "Понятно", + "learn_more_link.learn_more": "Узнать больше", "lightbox.close": "Закрыть", "lightbox.next": "Далее", "lightbox.previous": "Назад", @@ -845,6 +850,8 @@ "status.bookmark": "Добавить в закладки", "status.cancel_reblog_private": "Отменить продвижение", "status.cannot_reblog": "Этот пост не может быть продвинут", + "status.context.load_new_replies": "Доступны новые ответы", + "status.context.loading": "Проверяем, есть ли еще ответы", "status.continued_thread": "Продолжение предыдущего поста", "status.copy": "Скопировать ссылку на пост", "status.delete": "Удалить", @@ -871,6 +878,7 @@ "status.open": "Открыть пост", "status.pin": "Закрепить в профиле", "status.quote_error.filtered": "Скрыто одним из ваших фильтров", + "status.quote_error.not_available": "Пост недоступен", "status.read_more": "Читать далее", "status.reblog": "Продвинуть", "status.reblog_private": "Продвинуть для своей аудитории", diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 29de53637cd..32ea9f26c3b 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -599,6 +599,7 @@ pl: limited: Ograniczone title: Moderacja moderation_notes: + create: Dodaj notatkę moderacyjną title: Notatki moderacyjne private_comment: Prywatny komentarz public_comment: Publiczny komentarz @@ -1109,6 +1110,7 @@ pl: trending: Popularne username_blocks: add_new: Dodaj nową + block_registrations: Zablokuj rejestracje comparison: contains: Zawiera equals: Równa się @@ -1742,6 +1744,9 @@ pl: title: Nowe wspomnienie o Tobie poll: subject: Ankieta %{name} zakończyła się + quote: + subject: "%{name} cytował twój post" + title: Nowy cytat reblog: body: 'Twój wpis został podbity przez %{name}:' subject: Twój wpis został podbity przez %{name} @@ -2024,6 +2029,9 @@ pl: does_not_match_previous_name: nie pasuje do poprzedniej nazwy terms_of_service: title: Regulamin + terms_of_service_interstitial: + review_link: Przeglądnij Warunki Korzystania + title: Warunki korzystania z %{domain} zmieniają się themes: contrast: Mastodon (Wysoki kontrast) default: Mastodon (Ciemny) diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 001e48c650c..58f71bb4549 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -196,6 +196,7 @@ ru: create_relay: Создание ретранслятора create_unavailable_domain: Добавление домена в список недоступных create_user_role: Создание ролей + create_username_block: Создать правило имени пользователя demote_user: Разжалование пользователей destroy_announcement: Удаление объявлений destroy_canonical_email_block: Удаление блокировок e-mail @@ -209,6 +210,7 @@ ru: destroy_status: Удаление постов destroy_unavailable_domain: Исключение доменов из списка недоступных destroy_user_role: Удаление ролей + destroy_username_block: Удалить правило имени пользователя disable_2fa_user: Отключение 2FA disable_custom_emoji: Отключение эмодзи disable_relay: Отключение ретранслятора @@ -243,6 +245,7 @@ ru: update_report: Изменение жалоб update_status: Изменение постов update_user_role: Изменение ролей + update_username_block: Обновить правило имени пользователя actions: approve_appeal_html: "%{name} одобрил(а) обжалование действий модерации от %{target}" approve_user_html: "%{name} утвердил(а) регистрацию %{target}" @@ -261,6 +264,7 @@ ru: create_relay_html: "%{name} создал(а) ретранслятор %{target}" create_unavailable_domain_html: "%{name} приостановил доставку на узел %{target}" create_user_role_html: "%{name} создал(а) роль %{target}" + create_username_block_html: "%{name} создал(а) правило для имён пользователей, содержащих %{target}" demote_user_html: "%{name} разжаловал(а) пользователя %{target}" destroy_announcement_html: "%{name} удалил(а) объявление %{target}" destroy_canonical_email_block_html: "%{name} снял(а) блокировку e-mail с хэшем %{target}" @@ -274,6 +278,7 @@ ru: destroy_status_html: "%{name} удалил(а) пост пользователя %{target}" destroy_unavailable_domain_html: "%{name} возобновил доставку на узел %{target}" destroy_user_role_html: "%{name} удалил(а) роль %{target}" + destroy_username_block_html: "%{name} удалил(а) правило для имён пользователей, содержащих %{target}" disable_2fa_user_html: "%{name} отключил(а) требование двухэтапной авторизации для пользователя %{target}" disable_custom_emoji_html: "%{name} отключил(а) эмодзи %{target}" disable_relay_html: "%{name} отключил(а) ретранслятор %{target}" @@ -308,6 +313,7 @@ ru: update_report_html: "%{name} изменил(а) жалобу %{target}" update_status_html: "%{name} изменил(а) пост пользователя %{target}" update_user_role_html: "%{name} изменил(а) роль %{target}" + update_username_block_html: "%{name} обновил(а) правило для имён пользователей, содержащих %{target}" deleted_account: удалённая учётная запись empty: Журнал пуст. filter_by_action: Фильтр по действию @@ -499,15 +505,24 @@ ru: fasp: debug: callbacks: + created_at: Создано в delete: Удалить ip: IP-адрес + request_body: Тело запроса providers: + active: Активен base_url: Основной URL delete: Удалить + edit: Редактировать поставщика + finish_registration: Завершить регистрацию + name: Имя + providers: Поставщики + registration_requested: Требуется регистрация registrations: confirm: Подтвердить reject: Отклонить save: Сохранить + select_capabilities: Выберите возможности sign_in: status: Пост title: FASP @@ -585,6 +600,9 @@ ru: all: Все limited: Ограниченные title: Модерация + moderation_notes: + create: Добавить заметку модератора + title: Заметки модератора private_comment: Приватный комментарий public_comment: Публичный комментарий purge: Удалить данные @@ -799,11 +817,16 @@ ru: title: Роли rules: add_new: Добавить правило + add_translation: Добавить перевод delete: Удалить description_html: Хотя большинство утверждает, что прочитали и согласны с условиями обслуживания, обычно люди не читают их до тех пор, пока не возникнет проблема. Упростите просмотр правил вашего сервера с первого взгляда, предоставив их в виде простого маркированного списка. Старайтесь, чтобы отдельные правила были краткими и простыми, но старайтесь не разбивать их на множество отдельных элементов. edit: Редактировать правило empty: Правила сервера еще не определены. + move_down: Переместить вниз + move_up: Переместить вверх title: Правила сервера + translation: Перевод + translations: Переводы settings: about: manage_rules: Управление правилами на сервере @@ -829,6 +852,7 @@ ru: discovery: follow_recommendations: Рекомендации подписок preamble: Наблюдение интересного контента играет важную роль при открытии новых пользователей, которые могут не знать ни одного Mastodon. Контролируйте как работают различные функции обнаружения на вашем сервере. + privacy: Конфиденциальность profile_directory: Каталог профилей public_timelines: Публичные ленты publish_statistics: Опубликовать стаитстику @@ -1087,6 +1111,23 @@ ru: other: За последнюю неделю использовал %{count} человек title: Рекомендации и тренды trending: Популярное + username_blocks: + add_new: Добавить новое + block_registrations: Блокировать регистрации + comparison: + contains: Содержит + equals: Равен + contains_html: Содержит %{string} + created_msg: Успешно создано правило имени пользователя + delete: Удалить + edit: + title: Редактировать правило имени пользователя + matches_exactly_html: Равен %{string} + new: + create: Создать правило + title: Создать новое правило имени пользователя + not_permitted: Не разрешено + title: Правила имени пользователя warning_presets: add_new: Добавить delete: Удалить @@ -1705,6 +1746,10 @@ ru: title: Новое упоминание poll: subject: Опрос %{name} завершился + quote: + body: 'Ваш пост был процитирован %{name}:' + subject: "%{name} процитировал(а) ваш пост" + title: Новая цитата reblog: body: "%{name} продвинул(а) ваш пост:" subject: "%{name} продвинул(а) ваш пост" @@ -1987,6 +2032,10 @@ ru: does_not_match_previous_name: не совпадает с предыдущим именем terms_of_service: title: Пользовательское соглашение + terms_of_service_interstitial: + past_preamble_html: Мы изменили наше пользовательское соглашение с момента вашего последнего посещения. Мы рекомендуем вам ознакомиться с обновленным соглашением. + review_link: Посмотреть пользовательское соглашение + title: Изменяется пользовательское соглашение %{domain} themes: contrast: Mastodon (высококонтрастная) default: Mastodon (тёмная) diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 6faef6aa9ca..23204dea2db 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -56,10 +56,12 @@ pl: scopes: Wybór API, do których aplikacja będzie miała dostęp. Jeżeli wybierzesz nadrzędny zakres, nie musisz wybierać jego elementów. setting_aggregate_reblogs: Nie pokazuj nowych podbić dla wpisów, które zostały niedawno podbite (dotyczy tylko nowo otrzymanych podbić) setting_always_send_emails: Powiadomienia e-mail zwykle nie będą wysyłane, gdy używasz Mastodon + setting_default_quote_policy: To ustawienie będzie skuteczne tylko dla postów utworzonych z następną wersją Mastodon, ale możesz wybrać swoje preferencje w przygotowaniu. setting_default_sensitive: Wrażliwe multimedia są domyślnie schowane i mogą być odkryte kliknięciem setting_display_media_default: Ukrywaj zawartość multimedialną oznaczoną jako wrażliwa setting_display_media_hide_all: Zawsze ukrywaj zawartość multimedialną setting_display_media_show_all: Zawsze pokazuj zawartość multimedialną + setting_emoji_style: Jak wyświetlić emotikony. "Auto" spróbuje użyć natywnych emoji, ale wróci do Twemoji dla starszych przeglądarek. setting_system_scrollbars_ui: Stosuje się tylko do przeglądarek komputerowych opartych na Safari i Chrome setting_use_blurhash: Gradienty są oparte na kolorach ukrywanej zawartości, ale uniewidaczniają wszystkie szczegóły setting_use_pending_items: Ukryj aktualizacje osi czasu za kliknięciem, zamiast automatycznego przewijania strumienia @@ -75,6 +77,7 @@ pl: filters: action: Wybierz akcję do wykonania, gdy post pasuje do filtra actions: + blur: Ukryj media za ostrzeżeniem, bez ukrywania samego tekstu hide: Całkowicie ukryj przefiltrowaną zawartość, jakby nie istniała warn: Ukryj filtrowaną zawartość za ostrzeżeniem wskazującym tytuł filtra form_admin_settings: @@ -88,6 +91,7 @@ pl: favicon: WEBP, PNG, GIF, albo JPEG. Nadpisuje domyślną faviconę Mastodona. mascot: Nadpisuje ilustrację w zaawansowanym interfejsie internetowym. media_cache_retention_period: Media z wpisów od obcych użytkowników są cache'owane na twoim serwerze. Kiedy dana wartość jest dodatnia, media te będą usunięte po tylu dniach. Jeżeli usunięte media zostaną potem zażądane, oryginał zostanie ponownie pobrany (o ile jest dalej dostępny). Z powodu ograniczeń dot. częstotliwości z jaką karty podglądu linków dopytują się o dane od stron trzecich, zalecana wartość to min. 14 dni, bo karty podglądu linków nie będą wcześniej odświeżane na żądane. + min_age: Użytkownicy zostaną poproszeni o potwierdzenie daty urodzenia podczas rejestracji peers_api_enabled: Lista nazw domen, z którymi ten serwer spotkał się w fediverse. Nie są tu zawarte żadne dane o tym, czy użytkownik dokonuje federacji z danym serwerem, a jedynie, że jego serwer o tym wie. Jest to wykorzystywane przez serwisy, które zbierają statystyki dotyczące federacji w ogólnym sensie. profile_directory: Katalog profili zawiera listę wszystkich użytkowników, którzy zgodzili się na bycie znalezionymi. require_invite_text: Kiedy rejestracje wymagają ręcznego zatwierdzenia, ustaw pole "Dlaczego chcesz dołączyć?" jako obowiązkowe, a nie opcjonalne @@ -132,13 +136,18 @@ pl: name: Możesz zmieniać tylko wielkość liter, np. aby były bardziej widoczne terms_of_service: changelog: Może być stworzony przy pomocy składni Markdown. + effective_date: Uzasadnione ramy czasowe mogą być różne w dowolnym miejscu od 10 do 30 dni od daty powiadomienia użytkowników. text: Może być stworzony przy pomocy składni Markdown. terms_of_service_generator: admin_email: Zawiadomienia prawne obejmują środki zapobiegawcze, nakazy sądowe, wnioski o popełnienie sprawy oraz wnioski organów ścigania. + arbitration_address: Czy może być taki sam jak adres fizyczny powyżej lub „N/A” jeśli używasz adresu e-mail. + arbitration_website: Może być formularzem internetowym lub „N/A”, jeśli używasz adresu e-mail. choice_of_law: Miasto, region, terytorium lub stan, którego wewnętrzne prawo będzie regulowało wszelkie roszczenia. dmca_address: W przypadku operatorów z USA należy użyć adresu zarejestrowanego w DMCA Designated Agent Directory. Lista skrytek pocztowych dostępna jest na bezpośrednią prośbę użytkownika. Użyj DMCA Agent Post Office Box Waiver Request, aby wysłać email do Copyright Office z informacją, że jesteś domowym administratorm treści i z powodu obawy o zemstę lub odwetu za swoje działania, musisz użyć skrytki pocztowej, żeby usunąć swój adres domowy z dostępu publicznego. + dmca_email: Czy może ten sam adres e-mail używany dla powyższego "Adres e-mail dla prawnych zawiadomień"? domain: Unikalny numer identyfikacji świadczonej przez Ciebie usługi online. jurisdiction: Wymień państwo, w którym mieszkają osoby płacące rachunki. Jeżeli jest to spółka lub inny zarejestrowany podmiot, w zależności od przypadku podaj państwo, w którym jest zarejestrowany, a także miasto, region czy województwo. + min_age: Nie powinien być niższy niż minimalny wiek wymagany przez prawo twojego państwa. user: chosen_languages: Jeżeli zaznaczone, tylko wpisy w wybranych językach będą wyświetlane na publicznych osiach czasu role: Rola kontroluje uprawnienia użytkownika. @@ -148,6 +157,10 @@ pl: name: Publiczna nazwa roli, jeśli włączone jest wyświetlanie odznaki permissions_as_keys: Użytkownicy z tą rolą będą mieli dostęp do... position: Wyższa rola decyduje o rozwiązywaniu konfliktów w pewnych sytuacjach. Niektóre działania mogą być wykonywane tylko na rolach z niższym priorytetem + username_block: + allow_with_approval: Zamiast bezwzględnie uniemożliwiać rejestrację, pasujące rejestracje będą wymagały Twojej zgody + comparison: Proszę pamiętać o problemie Scunthorpe podczas blokowania częściowych dopasowań + username: Będą dopasowane niezależnie od otoczki i zwykłych homoglików, takich jak "4" dla "a" lub "3" dla "e" webhook: events: Wybierz zdarzenia do wysłania template: Poskładaj zawartość JSON, podstawiając zmienne. Użyjemy domyślnej wartości, jeżeli pole zostawisz pustym. @@ -255,6 +268,7 @@ pl: name: Hasztag filters: actions: + blur: Ukryj media z ostrzeżeniem hide: Ukryj całkowicie warn: Ukryj z ostrzeżeniem form_admin_settings: @@ -312,6 +326,7 @@ pl: follow_request: Powiadamiaj mnie e-mailem, gdy ktoś poprosi o pozwolenie na obserwowanie mnie mention: Powiadamiaj mnie e-mailem, gdy ktoś o mnie wspomni pending_account: Wyślij e-mail kiedy nowe konto potrzebuje recenzji + quote: Ktoś Cię cytował reblog: Powiadamiaj mnie e-mailem, gdy ktoś podbije mój wpis report: Nowe zgłoszenie zostało wysłane software_updates: diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml index 5c48d751b55..3979d9c6dea 100644 --- a/config/locales/simple_form.ru.yml +++ b/config/locales/simple_form.ru.yml @@ -237,6 +237,7 @@ ru: setting_display_media_default: По умолчанию setting_display_media_hide_all: Скрывать все setting_display_media_show_all: Показывать все + setting_emoji_style: Стиль эмодзи setting_expand_spoilers: Разворачивать все посты с предупреждением о содержании setting_hide_network: Скрыть мои связи setting_missing_alt_text_modal: Запрашивать подтверждение при публикации медиа без альтернативного текста @@ -319,6 +320,7 @@ ru: follow_request: Мне пришёл запрос на подписку mention: Меня упомянули в посте pending_account: Новая заявка на создание аккаунта + quote: Кто-то процитировал вас reblog: Мой пост продвинули report: Новое обращение отправлено software_updates: @@ -365,6 +367,9 @@ ru: name: Название permissions_as_keys: Разрешения position: Приоритет + username_block: + allow_with_approval: Разрешить регистрацию с одобрением + comparison: Метод сравнения webhook: events: Включенные события template: Шаблон полезной нагрузки From d9d7914a8dc03e55741d51cb37615137443205ec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 07:44:48 +0000 Subject: [PATCH 263/660] Update dependency vite to v7.1.2 (#35746) 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 2795331617a..da1ac62ba9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13898,8 +13898,8 @@ __metadata: linkType: hard "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0, vite@npm:^7.1.1": - version: 7.1.1 - resolution: "vite@npm:7.1.1" + version: 7.1.2 + resolution: "vite@npm:7.1.2" dependencies: esbuild: "npm:^0.25.0" fdir: "npm:^6.4.6" @@ -13948,7 +13948,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/391a5c8b8f287b7b1653dedbe952fb4cb93bf7c99b19dab915cf63497892427198fef637e943a3391eacfecf7f2e8f55c40d0fa065fabdd885641430d0b74af7 + checksum: 10c0/4ed825b20bc0f49db99cd382de9506b2721ccd47dcebd4a68e0ef65e3cdd2347fded52b306c34178308e0fd7fe78fd5ff517623002cb00710182ad3012c92ced languageName: node linkType: hard From 5ee83a680bc133d69b56784a86bfcd4304132463 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 12 Aug 2025 04:15:22 -0400 Subject: [PATCH 264/660] Update stoplight to version 5.3.1 (#35129) --- Gemfile | 2 +- Gemfile.lock | 8 +++---- .../concerns/signature_verification.rb | 12 ++++++---- app/services/bulk_import_row_service.rb | 22 +++++++++++++------ app/workers/activitypub/delivery_worker.rb | 10 +++++---- config/initializers/stoplight.rb | 6 +++-- lib/paperclip/attachment_extensions.rb | 13 ++++++----- 7 files changed, 44 insertions(+), 29 deletions(-) diff --git a/Gemfile b/Gemfile index 57ea00cc835..c5d6f558242 100644 --- a/Gemfile +++ b/Gemfile @@ -88,7 +88,7 @@ gem 'sidekiq-scheduler', '~> 6.0' gem 'sidekiq-unique-jobs', '> 8' gem 'simple_form', '~> 5.2' gem 'simple-navigation', '~> 4.4' -gem 'stoplight', '~> 4.1' +gem 'stoplight' gem 'strong_migrations' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' diff --git a/Gemfile.lock b/Gemfile.lock index db58971b6e8..077ed68f556 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -719,8 +719,6 @@ GEM redis (4.8.1) redis-client (0.25.2) connection_pool - redlock (1.3.2) - redis (>= 3.0.0, < 6.0) regexp_parser (2.11.0) reline (0.6.2) io-console (~> 0.5) @@ -855,8 +853,8 @@ GEM stackprof (0.2.27) starry (0.2.0) base64 - stoplight (4.1.1) - redlock (~> 1.0) + stoplight (5.3.1) + zeitwerk stringio (3.1.7) strong_migrations (2.5.0) activerecord (>= 7.1) @@ -1088,7 +1086,7 @@ DEPENDENCIES simplecov (~> 0.22) simplecov-lcov (~> 0.8) stackprof - stoplight (~> 4.1) + stoplight strong_migrations test-prof thor (~> 1.2) diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index b61a569860f..2bdd3558643 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -9,6 +9,8 @@ module SignatureVerification EXPIRATION_WINDOW_LIMIT = 12.hours CLOCK_SKEW_MARGIN = 1.hour + STOPLIGHT_COOL_OFF_TIME = 5.minutes.seconds + STOPLIGHT_THRESHOLD = 1 def require_account_signature! render json: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account @@ -107,10 +109,12 @@ module SignatureVerification end def stoplight_wrapper - Stoplight("source:#{request.remote_ip}") - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } + Stoplight( + "source:#{request.remote_ip}", + cool_off_time: STOPLIGHT_COOL_OFF_TIME, + threshold: STOPLIGHT_THRESHOLD, + tracked_errors: [HTTP::Error, OpenSSL::SSL::SSLError] + ) end def actor_refresh_key!(actor) diff --git a/app/services/bulk_import_row_service.rb b/app/services/bulk_import_row_service.rb index 26909dfe04f..ac5080f0ba4 100644 --- a/app/services/bulk_import_row_service.rb +++ b/app/services/bulk_import_row_service.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true class BulkImportRowService + STOPLIGHT_COOL_OFF_TIME = 5.minutes.seconds + STOPLIGHT_THRESHOLD = 1 + def call(row) @account = row.bulk_import.account @data = row.data @@ -10,7 +13,7 @@ class BulkImportRowService when :following, :blocking, :muting, :lists target_acct = @data['acct'] target_domain = domain(target_acct) - @target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } + @target_account = stoplight_wrapper(target_domain).run(stoplight_fallback) { ResolveAccountService.new.call(target_acct, { check_delivery_availability: true }) } return false if @target_account.nil? when :bookmarks target_uri = @data['uri'] @@ -18,7 +21,7 @@ class BulkImportRowService @target_status = ActivityPub::TagManager.instance.uri_to_resource(target_uri, Status) return false if @target_status.nil? && ActivityPub::TagManager.instance.local_uri?(target_uri) - @target_status ||= stoplight_wrapper(target_domain).run { ActivityPub::FetchRemoteStatusService.new.call(target_uri) } + @target_status ||= stoplight_wrapper(target_domain).run(stoplight_fallback) { ActivityPub::FetchRemoteStatusService.new.call(target_uri) } return false if @target_status.nil? end @@ -51,13 +54,18 @@ class BulkImportRowService TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain) end + def stoplight_fallback + ->(error) {} + end + def stoplight_wrapper(domain) if domain.present? - Stoplight("source:#{domain}") - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } + Stoplight( + "source:#{domain}", + cool_off_time: STOPLIGHT_COOL_OFF_TIME, + threshold: STOPLIGHT_THRESHOLD, + tracked_errors: [HTTP::Error, OpenSSL::SSL::SSLError] + ) else Stoplight('domain-blank') end diff --git a/app/workers/activitypub/delivery_worker.rb b/app/workers/activitypub/delivery_worker.rb index 7a1440ed15f..40b5c42404f 100644 --- a/app/workers/activitypub/delivery_worker.rb +++ b/app/workers/activitypub/delivery_worker.rb @@ -5,8 +5,8 @@ class ActivityPub::DeliveryWorker include RoutingHelper include JsonLdHelper + STOPLIGHT_COOL_OFF_TIME = 60 STOPLIGHT_FAILURE_THRESHOLD = 10 - STOPLIGHT_COOLDOWN = 60 sidekiq_options queue: 'push', retry: 16, dead: false @@ -75,9 +75,11 @@ class ActivityPub::DeliveryWorker end def stoplight_wrapper - Stoplight(@inbox_url) - .with_threshold(STOPLIGHT_FAILURE_THRESHOLD) - .with_cool_off_time(STOPLIGHT_COOLDOWN) + Stoplight( + @inbox_url, + cool_off_time: STOPLIGHT_COOL_OFF_TIME, + threshold: STOPLIGHT_FAILURE_THRESHOLD + ) end def failure_tracker diff --git a/config/initializers/stoplight.rb b/config/initializers/stoplight.rb index 0ade504f67b..c92a4693039 100644 --- a/config/initializers/stoplight.rb +++ b/config/initializers/stoplight.rb @@ -3,6 +3,8 @@ require 'stoplight' Rails.application.reloader.to_prepare do - Stoplight.default_data_store = Stoplight::DataStore::Redis.new(RedisConnection.new.connection) - Stoplight.default_notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)] + Stoplight.configure do |config| + config.data_store = Stoplight::DataStore::Redis.new(RedisConnection.new.connection) + config.notifiers = [Stoplight::Notifier::Logger.new(Rails.logger)] + end end diff --git a/lib/paperclip/attachment_extensions.rb b/lib/paperclip/attachment_extensions.rb index 401da112908..011e165ed74 100644 --- a/lib/paperclip/attachment_extensions.rb +++ b/lib/paperclip/attachment_extensions.rb @@ -73,8 +73,8 @@ module Paperclip @url_generator.for_as_default(style_name) end + STOPLIGHT_COOL_OFF_TIME = 30 STOPLIGHT_THRESHOLD = 10 - STOPLIGHT_COOLDOWN = 30 # We overwrite this method to put a circuit breaker around # calls to object storage, to stop hitting APIs that are slow @@ -84,11 +84,12 @@ module Paperclip # Don't go through Stoplight if we don't have anything object-storage-oriented to do return super if @queued_for_delete.empty? && @queued_for_write.empty? && !dirty? - Stoplight('object-storage') - .with_threshold(STOPLIGHT_THRESHOLD) - .with_cool_off_time(STOPLIGHT_COOLDOWN) - .with_error_handler { |error, handle| error.is_a?(Seahorse::Client::NetworkingError) ? handle.call(error) : raise(error) } - .run { super } + Stoplight( + 'object-storage', + cool_off_time: STOPLIGHT_COOL_OFF_TIME, + threshold: STOPLIGHT_THRESHOLD, + tracked_errors: [Seahorse::Client::NetworkingError] + ).run { super } end end end From 28b5477c6f73f2a136f3fe31942f69211e15df5a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:27:07 +0200 Subject: [PATCH 265/660] Update dependency rubocop-rails to v2.33.1 (#35747) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 077ed68f556..c9c1b7349df 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -719,7 +719,7 @@ GEM redis (4.8.1) redis-client (0.25.2) connection_pool - regexp_parser (2.11.0) + regexp_parser (2.11.1) reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) @@ -787,7 +787,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.33.0) + rubocop-rails (2.33.1) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) From 69ee043f9d4c6be578d5785c1ec311893c83d30c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:53:52 +0200 Subject: [PATCH 266/660] Update dependency rspec-rails to v8.0.2 (#35750) 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 c9c1b7349df..009cd1aa9b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -749,7 +749,7 @@ GEM rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.1) + rspec-rails (8.0.2) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) From 258e5c4938e88183fec010dd0af1e358dc482a2d Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 12 Aug 2025 14:19:29 +0200 Subject: [PATCH 267/660] Fix interaction policy changes in implicit updates not being saved (#35751) --- .../process_status_update_service.rb | 2 +- .../fetch_remote_status_service_spec.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index 023eef19a02..0da1335b5cc 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -74,7 +74,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService end def update_interaction_policies! - @status.quote_approval_policy = @status_parser.quote_policy + @status.update(quote_approval_policy: @status_parser.quote_policy) end def update_media_attachments! diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb index 2503a58ac20..07d05d762f1 100644 --- a/spec/services/activitypub/fetch_remote_status_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb @@ -316,6 +316,23 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do expect(existing_status.edits).to_not be_empty end end + + context 'with an implicit update to quoting policy' do + let(:object) do + note.merge({ + 'content' => existing_status.text, + 'interactionPolicy' => { + 'canQuote' => { + 'automaticApproval' => ['https://www.w3.org/ns/activitystreams#Public'], + }, + }, + }) + end + + it 'updates status' do + expect(existing_status.reload.quote_approval_policy).to eq(Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16) + end + end end end From 5847117573aff7c793141ee8963376c75c439955 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:54:22 +0200 Subject: [PATCH 268/660] Update dependency eslint-plugin-jsdoc to v53 (#35742) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index de656d33c1d..6922dd435d3 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-import": "~2.32.0", - "eslint-plugin-jsdoc": "^52.0.0", + "eslint-plugin-jsdoc": "^53.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 da1ac62ba9e..e07b8741a52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2680,7 +2680,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:^52.0.0" + eslint-plugin-jsdoc: "npm:^53.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" @@ -6986,9 +6986,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^52.0.0": - version: 52.0.2 - resolution: "eslint-plugin-jsdoc@npm:52.0.2" +"eslint-plugin-jsdoc@npm:^53.0.0": + version: 53.0.1 + resolution: "eslint-plugin-jsdoc@npm:53.0.1" dependencies: "@es-joy/jsdoccomment": "npm:~0.52.0" are-docs-informative: "npm:^0.0.2" @@ -7002,7 +7002,7 @@ __metadata: spdx-expression-parse: "npm:^4.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/e36d8c75a5a100f71f4f5287ce12ffdf15474194b2877dbe1e06c3c24a1c0c0c7f13c2d92cb289a80515b6fe1e43c4c0b19814b5c5b22a46ac2ca7df23ab55ad + checksum: 10c0/d629863faa98026edc09535e8095669d97be4e77173bfe6cbe7ea0fcd641cd2537bec58e25db433cc6f4103d9a2a13c1bbb3916a8ed4ff05b18c566971dec472 languageName: node linkType: hard From 783b33e2da7a7ff4b36a4ec5c0fc47d253886a13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:54:40 +0200 Subject: [PATCH 269/660] Update dependency pino to v9.8.0 (#35722) 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 e07b8741a52..b6115435b51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10308,8 +10308,8 @@ __metadata: linkType: hard "pino@npm:^9.0.0": - version: 9.7.0 - resolution: "pino@npm:9.7.0" + version: 9.8.0 + resolution: "pino@npm:9.8.0" dependencies: atomic-sleep: "npm:^1.0.0" fast-redact: "npm:^3.1.1" @@ -10324,7 +10324,7 @@ __metadata: thread-stream: "npm:^3.0.0" bin: pino: bin.js - checksum: 10c0/c7f8a83a9a9d728b4eff6d0f4b9367f031c91bcaa5806fbf0eedcc8e77faba593d59baf11a8fba0dd1c778bb17ca7ed01418ac1df4ec129faeedd4f3ecaff66f + checksum: 10c0/a6de40d8c777520a0f2bdd1727512db2dba51d0d8098500ec8326f1f95412e4e704ee4301287ccbbafbf7a7ab07999d5659333a7079d1b70341d0d69e4d40b29 languageName: node linkType: hard From 63d3f28b2033f5ecef55760505d385fea47ac254 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 12 Aug 2025 18:16:03 +0200 Subject: [PATCH 270/660] Fix reply indicator displaying wrong avatar in rare cases (#35756) --- .../mastodon/features/compose/components/reply_indicator.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx index cf5bae2e074..35733ac23b6 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx @@ -25,7 +25,7 @@ export const ReplyIndicator = () => {
- +
From eda8ddddd6ecfcf455495774ba47297ec2e678e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:38:54 +0200 Subject: [PATCH 271/660] Update dependency json-schema to v6 (#35754) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c5d6f558242..9b9bd40f2fa 100644 --- a/Gemfile +++ b/Gemfile @@ -146,7 +146,7 @@ group :test do gem 'climate_control' # Validate schemas in specs - gem 'json-schema', '~> 5.0' + gem 'json-schema', '~> 6.0' # Test harness fo rack components gem 'rack-test', '~> 2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 009cd1aa9b1..098168f006c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -365,7 +365,7 @@ GEM json-ld-preloaded (3.3.2) json-ld (~> 3.3) rdf (~> 3.3) - json-schema (5.2.1) + json-schema (6.0.0) addressable (~> 2.8) bigdecimal (~> 3.1) jsonapi-renderer (0.2.2) @@ -1003,7 +1003,7 @@ DEPENDENCIES jd-paperclip-azure (~> 3.0) json-ld json-ld-preloaded (~> 3.2) - json-schema (~> 5.0) + json-schema (~> 6.0) kaminari (~> 1.2) kt-paperclip (~> 7.2) letter_opener (~> 1.8) From c893b82ace8b2f6e2b7b7ae3954dce1393d5d881 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 13 Aug 2025 09:12:11 +0200 Subject: [PATCH 272/660] Remove unnecessary limitation on manual build-security workflow (#35752) --- .github/workflows/build-security.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-security.yml b/.github/workflows/build-security.yml index d3cb4e5e0ae..72729b544b3 100644 --- a/.github/workflows/build-security.yml +++ b/.github/workflows/build-security.yml @@ -9,7 +9,6 @@ permissions: jobs: compute-suffix: runs-on: ubuntu-latest - if: github.repository == 'mastodon/mastodon' steps: - id: version_vars env: From bce4a572cdc78b8f3d3c5cb8c1afa72f73394b68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:26:03 +0200 Subject: [PATCH 273/660] New Crowdin Translations (automated) (#35759) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/cs.json | 2 +- app/javascript/mastodon/locales/nn.json | 15 +++++++++++ config/locales/nn.yml | 36 +++++++++++++++++++++++++ config/locales/simple_form.nn.yml | 15 +++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index a6e82882953..bd8d5a5d8bc 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -510,7 +510,7 @@ "lightbox.zoom_out": "Přizpůsobit velikost", "limited_account_hint.action": "Přesto profil zobrazit", "limited_account_hint.title": "Tento profil byl skryt moderátory {domain}.", - "link_preview.author": "Podle {name}", + "link_preview.author": "Od {name}", "link_preview.more_from_author": "Více od {name}", "link_preview.shares": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}", "lists.add_member": "Přidat", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 4d7b6d401f0..213e89fc444 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Fjern fylgjar", "confirmations.remove_from_followers.message": "{name} vil ikkje fylgja deg meir. Vil du halda fram?", "confirmations.remove_from_followers.title": "Fjern fylgjar?", + "confirmations.revoke_quote.confirm": "Fjern innlegget", + "confirmations.revoke_quote.message": "Du kan ikkje angra denne handlinga.", + "confirmations.revoke_quote.title": "Fjern innlegget?", "confirmations.unfollow.confirm": "Slutt å fylgja", "confirmations.unfollow.message": "Er du sikker på at du vil slutta å fylgja {name}?", "confirmations.unfollow.title": "Slutt å fylgja brukaren?", @@ -498,6 +501,8 @@ "keyboard_shortcuts.translate": "å omsetje eit innlegg", "keyboard_shortcuts.unfocus": "for å fokusere vekk skrive-/søkefeltet", "keyboard_shortcuts.up": "Flytt opp på lista", + "learn_more_link.got_it": "Forstått", + "learn_more_link.learn_more": "Lær meir", "lightbox.close": "Lukk", "lightbox.next": "Neste", "lightbox.previous": "Førre", @@ -598,6 +603,7 @@ "notification.label.mention": "Omtale", "notification.label.private_mention": "Privat omtale", "notification.label.private_reply": "Privat svar", + "notification.label.quote": "{name} siterte innlegget ditt", "notification.label.reply": "Svar", "notification.mention": "Omtale", "notification.mentioned_you": "{name} nemnde deg", @@ -655,6 +661,7 @@ "notifications.column_settings.mention": "Omtaler:", "notifications.column_settings.poll": "Røysteresultat:", "notifications.column_settings.push": "Pushvarsel", + "notifications.column_settings.quote": "Sitat:", "notifications.column_settings.reblog": "Framhevingar:", "notifications.column_settings.show": "Vis i kolonne", "notifications.column_settings.sound": "Spel av lyd", @@ -845,6 +852,8 @@ "status.bookmark": "Set bokmerke", "status.cancel_reblog_private": "Opphev framheving", "status.cannot_reblog": "Du kan ikkje framheva dette innlegget", + "status.context.load_new_replies": "Nye svar finst", + "status.context.loading": "Ser etter fleire svar", "status.continued_thread": "Framhald til tråden", "status.copy": "Kopier lenke til status", "status.delete": "Slett", @@ -871,6 +880,11 @@ "status.open": "Utvid denne statusen", "status.pin": "Fest på profil", "status.quote_error.filtered": "Gøymt på grunn av eitt av filtra dine", + "status.quote_error.not_available": "Innlegget er ikkje tilgjengeleg", + "status.quote_error.pending_approval": "Innlegget ventar", + "status.quote_error.pending_approval_popout.body": "Sitat frå rundt om i allheimen kan ta tid å visa, fordi ulike tenarar har ulike protokollar.", + "status.quote_error.pending_approval_popout.title": "Ventande sitat? Ikkje stress", + "status.quote_post_author": "Siterte eit innlegg av @{name}", "status.read_more": "Les meir", "status.reblog": "Framhev", "status.reblog_private": "Framhev til dei originale mottakarane", @@ -885,6 +899,7 @@ "status.reply": "Svar", "status.replyAll": "Svar til tråd", "status.report": "Rapporter @{name}", + "status.revoke_quote": "Fjern innlegget mitt frå @{name} sitt innlegg", "status.sensitive_warning": "Ømtolig innhald", "status.share": "Del", "status.show_less_all": "Vis mindre for alle", diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 14c26ea74a0..0ae6bf02237 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -190,6 +190,7 @@ nn: create_relay: Opprett eit relé create_unavailable_domain: Opprett utilgjengeleg domene create_user_role: Opprett rolle + create_username_block: Lag regel for brukarnamn demote_user: Degrader brukar destroy_announcement: Slett lysinga destroy_canonical_email_block: Fjern e-postblokkering @@ -203,6 +204,7 @@ nn: destroy_status: Slett status destroy_unavailable_domain: Slett utilgjengeleg domene destroy_user_role: Øydelegg rolle + destroy_username_block: Slett regel for brukarnamn disable_2fa_user: Skruv av 2FA disable_custom_emoji: Skruv av tilpassa emoji disable_relay: Skru av reléet @@ -237,6 +239,7 @@ nn: update_report: Oppdater rapport update_status: Oppdater tut update_user_role: Oppdater rolla + update_username_block: Oppdater regel for brukarnamn actions: approve_appeal_html: "%{name} godkjende klagen frå %{target} på modereringa" approve_user_html: "%{name} godkjende registreringa til %{target}" @@ -255,6 +258,7 @@ nn: create_relay_html: "%{name} laga reléet %{target}" create_unavailable_domain_html: "%{name} stogga levering til domenet %{target}" create_user_role_html: "%{name} oppretta rolla %{target}" + create_username_block_html: "%{name} laga ein regel for brukarnamn som inneheld %{target}" demote_user_html: "%{name} degraderte brukaren %{target}" destroy_announcement_html: "%{name} sletta kunngjeringa %{target}" destroy_canonical_email_block_html: "%{name} avblokkerte e-post med hash %{target}" @@ -268,6 +272,7 @@ nn: destroy_status_html: "%{name} fjerna innlegget frå %{target}" destroy_unavailable_domain_html: "%{name} tok opp att levering til domenet %{target}" destroy_user_role_html: "%{name} sletta rolla %{target}" + destroy_username_block_html: "%{name} fjerna regelen for brukarnamn som inneheld %{target}" disable_2fa_user_html: "%{name} tok vekk krav om tofaktorautentisering for brukaren %{target}" disable_custom_emoji_html: "%{name} deaktiverte emojien %{target}" disable_relay_html: "%{name} skrudde av reléet %{target}" @@ -302,6 +307,7 @@ nn: update_report_html: "%{name} oppdaterte rapporten %{target}" update_status_html: "%{name} oppdaterte innlegg av %{target}" update_user_role_html: "%{name} endret %{target} -rolle" + update_username_block_html: "%{name} oppdaterte regelen for brukarnamn som inneheld %{target}" deleted_account: sletta konto empty: Ingen loggar funne. filter_by_action: Sorter etter handling @@ -1085,6 +1091,25 @@ nn: other: Brukt av %{count} personer i løpet av den seneste uken title: Anbefalingar og trendar trending: Trender + username_blocks: + add_new: Lag ny + block_registrations: Blokker registreringar + comparison: + contains: Inneheld + equals: Er lik + contains_html: Inneheld %{string} + created_msg: Laga regelen for brukarnamn + delete: Slett + edit: + title: Rediger regelen for brukarnamn + matches_exactly_html: Er lik %{string} + new: + create: Lag regel + title: Lag ein ny regel for brukarnamn + no_username_block_selected: Ingen brukarnamnreglar vart endra fordi du ikkje valde nokon + not_permitted: Ikkje tillate + title: Reglar for brukarnamn + updated_msg: Oppdaterte regelen for brukarnamn warning_presets: add_new: Legg til ny delete: Slett @@ -1349,6 +1374,10 @@ nn: basic_information: Grunnleggande informasjon hint_html: "Tilpass kva folk ser på den offentlege profilen din og ved sida av innlegga dine. Andre vil i større grad fylgja og samhandla med deg når du har eit profilbilete og har fyllt ut profilen din." other: Anna + emoji_styles: + auto: Auto + native: Innebygd + twemoji: Twemoji errors: '400': Søknaden du sende var ugyldig eller sett opp feil. '403': Du har ikkje løyve til å sjå denne sida. @@ -1658,6 +1687,10 @@ nn: title: Ny omtale poll: subject: Meiningsmålinga frå %{name} er avslutta + quote: + body: 'Innlegget ditt vart sitert av %{name}:' + subject: "%{name} siterte innlegget ditt" + title: Nytt sitat reblog: body: 'Statusen din vart framheva av %{name}:' subject: "%{name} framheva statusen din" @@ -1868,6 +1901,7 @@ nn: edited_at_html: Redigert %{date} errors: in_reply_not_found: Det ser ut til at tutet du freistar å svara ikkje finst. + quoted_status_not_found: Innlegget du prøver å sitera ser ikkje ut til å finnast. over_character_limit: øvregrensa for teikn, %{max}, er nådd pin_errors: direct: Innlegg som bare er synlige for nevnte brukere kan ikke festes @@ -1875,6 +1909,8 @@ nn: ownership: Du kan ikkje festa andre sine tut reblog: Ei framheving kan ikkje festast quote_policies: + followers: Berre dei som fylgjer deg + nobody: Ingen public: Alle title: "%{name}: «%{quote}»" visibilities: diff --git a/config/locales/simple_form.nn.yml b/config/locales/simple_form.nn.yml index 12ae70ffa40..54e45d29e4e 100644 --- a/config/locales/simple_form.nn.yml +++ b/config/locales/simple_form.nn.yml @@ -56,10 +56,12 @@ nn: scopes: API-ane som programmet vil få tilgjenge til. Ettersom du vel eit toppnivåomfang tarv du ikkje velja einskilde API-ar. setting_aggregate_reblogs: Ikkje vis nye framhevingar for tut som nyleg har vorte heva fram (Påverkar berre nylege framhevingar) setting_always_send_emails: Vanlegvis vil ikkje e-postvarsel bli sendt når du brukar Mastodon aktivt + setting_default_quote_policy: Denne innstillinga har berre verknad for innlegg som er laga med den neste utgåva av Mastodon, men du kan velja kva du vil ha i førebuingane. setting_default_sensitive: Sensitive media vert gøymde som standard, og du syner dei ved å klikka på dei setting_display_media_default: Gøym media som er merka som sensitive setting_display_media_hide_all: Alltid skjul alt media setting_display_media_show_all: Vis alltid media + setting_emoji_style: Korleis du skal visa smilefjes. «Auto» prøver å visa innebygde smilefjes, men bruker Twemoji som reserveløysing for eldre nettlesarar. setting_system_scrollbars_ui: Gjeld berre skrivebordsnettlesarar som er bygde på Safari og Chrome setting_use_blurhash: Overgangar er basert på fargane til skjulte grafikkelement, men gjer detaljar utydelege setting_use_pending_items: Gøym tidslineoppdateringar bak eit klikk, i staden for å rulla ned automatisk @@ -148,6 +150,9 @@ nn: min_age: Skal ikkje vere under minstealder som krevst av lover i jurisdiksjonen din. user: chosen_languages: Når merka vil berre tuta på dei valde språka synast på offentlege tidsliner + date_of_birth: + one: Me må sikra oss at du er minst %{count} for å bruka %{domain}. Me lagrar ikkje dette. + other: Me må sikra oss at du er minst %{count} for å bruka %{domain}. Me lagrar ikkje dette. role: Rolla kontrollerer kva løyve brukaren har. user_role: color: Fargen som skal nyttast for denne rolla i heile brukargrensesnittet, som RGB i hex-format @@ -155,6 +160,10 @@ nn: name: Offentleg namn på rolla, dersom rolla skal visast som eit emblem permissions_as_keys: Brukarar med denne rolla vil ha tilgang til... position: Høgare rolle avgjer konfliktløysing i visse situasjonar. Visse handlingar kan berre utførast på roller med lågare prioritet + username_block: + allow_with_approval: I staden for å hindra registreringar i det heile, må du godkjenna registreringar som passar + comparison: Ver merksam på Scunthorpe-problemet når du blokkerer delvise treff + username: Vil passa uansett store og små bokstavar og vanlege homoglyfar som «4» for «a» eller «3» for «e» webhook: events: Vel hendingar å senda template: Skriv di eiga JSON nyttelast ved å bruka variabel interpolering. La stå tom for standard JSON. @@ -237,6 +246,7 @@ nn: setting_display_media_default: Standard setting_display_media_hide_all: Gøym alle setting_display_media_show_all: Vis alle + setting_emoji_style: Stil for smilefjes setting_expand_spoilers: Vid alltid ut tut som er merka med innhaldsåtvaringar setting_hide_network: Gøym nettverket ditt setting_missing_alt_text_modal: Vis stadfestingsdialog før du legg ut media utan alt-tekst @@ -319,6 +329,7 @@ nn: follow_request: Send e-post når nokon spør om å fylgja deg mention: Send e-post når nokon nemner deg pending_account: Send e-post når ein ny konto treng gjennomgang + quote: Nokon siterte deg reblog: Send e-post når nokon framhevar statusen din report: Ny rapport er sendt software_updates: @@ -365,6 +376,10 @@ nn: name: Namn permissions_as_keys: Løyve position: Prioritet + username_block: + allow_with_approval: Tillat registreringar med godkjenning + comparison: Samanlikningsmetode + username: Ord som skal passa webhook: events: Aktiverte hendingar template: Nyttelastmal From 613cbf720c221dc51af95c2a13235e37ca49012b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 07:28:21 +0000 Subject: [PATCH 274/660] Update dependency eslint-plugin-jsdoc to v54 (#35760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6922dd435d3..10de8458f1d 100644 --- a/package.json +++ b/package.json @@ -170,7 +170,7 @@ "eslint-import-resolver-typescript": "^4.2.5", "eslint-plugin-formatjs": "^5.3.1", "eslint-plugin-import": "~2.32.0", - "eslint-plugin-jsdoc": "^53.0.0", + "eslint-plugin-jsdoc": "^54.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 b6115435b51..4ff3c9afc26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2680,7 +2680,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:^53.0.0" + eslint-plugin-jsdoc: "npm:^54.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" eslint-plugin-promise: "npm:~7.2.1" eslint-plugin-react: "npm:^7.37.4" @@ -6986,9 +6986,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-jsdoc@npm:^53.0.0": - version: 53.0.1 - resolution: "eslint-plugin-jsdoc@npm:53.0.1" +"eslint-plugin-jsdoc@npm:^54.0.0": + version: 54.0.0 + resolution: "eslint-plugin-jsdoc@npm:54.0.0" dependencies: "@es-joy/jsdoccomment": "npm:~0.52.0" are-docs-informative: "npm:^0.0.2" @@ -7002,7 +7002,7 @@ __metadata: spdx-expression-parse: "npm:^4.0.0" peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - checksum: 10c0/d629863faa98026edc09535e8095669d97be4e77173bfe6cbe7ea0fcd641cd2537bec58e25db433cc6f4103d9a2a13c1bbb3916a8ed4ff05b18c566971dec472 + checksum: 10c0/cf0a388fc670ababe26f9584c467bc8c1592aa83affcf16118d8181c186a6d8f02a8ea65250766b45168fca5cb879a6af66e8457cdb98f0f923bd927572e2de5 languageName: node linkType: hard From 7cf53dbf639aeb7c01a4d367226be667ed1dc7a2 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 13 Aug 2025 15:52:29 +0200 Subject: [PATCH 275/660] Redirect on success for standalone compose (#35763) --- app/javascript/mastodon/actions/compose.js | 5 ++++- .../mastodon/features/compose/components/compose_form.jsx | 3 ++- .../features/compose/containers/compose_form_container.js | 8 ++++++-- .../mastodon/features/standalone/compose/index.jsx | 2 +- spec/system/share_entrypoint_spec.rb | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 28c90381e06..9dfa4041bdc 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -183,7 +183,7 @@ export function directCompose(account) { }; } -export function submitCompose() { +export function submitCompose(successCallback) { return function (dispatch, getState) { const status = getState().getIn(['compose', 'text'], ''); const media = getState().getIn(['compose', 'media_attachments']); @@ -241,6 +241,9 @@ export function submitCompose() { dispatch(insertIntoTagHistory(response.data.tags, status)); dispatch(submitComposeSuccess({ ...response.data })); + if (typeof successCallback === 'function') { + successCallback(response.data); + } // To make the app more responsive, immediately push the status // into the columns diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 5bc77c4bcd8..9fbaa3450c4 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -73,6 +73,7 @@ class ComposeForm extends ImmutablePureComponent { singleColumn: PropTypes.bool, lang: PropTypes.string, maxChars: PropTypes.number, + redirectOnSuccess: PropTypes.bool, }; static defaultProps = { @@ -329,7 +330,7 @@ class ComposeForm extends ImmutablePureComponent { > {intl.formatMessage( this.props.isEditing ? - messages.saveChanges : + messages.saveChanges : (this.props.isInReply ? messages.reply : messages.publish) )} diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 15ccabf7487..5f86426c4d4 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -34,7 +34,7 @@ const mapStateToProps = state => ({ maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500), }); -const mapDispatchToProps = (dispatch) => ({ +const mapDispatchToProps = (dispatch, props) => ({ onChange (text) { dispatch(changeCompose(text)); @@ -47,7 +47,11 @@ const mapDispatchToProps = (dispatch) => ({ modalProps: {}, })); } else { - dispatch(submitCompose()); + dispatch(submitCompose((status) => { + if (props.redirectOnSuccess) { + window.location.assign(status.url); + } + })); } }, diff --git a/app/javascript/mastodon/features/standalone/compose/index.jsx b/app/javascript/mastodon/features/standalone/compose/index.jsx index 3aff78ffee5..5d336275d4f 100644 --- a/app/javascript/mastodon/features/standalone/compose/index.jsx +++ b/app/javascript/mastodon/features/standalone/compose/index.jsx @@ -5,7 +5,7 @@ import ModalContainer from 'mastodon/features/ui/containers/modal_container'; const Compose = () => ( <> - + diff --git a/spec/system/share_entrypoint_spec.rb b/spec/system/share_entrypoint_spec.rb index b55ea316575..0f07d96efeb 100644 --- a/spec/system/share_entrypoint_spec.rb +++ b/spec/system/share_entrypoint_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Share page', :js, :streaming do fill_in_form expect(page) - .to have_css('.notification-bar-message', text: frontend_translations('compose.published.body')) + .to have_current_path(%r{/@bob/[0-9]+}) end def fill_in_form From 49a6e4cbb5f0a30a52162b65b1046b99d29faccc Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 13 Aug 2025 16:09:45 +0200 Subject: [PATCH 276/660] Move composer buttons to top (#35749) --- .../compose/components/compose_form.jsx | 90 +++++++++---------- .../compose/components/language_dropdown.tsx | 2 +- .../styles/mastodon/components.scss | 11 ++- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 9fbaa3450c4..aa086d4aa6c 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -256,62 +256,60 @@ class ComposeForm extends ImmutablePureComponent {
-
- + - {this.props.spoiler && ( -
-
+ {this.props.spoiler && ( +
+
- + -
-
- )} +
+
+ )} - +
+ +
+ +
-
- - -
-
diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.tsx b/app/javascript/mastodon/features/compose/components/language_dropdown.tsx index d11891308f5..72742153b1c 100644 --- a/app/javascript/mastodon/features/compose/components/language_dropdown.tsx +++ b/app/javascript/mastodon/features/compose/components/language_dropdown.tsx @@ -396,7 +396,7 @@ export const LanguageDropdown: React.FC = () => { warning: guess !== '' && guess !== value, })} > - + {current[2] ?? value} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d6f0087cc67..cca92a46d94 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -602,15 +602,12 @@ body > [data-popper-placement] { &__highlightable { display: flex; flex-direction: column; - gap: 16px; flex: 0 1 auto; border-radius: 4px; border: 1px solid var(--background-border-color); transition: border-color 300ms linear; - min-height: 0; position: relative; background: var(--input-background-color); - overflow-y: auto; &.active { transition: none; @@ -705,6 +702,8 @@ body > [data-popper-placement] { display: flex; align-items: center; gap: 8px; + margin: 8px; + flex-wrap: wrap; & > div { overflow: hidden; @@ -715,6 +714,7 @@ body > [data-popper-placement] { &__uploads { padding: 0 12px; aspect-ratio: 3/2; + flex-shrink: 0; } .media-gallery { @@ -813,7 +813,6 @@ body > [data-popper-placement] { flex-direction: column; gap: 12px; padding: 12px; - padding-top: 0; } &__submit { @@ -874,6 +873,7 @@ body > [data-popper-placement] { } &__poll { + margin-top: 8px; display: flex; flex-direction: column; align-self: stretch; @@ -3518,11 +3518,10 @@ a.account__display-name { display: flex; flex-direction: column; height: calc(100% - 10px); - overflow-y: hidden; + overflow-y: auto; .compose-form { flex: 1 1 auto; - min-height: 0; } } From 2648bbdc51ad42eae889ee44033566bffe141d3a Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 13 Aug 2025 17:51:16 +0200 Subject: [PATCH 277/660] Add `PUT /api/v1/statuses/:status_id/interaction_policy` (#35769) --- .../interaction_policies_controller.rb | 33 +++++++ app/controllers/api/v1/statuses_controller.rb | 18 +--- .../api/interaction_policies_concern.rb | 22 +++++ config/routes/api.rb | 2 + .../v1/statuses/interaction_policies_spec.rb | 94 +++++++++++++++++++ 5 files changed, 152 insertions(+), 17 deletions(-) create mode 100644 app/controllers/api/v1/statuses/interaction_policies_controller.rb create mode 100644 app/controllers/concerns/api/interaction_policies_concern.rb create mode 100644 spec/requests/api/v1/statuses/interaction_policies_spec.rb diff --git a/app/controllers/api/v1/statuses/interaction_policies_controller.rb b/app/controllers/api/v1/statuses/interaction_policies_controller.rb new file mode 100644 index 00000000000..8b822185f69 --- /dev/null +++ b/app/controllers/api/v1/statuses/interaction_policies_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::BaseController + include Api::InteractionPoliciesConcern + + before_action -> { doorkeeper_authorize! :write, :'write:statuses' } + before_action -> { check_feature_enabled } + + def update + authorize @status, :update? + + @status.update!(quote_approval_policy: quote_approval_policy) + + broadcast_updates! if @status.quote_approval_policy_previously_changed? + + render json: @status, serializer: REST::StatusSerializer + end + + private + + def status_params + params.permit(:quote_approval_policy) + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.outgoing_quotes_enabled? + end + + def broadcast_updates! + DistributionWorker.perform_async(@status.id, { 'update' => true }) + ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id) + end +end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index fdf1e7a4685..93dbd8f9d1c 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -3,6 +3,7 @@ class Api::V1::StatusesController < Api::BaseController include Authorization include AsyncRefreshesConcern + include Api::InteractionPoliciesConcern before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy] @@ -205,23 +206,6 @@ class Api::V1::StatusesController < Api::BaseController ) end - def quote_approval_policy - # TODO: handle `nil` separately - return nil unless Mastodon::Feature.outgoing_quotes_enabled? && status_params[:quote_approval_policy].present? - - case status_params[:quote_approval_policy] - when 'public' - Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 - when 'followers' - Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16 - when 'nobody' - 0 - else - # TODO: raise more useful message - raise ActiveRecord::RecordInvalid - end - end - def serializer_for_status @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer end diff --git a/app/controllers/concerns/api/interaction_policies_concern.rb b/app/controllers/concerns/api/interaction_policies_concern.rb new file mode 100644 index 00000000000..21a4cf6c56f --- /dev/null +++ b/app/controllers/concerns/api/interaction_policies_concern.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Api::InteractionPoliciesConcern + extend ActiveSupport::Concern + + def quote_approval_policy + # TODO: handle `nil` separately + return nil unless Mastodon::Feature.outgoing_quotes_enabled? && status_params[:quote_approval_policy].present? + + case status_params[:quote_approval_policy] + when 'public' + Status::QUOTE_APPROVAL_POLICY_FLAGS[:public] << 16 + when 'followers' + Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16 + when 'nobody' + 0 + else + # TODO: raise more useful message + raise ActiveRecord::RecordInvalid + end + end +end diff --git a/config/routes/api.rb b/config/routes/api.rb index 83190610d0b..f8b903c7b94 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -39,6 +39,8 @@ namespace :api, format: false do resource :history, only: :show resource :source, only: :show + resource :interaction_policy, only: :update + post :translate, to: 'translations#create' end diff --git a/spec/requests/api/v1/statuses/interaction_policies_spec.rb b/spec/requests/api/v1/statuses/interaction_policies_spec.rb new file mode 100644 index 00000000000..f2d7eb856e4 --- /dev/null +++ b/spec/requests/api/v1/statuses/interaction_policies_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Interaction policies', feature: :outgoing_quotes do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + let(:status) { Fabricate(:status, account: user.account) } + let(:params) { { quote_approval_policy: 'followers' } } + + describe 'PUT /api/v1/statuses/:status_id/interaction_policy' do + subject do + put "/api/v1/statuses/#{status.id}/interaction_policy", headers: headers, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read read:statuses' + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + expect { subject } + .to_not(change { status.reload.quote_approval_policy }) + + expect(response).to have_http_status(401) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'with a status from a different user' do + let(:status) { Fabricate(:status) } + + it 'returns http unauthorized' do + expect { subject } + .to_not(change { status.reload.quote_approval_policy }) + + expect(response).to have_http_status(403) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when changing the interaction policy' do + it 'changes the interaction policy, returns the updated status, and schedules distribution jobs' do + expect { subject } + .to change { status.reload.quote_approval_policy }.to(Status::QUOTE_APPROVAL_POLICY_FLAGS[:followers] << 16) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to include( + 'quote_approval' => match( + 'automatic' => ['followers'], + 'manual' => [], + 'current_user' => 'automatic' + ) + ) + + expect(DistributionWorker) + .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) + expect(ActivityPub::StatusUpdateDistributionWorker) + .to have_enqueued_sidekiq_job(status.id) + end + end + + context 'when not changing the interaction policy' do + let(:params) { { quote_approval_policy: 'nobody' } } + + it 'keeps the interaction policy, returns the status, and does not schedule distribution jobs' do + expect { subject } + .to_not(change { status.reload.quote_approval_policy }.from(0)) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to include( + 'quote_approval' => match( + 'automatic' => [], + 'manual' => [], + 'current_user' => 'automatic' + ) + ) + + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job + end + end + end +end From e2e19544ae16ea3a0af4cedec678a031a78779ed Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 13 Aug 2025 22:45:10 +0200 Subject: [PATCH 278/660] Update Vagrantfile (#35765) --- Vagrantfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index ce456060cdd..0a343670240 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -54,6 +54,7 @@ sudo apt-get install \ pkg-config \ protobuf-compiler \ zlib1g-dev \ + libvips42t64 \ -y # Install rvm @@ -134,7 +135,7 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| - config.vm.box = "ubuntu/focal64" + config.vm.box = "bento/ubuntu-24.04" config.vm.provider :virtualbox do |vb| vb.name = "mastodon" From c0f64a6603d57095ff1e9748d42a82fc7d2718d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:34:27 +0200 Subject: [PATCH 279/660] Update dependency rails to v8.0.2.1 (#35776) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 106 +++++++++++++++++++++++++-------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 098168f006c..a8374c0417f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,29 +10,29 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + actioncable (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actionmailbox (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) mail (>= 2.8.0) - actionmailer (8.0.2) - actionpack (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activesupport (= 8.0.2) + actionmailer (8.0.2.1) + actionpack (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activesupport (= 8.0.2.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.2) - actionview (= 8.0.2) - activesupport (= 8.0.2) + actionpack (8.0.2.1) + actionview (= 8.0.2.1) + activesupport (= 8.0.2.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -40,15 +40,15 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.2) - actionpack (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + actiontext (8.0.2.1) + actionpack (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.2) - activesupport (= 8.0.2) + actionview (8.0.2.1) + activesupport (= 8.0.2.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -58,22 +58,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.0.2) - activesupport (= 8.0.2) + activejob (8.0.2.1) + activesupport (= 8.0.2.1) globalid (>= 0.3.6) - activemodel (8.0.2) - activesupport (= 8.0.2) - activerecord (8.0.2) - activemodel (= 8.0.2) - activesupport (= 8.0.2) + activemodel (8.0.2.1) + activesupport (= 8.0.2.1) + activerecord (8.0.2.1) + activemodel (= 8.0.2.1) + activesupport (= 8.0.2.1) timeout (>= 0.4.0) - activestorage (8.0.2) - actionpack (= 8.0.2) - activejob (= 8.0.2) - activerecord (= 8.0.2) - activesupport (= 8.0.2) + activestorage (8.0.2.1) + actionpack (= 8.0.2.1) + activejob (= 8.0.2.1) + activerecord (= 8.0.2.1) + activesupport (= 8.0.2.1) marcel (~> 1.0) - activesupport (8.0.2) + activesupport (8.0.2.1) base64 benchmark (>= 0.3) bigdecimal @@ -667,20 +667,20 @@ GEM rack (>= 1.3) rackup (2.2.1) rack (>= 3) - rails (8.0.2) - actioncable (= 8.0.2) - actionmailbox (= 8.0.2) - actionmailer (= 8.0.2) - actionpack (= 8.0.2) - actiontext (= 8.0.2) - actionview (= 8.0.2) - activejob (= 8.0.2) - activemodel (= 8.0.2) - activerecord (= 8.0.2) - activestorage (= 8.0.2) - activesupport (= 8.0.2) + rails (8.0.2.1) + actioncable (= 8.0.2.1) + actionmailbox (= 8.0.2.1) + actionmailer (= 8.0.2.1) + actionpack (= 8.0.2.1) + actiontext (= 8.0.2.1) + actionview (= 8.0.2.1) + activejob (= 8.0.2.1) + activemodel (= 8.0.2.1) + activerecord (= 8.0.2.1) + activestorage (= 8.0.2.1) + activesupport (= 8.0.2.1) bundler (>= 1.15.0) - railties (= 8.0.2) + railties (= 8.0.2.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -691,9 +691,9 @@ GEM rails-i18n (8.0.1) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.0.2) - actionpack (= 8.0.2) - activesupport (= 8.0.2) + railties (8.0.2.1) + actionpack (= 8.0.2.1) + activesupport (= 8.0.2.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) From 2112416761e4592d7fa5bdfa92b104953afe5dfa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:34:30 +0200 Subject: [PATCH 280/660] Update dependency @rails/ujs to v7.1.502 (#35772) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 10de8458f1d..d7fbcf09ef2 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^2.1.1", "@optimize-lodash/rollup-plugin": "^5.0.2", - "@rails/ujs": "7.1.501", + "@rails/ujs": "7.1.502", "@react-spring/web": "^9.7.5", "@reduxjs/toolkit": "^2.0.1", "@use-gesture/react": "^10.3.1", diff --git a/yarn.lock b/yarn.lock index 4ff3c9afc26..2c9c9c23661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2618,7 +2618,7 @@ __metadata: "@gamestdio/websocket": "npm:^0.3.2" "@github/webauthn-json": "npm:^2.1.1" "@optimize-lodash/rollup-plugin": "npm:^5.0.2" - "@rails/ujs": "npm:7.1.501" + "@rails/ujs": "npm:7.1.502" "@react-spring/web": "npm:^9.7.5" "@reduxjs/toolkit": "npm:^2.0.1" "@storybook/addon-a11y": "npm:^9.1.1" @@ -3109,10 +3109,10 @@ __metadata: languageName: node linkType: hard -"@rails/ujs@npm:7.1.501": - version: 7.1.501 - resolution: "@rails/ujs@npm:7.1.501" - checksum: 10c0/b75a30f36ff219264e0926da1ffcd14c2a5d6aee5be29da4dc81f9a45843875da79ac19cf7ed9a3f11a39084398d0ae4a75a8edb28ba94907db3081572af62b0 +"@rails/ujs@npm:7.1.502": + version: 7.1.502 + resolution: "@rails/ujs@npm:7.1.502" + checksum: 10c0/79b46e8abd03e3fc633d93cc4e4c23838c628b775802fb38c2ce68b0e609ce287a67b81db112a93cc78c07ec82ca3b4cf87e74eb556d35148ce5f64c8be9201f languageName: node linkType: hard From 734dbbcb14b9483f67c43d467a4324f554edf5b2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 07:35:12 +0000 Subject: [PATCH 281/660] Update dependency rubocop-rails to v2.33.3 (#35766) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a8374c0417f..5015fb527b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -719,7 +719,7 @@ GEM redis (4.8.1) redis-client (0.25.2) connection_pool - regexp_parser (2.11.1) + regexp_parser (2.11.2) reline (0.6.2) io-console (~> 0.5) request_store (1.7.0) @@ -787,7 +787,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.33.1) + rubocop-rails (2.33.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) From 9fc81adc7b931472042585cc75a3e67e6ac99925 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:51:51 +0200 Subject: [PATCH 282/660] Update opentelemetry-ruby (non-major) (#35777) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile | 6 +++--- Gemfile.lock | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 9b9bd40f2fa..b3d0a818d5e 100644 --- a/Gemfile +++ b/Gemfile @@ -109,10 +109,10 @@ group :opentelemetry do gem 'opentelemetry-instrumentation-active_job', '~> 0.8.0', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false - gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false - gem 'opentelemetry-instrumentation-faraday', '~> 0.27.0', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.24.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.28.0', require: false gem 'opentelemetry-instrumentation-http', '~> 0.25.0', require: false - gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.24.0', require: false gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false gem 'opentelemetry-instrumentation-pg', '~> 0.30.0', require: false gem 'opentelemetry-instrumentation-rack', '~> 0.26.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 5015fb527b8..464d5bd2c97 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -547,19 +547,19 @@ GEM opentelemetry-instrumentation-concurrent_ruby (0.22.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-excon (0.23.0) + opentelemetry-instrumentation-excon (0.24.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-faraday (0.27.0) + opentelemetry-instrumentation-faraday (0.28.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-http (0.25.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-http_client (0.23.0) + opentelemetry-instrumentation-http_client (0.24.0) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) - opentelemetry-instrumentation-net_http (0.23.0) + opentelemetry-instrumentation-net_http (0.23.1) opentelemetry-api (~> 1.0) opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-pg (0.30.1) @@ -1030,10 +1030,10 @@ DEPENDENCIES opentelemetry-instrumentation-active_job (~> 0.8.0) opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) - opentelemetry-instrumentation-excon (~> 0.23.0) - opentelemetry-instrumentation-faraday (~> 0.27.0) + opentelemetry-instrumentation-excon (~> 0.24.0) + opentelemetry-instrumentation-faraday (~> 0.28.0) opentelemetry-instrumentation-http (~> 0.25.0) - opentelemetry-instrumentation-http_client (~> 0.23.0) + opentelemetry-instrumentation-http_client (~> 0.24.0) opentelemetry-instrumentation-net_http (~> 0.23.0) opentelemetry-instrumentation-pg (~> 0.30.0) opentelemetry-instrumentation-rack (~> 0.26.0) From 93923a4af2d188cf5bf9cfba7695c2eb8b46289b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 14 Aug 2025 03:57:18 -0400 Subject: [PATCH 283/660] First pass coverage addition for antispam class (#35771) --- app/lib/antispam.rb | 12 ++++++++-- spec/lib/antispam_spec.rb | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 spec/lib/antispam_spec.rb diff --git a/app/lib/antispam.rb b/app/lib/antispam.rb index 69c862a5c10..7d8ed84d0e0 100644 --- a/app/lib/antispam.rb +++ b/app/lib/antispam.rb @@ -71,8 +71,16 @@ class Antispam end def report_if_needed!(account) - return if Report.unresolved.exists?(account: Account.representative, target_account: account) + return if system_reports.unresolved.exists?(target_account: account) - Report.create!(account: Account.representative, target_account: account, category: :spam, comment: 'Account automatically reported for posting a banned URL') + system_reports.create!( + category: :spam, + comment: 'Account automatically reported for posting a banned URL', + target_account: account + ) + end + + def system_reports + Account.representative.reports end end diff --git a/spec/lib/antispam_spec.rb b/spec/lib/antispam_spec.rb new file mode 100644 index 00000000000..869fd4c211d --- /dev/null +++ b/spec/lib/antispam_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Antispam do + describe '#local_preflight_check!' do + subject { described_class.new.local_preflight_check!(status) } + + let(:status) { Fabricate :status } + + context 'when there is no spammy text registered' do + it { is_expected.to be_nil } + end + + context 'with spammy text' do + before { redis.sadd 'antispam:spammy_texts', 'https://banned.example' } + + context 'when status matches' do + let(:status) { Fabricate :status, text: 'I use https://banned.example urls in my text' } + + it 'raises error and reports' do + expect { subject } + .to raise_error(described_class::SilentlyDrop) + .and change(spam_reports, :count).by(1) + end + + context 'when report already exists' do + before { Fabricate :report, account: Account.representative, target_account: status.account } + + it 'raises error and does not report' do + expect { subject } + .to raise_error(described_class::SilentlyDrop) + .and not_change(spam_reports, :count) + end + end + + def spam_reports + Account.representative.reports.where(target_account: status.account).spam + end + end + + context 'when status does not match' do + it { is_expected.to be_nil } + end + end + end +end From ac0581fd220b0c67d887de21773c78361dcbdf8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:36:38 +0200 Subject: [PATCH 284/660] Update devDependencies (non-major) (#35660) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Renaud Chaput --- .storybook/static/mockServiceWorker.js | 2 +- CHANGELOG.md | 2 - .../styles/mastodon/css_variables.scss | 3 +- yarn.lock | 349 +++++++++--------- 4 files changed, 170 insertions(+), 186 deletions(-) diff --git a/.storybook/static/mockServiceWorker.js b/.storybook/static/mockServiceWorker.js index de7bc0f292d..be4527c7ee5 100644 --- a/.storybook/static/mockServiceWorker.js +++ b/.storybook/static/mockServiceWorker.js @@ -7,7 +7,7 @@ * - Please do NOT modify this file. */ -const PACKAGE_VERSION = '2.10.2' +const PACKAGE_VERSION = '2.10.4' const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/CHANGELOG.md b/CHANGELOG.md index b3af469bb35..a6684e2067a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -583,7 +583,6 @@ The following changelog entries focus on changes visible to users, administrator You can now separately filter or drop notifications from people you don't follow, people who don't follow you, accounts created within the past 30 days, as well as unsolicited private mentions, and accounts limited by the moderation.\ Instead of being outright dropped, notifications that you chose to filter are put in a separate “Filtered notifications” box that you can review separately without it clogging your main notifications.\ This adds the following REST API endpoints: - - `GET /api/v2/notifications/policy`: https://docs.joinmastodon.org/methods/notifications/#get-policy - `PATCH /api/v2/notifications/policy`: https://docs.joinmastodon.org/methods/notifications/#update-the-filtering-policy-for-notifications - `GET /api/v1/notifications/requests`: https://docs.joinmastodon.org/methods/notifications/#get-requests @@ -595,7 +594,6 @@ The following changelog entries focus on changes visible to users, administrator - `GET /api/v1/notifications/requests/merged`: https://docs.joinmastodon.org/methods/notifications/#requests-merged In addition, accepting one or more notification requests generates a new streaming event: - - `notifications_merged`: an event of this type indicates accepted notification requests have finished merging, and the notifications list should be refreshed - **Add notifications of severed relationships** (#27511, #29665, #29668, #29670, #29700, #29714, #29712, and #29731 by @ClearlyClaire and @Gargron)\ diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/css_variables.scss index 16ed033b968..7f27c12f774 100644 --- a/app/javascript/styles/mastodon/css_variables.scss +++ b/app/javascript/styles/mastodon/css_variables.scss @@ -5,7 +5,8 @@ :root { --dropdown-border-color: #{lighten($ui-base-color, 4%)}; --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)}; - --dropdown-shadow: 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)}, + --dropdown-shadow: + 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)}, 0 8px 10px -6px #{rgba($base-shadow-color, 0.25)}; --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)}; --modal-background-variant-color: #{rgba($ui-base-color, 0.7)}; diff --git a/yarn.lock b/yarn.lock index 2c9c9c23661..0293865ca29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1295,7 +1295,7 @@ __metadata: languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^4.0.2, @csstools/media-query-list-parser@npm:^4.0.3": +"@csstools/media-query-list-parser@npm:^4.0.3": version: 4.0.3 resolution: "@csstools/media-query-list-parser@npm:4.0.3" peerDependencies: @@ -2262,17 +2262,14 @@ __metadata: linkType: hard "@formatjs/cli@npm:^6.1.1": - version: 6.3.14 - resolution: "@formatjs/cli@npm:6.3.14" + version: 6.7.2 + resolution: "@formatjs/cli@npm:6.7.2" peerDependencies: - "@glimmer/env": ^0.1.7 - "@glimmer/reference": ^0.91.1 || ^0.92.0 || ^0.93.0 - "@glimmer/syntax": ^0.92.0 || ^0.93.0 - "@glimmer/validator": ^0.92.0 || ^0.93.0 - "@vue/compiler-core": ^3.4.0 - content-tag: ^2.0.1 || ^3.0.0 - ember-template-recast: ^6.1.4 - vue: ^3.4.0 + "@glimmer/syntax": ^0.94.9 + "@vue/compiler-core": ^3.5.12 + content-tag: ^3.0.0 + ember-template-recast: ^6.1.5 + vue: ^3.5.12 peerDependenciesMeta: "@glimmer/env": optional: true @@ -2292,7 +2289,7 @@ __metadata: optional: true bin: formatjs: bin/formatjs - checksum: 10c0/b4c83ed7fdc8dcd48b2f48fa9cca65b52472fb096eb028517a872f8a71ed3964f4b0a6bbc607f821a9504f396fe7341ef4d9ad44a381a37f280ed7547de66f41 + checksum: 10c0/fbcb1d35915a5b1542e4fa3f3f79e23fa5988681e6c238d7e8a3d4d32e18df3e5c36cebe01b04e011bb5c91f96f4d08d36af750259aa799d3e81943084e2f0c2 languageName: node linkType: hard @@ -2595,12 +2592,10 @@ __metadata: languageName: node linkType: hard -"@keyv/serialize@npm:^1.0.3": - version: 1.0.3 - resolution: "@keyv/serialize@npm:1.0.3" - dependencies: - buffer: "npm:^6.0.3" - checksum: 10c0/24a257870b0548cfe430680c2ae1641751e6a6ec90c573eaf51bfe956839b6cfa462b4d2827157363b6d620872d32d69fa2f37210a864ba488f8ec7158436398 +"@keyv/serialize@npm:^1.1.0": + version: 1.1.0 + resolution: "@keyv/serialize@npm:1.1.0" + checksum: 10c0/30e34adf4fff52374c2c531e3ff215eed6414350ee56eebcb98c422feaff171b4900c73082a72399a6bfbc5ce60fbb6f968594110c960521923499146bc68c20 languageName: node linkType: hard @@ -3476,37 +3471,37 @@ __metadata: linkType: hard "@storybook/addon-a11y@npm:^9.1.1": - version: 9.1.1 - resolution: "@storybook/addon-a11y@npm:9.1.1" + version: 9.1.2 + resolution: "@storybook/addon-a11y@npm:9.1.2" dependencies: "@storybook/global": "npm:^5.0.0" axe-core: "npm:^4.2.0" peerDependencies: - storybook: ^9.1.1 - checksum: 10c0/bf5eba0a51ffec20c8c4432985494295115bcf48e0807e4ca21314845d4aaaaaae9122d4be4f78a2fc4c15caa5e1207c01e118724c2cbecbd80aa8a5f6826924 + storybook: ^9.1.2 + checksum: 10c0/36fc399db0af0acff6542c7e2aa54ef715dcff0e8a7f12fec3468dfdee2d83651c1d02c7226a420269d18f522dbaa96fa6faacb9c647c2a65518cece9d38582b languageName: node linkType: hard "@storybook/addon-docs@npm:^9.1.1": - version: 9.1.1 - resolution: "@storybook/addon-docs@npm:9.1.1" + version: 9.1.2 + resolution: "@storybook/addon-docs@npm:9.1.2" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:9.1.1" + "@storybook/csf-plugin": "npm:9.1.2" "@storybook/icons": "npm:^1.4.0" - "@storybook/react-dom-shim": "npm:9.1.1" + "@storybook/react-dom-shim": "npm:9.1.2" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.1 - checksum: 10c0/92b3ac089a38b892319de5ec02ca4ae477e38d88aef8561f6cbb1f15dd69ad856c194fca4514c783983baba063f428994a7d7f2aed98204b931f8e684e681194 + storybook: ^9.1.2 + checksum: 10c0/b17a3a8d3b9ad70f7cd8f8295f8cf7a10a6c39ab69e752f3acfb2260809055f85088a6382a2fc729b48860854b94a67faca239ff00bbe0e7e9553113cb2542fb languageName: node linkType: hard "@storybook/addon-vitest@npm:^9.1.1": - version: 9.1.1 - resolution: "@storybook/addon-vitest@npm:9.1.1" + version: 9.1.2 + resolution: "@storybook/addon-vitest@npm:9.1.2" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.4.0" @@ -3515,7 +3510,7 @@ __metadata: peerDependencies: "@vitest/browser": ^3.0.0 "@vitest/runner": ^3.0.0 - storybook: ^9.1.1 + storybook: ^9.1.2 vitest: ^3.0.0 peerDependenciesMeta: "@vitest/browser": @@ -3524,31 +3519,31 @@ __metadata: optional: true vitest: optional: true - checksum: 10c0/a4770aad2f3e4ae10e3d7ae7083354e98287d095644aae87af62c59c9a97ec7e57cf25620c32e2e5f9261a3686a6efabea0a830061500f63e78a49a2bac6f130 + checksum: 10c0/75eacf6757d9ab6d0ad8c496d55a1548ab67f098a7ceb431900e8b6eb98ac8ac2235382a44a26765607be07e1b09c0e2a34ee9b846c234da6073d38aabc0ea4d languageName: node linkType: hard -"@storybook/builder-vite@npm:9.1.1": - version: 9.1.1 - resolution: "@storybook/builder-vite@npm:9.1.1" +"@storybook/builder-vite@npm:9.1.2": + version: 9.1.2 + resolution: "@storybook/builder-vite@npm:9.1.2" dependencies: - "@storybook/csf-plugin": "npm:9.1.1" + "@storybook/csf-plugin": "npm:9.1.2" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^9.1.1 + storybook: ^9.1.2 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/334235a64e05d6fb1e1cdf23f41ac211d1e55429e425e1aea33d9b4469503aa328eb5d9cad24e6328b392098afb2c884943d2c304c1eb3cf55ce25dcc3ad4414 + checksum: 10c0/2411e593903bc61336f2a2c6f48e7314dcc8c776346eff0f6fec28e9fc8e3a90d3f8d6561f30d1caf490349d34c7690f8addf4c56fa1fd778f0dfda49cf3aa97 languageName: node linkType: hard -"@storybook/csf-plugin@npm:9.1.1": - version: 9.1.1 - resolution: "@storybook/csf-plugin@npm:9.1.1" +"@storybook/csf-plugin@npm:9.1.2": + version: 9.1.2 + resolution: "@storybook/csf-plugin@npm:9.1.2" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^9.1.1 - checksum: 10c0/d29b5685ef79eacbcd891977f95a58238f104004b014f88ee59eab6e5995df31010435aa222a27cdf54056accc43239c44f7e8e461c263c60b09d7d2383be8b8 + storybook: ^9.1.2 + checksum: 10c0/a145da545844b9b2af345d43d8f2c035dd801bd6414b4a9a2037dfa950250d08133a956226c49c36a79ffda171ad9388a0f1621c04cfed77e5c342817f4a275e languageName: node linkType: hard @@ -3569,25 +3564,25 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:9.1.1": - version: 9.1.1 - resolution: "@storybook/react-dom-shim@npm:9.1.1" +"@storybook/react-dom-shim@npm:9.1.2": + version: 9.1.2 + resolution: "@storybook/react-dom-shim@npm:9.1.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.1 - checksum: 10c0/ea2725719e04871b56c0a3755a7791034abd1d8ebb51392ed5f1eb2d21aee873444080509efe69c919e43121c299e06e2266d603478d059efe38762b0ae96ae5 + storybook: ^9.1.2 + checksum: 10c0/7547cb0fdcf8098c00017cbfb501f11a34ae73b9e13984520b8143e709b4b8ec1acf7fed9ce51dbb5b5af5dcd657396da17ef1f262f60efdd4956f3e26b3c704 languageName: node linkType: hard "@storybook/react-vite@npm:^9.1.1": - version: 9.1.1 - resolution: "@storybook/react-vite@npm:9.1.1" + version: 9.1.2 + resolution: "@storybook/react-vite@npm:9.1.2" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:9.1.1" - "@storybook/react": "npm:9.1.1" + "@storybook/builder-vite": "npm:9.1.2" + "@storybook/react": "npm:9.1.2" find-up: "npm:^7.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -3596,27 +3591,27 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.1 + storybook: ^9.1.2 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/fa79e5a9be38f229a8cc0db85452e651081740cd3f9e6951c0fc3e195437124f8a792494b90a613c673ef9410d565d5f459276e157f073367ea28f0293d10167 + checksum: 10c0/afed36a0219599577b255042a9c9ac1af0106003ac37e2e9b5846a42b4e8729ff0e8b7ae6018d3ac85b69e918c2a20d554cd484de7345e5fb4974df92914e059 languageName: node linkType: hard -"@storybook/react@npm:9.1.1": - version: 9.1.1 - resolution: "@storybook/react@npm:9.1.1" +"@storybook/react@npm:9.1.2": + version: 9.1.2 + resolution: "@storybook/react@npm:9.1.2" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:9.1.1" + "@storybook/react-dom-shim": "npm:9.1.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^9.1.1 + storybook: ^9.1.2 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/343598e9c1bb2e53b7d6eaaa50b5c024c109fb825f6f4869a82da0ccd428400e5a93eba494e810f6bfbf6bff9b73810595ab8ffdd1530df8b4f2b20e617623e3 + checksum: 10c0/ea3d9fa25825fde5022942579db9a57154e57cb37244b0d54bb189679a37f20c20906041898f5fcfd4867043ea789384c2d968f334f9d0c55958add0b18fb6ea languageName: node linkType: hard @@ -5402,13 +5397,6 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -5528,16 +5516,6 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^6.0.3": - version: 6.0.3 - resolution: "buffer@npm:6.0.3" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.2.1" - checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 - languageName: node - linkType: hard - "bufferutil@npm:^4.0.7": version: 4.0.9 resolution: "bufferutil@npm:4.0.9" @@ -5582,13 +5560,13 @@ __metadata: languageName: node linkType: hard -"cacheable@npm:^1.9.0": - version: 1.9.0 - resolution: "cacheable@npm:1.9.0" +"cacheable@npm:^1.10.3": + version: 1.10.3 + resolution: "cacheable@npm:1.10.3" dependencies: - hookified: "npm:^1.8.2" - keyv: "npm:^5.3.3" - checksum: 10c0/1ca171dd2f7c3a929d84b3f94e712cb8fbbfb7c636f19ba01657cf89c6ea4316f21f2beb6c696fa5c87d42f52620f4a7c2dfecb41bf71ee3b249d539895256c4 + hookified: "npm:^1.10.0" + keyv: "npm:^5.4.0" + checksum: 10c0/eaa483140133b58dbd5c9811688137016c263a874886ce98f9590d252fb92859633929b36aa4c05ec67aee70cc1c9ba9aa1be02e53365604dd0202a88e44fef8 languageName: node linkType: hard @@ -5678,10 +5656,10 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.4.1": - version: 5.4.1 - resolution: "chalk@npm:5.4.1" - checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef +"chalk@npm:^5.5.0": + version: 5.5.0 + resolution: "chalk@npm:5.5.0" + checksum: 10c0/23063b544f7c2fe57d25ff814807de561f8adfff72e4f0051051eaa606f772586470507ccd38d89166300eeaadb0164acde8bb8a0716a0f2d56ccdf3761d5e4f languageName: node linkType: hard @@ -5864,10 +5842,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:^13.1.0": - version: 13.1.0 - resolution: "commander@npm:13.1.0" - checksum: 10c0/7b8c5544bba704fbe84b7cab2e043df8586d5c114a4c5b607f83ae5060708940ed0b5bd5838cf8ce27539cde265c1cbd59ce3c8c6b017ed3eec8943e3a415164 +"commander@npm:^14.0.0": + version: 14.0.0 + resolution: "commander@npm:14.0.0" + checksum: 10c0/73c4babfa558077868d84522b11ef56834165d472b9e86a634cd4c3ae7fc72d59af6377d8878e06bd570fe8f3161eced3cbe383c38f7093272bb65bd242b595b languageName: node linkType: hard @@ -6217,7 +6195,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -7309,9 +7287,9 @@ __metadata: linkType: hard "fake-indexeddb@npm:^6.0.1": - version: 6.0.1 - resolution: "fake-indexeddb@npm:6.0.1" - checksum: 10c0/60f4ccdfd5ecb37bb98019056c688366847840cce7146e0005c5ca54823238455403b0a8803b898a11cf80f6147b1bb553457c6af427a644a6e64566cdbe42ec + version: 6.1.0 + resolution: "fake-indexeddb@npm:6.1.0" + checksum: 10c0/f2bae6cf3ed38619ccc536ee1c0d72a1ba721da24d840e9c0993800c031a5c1d8e61adc00ea31f0c2a2380447c57bc953bd4128b08dabf559c7cdf03c8893239 languageName: node linkType: hard @@ -7412,12 +7390,12 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^10.0.8": - version: 10.1.0 - resolution: "file-entry-cache@npm:10.1.0" +"file-entry-cache@npm:^10.1.3": + version: 10.1.3 + resolution: "file-entry-cache@npm:10.1.3" dependencies: - flat-cache: "npm:^6.1.9" - checksum: 10c0/9464848577f68809237ffdf11c09a285d930ed4cda8cf392ee44ac8fa70658e41bbe60d08d883285d6af798a26f008dd8dfa94a31d42ecf1ba5a7bcd6dd8b07d + flat-cache: "npm:^6.1.12" + checksum: 10c0/7365c3358698f5ccf085c164989ad48f1d9341157895577d7c34bf4f9c258d2410f4d2c749c73232111aab9e2fdd632ef6941f2c2d3acdd3a7f3daf2c840bd54 languageName: node linkType: hard @@ -7501,14 +7479,14 @@ __metadata: languageName: node linkType: hard -"flat-cache@npm:^6.1.9": - version: 6.1.9 - resolution: "flat-cache@npm:6.1.9" +"flat-cache@npm:^6.1.12": + version: 6.1.12 + resolution: "flat-cache@npm:6.1.12" dependencies: - cacheable: "npm:^1.9.0" + cacheable: "npm:^1.10.3" flatted: "npm:^3.3.3" - hookified: "npm:^1.8.2" - checksum: 10c0/ca9241fab68154e9a4efe8875beff43cb7b2de65628d15dcf8488dc69bca3f8e98085a707c3395d03b1022f586364b0f37aa5dd5cc085a8cf7481516757ac864 + hookified: "npm:^1.10.0" + checksum: 10c0/9c7e22ebc68edef373170a2171fe4d7d68eecd18953fbd16f5f3e9c32c36491b61ab0468e07242a5bbed58b36d139a41d3c33b23fc013fc535a41b00546c14f0 languageName: node linkType: hard @@ -8021,10 +7999,10 @@ __metadata: languageName: node linkType: hard -"hookified@npm:^1.8.2": - version: 1.9.0 - resolution: "hookified@npm:1.9.0" - checksum: 10c0/28145882504e40ef58f328554966520c56dc2aaca92457996a5cd68fda6f92e38d3ca283e7ebbf3d71f36cda887234ce580abfa6532ade906be7810a812f15fa +"hookified@npm:^1.10.0": + version: 1.11.0 + resolution: "hookified@npm:1.11.0" + checksum: 10c0/c74d28e90c55247ffc036a5cabd0681e715f50db8c6b1f47e10253b577e355f3dcd71bb96565a23467f72a8695ec2d482e5801e2d9d99ac24bdc179fef635ba0 languageName: node linkType: hard @@ -8146,13 +8124,6 @@ __metadata: languageName: node linkType: hard -"ieee754@npm:^1.2.1": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - "ignore@npm:^5.2.0": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -8160,7 +8131,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^7.0.0, ignore@npm:^7.0.3": +"ignore@npm:^7.0.0, ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d @@ -8986,12 +8957,12 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^5.3.3": - version: 5.3.3 - resolution: "keyv@npm:5.3.3" +"keyv@npm:^5.4.0": + version: 5.5.0 + resolution: "keyv@npm:5.5.0" dependencies: - "@keyv/serialize": "npm:^1.0.3" - checksum: 10c0/6b9064d061784e80a5dc500453b03cacb099f06fddd0346063519371d563a66771237e04467f3387b60d8a33a3c99864288991274921fb1338c6adf638574924 + "@keyv/serialize": "npm:^1.1.0" + checksum: 10c0/2db63fd2abcdf71929f032569673b6edd0de111edb012411658e2589dc5f49793a98aecd56c67fafda3f90a31f32e35555a97f8621040728260c66ad8daeea48 languageName: node linkType: hard @@ -9016,6 +8987,13 @@ __metadata: languageName: node linkType: hard +"known-css-properties@npm:^0.37.0": + version: 0.37.0 + resolution: "known-css-properties@npm:0.37.0" + checksum: 10c0/e0ec08cae580e8833254b690971f73ec6f78ac461820a3c755b4a0b62c5b871501753b4aa60b30576a0f621ba44b231235cf9f35ab89e2e7de5448dfe0600241 + languageName: node + linkType: hard + "lande@npm:^1.0.10": version: 1.0.10 resolution: "lande@npm:1.0.10" @@ -9073,28 +9051,28 @@ __metadata: linkType: hard "lint-staged@npm:^16.0.0": - version: 16.0.0 - resolution: "lint-staged@npm:16.0.0" + version: 16.1.5 + resolution: "lint-staged@npm:16.1.5" dependencies: - chalk: "npm:^5.4.1" - commander: "npm:^13.1.0" - debug: "npm:^4.4.0" + chalk: "npm:^5.5.0" + commander: "npm:^14.0.0" + debug: "npm:^4.4.1" lilconfig: "npm:^3.1.3" - listr2: "npm:^8.3.3" + listr2: "npm:^9.0.1" micromatch: "npm:^4.0.8" - nano-spawn: "npm:^1.0.0" + nano-spawn: "npm:^1.0.2" pidtree: "npm:^0.6.0" string-argv: "npm:^0.3.2" - yaml: "npm:^2.7.1" + yaml: "npm:^2.8.1" bin: lint-staged: bin/lint-staged.js - checksum: 10c0/8778dbe7892bbf14e378d612d1649c1e3df38a8ddf14cf35962b6e8a962be72efb1ebb48a697e38366be97d25b8d2599cad3c26ac5afc0d0460452484e27924d + checksum: 10c0/771e7be871f1d74ed09ef4e4eae5f835ed962965db7709be26cccf71bef8fed34f8d5d92f193b2a6fad32c12d955850aa74008e6180fabea8a7a6666cba2ac39 languageName: node linkType: hard -"listr2@npm:^8.3.3": - version: 8.3.3 - resolution: "listr2@npm:8.3.3" +"listr2@npm:^9.0.1": + version: 9.0.1 + resolution: "listr2@npm:9.0.1" dependencies: cli-truncate: "npm:^4.0.0" colorette: "npm:^2.0.20" @@ -9102,7 +9080,7 @@ __metadata: log-update: "npm:^6.1.0" rfdc: "npm:^1.4.1" wrap-ansi: "npm:^9.0.0" - checksum: 10c0/0792f8a7fd482fa516e21689e012e07081cab3653172ca606090622cfa0024c784a1eba8095a17948a0e9a4aa98a80f7c9c90f78a0dd35173d6802f9cc123a82 + checksum: 10c0/73462e84a3c4f05de5a3cdea5eaa0209c6ab04a2fdb4046545049806e9ba17b6ee84a097ebf7ffc0e903b0f2a9094c0c480cd2f2bb21d7d21e20969e17a3c32b languageName: node linkType: hard @@ -9588,8 +9566,8 @@ __metadata: linkType: hard "msw@npm:^2.10.2": - version: 2.10.2 - resolution: "msw@npm:2.10.2" + version: 2.10.4 + resolution: "msw@npm:2.10.4" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.1" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -9616,7 +9594,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10c0/fb44961e17e12864b4764b4c015f6ce7c907081f8dcd237ecd635eab00b787847406fbd36a2bcf2ef4c21114a3610ac03c7f93f3080f509a69b0c1c5285fd683 + checksum: 10c0/48dff36c7cf8ad504bb8f8a2ff6946cf5727752c140681eb68da00991d9fe56224bace970476771a9fffae136256c389c591d71368a6967d053dbad6b6df3346 languageName: node linkType: hard @@ -9627,10 +9605,10 @@ __metadata: languageName: node linkType: hard -"nano-spawn@npm:^1.0.0": - version: 1.0.1 - resolution: "nano-spawn@npm:1.0.1" - checksum: 10c0/e03edc6971f653bc4651f2413b2011772a7c18797c0a4e986ff8eaea3adf4f017697d4d494ffb4ba6bce907b42abbeb0f7f681dbf336c84a324c940fb64c1dec +"nano-spawn@npm:^1.0.2": + version: 1.0.2 + resolution: "nano-spawn@npm:1.0.2" + checksum: 10c0/d8cec78f127a44aa5e38be01746b3d963a8dcf8b00b4a05bf259b5369af2225b8c7dc9d12517050b90234e5c3eeea4ece5d18a5f9c6c3462b56f9f595f07e632 languageName: node linkType: hard @@ -10278,8 +10256,8 @@ __metadata: linkType: hard "pino-pretty@npm:^13.0.0": - version: 13.0.0 - resolution: "pino-pretty@npm:13.0.0" + version: 13.1.1 + resolution: "pino-pretty@npm:13.1.1" dependencies: colorette: "npm:^2.0.7" dateformat: "npm:^4.6.3" @@ -10291,12 +10269,12 @@ __metadata: on-exit-leak-free: "npm:^2.1.0" pino-abstract-transport: "npm:^2.0.0" pump: "npm:^3.0.0" - secure-json-parse: "npm:^2.4.0" + secure-json-parse: "npm:^4.0.0" sonic-boom: "npm:^4.0.1" - strip-json-comments: "npm:^3.1.1" + strip-json-comments: "npm:^5.0.2" bin: pino-pretty: bin.js - checksum: 10c0/015dac25006c1b9820b9e01fccb8a392a019e12b30e6bfc3f3f61ecca8dbabcd000a8f3f64410b620b7f5d08579ba85e6ef137f7fbeaad70d46397a97a5f75ea + checksum: 10c0/845c07afd3d73cb96ad2049cfa7fca12b8280a51e30d6db8b490857690637556bb8e7f05b2fa640b3e4a7edd9b1369110042d670fda743ef98fe3be29876c8c7 languageName: node linkType: hard @@ -10328,27 +10306,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.54.1": - version: 1.54.1 - resolution: "playwright-core@npm:1.54.1" +"playwright-core@npm:1.54.2": + version: 1.54.2 + resolution: "playwright-core@npm:1.54.2" bin: playwright-core: cli.js - checksum: 10c0/b821262b024d7753b1bfa71eb2bc99f2dda12a869d175b2e1bc6ac2764bd661baf36d9d42f45caf622854ad7e4a6077b9b57014c74bb5a78fe339c9edf1c9019 + checksum: 10c0/44850e20bf35237c8c3dedf1096c655f8af939dde53c5469f72cae3dd744966858a302419b909a73d7a2093323123e7ebcc0fdd55151b4193afb7812c1fd2c88 languageName: node linkType: hard "playwright@npm:^1.54.1": - version: 1.54.1 - resolution: "playwright@npm:1.54.1" + version: 1.54.2 + resolution: "playwright@npm:1.54.2" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.54.1" + playwright-core: "npm:1.54.2" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/c5fedae31a03a1f4c4846569aef3ffb98da23000a4d255abfc8c2ede15b43cc7cd87b80f6fa078666c030373de8103787cf77ef7653ae9458aabbbd4320c2599 + checksum: 10c0/6f642fa70179eee5d5bf8a90df2a6147c9638ff926f4f3ad0a0517396b8a3fe00ccebf13377e032a75b3f0b2610ec1562293e0cfc3bde234181c7a50af8af80a languageName: node linkType: hard @@ -10772,7 +10750,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.3, postcss@npm:^8.5.6": +"postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -10821,11 +10799,11 @@ __metadata: linkType: hard "prettier@npm:^3.3.3": - version: 3.4.2 - resolution: "prettier@npm:3.4.2" + version: 3.6.2 + resolution: "prettier@npm:3.6.2" bin: prettier: bin/prettier.cjs - checksum: 10c0/99e076a26ed0aba4ebc043880d0f08bbb8c59a4c6641cdee6cdadf2205bdd87aa1d7823f50c3aea41e015e99878d37c58d7b5f0e663bba0ef047f94e36b96446 + checksum: 10c0/488cb2f2b99ec13da1e50074912870217c11edaddedeadc649b1244c749d15ba94e846423d062e2c4c9ae683e2d65f754de28889ba06e697ac4f988d44f45812 languageName: node linkType: hard @@ -12030,10 +12008,10 @@ __metadata: languageName: node linkType: hard -"secure-json-parse@npm:^2.4.0": - version: 2.7.0 - resolution: "secure-json-parse@npm:2.7.0" - checksum: 10c0/f57eb6a44a38a3eeaf3548228585d769d788f59007454214fab9ed7f01fbf2e0f1929111da6db28cf0bcc1a2e89db5219a59e83eeaec3a54e413a0197ce879e4 +"secure-json-parse@npm:^4.0.0": + version: 4.0.0 + resolution: "secure-json-parse@npm:4.0.0" + checksum: 10c0/1a298cf00e1de91e833cee5eb406d6e77fb2f7eca9bef3902047d49e7f5d3e6c21b5de61ff73466c831e716430bfe87d732a6e645a7dabb5f1e8a8e4d3e15eb4 languageName: node linkType: hard @@ -12527,8 +12505,8 @@ __metadata: linkType: hard "storybook@npm:^9.1.1": - version: 9.1.1 - resolution: "storybook@npm:9.1.1" + version: 9.1.2 + resolution: "storybook@npm:9.1.2" dependencies: "@storybook/global": "npm:^5.0.0" "@testing-library/jest-dom": "npm:^6.6.3" @@ -12549,7 +12527,7 @@ __metadata: optional: true bin: storybook: ./bin/index.cjs - checksum: 10c0/efd2665547dc6bfd4cabd00ee1a6368c7cec27a3e4c16bc875ae06bb5d9ee74120c11102699165530f31fe8becb47212d80632b1fefcafa60fef94b3cd0bf50f + checksum: 10c0/3a575f94913f9000a3591e5c685f4eabf75fa78ce306f8b0d48e9c72e46028df31f6d15955b8a338be2bf48dadca6550b65782783d8b3cb4b737ba9f3887d007 languageName: node linkType: hard @@ -12757,6 +12735,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:^5.0.2": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10c0/daaf20b29f69fb51112698f4a9a662490dbb78d5baf6127c75a0a83c2ac6c078a8c0f74b389ad5e0519d6fc359c4a57cb9971b1ae201aef62ce45a13247791e0 + languageName: node + linkType: hard + "strip-literal@npm:^3.0.0": version: 3.0.0 resolution: "strip-literal@npm:3.0.0" @@ -12850,12 +12835,12 @@ __metadata: linkType: hard "stylelint@npm:^16.19.1": - version: 16.19.1 - resolution: "stylelint@npm:16.19.1" + version: 16.23.1 + resolution: "stylelint@npm:16.23.1" dependencies: - "@csstools/css-parser-algorithms": "npm:^3.0.4" - "@csstools/css-tokenizer": "npm:^3.0.3" - "@csstools/media-query-list-parser": "npm:^4.0.2" + "@csstools/css-parser-algorithms": "npm:^3.0.5" + "@csstools/css-tokenizer": "npm:^3.0.4" + "@csstools/media-query-list-parser": "npm:^4.0.3" "@csstools/selector-specificity": "npm:^5.0.0" "@dual-bundle/import-meta-resolve": "npm:^4.1.0" balanced-match: "npm:^2.0.0" @@ -12863,24 +12848,24 @@ __metadata: cosmiconfig: "npm:^9.0.0" css-functions-list: "npm:^3.2.3" css-tree: "npm:^3.1.0" - debug: "npm:^4.3.7" + debug: "npm:^4.4.1" fast-glob: "npm:^3.3.3" fastest-levenshtein: "npm:^1.0.16" - file-entry-cache: "npm:^10.0.8" + file-entry-cache: "npm:^10.1.3" global-modules: "npm:^2.0.0" globby: "npm:^11.1.0" globjoin: "npm:^0.1.4" html-tags: "npm:^3.3.1" - ignore: "npm:^7.0.3" + ignore: "npm:^7.0.5" imurmurhash: "npm:^0.1.4" is-plain-object: "npm:^5.0.0" - known-css-properties: "npm:^0.36.0" + known-css-properties: "npm:^0.37.0" mathml-tag-names: "npm:^2.1.3" meow: "npm:^13.2.0" micromatch: "npm:^4.0.8" normalize-path: "npm:^3.0.0" picocolors: "npm:^1.1.1" - postcss: "npm:^8.5.3" + postcss: "npm:^8.5.6" postcss-resolve-nested-selector: "npm:^0.1.6" postcss-safe-parser: "npm:^7.0.1" postcss-selector-parser: "npm:^7.1.0" @@ -12893,7 +12878,7 @@ __metadata: write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10c0/e633f323ff730e8f2ac982067e4caa9a6c98b81a519e7adff96fa7a7d047f68a24c0dd2d81f3511b0943d99c915f20f19da911d16d47336705ea70d46e960c89 + checksum: 10c0/18d01587396cce68b59e4a89a7c89d5eb7e76ee7cc27dd109b0f8f241625eb0ffe87763f67b2d20df0f23a243443591fa2514300311a48a945bd6a3bc14db36b languageName: node linkType: hard @@ -14545,12 +14530,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.7.1": - version: 2.8.0 - resolution: "yaml@npm:2.8.0" +"yaml@npm:^2.8.1": + version: 2.8.1 + resolution: "yaml@npm:2.8.1" bin: yaml: bin.mjs - checksum: 10c0/f6f7310cf7264a8107e72c1376f4de37389945d2fb4656f8060eca83f01d2d703f9d1b925dd8f39852a57034fafefde6225409ddd9f22aebfda16c6141b71858 + checksum: 10c0/7c587be00d9303d2ae1566e03bc5bc7fe978ba0d9bf39cc418c3139d37929dfcb93a230d9749f2cb578b6aa5d9ebebc322415e4b653cb83acd8bc0bc321707f3 languageName: node linkType: hard From b0ce1ce49dd48a47439a3127bbf12b44d7c30cf1 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 14 Aug 2025 15:35:19 +0200 Subject: [PATCH 285/660] Fix `tootctl admin create` not bypassing reserved username checks (#35779) --- app/models/account.rb | 2 +- spec/lib/mastodon/cli/accounts_spec.rb | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index c23549c22d9..5fa1f0cebf6 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -116,7 +116,7 @@ class Account < ApplicationRecord # Local user validations validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && !actor_type_application? } - validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? } + validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? && !user&.bypass_registration_checks } validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index e60f5a0cf71..111703a18bb 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -32,6 +32,7 @@ RSpec.describe Mastodon::CLI::Accounts do describe '#create' do let(:action) { :create } + let(:username) { 'tootctl_username' } shared_examples 'a new user with given email address and username' do it 'creates user and accounts from options and displays success message' do @@ -48,18 +49,24 @@ RSpec.describe Mastodon::CLI::Accounts do end def account_from_options - Account.find_local('tootctl_username') + Account.find_local(username) end end context 'when required USERNAME and --email are provided' do - let(:arguments) { ['tootctl_username'] } + let(:arguments) { [username] } context 'with USERNAME and --email only' do let(:options) { { email: 'tootctl@example.com' } } it_behaves_like 'a new user with given email address and username' + context 'with a reserved username' do + let(:username) { 'security' } + + it_behaves_like 'a new user with given email address and username' + end + context 'with invalid --email value' do let(:options) { { email: 'invalid' } } From a2cddb9eac8e654edb504c2f6ce9599b337d706d Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 14 Aug 2025 15:58:25 +0200 Subject: [PATCH 286/660] Disallow making private posts quotable (#35780) --- .../status/interaction_policy_concern.rb | 8 ++++++ .../v1/statuses/interaction_policies_spec.rb | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/app/models/concerns/status/interaction_policy_concern.rb b/app/models/concerns/status/interaction_policy_concern.rb index 6ad047fd8d5..1f57ccd6bbc 100644 --- a/app/models/concerns/status/interaction_policy_concern.rb +++ b/app/models/concerns/status/interaction_policy_concern.rb @@ -10,6 +10,10 @@ module Status::InteractionPolicyConcern followed: (1 << 3), }.freeze + included do + before_validation :downgrade_quote_policy, if: -> { local? && !distributable? } + end + def quote_policy_as_keys(kind) case kind when :automatic @@ -52,4 +56,8 @@ module Status::InteractionPolicyConcern :denied end + + def downgrade_quote_policy + self.quote_approval_policy = 0 + end end diff --git a/spec/requests/api/v1/statuses/interaction_policies_spec.rb b/spec/requests/api/v1/statuses/interaction_policies_spec.rb index f2d7eb856e4..6b988bb523d 100644 --- a/spec/requests/api/v1/statuses/interaction_policies_spec.rb +++ b/spec/requests/api/v1/statuses/interaction_policies_spec.rb @@ -90,5 +90,31 @@ RSpec.describe 'Interaction policies', feature: :outgoing_quotes do .to_not have_enqueued_sidekiq_job end end + + context 'when trying to change the interaction policy of a private post' do + let(:status) { Fabricate(:status, account: user.account, visibility: :private) } + let(:params) { { quote_approval_policy: 'public' } } + + it 'keeps the interaction policy, returns the status, and does not schedule distribution jobs' do + expect { subject } + .to_not(change { status.reload.quote_approval_policy }.from(0)) + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to include( + 'quote_approval' => match( + 'automatic' => [], + 'manual' => [], + 'current_user' => 'automatic' + ) + ) + + expect(DistributionWorker) + .to_not have_enqueued_sidekiq_job + expect(ActivityPub::StatusUpdateDistributionWorker) + .to_not have_enqueued_sidekiq_job + end + end end end From 651e51a82eba542c4d8c3fec5edd16420ea1ff3e Mon Sep 17 00:00:00 2001 From: Echo Date: Thu, 14 Aug 2025 17:04:32 +0200 Subject: [PATCH 287/660] Allow editing status quote policy (#35762) --- .../mastodon/actions/statuses_typed.ts | 11 +- app/javascript/mastodon/api/statuses.ts | 21 +- app/javascript/mastodon/api_types/quotes.ts | 12 +- app/javascript/mastodon/api_types/statuses.ts | 9 +- .../mastodon/components/dropdown/index.tsx | 114 +++++++ .../mastodon/components/dropdown_selector.tsx | 6 +- .../mastodon/components/status_action_bar.jsx | 12 +- .../mastodon/containers/status_container.jsx | 4 + .../compose/components/privacy_dropdown.jsx | 2 +- .../features/status/components/action_bar.jsx | 12 +- .../mastodon/features/status/index.jsx | 6 + .../features/ui/components/modal_root.jsx | 2 + .../ui/components/visibility_modal.tsx | 293 ++++++++++++++++++ app/javascript/mastodon/locales/en.json | 16 +- app/javascript/mastodon/reducers/statuses.js | 17 + .../mastodon/store/typed_functions.ts | 5 +- app/javascript/mastodon/utils/environment.ts | 6 +- .../styles/mastodon/components.scss | 59 +++- 18 files changed, 591 insertions(+), 16 deletions(-) create mode 100644 app/javascript/mastodon/components/dropdown/index.tsx create mode 100644 app/javascript/mastodon/features/ui/components/visibility_modal.tsx diff --git a/app/javascript/mastodon/actions/statuses_typed.ts b/app/javascript/mastodon/actions/statuses_typed.ts index cc9c389cdab..f34d9f2bc37 100644 --- a/app/javascript/mastodon/actions/statuses_typed.ts +++ b/app/javascript/mastodon/actions/statuses_typed.ts @@ -1,8 +1,10 @@ import { createAction } from '@reduxjs/toolkit'; -import { apiGetContext } from 'mastodon/api/statuses'; +import { apiGetContext, apiSetQuotePolicy } from 'mastodon/api/statuses'; import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; +import type { ApiQuotePolicy } from '../api_types/quotes'; + import { importFetchedStatuses } from './importer'; export const fetchContext = createDataLoadingThunk( @@ -23,3 +25,10 @@ export const fetchContext = createDataLoadingThunk( export const completeContextRefresh = createAction<{ statusId: string }>( 'status/context/complete', ); + +export const setStatusQuotePolicy = createDataLoadingThunk( + 'status/setQuotePolicy', + ({ statusId, policy }: { statusId: string; policy: ApiQuotePolicy }) => { + return apiSetQuotePolicy(statusId, policy); + }, +); diff --git a/app/javascript/mastodon/api/statuses.ts b/app/javascript/mastodon/api/statuses.ts index 48eff2a692f..123f2759d09 100644 --- a/app/javascript/mastodon/api/statuses.ts +++ b/app/javascript/mastodon/api/statuses.ts @@ -1,5 +1,10 @@ -import api, { getAsyncRefreshHeader } from 'mastodon/api'; -import type { ApiContextJSON } from 'mastodon/api_types/statuses'; +import api, { apiRequestPut, getAsyncRefreshHeader } from 'mastodon/api'; +import type { + ApiContextJSON, + ApiStatusJSON, +} from 'mastodon/api_types/statuses'; + +import type { ApiQuotePolicy } from '../api_types/quotes'; export const apiGetContext = async (statusId: string) => { const response = await api().request({ @@ -12,3 +17,15 @@ export const apiGetContext = async (statusId: string) => { refresh: getAsyncRefreshHeader(response), }; }; + +export const apiSetQuotePolicy = async ( + statusId: string, + policy: ApiQuotePolicy, +) => { + return apiRequestPut( + `v1/statuses/${statusId}/interaction_policy`, + { + quote_approval_policy: policy, + }, + ); +}; diff --git a/app/javascript/mastodon/api_types/quotes.ts b/app/javascript/mastodon/api_types/quotes.ts index 8c0ea10fc3c..981c047c136 100644 --- a/app/javascript/mastodon/api_types/quotes.ts +++ b/app/javascript/mastodon/api_types/quotes.ts @@ -1,7 +1,7 @@ import type { ApiStatusJSON } from './statuses'; export type ApiQuoteState = 'accepted' | 'pending' | 'revoked' | 'unauthorized'; -export type ApiQuotePolicy = 'public' | 'followers' | 'nobody'; +export type ApiQuotePolicy = 'public' | 'followers' | 'nobody' | 'unknown'; interface ApiQuoteEmptyJSON { state: Exclude; @@ -21,3 +21,13 @@ interface ApiQuoteAcceptedJSON { } export type ApiQuoteJSON = ApiQuoteAcceptedJSON | ApiQuoteEmptyJSON; + +export interface ApiQuotePolicyJSON { + automatic: ApiQuotePolicy[]; + manual: ApiQuotePolicy[]; + current_user: ApiQuotePolicy; +} + +export function isQuotePolicy(policy: string): policy is ApiQuotePolicy { + return ['public', 'followers', 'nobody'].includes(policy); +} diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index cd0b1001ac5..0127f6334bf 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -4,7 +4,7 @@ import type { ApiAccountJSON } from './accounts'; import type { ApiCustomEmojiJSON } from './custom_emoji'; import type { ApiMediaAttachmentJSON } from './media_attachments'; import type { ApiPollJSON } from './polls'; -import type { ApiQuoteJSON } from './quotes'; +import type { ApiQuoteJSON, ApiQuotePolicyJSON } from './quotes'; // See app/modals/status.rb export type StatusVisibility = @@ -120,9 +120,16 @@ export interface ApiStatusJSON { card?: ApiPreviewCardJSON; poll?: ApiPollJSON; quote?: ApiQuoteJSON; + quote_approval?: ApiQuotePolicyJSON; } export interface ApiContextJSON { ancestors: ApiStatusJSON[]; descendants: ApiStatusJSON[]; } + +export interface ApiStatusSourceJSON { + id: string; + text: string; + spoiler_text: string; +} diff --git a/app/javascript/mastodon/components/dropdown/index.tsx b/app/javascript/mastodon/components/dropdown/index.tsx new file mode 100644 index 00000000000..1e442f8159e --- /dev/null +++ b/app/javascript/mastodon/components/dropdown/index.tsx @@ -0,0 +1,114 @@ +import { useCallback, useId, useMemo, useRef, useState } from 'react'; +import type { ComponentPropsWithoutRef, FC } from 'react'; + +import { FormattedMessage } from 'react-intl'; +import type { MessageDescriptor } from 'react-intl'; + +import classNames from 'classnames'; + +import Overlay from 'react-overlays/Overlay'; + +import type { SelectItem } from '../dropdown_selector'; +import { DropdownSelector } from '../dropdown_selector'; + +interface DropdownProps { + title: string; + disabled?: boolean; + items: SelectItem[]; + onChange: (value: string) => void; + current: string; + emptyText?: MessageDescriptor; + classPrefix: string; +} + +export const Dropdown: FC< + DropdownProps & Omit, keyof DropdownProps> +> = ({ + title, + disabled, + items, + current, + onChange, + classPrefix, + className, + ...buttonProps +}) => { + const buttonRef = useRef(null); + const accessibilityId = useId(); + + const [open, setOpen] = useState(false); + const handleToggle = useCallback(() => { + if (!disabled) { + setOpen((prevOpen) => !prevOpen); + } + }, [disabled]); + const handleClose = useCallback(() => { + setOpen(false); + }, []); + const currentText = useMemo( + () => items.find((i) => i.value === current)?.text, + [current, items], + ); + return ( + <> + + + + {({ props, placement }) => ( +
+
+ +
+
+ )} +
+ + ); +}; diff --git a/app/javascript/mastodon/components/dropdown_selector.tsx b/app/javascript/mastodon/components/dropdown_selector.tsx index 99bbd182e56..9299e7d6bd7 100644 --- a/app/javascript/mastodon/components/dropdown_selector.tsx +++ b/app/javascript/mastodon/components/dropdown_selector.tsx @@ -13,8 +13,8 @@ const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; -export interface SelectItem { - value: string; +export interface SelectItem { + value: Value; icon?: string; iconComponent?: IconProp; text: string; @@ -24,7 +24,7 @@ export interface SelectItem { interface Props { value: string; - classNamePrefix: string; + classNamePrefix?: string; style?: React.CSSProperties; items: SelectItem[]; onChange: (value: string) => void; diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 663fc53407c..69ca9817a2c 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -29,6 +29,7 @@ import { Dropdown } from 'mastodon/components/dropdown_menu'; import { me } from '../initial_state'; import { IconButton } from './icon_button'; +import { isFeatureEnabled } from '../utils/environment'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -68,6 +69,7 @@ const messages = defineMessages({ filter: { id: 'status.filter', defaultMessage: 'Filter this post' }, openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' }, + quotePolicyChange: { id: 'status.quote_policy_change', defaultMessage: 'Change who can quote' }, }); const mapStateToProps = (state, { status }) => { @@ -89,6 +91,7 @@ class StatusActionBar extends ImmutablePureComponent { onReblog: PropTypes.func, onDelete: PropTypes.func, onRevokeQuote: PropTypes.func, + onQuotePolicyChange: PropTypes.func, onDirect: PropTypes.func, onMention: PropTypes.func, onMute: PropTypes.func, @@ -200,7 +203,11 @@ class StatusActionBar extends ImmutablePureComponent { handleRevokeQuoteClick = () => { this.props.onRevokeQuote(this.props.status); - } + }; + + handleQuotePolicyChange = () => { + this.props.onQuotePolicyChange(this.props.status); + }; handleBlockClick = () => { const { status, relationship, onBlock, onUnblock } = this.props; @@ -291,6 +298,9 @@ class StatusActionBar extends ImmutablePureComponent { if (writtenByMe || withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); + if (writtenByMe && isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) { + menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange }); + } menu.push(null); } diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index f3bc8fe5ff3..7d5c6e4f2f7 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -115,6 +115,10 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }})); }, + onQuotePolicyChange(status) { + dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } })); + }, + onEdit (status) { dispatch((_, getState) => { let state = getState(); diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index 15df5ab7297..258291ae492 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -14,7 +14,7 @@ import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; import { DropdownSelector } from 'mastodon/components/dropdown_selector'; import { Icon } from 'mastodon/components/icon'; -const messages = defineMessages({ +export const messages = defineMessages({ public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' }, unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Quiet public' }, diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 5d6625fc103..15f193510d7 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -26,6 +26,7 @@ import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/ import { IconButton } from '../../../components/icon_button'; import { Dropdown } from 'mastodon/components/dropdown_menu'; import { me } from '../../../initial_state'; +import { isFeatureEnabled } from '@/mastodon/utils/environment'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -62,6 +63,7 @@ const messages = defineMessages({ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, revokeQuote: { id: 'status.revoke_quote', defaultMessage: 'Remove my post from @{name}’s post' }, + quotePolicyChange: { id: 'status.quote_policy_change', defaultMessage: 'Change who can quote' }, }); const mapStateToProps = (state, { status }) => { @@ -84,6 +86,7 @@ class ActionBar extends PureComponent { onBookmark: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onRevokeQuote: PropTypes.func, + onQuotePolicyChange: PropTypes.func, onEdit: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -122,7 +125,11 @@ class ActionBar extends PureComponent { handleRevokeQuoteClick = () => { this.props.onRevokeQuote(this.props.status); - } + }; + + handleQuotePolicyChange = () => { + this.props.onQuotePolicyChange(this.props.status); + }; handleRedraftClick = () => { this.props.onDelete(this.props.status, true); @@ -240,6 +247,9 @@ class ActionBar extends PureComponent { } menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); + if (isFeatureEnabled('outgoing_quotes') && !['private', 'direct'].includes(status.get('visibility'))) { + menu.push({ text: intl.formatMessage(messages.quotePolicyChange), action: this.handleQuotePolicyChange }); + } menu.push(null); menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true }); diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 7bdcdb89711..3cfda6e837f 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -265,6 +265,11 @@ class Status extends ImmutablePureComponent { dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }})); }; + handleQuotePolicyChange = (status) => { + const { dispatch } = this.props; + dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } })); + }; + handleEditClick = (status) => { const { dispatch, askReplyConfirmation } = this.props; @@ -642,6 +647,7 @@ class Status extends ImmutablePureComponent { onBookmark={this.handleBookmarkClick} onDelete={this.handleDeleteClick} onRevokeQuote={this.handleRevokeQuoteClick} + onQuotePolicyChange={this.handleQuotePolicyChange} onEdit={this.handleEditClick} onDirect={this.handleDirectClick} onMention={this.handleMentionClick} diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx index 3b7a24faaf4..c02cacd6595 100644 --- a/app/javascript/mastodon/features/ui/components/modal_root.jsx +++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx @@ -43,6 +43,7 @@ import { ImageModal } from './image_modal'; import MediaModal from './media_modal'; import { ModalPlaceholder } from './modal_placeholder'; import VideoModal from './video_modal'; +import { VisibilityModal } from './visibility_modal'; export const MODAL_COMPONENTS = { 'MEDIA': () => Promise.resolve({ default: MediaModal }), @@ -76,6 +77,7 @@ export const MODAL_COMPONENTS = { 'CLOSED_REGISTRATIONS': ClosedRegistrationsModal, 'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal, 'ANNUAL_REPORT': AnnualReportModal, + 'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }), }; export default class ModalRoot extends PureComponent { diff --git a/app/javascript/mastodon/features/ui/components/visibility_modal.tsx b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx new file mode 100644 index 00000000000..82a1a482a3f --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx @@ -0,0 +1,293 @@ +import { forwardRef, useCallback, useId, useMemo } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { changeComposeVisibility } from '@/mastodon/actions/compose'; +import { setStatusQuotePolicy } from '@/mastodon/actions/statuses_typed'; +import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; +import { isQuotePolicy } from '@/mastodon/api_types/quotes'; +import type { StatusVisibility } from '@/mastodon/api_types/statuses'; +import { Dropdown } from '@/mastodon/components/dropdown'; +import type { SelectItem } from '@/mastodon/components/dropdown_selector'; +import { IconButton } from '@/mastodon/components/icon_button'; +import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/mastodon/store'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; + +import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, + buttonTitle: { + id: 'visibility_modal.button_title', + defaultMessage: 'Set visibility', + }, + quotePublic: { + id: 'visibility_modal.quote_public', + defaultMessage: 'Anyone', + }, + quoteFollowers: { + id: 'visibility_modal.quote_followers', + defaultMessage: 'Followers only', + }, + quoteNobody: { + id: 'visibility_modal.quote_nobody', + defaultMessage: 'No one', + }, +}); + +interface VisibilityModalProps extends BaseConfirmationModalProps { + statusId: string; +} + +const selectStatusPolicy = createAppSelector( + [(state) => state.statuses, (_state, statusId: string) => statusId], + (statuses, statusId) => { + const status = statuses.get(statusId); + if (!status) { + return 'public'; + } + const policy = + (status.getIn(['quote_approval', 'automatic', 0]) as string) || 'nobody'; + const visibility = status.get('visibility') as StatusVisibility; + + // If the status is private or direct, it cannot be quoted by anyone. + if (visibility === 'private' || visibility === 'direct') { + return 'nobody'; + } + + // If the status has a specific quote policy, return it. + if (isQuotePolicy(policy)) { + return policy; + } + + // Otherwise, return the default based on visibility. + if (visibility === 'unlisted') { + return 'followers'; + } + return 'public'; + }, +); + +export const VisibilityModal: FC = forwardRef( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + ({ onClose, statusId }, ref) => { + const intl = useIntl(); + const currentVisibility = useAppSelector( + (state) => + (state.statuses.getIn([statusId, 'visibility'], 'public') as + | StatusVisibility + | undefined) ?? 'public', + ); + const currentQuotePolicy = useAppSelector((state) => + selectStatusPolicy(state, statusId), + ); + const disableQuotePolicy = + currentVisibility === 'private' || currentVisibility === 'direct'; + const isSaving = useAppSelector( + (state) => + state.statuses.getIn([statusId, 'isSavingQuotePolicy']) === true, + ); + + const visibilityItems = useMemo[]>( + () => [ + { + value: 'public', + text: intl.formatMessage(privacyMessages.public_short), + meta: intl.formatMessage(privacyMessages.public_long), + }, + { + value: 'unlisted', + text: intl.formatMessage(privacyMessages.unlisted_short), + meta: intl.formatMessage(privacyMessages.unlisted_long), + }, + { + value: 'private', + text: intl.formatMessage(privacyMessages.private_short), + meta: intl.formatMessage(privacyMessages.private_long), + }, + { + value: 'direct', + text: intl.formatMessage(privacyMessages.direct_short), + meta: intl.formatMessage(privacyMessages.direct_long), + }, + ], + [intl], + ); + const quoteItems = useMemo[]>( + () => [ + { value: 'public', text: intl.formatMessage(messages.quotePublic) }, + { + value: 'followers', + text: intl.formatMessage(messages.quoteFollowers), + }, + { value: 'nobody', text: intl.formatMessage(messages.quoteNobody) }, + ], + [intl], + ); + + const dispatch = useAppDispatch(); + const handleVisibilityChange = useCallback( + (value: string) => { + // Published statuses cannot change visibility. + if (statusId) { + return; + } + dispatch(changeComposeVisibility(value)); + }, + [dispatch, statusId], + ); + const handleQuotePolicyChange = useCallback( + (value: string) => { + if (isQuotePolicy(value) && !disableQuotePolicy) { + void dispatch(setStatusQuotePolicy({ policy: value, statusId })); + } + }, + [disableQuotePolicy, dispatch, statusId], + ); + + const privacyDropdownId = useId(); + const quoteDropdownId = useId(); + + return ( +
+
+ + + {(chunks) => ( + {chunks} + )} + +
+
+
+ ( + {chunks} + ), + }} + tagName='p' + /> +
+
+ + + +
+
+
+ ); + }, +); +VisibilityModal.displayName = 'VisibilityModal'; + +const QuotePolicyHelper: FC<{ + policy: ApiQuotePolicy; + visibility: StatusVisibility; +}> = ({ policy, visibility }) => { + if (visibility === 'unlisted' && policy !== 'nobody') { + return ( +

+ +

+ ); + } + + if (visibility === 'private') { + return ( +

+ +

+ ); + } + + if (visibility === 'direct') { + return ( +

+ +

+ ); + } + + return null; +}; diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 6b796f9f81d..bee9b0410f0 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Your handle:", "domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.", "domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.", + "dropdown.empty": "Select an option", "embed.instructions": "Embed this post on your website by copying the code below.", "embed.preview": "Here is what it will look like:", "emoji_button.activity": "Activity", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Post pending", "status.quote_error.pending_approval_popout.body": "Quotes shared across the Fediverse may take time to display, as different servers have different protocols.", "status.quote_error.pending_approval_popout.title": "Pending quote? Remain calm", + "status.quote_policy_change": "Change who can quote", "status.quote_post_author": "Quoted a post by @{name}", "status.read_more": "Read more", "status.reblog": "Boost", @@ -959,5 +961,17 @@ "video.skip_forward": "Skip forward", "video.unmute": "Unmute", "video.volume_down": "Volume down", - "video.volume_up": "Volume up" + "video.volume_up": "Volume up", + "visibility_modal.button_title": "Set visibility", + "visibility_modal.header": "Visibility and interaction", + "visibility_modal.helper.direct_quoting": "Private mentions can't be quoted.", + "visibility_modal.helper.privacy_editing": "Published posts cannot change their visibility.", + "visibility_modal.helper.private_quoting": "Follower-only posts can't be quoted.", + "visibility_modal.helper.unlisted_quoting": "When people quote you, their post will also be hidden from trending timelines.", + "visibility_modal.instructions": "Control who can interact with this post. Global settings can be found under Preferences > Other.", + "visibility_modal.privacy_label": "Privacy", + "visibility_modal.quote_followers": "Followers only", + "visibility_modal.quote_label": "Change who can quote", + "visibility_modal.quote_nobody": "No one", + "visibility_modal.quote_public": "Anyone" } diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 239ab13920e..13ff5e016e5 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -29,6 +29,7 @@ import { STATUS_FETCH_REQUEST, STATUS_FETCH_FAIL, } from '../actions/statuses'; +import { setStatusQuotePolicy } from '../actions/statuses_typed'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -70,6 +71,22 @@ const initialState = ImmutableMap(); /** @type {import('@reduxjs/toolkit').Reducer} */ export default function statuses(state = initialState, action) { + if (setStatusQuotePolicy.pending.match(action)) { + const status = state.get(action.meta.arg.statusId); + if (status) { + return state.setIn([action.meta.arg.statusId, 'isSavingQuotePolicy'], true); + } + } else if (setStatusQuotePolicy.fulfilled.match(action)) { + const status = state.get(action.payload.id); + if (status) { + return state + .setIn([action.payload.id, 'quote_approval'], action.payload.quote_approval) + .deleteIn([action.payload.id, 'isSavingQuotePolicy']); + } + } else if (setStatusQuotePolicy.rejected.match(action)) { + return state.deleteIn([action.meta.arg.statusId, 'isSavingQuotePolicy']); + } + switch(action.type) { case STATUS_FETCH_REQUEST: return state.setIn([action.id, 'isLoading'], true); diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index 69f6028be2e..3204d13ee42 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -40,7 +40,10 @@ interface AppThunkConfig { fulfilledMeta: AppMeta; rejectedMeta: AppMeta; } -type AppThunkApi = Pick, 'getState' | 'dispatch'>; +export type AppThunkApi = Pick< + GetThunkAPI, + 'getState' | 'dispatch' +>; interface AppThunkOptions { useLoadingBar?: boolean; diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index c5fe46bc931..fc4448740f4 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -12,7 +12,11 @@ export function isProduction() { else return import.meta.env.PROD; } -export type Features = 'modern_emojis'; +export type Features = + | 'modern_emojis' + | 'outgoing_quotes' + | 'fasp' + | 'http_message_signatures'; export function isFeatureEnabled(feature: Features) { return initialState?.features.includes(feature) ?? false; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index cca92a46d94..1de7b74c501 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5402,7 +5402,8 @@ a.status-card { } .privacy-dropdown__dropdown, -.language-dropdown__dropdown { +.language-dropdown__dropdown, +.visibility-dropdown__dropdown { box-shadow: var(--dropdown-shadow); background: var(--dropdown-background-color); backdrop-filter: $backdrop-blur-filter; @@ -5431,7 +5432,8 @@ a.status-card { z-index: 9999; } -.privacy-dropdown__option { +.privacy-dropdown__option, +.visibility-dropdown__option { font-size: 14px; line-height: 20px; letter-spacing: 0.25px; @@ -5577,6 +5579,39 @@ a.status-card { } } +.visibility-dropdown { + &__overlay[data-popper-placement] { + z-index: 9999; + } + + &__label.disabled { + cursor: default; + opacity: 0.5; + } + + &__button { + color: $primary-text-color; + background: var(--dropdown-background-color); + border: 1px solid var(--dropdown-border-color); + padding: 8px 12px; + width: 100%; + text-align: left; + border-radius: 4px; + font-size: 14px; + height: 40px; + + &:disabled { + cursor: default; + } + } + + &__helper { + margin-top: 4px; + font-size: 0.8em; + color: $dark-text-color; + } +} + .search { margin-bottom: 32px; position: relative; @@ -5869,6 +5904,17 @@ a.status-card { } } +.modal-root label { + cursor: pointer; + display: block; + + > span { + display: block; + font-weight: 500; + margin-bottom: 8px; + } +} + .video-modal .video-player { max-height: 80vh; max-width: 100vw; @@ -6375,6 +6421,15 @@ a.status-card { letter-spacing: 0.25px; overflow-y: auto; + &__description { + margin: 24px 24px 0; + color: $darker-text-color; + + a { + color: inherit; + } + } + &__form { display: flex; flex-direction: column; From 4de21056ff70ffc7eb688ae2f825965bd58ae98c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 14 Aug 2025 11:25:31 -0400 Subject: [PATCH 288/660] Update sidekiq to version 8.0.7 (#34824) --- Gemfile | 2 +- Gemfile.lock | 14 +++++++------- README.md | 2 +- app/workers/concerns/bulk_mailing_concern.rb | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index b3d0a818d5e..7af47881b75 100644 --- a/Gemfile +++ b/Gemfile @@ -82,7 +82,7 @@ gem 'rqrcode', '~> 3.0' gem 'ruby-progressbar', '~> 1.13' gem 'sanitize', '~> 7.0' gem 'scenic', '~> 1.7' -gem 'sidekiq', '< 8' +gem 'sidekiq', '< 9' gem 'sidekiq-bulk', '~> 0.2.0' gem 'sidekiq-scheduler', '~> 6.0' gem 'sidekiq-unique-jobs', '> 8' diff --git a/Gemfile.lock b/Gemfile.lock index 464d5bd2c97..b0bf819481f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -823,12 +823,12 @@ GEM securerandom (0.4.1) shoulda-matchers (6.5.0) activesupport (>= 5.2.0) - sidekiq (7.3.9) - base64 - connection_pool (>= 2.3.0) - logger - rack (>= 2.2.4) - redis-client (>= 0.22.2) + sidekiq (8.0.7) + connection_pool (>= 2.5.0) + json (>= 2.9.0) + logger (>= 1.6.2) + rack (>= 3.1.0) + redis-client (>= 0.23.2) sidekiq-bulk (0.2.0) sidekiq sidekiq-scheduler (6.0.1) @@ -1077,7 +1077,7 @@ DEPENDENCIES sanitize (~> 7.0) scenic (~> 1.7) shoulda-matchers - sidekiq (< 8) + sidekiq (< 9) sidekiq-bulk (~> 0.2.0) sidekiq-scheduler (~> 6.0) sidekiq-unique-jobs (> 8) diff --git a/README.md b/README.md index 12027289659..5c0e596b727 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub] - **Ruby** 3.2+ - **PostgreSQL** 13+ -- **Redis** 6.2+ +- **Redis** 7.0+ - **Node.js** 20+ This repository includes deployment configurations for **Docker and docker-compose**, as well as for other environments like Heroku and Scalingo. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). A [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the main documentation. diff --git a/app/workers/concerns/bulk_mailing_concern.rb b/app/workers/concerns/bulk_mailing_concern.rb index 5f8154d7fa6..98fcc0541df 100644 --- a/app/workers/concerns/bulk_mailing_concern.rb +++ b/app/workers/concerns/bulk_mailing_concern.rb @@ -7,7 +7,7 @@ module BulkMailingConcern job_class = ActionMailer::MailDeliveryJob Sidekiq::Client.push_bulk({ - 'class' => ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper, + 'class' => Sidekiq::ActiveJob::Wrapper, 'wrapped' => job_class, 'queue' => mailer_class.deliver_later_queue_name, 'args' => args_array.map do |args| From 25f1a515f86d4b84e72b6f28cfea46a28a904347 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 07:12:01 +0000 Subject: [PATCH 289/660] New Crowdin Translations (automated) (#35788) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/az.json | 268 ++++++++++++++++++++- app/javascript/mastodon/locales/br.json | 6 +- app/javascript/mastodon/locales/de.json | 16 +- app/javascript/mastodon/locales/es-AR.json | 16 +- app/javascript/mastodon/locales/fo.json | 16 +- app/javascript/mastodon/locales/hu.json | 16 +- app/javascript/mastodon/locales/is.json | 11 +- app/javascript/mastodon/locales/sv.json | 13 +- app/javascript/mastodon/locales/uk.json | 8 +- app/javascript/mastodon/locales/vi.json | 16 +- app/javascript/mastodon/locales/zh-TW.json | 16 +- config/locales/az.yml | 3 + config/locales/devise.az.yml | 14 ++ config/locales/doorkeeper.az.yml | 165 +++++++++++-- config/locales/pl.yml | 3 + config/locales/ru.yml | 2 +- config/locales/simple_form.pl.yml | 5 + config/locales/simple_form.sv.yml | 3 + config/locales/sv.yml | 16 ++ 19 files changed, 585 insertions(+), 28 deletions(-) diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index 7540c8fa4b6..a243952ec7a 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tanıdıcınız:", "domain_pill.your_server": "Bütün paylaşımlarınızın yaşadığı rəqəmsal ev. Buranı bəyənmirsiniz? İstədiyiniz vaxt serverdən köçün və izləyicilərinizi də aparın.", "domain_pill.your_username": "Serverdəki unikal identifikatoruz. Fərqli serverlərdə eyni istifadəçi adı ilə istifadəçilər tapmaq mümkündür.", + "dropdown.empty": "Bir seçim et", "embed.instructions": "Aşağıdakı kodu kopyalayaraq bu postu veb-saytınıza yerləşdirin.", "embed.preview": "Belə görünəcək:", "emoji_button.activity": "Aktivlik", @@ -324,6 +325,9 @@ "empty_column.follow_requests": "İzləmə sorğularınız yoxdur. Qəbul etdikdə burada görəcəksiniz.", "empty_column.followed_tags": "Heç bir heşteq izləmirsiniz. İzlədikdə burada görünəcək.", "empty_column.hashtag": "Bu heşteqdə hələ ki, heç nə yoxdur.", + "empty_column.home": "Əsas zaman xəttiniz boşdur! Doldurmaq üçün bir neçə istifadəçini izləyin.", + "empty_column.list": "Hələ bu siyahıda heç nə yoxdur. Bu siyahıdakı üzvlər yeni göndəriş paylaşdığı zaman burada görünəcək.", + "empty_column.mutes": "Hələ heç bir istifadəçini səssizə almamısınız.", "empty_column.notification_requests": "Hamısı hazırdır! Burada heç nə yoxdur. Yeni bildiriş aldığınız zaman, ayarlarınıza görə burada görünəcək.", "empty_column.notifications": "Hələ heç bir bildirişiniz yoxdur. Başqaları sizinlə qarşılıqlı əlaqə qurduğu zaman, onu burada görəcəksiniz.", "empty_column.public": "Burada hələ heç nə yoxdur! Buranı doldurmaq üçün hər kəsə açıq bir şey yazın və ya digər serverlərdəki istifadəçiləri izləyin.", @@ -351,6 +355,7 @@ "filter_modal.added.settings_link": "ayarlar səhifəsi", "filter_modal.added.short_explanation": "Bu göndəriş, aşağıdakı filtr kateqoriyasına əlavə edilib: {title}.", "filter_modal.added.title": "Filtr əlavə edilib!", + "filter_modal.select_filter.context_mismatch": "bu kontektsə tətbiq olunmur", "filter_modal.select_filter.expired": "müddəti bitib", "filter_modal.select_filter.prompt_new": "Yeni kateqoriya: {name}", "filter_modal.select_filter.search": "Axtar və ya yarat", @@ -358,6 +363,7 @@ "filter_modal.select_filter.title": "Bu göndərişi filtrlə", "filter_modal.title.status": "Bir göndərişi filtrlə", "filter_warning.matches_filter": "“{title}” filtri ilə uyuşur", + "filtered_notifications_banner.pending_requests": "Tanıdığınız {count, plural, =0 {heç kimdən} one {bir şəxsdən} other {# şəxsdən}}", "filtered_notifications_banner.title": "Filtrlənmiş bildirişlər", "firehose.all": "Hamısı", "firehose.local": "Bu server", @@ -379,6 +385,7 @@ "follow_suggestions.popular_suggestion_longer": "{domain} üzərində məşhur", "follow_suggestions.similar_to_recently_followed_longer": "Son zaman izlədiyiniz profillərə oxşar", "follow_suggestions.view_all": "Hamısına bax", + "follow_suggestions.who_to_follow": "İzləyə bilərsən", "followed_tags": "İzlənilən mövzu etiketləri", "footer.about": "Haqqında", "footer.directory": "Profil kataloqu", @@ -442,10 +449,48 @@ "interaction_modal.action.reblog": "Davam etmək üçün hesabınızdan təkrar göndərməlisiniz.", "interaction_modal.action.reply": "Davam etmək üçün hesabınızdan cavab verməlisiniz.", "interaction_modal.action.vote": "Davam etmək üçün hesabınızdan səs verməlisiniz.", + "interaction_modal.go": "Get", + "interaction_modal.no_account_yet": "Hələ heç bir hesabınız yoxdur?", + "interaction_modal.on_another_server": "Fərqli bir serverdə", + "interaction_modal.on_this_server": "Bu serverdə", + "interaction_modal.title.favourite": "{name} - göndərişini sevimlilərə əlavə et", + "interaction_modal.title.follow": "{name} - izlə", "interaction_modal.title.reblog": "{name} - göndərişini təkrar paylaş", + "interaction_modal.title.reply": "{name} - göndərişinə cavab ver", + "interaction_modal.title.vote": "{name} - anketində səs ver", + "interaction_modal.username_prompt": "Məs: {example}", + "intervals.full.days": "{number, plural, one {# gün} other {# gün}}", + "intervals.full.hours": "{number, plural, one {# saat} other {# saat}}", + "intervals.full.minutes": "{number, plural, one {# dəqiqə} other {# dəqiqə}}", + "keyboard_shortcuts.back": "Geri get", + "keyboard_shortcuts.blocked": "Əngəllənən istifadəçilərin siyahısını aç", "keyboard_shortcuts.boost": "Göndərişi təkrar paylaş", + "keyboard_shortcuts.column": "Sütuna fokuslan", + "keyboard_shortcuts.compose": "Mətn yazma sahəsinə fokuslan", + "keyboard_shortcuts.description": "Açıqlama", + "keyboard_shortcuts.direct": "şəxsi adçəkmələr sütununu açmaq üçün", + "keyboard_shortcuts.down": "Siyahıda aşağı daşı", + "keyboard_shortcuts.enter": "Göndərişi aç", + "keyboard_shortcuts.favourite": "Göndərişi sevimlilərə əlavə et", + "keyboard_shortcuts.favourites": "Sevimli siyahını aç", + "keyboard_shortcuts.heading": "Klaviatura qısayolları", + "keyboard_shortcuts.home": "Əsas ekran zaman xəttini aç", + "keyboard_shortcuts.hotkey": "Qısayol düyməsi", + "keyboard_shortcuts.legend": "Bu əfsanəni nümayiş etdir", + "keyboard_shortcuts.local": "Lokal zaman xəttini aç", + "keyboard_shortcuts.mention": "Müəllifin adını çək", + "keyboard_shortcuts.muted": "Səssizdəki istifadəçilərin siyahısını aç", + "keyboard_shortcuts.my_profile": "Profilinizi açın", + "keyboard_shortcuts.notifications": "Bildirişlər sütununu aç", + "keyboard_shortcuts.open_media": "Medianı aç", "keyboard_shortcuts.profile": "Müəllifin profilini aç", "keyboard_shortcuts.reply": "Göndərişə cavab ver", + "keyboard_shortcuts.requests": "İzləmə istəyi siyahısını aç", + "keyboard_shortcuts.search": "Axtarış çubuğuna fokuslan", + "keyboard_shortcuts.spoilers": "CW xanasını göstər/gizlət", + "keyboard_shortcuts.start": "\"Başlayaq\" sütununu aç", + "keyboard_shortcuts.toggle_hidden": "CW arxasındakı mətni göstər/gizlət", + "keyboard_shortcuts.toggle_sensitivity": "Medianı göstər/gizlət", "keyboard_shortcuts.toot": "Yeni bir göndəriş başlat", "keyboard_shortcuts.translate": "bir göndərişi tərcümə etmək üçün", "keyboard_shortcuts.unfocus": "Fokusu göndəriş yazma xanasından/axtarışdan götür", @@ -502,13 +547,59 @@ "mute_modal.you_wont_see_posts": "Onlar hələ də göndərişlərinizi görə biləcək, ancaq onların göndərişlərini görməyəcəksiniz.", "navigation_bar.about": "Haqqında", "navigation_bar.account_settings": "Parol və təhlükəsizlik", + "navigation_bar.administration": "Administrasiya", "navigation_bar.advanced_interface": "Qabaqcıl veb interfeysində aç", "navigation_bar.automated_deletion": "Göndərişin avtomatik silinməsi", "navigation_bar.blocks": "Əngəllənmiş istifadəçilər", + "navigation_bar.bookmarks": "Əlfəcinlər", + "navigation_bar.direct": "Şəxsi adçəkmələr", + "navigation_bar.domain_blocks": "Əngəllənmiş domenlər", + "navigation_bar.favourites": "Sevimlilər", + "navigation_bar.filters": "Səssizə alınmış sözlər", + "navigation_bar.follow_requests": "İzləmə istəkləri", + "navigation_bar.followed_tags": "İzlənilən mövzu etiketləri", "navigation_bar.follows_and_followers": "İzlənilənlər və izləyicilər", "navigation_bar.import_export": "Daxilə və xaricə köçürmə", + "navigation_bar.lists": "Siyahılar", + "navigation_bar.live_feed_local": "Canlı lent (lokal)", + "navigation_bar.live_feed_public": "Canlı lent (hər kəsə açıq)", + "navigation_bar.logout": "Çıxış", "navigation_bar.moderation": "Moderasiya", + "navigation_bar.more": "Daha çox", + "navigation_bar.mutes": "Səssizə alınmış istifadəçilər", + "navigation_bar.opened_in_classic_interface": "Göndərişlər, hesablar və digər müəyyən səhifələr klassik veb interfeysində ilkin olaraq açılır.", + "navigation_bar.preferences": "Tərcihlər", + "navigation_bar.privacy_and_reach": "Gizlilik və əlçatanlıq", + "navigation_bar.search": "Axtar", + "navigation_bar.search_trends": "Axtar / Trendlər", + "navigation_panel.collapse_followed_tags": "İzlənilən mövzu etiketləri menyusunu yığcamlaşdır", + "navigation_panel.collapse_lists": "Siyahı menyusunu yığcamlaşdır", + "navigation_panel.expand_followed_tags": "İzlənilən mövzu etiketləri menyusunu genişləndir", + "navigation_panel.expand_lists": "Siyahı menyusunu genişləndir", "not_signed_in_indicator.not_signed_in": "Bu resursa erişmək üçün giriş etməlisiniz.", + "notification.admin.report": "{name} şikayət etdi: {target}", + "notification.admin.report_account": "{name}, {category} üçün şikayət etdi: {target} - {count, plural, one {bir göndəriş} other {# göndəriş}}", + "notification.admin.report_account_other": "{name} şikayət etdi: {target} - {count, plural, one {bir göndəriş} other {# göndəriş}}", + "notification.admin.report_statuses": "{name}, {category} üçün şikayət etdi: {target}", + "notification.admin.report_statuses_other": "{name} şikayət etdi: {target}", + "notification.admin.sign_up": "{name} qeydiyyatdan keçib", + "notification.admin.sign_up.name_and_others": "{name} və digər {count, plural, one {# nəfər} other {# nəfər}} qeydiyyatdan keçib", + "notification.annual_report.view": "#Wrapstodon-a bax", + "notification.favourite": "{name} göndərişinizi sevimlilərinə əlavə etdi", + "notification.favourite.name_and_others_with_link": "{name} və digər {count, plural, one {# nəfər} other {# nəfər}} göndərişinizi sevimlilərinə əlavə etdi", + "notification.favourite_pm": "{name}, şəxsi adçəkmənizi sevimlilərinə əlavə etdi", + "notification.favourite_pm.name_and_others_with_link": "{name} və digər {count, plural, one {# nəfər} other {# nəfər}} şəxsi adçəkmənizi sevimlilərinə əlavə etdi", + "notification.follow": "{name} sizi izləyir", + "notification.follow.name_and_others": "{name} və digər {count, plural, one {# nəfər} other {# nəfər}} sizi izləyir", + "notification.follow_request": "{name} sizi izləmək üçün istək göndərdi", + "notification.follow_request.name_and_others": "{name} və digər {count, plural, one {# nəfər} other {# nəfər}} sizi izləmək üçün istək göndərdi", + "notification.label.mention": "Adçəkmə", + "notification.label.private_mention": "Şəxsi adçəkmə", + "notification.label.private_reply": "Şəxsi cavab", + "notification.label.quote": "{name} göndərişinizi sitat gətirdi", + "notification.label.reply": "Cavab", + "notification.mention": "Adçəkmə", + "notification.mentioned_you": "{name} adınızı çəkdi", "notification.moderation-warning.learn_more": "Daha ətraflı", "notification.moderation_warning": "Bir moderasiya xəbərdarlığı aldınız", "notification.moderation_warning.action_delete_statuses": "Bəzi göndərişləriniz silindi.", @@ -518,8 +609,11 @@ "notification.moderation_warning.action_sensitive": "Göndərişləriniz artıq həssas olaraq işarələnəcək.", "notification.moderation_warning.action_silence": "Hesabınız məhdudlaşdırılıb.", "notification.moderation_warning.action_suspend": "Hesabınızın fəaliyyəti dayandırılıb.", + "notification.own_poll": "Anketiniz bitdi", + "notification.poll": "Səs verdiyiniz anket bitdi", "notification.reblog": "{name} göndərişinizi təkrar paylaşdı", "notification.reblog.name_and_others_with_link": "{name} və {count, plural, one {digər # nəfər} other {digər # nəfər}} göndərişinizi təkrar paylaşdı", + "notification.relationships_severance_event": "{name} ilə bağlantı qopdu", "notification.relationships_severance_event.account_suspension": "{from} admini {target} fəaliyyətini dayandırıb, bu da o deməkdir ki, artıq onlardan güncəlləmələr ala və ya onlarla qarşılıqlı əlaqə qura bilməyəcəyiniz.", "notification.relationships_severance_event.domain_block": "{target}, {from} admini tərəfindən əngəllənib, buna {followersCount} izləyiciniz və izlədiyiniz {followingCount, plural, one {# hesab} other {# hesab}} daxildir.", "notification.relationships_severance_event.learn_more": "Daha ətraflı", @@ -527,29 +621,177 @@ "notification.status": "{name} indicə paylaşdı", "notification.update": "{name} bir göndərişə düzəliş etdi", "notification_requests.accept": "Qəbul et", + "notification_requests.accept_multiple": "{count, plural, one {# istəyi qəbul et…} other {# istəyi qəbul et…}}", + "notification_requests.confirm_accept_multiple.button": "{count, plural, one {İstəyi qəbul et} other {İstəkləri qəbul et}}", + "notification_requests.confirm_accept_multiple.message": "{count, plural, one {Bir bildiriş istəyini} other {# bildiriş istəklərini}} qəbul etmək üzrəsiniz. Davam etmək istədiyinizə əminsiniz?", + "notification_requests.confirm_accept_multiple.title": "Bildiriş istəkləri qəbul edilsin?", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {İstəyi rədd et} other {İstəkləri rədd et}}", "notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {bir bildiriş sorğusunu} other {# bildiriş sorğusunu}} bağlamaq üzrəsiniz. {count, plural, one {Ona} other {Onlara}} yenidən asanlıqla erişə bilməyəcəksiniz. Davam etmək istədiyinizə əminsiniz?", + "notification_requests.confirm_dismiss_multiple.title": "Bildiriş istəklərinə rədd cavabı verilsin?", + "notification_requests.dismiss": "Rədd et", + "notification_requests.dismiss_multiple": "{count, plural, one {# istəyi rədd et…} other {# istəyi rədd et…}}", "notification_requests.edit_selection": "Düzəliş et", "notification_requests.exit_selection": "Hazırdır", "notification_requests.explainer_for_limited_account": "Hesab, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", "notification_requests.explainer_for_limited_remote_account": "Hesab və ya onun serveri, bir moderator tərəfindən məhdudlaşdırıldığı üçün bu hesabın bildirişləri filtrləndi.", + "notification_requests.maximize": "Böyüt", + "notification_requests.minimize_banner": "Filtrlənmiş bildirişlər bannerini kiçilt", + "notification_requests.notifications_from": "{name} - bildirişləri", + "notification_requests.title": "Filtrlənmiş bildirişlər", + "notification_requests.view": "Bildirişlərə bax", + "notifications.clear": "Bildirişləri təmizlə", + "notifications.clear_confirmation": "Bütün bildirişlərinizi həmişəlik təmizləmək istədiyinizə əminsiniz?", + "notifications.clear_title": "Bildirişlər təmizlənsin?", + "notifications.column_settings.admin.report": "Yeni hesabatlar:", + "notifications.column_settings.admin.sign_up": "Yeni qeydiyyatlar:", + "notifications.column_settings.alert": "Masaüstü bildirişlər", + "notifications.column_settings.favourite": "Sevimlilər:", + "notifications.column_settings.filter_bar.advanced": "Bütün kateqoriyaları nümayiş etdir", + "notifications.column_settings.filter_bar.category": "Cəld filtr çubuğu", "notifications.column_settings.follow": "Yeni izləyicilər:", + "notifications.column_settings.follow_request": "Yeni izləmə istəkləri:", + "notifications.column_settings.group": "Qrup", + "notifications.column_settings.mention": "Adçəkmələr:", + "notifications.column_settings.poll": "Anket nəticələri:", + "notifications.column_settings.push": "Ani bildirişlər", + "notifications.column_settings.quote": "Sitatlar:", "notifications.column_settings.reblog": "Təkrar paylaşmalar:", + "notifications.column_settings.show": "Sütunda göstər", + "notifications.column_settings.sound": "Səs oxut", + "notifications.column_settings.status": "Yeni göndərişlər:", + "notifications.column_settings.unread_notifications.category": "Oxunmamış bildirişlər", + "notifications.column_settings.unread_notifications.highlight": "Oxunmamış bildirişləri vurğula", + "notifications.column_settings.update": "Düzəlişlər:", + "notifications.filter.all": "Hamısı", "notifications.filter.boosts": "Təkrar paylaşmalar", + "notifications.filter.favourites": "Sevimlilər", "notifications.filter.follows": "İzlənilənlər", + "notifications.filter.mentions": "Adçəkmələr", + "notifications.filter.polls": "Anket nəticələri", "notifications.filter.statuses": "İzlədiyiniz şəxslərdən güncəlləmələr", + "notifications.grant_permission": "İcazəni ver.", + "notifications.group": "{count} bildiriş", + "notifications.mark_as_read": "Hər bir bildiriş oxunmuş olaraq işarələ", + "notifications.permission_denied": "Brauzer icazələri istəyinə daha əvvəl rədd cavabı verildiyi üçün masaüstü bildirişləri əlçatmazdır", + "notifications.permission_denied_alert": "Brauzer icazəsinə daha əvvəl rədd cavabı verildiyi üçün masaüstü bildirişləri fəallaşdırıla bilmir", + "notifications.permission_required": "Tələb olunan icazə verilmədiyi üçün masaüstü bildirişləri əlçatmazdır.", + "notifications.policy.accept": "Qəbul et", + "notifications.policy.accept_hint": "Bildirişlərdə göstər", + "notifications.policy.drop": "Yox say", + "notifications.policy.filter": "Filtr", + "notifications.policy.filter_hint": "Filtrlənmiş bildirişlər gələn qutusuna göndər", "notifications.policy.filter_limited_accounts_hint": "Server moderatorları tərəfindən məhdudlaşdırılıb", "notifications.policy.filter_limited_accounts_title": "Moderasiya edilmiş hesablar", + "notifications.policy.filter_new_accounts.hint": "Son {days, plural, one {bir gündə} other {# gündə}} yaradıldı", + "notifications.policy.filter_new_accounts_title": "Yeni hesablar", + "notifications.policy.filter_not_followers_hint": "Sizi {days, plural, one {bir gündən} other {# gündən}} az müddətdir izləyən insanlar daxildir", + "notifications.policy.filter_not_followers_title": "Sizi izləməyən insanlar", + "notifications.policy.filter_not_following_hint": "Onları manual qəbul edənə qədər", + "notifications.policy.filter_not_following_title": "İzləmədiyiniz insanlar", + "notifications.policy.title": "Bildirişləri idarə et…", + "notifications_permission_banner.enable": "Masaüstü bildirişləri fəallaşdır", + "notifications_permission_banner.title": "Heç nəyi buraxmayın", + "onboarding.follows.back": "Geri", + "onboarding.follows.done": "Hazırdır", + "onboarding.follows.search": "Axtar", + "onboarding.follows.title": "Başlamaq üçün insanları izləyin", "onboarding.profile.discoverable": "Profilimi kəşf edilə bilən et", "onboarding.profile.discoverable_hint": "Mastodon-da kəşf edilə bilməni aktivləşdirsəniz, göndərişləriniz axtarış nəticələrində və trendlərdə görünə bilər və profiliniz sizinlə oxşar maraqlara sahib şəxslərə təklif edilə bilər.", "onboarding.profile.display_name": "Ekran adı", + "onboarding.profile.display_name_hint": "Tam adınız və ya ləqəbiniz…", + "onboarding.profile.note": "Bioqrafiya", "onboarding.profile.note_hint": "Digər insanların @adını_çəkə və ya #mövzu_etiketləri istifadə edə bilərsiniz…", + "onboarding.profile.save_and_continue": "Saxla və davam et", "onboarding.profile.title": "Profili ayarla", + "onboarding.profile.upload_avatar": "Profil şəkli yüklə", + "onboarding.profile.upload_header": "Profil başlığı yüklə", "password_confirmation.exceeds_maxlength": "Parol təsdiqi, maksimum parol uzunluğunu aşır", "password_confirmation.mismatching": "Parol təsdiqi uyuşmur", + "picture_in_picture.restore": "Geri qoy", + "poll.closed": "Bağlandı", + "poll.refresh": "Təzələ", + "poll.reveal": "Nəticələrə bax", + "poll.total_people": "{count, plural, one {# nəfər} other {# nəfər}}", + "poll.total_votes": "{count, plural, one {# səs} other {# səs}}", + "poll.vote": "Səs ver", + "poll.voted": "Bu cavaba səs verdiniz", + "poll.votes": "{votes, plural, one {# səs} other {# səs}}", + "poll_button.add_poll": "Bir anket əlavə et", + "poll_button.remove_poll": "Anketi sil", + "privacy.change": "Göndəriş gizliliyini dəyişdir", + "privacy.direct.long": "Göndərişdə adı çəkilən hər kəs", + "privacy.direct.short": "Şəxsi adçəkmə", "privacy.private.long": "Yalnız izləyiciləriniz", "privacy.private.short": "İzləyicilər", + "privacy.public.long": "Mastodon-da olan və olmayan hər kəs", + "privacy.public.short": "Hər kəsə açıq", + "privacy.unlisted.short": "Səssiz hər kəsə açıq", "privacy_policy.last_updated": "Son güncəlləmə {date}", + "privacy_policy.title": "Gizlilik Siyasəti", + "recommended": "Tövsiyə edilən", + "refresh": "Təzələ", + "regeneration_indicator.please_stand_by": "Lütfən gözləyin.", + "regeneration_indicator.preparing_your_home_feed": "Əsas ekran lentiniz hazırlanır…", + "relative_time.days": "{number} gü", + "relative_time.full.days": "{number, plural, one {# gün} other {# gün}} əvvəl", + "relative_time.full.hours": "{number, plural, one {# saat} other {# saat}} əvvəl", + "relative_time.full.just_now": "indicə", + "relative_time.full.minutes": "{number, plural, one {# dəqiqə} other {# dəqiqə}} əvvəl", + "relative_time.full.seconds": "{number, plural, one {# saniyə} other {# saniyə}} əvvəl", + "relative_time.hours": "{number} sa", + "relative_time.just_now": "indi", + "relative_time.minutes": "{number} dəq", + "relative_time.seconds": "{number} san", + "relative_time.today": "bu gün", + "reply_indicator.attachments": "{count, plural, one {# qoşma} other {# qoşma}}", + "reply_indicator.cancel": "İmtina", + "reply_indicator.poll": "Anket", + "report.block": "Əngəllə", + "report.block_explanation": "Onun göndərişlərini görməyəcəksiniz. O, göndərişlərinizi görə və ya sizi izləyə bilməz. Əngəllədiyinizi anlaya biləcək.", + "report.categories.legal": "Hüquqi", + "report.categories.other": "Digər", + "report.categories.spam": "Spam", + "report.categories.violation": "Məzmun, bir və ya daha çox server qaydasını pozur", "report.category.subtitle": "Ən çox uyuşanı seçin", + "report.category.title": "Bu {type} ilə bağlı nələrin baş verdiyini bizə deyin", + "report.category.title_account": "profil", + "report.category.title_status": "göndəriş", + "report.close": "Hazırdır", + "report.comment.title": "Bilməyimizi istədiyiniz başqa nəsə var?", + "report.forward": "Bura yönləndir: {target}", + "report.forward_hint": "Hesab, başqa bir serverdəndir. Hesabatın anonim bir kopyası ora da göndərilsin?", + "report.mute": "Səssizə al", + "report.mute_explanation": "Onun göndərişlərini görməyəcəksiniz. O, sizi izləməyə və göndərişlərinizi görməyə davam edə bilər, ancaq səssizə alındığını bilməyəcək.", + "report.next": "Növbəti", + "report.placeholder": "Əlavə rəylər", + "report.reasons.dislike": "Bunu bəyənmədim", + "report.reasons.dislike_description": "Bu, görmək istədiyiniz bir şey deyil", + "report.reasons.legal": "Qanunsuzdur", + "report.reasons.legal_description": "Sizin və ya serverinizin olduğu ölkənin qanunlarını pozduğuna inanırsınız", + "report.reasons.other": "Başqa bir şeydir", + "report.reasons.other_description": "Problem, digər kateqoriyalara uyğun gəlmir", + "report.reasons.spam": "Spamdır", + "report.reasons.spam_description": "Zərərli keçidlər, saxta qarşılıqlı əlaqə və ya təkrarlanan cavablar", + "report.reasons.violation": "Server qaydalarını pozur", + "report.reasons.violation_description": "Müəyyən qaydaları pozduğundan xəbərdarsınız", + "report.rules.subtitle": "Uyğun olanların hamısını seçin", + "report.rules.title": "Hansı qaydalar pozulub?", + "report.statuses.subtitle": "Uyğun olanların hamısını seçin", + "report.statuses.title": "Bu şikayəti dəstəkləyən hər hansısa bir göndəriş var?", + "report.submit": "Təqdim et", + "report.target": "{target} şikayət edilir", + "report.thanks.take_action": "Mastodon-da nə görə biləcəyinizi idarə etmək üçün seçimləriniz bunlardır:", + "report.thanks.take_action_actionable": "Biz bunu incələdiyimiz müddətdə, siz @{name} ilə bağlı bunları edə bilərsiniz:", + "report.thanks.title": "Bunu görmək istəmirsiniz?", + "report.thanks.title_actionable": "Şikayət etdiyiniz üçün təşəkkürlər, məsələyə baxacağıq.", + "report.unfollow": "@{name} - izləmədən çıxart", + "report.unfollow_explanation": "Bu hesabı izləyirsiniz. Əsas ekran lentinizdə onun göndərişlərini artıq görmək istəmirsinizsə, onu izləmədən çıxarın.", + "report_notification.attached_statuses": "{count, plural, one {{count} göndəriş} other {{count} göndəriş}} əlavə edildi", + "report_notification.categories.legal": "Hüquqi", + "report_notification.categories.legal_sentence": "qanunsuz məzmun", + "report_notification.categories.other": "Digər", + "report_notification.categories.other_sentence": "digər", + "report_notification.categories.spam": "Spam", "report_notification.categories.spam_sentence": "spam", "report_notification.categories.violation": "Qayda pozuntusu", "report_notification.categories.violation_sentence": "qayda pozuntusu", @@ -581,6 +823,7 @@ "search_results.title": "\"{q}\" axtar", "server_banner.about_active_users": "Son 30 gündə bu serveri istifadə edənlər (aylıq aktiv istifadəçilər)", "server_banner.active_users": "aktiv istifadəçilər", + "server_banner.administered_by": "Administrasiya:", "server_banner.is_one_of_many": "{domain}, fediverse-də iştirak etmək üçün istifadə edə biləcəyiniz bir neçə müstəqil Mastodon serverlərindən biridir.", "server_banner.server_stats": "Server statistikaları:", "sign_in_banner.create_account": "Hesab yarat", @@ -597,7 +840,10 @@ "status.cannot_reblog": "Bu göndəriş təkrar paylaşıla bilməz", "status.context.load_new_replies": "Yeni cavablar mövcuddur", "status.context.loading": "Daha çox cavab yoxlanılır", + "status.continued_thread": "Davam edən mövzu", + "status.copy": "Göndəriş keçidini kopyala", "status.delete": "Sil", + "status.detailed_status": "Detallı danışıq görünüşü", "status.direct": "Şəxsi olaraq adını çək: @{name}", "status.direct_indicator": "Şəxsi olaraq adını çək", "status.edit": "Düzəliş et", @@ -620,6 +866,8 @@ "status.quote_error.filtered": "Bəzi filtrlərinizə görə gizlidir", "status.quote_error.not_available": "Göndəriş əlçatmazdır", "status.quote_error.pending_approval": "Göndəriş gözləmədədir", + "status.quote_error.pending_approval_popout.title": "Gözləyən sitat var? Səbrli olun.", + "status.quote_post_author": "@{name} göndərişini sitat gətirdi", "status.read_more": "Daha çoxunu oxu", "status.reblog": "Təkrar paylaş", "status.reblog_private": "Orijinal görünmə ilə təkrar paylaş", @@ -628,18 +876,23 @@ "status.reblogs.empty": "Hələ heç kim bu göndərişi təkrar paylaşmayıb. Kimsə paylaşdığı zaman, burada görünəcək.", "status.remove_bookmark": "Əlfəcini sil", "status.remove_favourite": "Sevimlilərdən sil", + "status.replied_in_thread": "Mövzuda cavablandırıldı", "status.replied_to": "Cavab verildi: {name}", "status.reply": "Cavabla", + "status.replyAll": "Mövzuda cavab ver", "status.report": "Bildir: @{name}", + "status.revoke_quote": "@{name} - göndərişindən mənim göndərişimi sil", "status.sensitive_warning": "Həssas məzmun", "status.share": "Paylaş", "status.show_less_all": "Hamısı üçün daha az göstər", "status.show_more_all": "Hamısı üçün daha çox göstər", "status.show_original": "Orijinalı göstər", + "status.title.with_attachments": "{user} {attachmentCount, plural, one {bir qoşma} other {{attachmentCount} qoşma}} paylaşdı", "status.translate": "Tərcümə et", "status.translated_from_with": "{provider} ilə {lang} dilindən tərcümə edilib", "status.uncached_media_warning": "Önizləmə mövcud deyil", "status.unmute_conversation": "Danışığın səsini aç", + "subscribed_languages.lead": "Dəyişiklikdən sonra əsas ekran və siyahı zaman xəttinizdə yalnız seçdiyiniz dillərdəki göndərişlər görünəcək. Bütün dillərdə göndəriş almaq üçün heç birini seçməyin.", "subscribed_languages.save": "Dəyişiklikləri saxla", "subscribed_languages.target": "{target} üçün abunə olunmuş dilləri dəyişdir", "tabs_bar.home": "Ana səhifə", @@ -655,6 +908,7 @@ "time_remaining.minutes": "{number, plural, one {# dəqiqə} other {# dəqiqə}} qalıb", "time_remaining.moments": "Bir neçə dəqiqə qalıb", "time_remaining.seconds": "{number, plural, one {# saniyə} other {# saniyə}} qalıb", + "trends.counter_by_accounts": "Son {days, plural, one {bir gündə} other {{days} gündə}} {count, plural, one {{counter} nəfər} other {{counter} nəfər}}", "trends.trending_now": "İndi trenddədir", "ui.beforeunload": "Mastodon-u tərk etsəniz, qaralamanız itəcək.", "units.short.billion": "{count} mlyrd", @@ -686,5 +940,17 @@ "video.skip_forward": "İrəli ötür", "video.unmute": "Səsi aç", "video.volume_down": "Həcmi azalt", - "video.volume_up": "Həcmi artır" + "video.volume_up": "Həcmi artır", + "visibility_modal.button_title": "Görünməni ayarla", + "visibility_modal.header": "Görünmə və qarşılıqlı əlaqə", + "visibility_modal.helper.direct_quoting": "Şəxsi adçəkmələr, sitat gətirilə bilməz.", + "visibility_modal.helper.privacy_editing": "Dərc edilən göndərişlərin görünməsi dəyişdirilə bilməz.", + "visibility_modal.helper.private_quoting": "Yalnız izləyicilərə xas göndərişlər, sitat gətirilə bilməz.", + "visibility_modal.helper.unlisted_quoting": "İnsanlar sizdən sitat gətirdiyi zaman, onların göndərişləri də trend zaman xəttindən gizlədiləcək.", + "visibility_modal.instructions": "Bu göndərişlə kimin əlaqə qura biləcəyini idarə edin. Qlobal ayarlar Tərcihlər > Digər bölməsinin altında tapıla bilər.", + "visibility_modal.privacy_label": "Gizlilik", + "visibility_modal.quote_followers": "Yalnız izləyicilər", + "visibility_modal.quote_label": "Kimin sitat gətirə biləcəyini dəyişdir", + "visibility_modal.quote_nobody": "Heç kim", + "visibility_modal.quote_public": "Hər kəs" } diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index cdca2446ca0..f07edacd0ca 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -637,5 +637,9 @@ "video.fullscreen": "Skramm a-bezh", "video.hide": "Kuzhat ar video", "video.pause": "Paouez", - "video.play": "Lenn" + "video.play": "Lenn", + "visibility_modal.privacy_label": "Prevezded", + "visibility_modal.quote_followers": "Tud koumanantet hepken", + "visibility_modal.quote_nobody": "Den ebet", + "visibility_modal.quote_public": "Pep den" } diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 018f66935cd..c5cc80edd42 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Deine Adresse:", "domain_pill.your_server": "Deine digitale Heimat. Hier „leben“ alle Beiträge von dir. Falls es dir hier nicht gefällt, kannst du jederzeit den Server wechseln und ebenso deine Follower übertragen.", "domain_pill.your_username": "Deine eindeutige Identität auf diesem Server. Es ist möglich, Profile mit dem gleichen Profilnamen auf verschiedenen Servern zu finden.", + "dropdown.empty": "Option auswählen", "embed.instructions": "Du kannst diesen Beitrag auf deiner Website einbetten, indem du den nachfolgenden Code kopierst.", "embed.preview": "Vorschau:", "emoji_button.activity": "Aktivitäten", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Beitragsveröffentlichung ausstehend", "status.quote_error.pending_approval_popout.body": "Zitierte Beiträge, die im Fediverse geteilt werden, benötigen einige Zeit, bis sie überall angezeigt werden, da die verschiedenen Server unterschiedliche Protokolle nutzen.", "status.quote_error.pending_approval_popout.title": "Zitierter Beitrag noch nicht freigegeben? Immer mit der Ruhe", + "status.quote_policy_change": "Ändern, wer zitieren darf", "status.quote_post_author": "Zitierte einen Beitrag von @{name}", "status.read_more": "Gesamten Beitrag anschauen", "status.reblog": "Teilen", @@ -959,5 +961,17 @@ "video.skip_forward": "Vorspulen", "video.unmute": "Stummschaltung aufheben", "video.volume_down": "Leiser", - "video.volume_up": "Lauter" + "video.volume_up": "Lauter", + "visibility_modal.button_title": "Sichtbarkeit festlegen", + "visibility_modal.header": "Sichtbarkeit und Interaktion", + "visibility_modal.helper.direct_quoting": "Private Erwähnungen können nicht zitiert werden.", + "visibility_modal.helper.privacy_editing": "Die Sichtbarkeit bereits veröffentlichter Beiträge kann nachträglich nicht mehr geändert werden.", + "visibility_modal.helper.private_quoting": "Beiträge, die nur für deine Follower bestimmt sind, können nicht zitiert werden.", + "visibility_modal.helper.unlisted_quoting": "Sollten dich andere zitieren, werden ihre zitierten Beiträge ebenfalls nicht in den Trends und öffentlichen Timelines angezeigt.", + "visibility_modal.instructions": "Bestimme, wer mit diesem Beitrag interagieren darf. Allgemeingültige Einstellungen findest du unter Einstellungen > Erweitert.", + "visibility_modal.privacy_label": "Datenschutz", + "visibility_modal.quote_followers": "Nur Follower", + "visibility_modal.quote_label": "Ändern, wer zitieren darf", + "visibility_modal.quote_nobody": "Niemand", + "visibility_modal.quote_public": "Alle" } diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 2b2ce14d5bc..28645d5c838 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tu alias:", "domain_pill.your_server": "Tu hogar digital, donde residen todos tus mensajes. ¿No te gusta este sitio? Mudate a otro servidor en cualquier momento y llevate a tus seguidores.", "domain_pill.your_username": "Tu identificador único en este servidor. Es posible encontrar cuentas con el mismo nombre de usuario en diferentes servidores.", + "dropdown.empty": "Seleccioná una opción", "embed.instructions": "Insertá este mensaje a tu sitio web copiando el código de abajo.", "embed.preview": "Así es cómo se verá:", "emoji_button.activity": "Actividad", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Mensaje pendiente", "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que diferentes servidores tienen diferentes protocolos.", "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Esperá un momento", + "status.quote_policy_change": "Cambiá quién puede citar", "status.quote_post_author": "Se citó un mensaje de @{name}", "status.read_more": "Leé más", "status.reblog": "Adherir", @@ -959,5 +961,17 @@ "video.skip_forward": "Adelantar", "video.unmute": "Quitar silenciado", "video.volume_down": "Bajar volumen", - "video.volume_up": "Subir volumen" + "video.volume_up": "Subir volumen", + "visibility_modal.button_title": "Establecer visibilidad", + "visibility_modal.header": "Visibilidad e interacción", + "visibility_modal.helper.direct_quoting": "No se pueden citar las menciones privadas.", + "visibility_modal.helper.privacy_editing": "No se puede cambiar la visibilidad a los mensajes ya enviados.", + "visibility_modal.helper.private_quoting": "No se pueden citar los mensajes destinados solo a seguidores.", + "visibility_modal.helper.unlisted_quoting": "Cuando otras cuentas te citen, sus publicaciones también se ocultarán de las líneas temporales de tendencias.", + "visibility_modal.instructions": "Controlá quién puede interactuar con este mensaje. Los ajustes globales se pueden encontrar en «Configuración» > «Otras opciones».", + "visibility_modal.privacy_label": "Privacidad", + "visibility_modal.quote_followers": "Solo para seguidores", + "visibility_modal.quote_label": "Cambiá quién puede citar", + "visibility_modal.quote_nobody": "Ninguna cuenta", + "visibility_modal.quote_public": "Cualquier cuenta" } diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 7caaf0c2305..1edf56d8882 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Títt hald:", "domain_pill.your_server": "Títt talgilda heim, har allir tínir postar liva. Dámar tað ikki hendan? Flyt til ein annan ambætara tá tú hevur hug til tess og tak fylgjarar tínar við eisini.", "domain_pill.your_username": "Títt eyðmerki á hesum ambætaranum. Tað er møguligt at finna brúkarar við tí sama brúkaranavninum á ymiskum ambætarum.", + "dropdown.empty": "Vel ein møguleika", "embed.instructions": "Fell hendan postin inní á tínum vevstaði við at taka avrit av koduni niðanfyri.", "embed.preview": "Soleiðis fer tað at síggja út:", "emoji_button.activity": "Virksemi", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Postur bíðar", "status.quote_error.pending_approval_popout.body": "Sitatir, sum eru deild tvørtur um fediversið, kunnu taka nakað av tíð at vísast, tí ymiskir ambætarar hava ymiskar protokollir.", "status.quote_error.pending_approval_popout.title": "Bíðar eftir sitati? Tak tað róligt", + "status.quote_policy_change": "Broyt hvør kann sitera", "status.quote_post_author": "Siteraði ein post hjá @{name}", "status.read_more": "Les meira", "status.reblog": "Stimbra", @@ -959,5 +961,17 @@ "video.skip_forward": "Leyp um frameftir", "video.unmute": "Doyv ikki", "video.volume_down": "Minka ljóðstyrki", - "video.volume_up": "Øk um ljóðstyrki" + "video.volume_up": "Øk um ljóðstyrki", + "visibility_modal.button_title": "Set sýni", + "visibility_modal.header": "Sýni og samvirkni", + "visibility_modal.helper.direct_quoting": "Privatar umrøður kunnu ikki siterast.", + "visibility_modal.helper.privacy_editing": "Útgivnir postar kunnnu ikki broyta sýni.", + "visibility_modal.helper.private_quoting": "Postar, sum einans eru fyri fylgjarar, kunnu ikki siterast.", + "visibility_modal.helper.unlisted_quoting": "Tá fólk sitera teg, so vera teirra postar eisini fjaldir frá tíðarlinjum við ráki.", + "visibility_modal.instructions": "Stýr, hvør kann virka saman við hesum postinum. Globalar stillingar finnast undir Stilingar > Onnur.", + "visibility_modal.privacy_label": "Privatlív", + "visibility_modal.quote_followers": "Einans fylgjarar", + "visibility_modal.quote_label": "Broyt hvør kann sitera", + "visibility_modal.quote_nobody": "Eingin", + "visibility_modal.quote_public": "Ein og hvør" } diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 5ba44f9c758..39c7a4825d6 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Saját fióknév:", "domain_pill.your_server": "A digitális otthonod, ahol a bejegyzéseid találhatók. Nem tetszik a mostani? Válts kiszolgálót bármikor, és vidd magaddal a követőidet is.", "domain_pill.your_username": "Az egyedi azonosítód ezen a kiszolgálón. Lehet, hogy ugyanazon felhasználónév különböző kiszolgálókon is megtalálható.", + "dropdown.empty": "Válassz egy lehetőséget", "embed.instructions": "Ágyazd be ezt a bejegyzést a weboldaladba az alábbi kód kimásolásával.", "embed.preview": "Így fog kinézni:", "emoji_button.activity": "Tevékenység", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "A bejegyzés függőben van", "status.quote_error.pending_approval_popout.body": "A Födiverzumon keresztül megosztott idézetek megjelenítése eltarthat egy darabig, mivel a különböző kiszolgálók különböző protokollokat használnak.", "status.quote_error.pending_approval_popout.title": "Függőben lévő idézet? Maradj nyugodt.", + "status.quote_policy_change": "Módosítás, hogy kik idézhetnek", "status.quote_post_author": "Idézte @{name} bejegyzését", "status.read_more": "Bővebben", "status.reblog": "Megtolás", @@ -959,5 +961,17 @@ "video.skip_forward": "Előreugrás", "video.unmute": "Némítás feloldása", "video.volume_down": "Hangerő le", - "video.volume_up": "Hangerő fel" + "video.volume_up": "Hangerő fel", + "visibility_modal.button_title": "Láthatóság beállítása", + "visibility_modal.header": "Láthatóság és interakció", + "visibility_modal.helper.direct_quoting": "A privát említések nem idézhetőek.", + "visibility_modal.helper.privacy_editing": "A közzétett bejegyzések láthatósága nem módosítható.", + "visibility_modal.helper.private_quoting": "A csak követőknek szánt bejegyzéseket nem lehet idézni.", + "visibility_modal.helper.unlisted_quoting": "Amikor idéznek tőled, a bejegyzésük rejtve lesz a felkapott bejegyzések hírfolyamain is.", + "visibility_modal.instructions": "Döntsd el, hogy ki léphet interakcióba a bejegyzéssel. A globális beállítások a Beállítások > Egyéb alatt találhatóak.", + "visibility_modal.privacy_label": "Adatvédelem", + "visibility_modal.quote_followers": "Csak követőknek", + "visibility_modal.quote_label": "Módosítás, hogy kik idézhetnek", + "visibility_modal.quote_nobody": "Senki", + "visibility_modal.quote_public": "Bárki" } diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index a3c9e8733cc..edee4b67f43 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Kennislóðin þín:", "domain_pill.your_server": "Stafrænt heimili þitt, þar sem allar færslur þínar eru hýstar. Kanntu ekki við þennan netþjón? Þú getur flutt þig á milli netþjóna hvenær sem er og tekið með þér alla fylgjendurna þína.", "domain_pill.your_username": "Sértækt auðkenni þitt á þessum netþjóni. Það er mögulegt að finna notendur með sama notandanafn á mismunandi netþjónum.", + "dropdown.empty": "Veldu valkost", "embed.instructions": "Felldu þessa færslu inn í vefsvæðið þitt með því að afrita kóðann hér fyrir neðan.", "embed.preview": "Svona mun þetta líta út:", "emoji_button.activity": "Virkni", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Færsla í bið", "status.quote_error.pending_approval_popout.body": "Tilvitnanir sem deilt er út um samfélagsnetið geta þurft nokkurn tíma áður en þær birtast, því mismunandi netþjónar geta haft mismunandi samskiptareglur.", "status.quote_error.pending_approval_popout.title": "Færsla í bið? Verum róleg", + "status.quote_policy_change": "Breyttu því hver getur tilvitnað", "status.quote_post_author": "Vitnaði í færslu frá @{name}", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", @@ -959,5 +961,12 @@ "video.skip_forward": "Stökkva áfram", "video.unmute": "Hætta að þagga", "video.volume_down": "Lækka hljóðstyrk", - "video.volume_up": "Hækka hljóðstyrk" + "video.volume_up": "Hækka hljóðstyrk", + "visibility_modal.button_title": "Stilla sýnileika", + "visibility_modal.header": "Sýnileiki og gagnvirkni", + "visibility_modal.privacy_label": "Persónuvernd", + "visibility_modal.quote_followers": "Einungis fylgjendur", + "visibility_modal.quote_label": "Breyttu því hver getur tilvitnað", + "visibility_modal.quote_nobody": "Enginn", + "visibility_modal.quote_public": "Hver sem er" } diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 45ca92bebeb..c1643ab9be3 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -245,6 +245,8 @@ "confirmations.remove_from_followers.confirm": "Ta bort följare", "confirmations.remove_from_followers.message": "{name} kommer att sluta följa dig. Är du säker på att du vill fortsätta?", "confirmations.remove_from_followers.title": "Ta bort följare?", + "confirmations.revoke_quote.confirm": "Ta bort inlägg", + "confirmations.revoke_quote.title": "Ta bort inlägg?", "confirmations.unfollow.confirm": "Avfölj", "confirmations.unfollow.message": "Är du säker på att du vill avfölja {name}?", "confirmations.unfollow.title": "Avfölj användare?", @@ -289,6 +291,7 @@ "domain_pill.your_handle": "Ditt handtag:", "domain_pill.your_server": "Ditt digitala hem, där alla dina inlägg bor. Gillar du inte just denna? Byt server när som helst och ta med dina anhängare också.", "domain_pill.your_username": "Din unika identifierare på denna server. Det är möjligt att hitta användare med samma användarnamn på olika servrar.", + "dropdown.empty": "Välj ett alternativ", "embed.instructions": "Bädda in detta inlägg på din webbplats genom att kopiera koden nedan.", "embed.preview": "Så här kommer det att se ut:", "emoji_button.activity": "Aktivitet", @@ -498,6 +501,8 @@ "keyboard_shortcuts.translate": "för att översätta ett inlägg", "keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält", "keyboard_shortcuts.up": "Flytta uppåt i listan", + "learn_more_link.got_it": "Jag förstår", + "learn_more_link.learn_more": "Läs mer", "lightbox.close": "Stäng", "lightbox.next": "Nästa", "lightbox.previous": "Tidigare", @@ -873,6 +878,7 @@ "status.open": "Utvidga detta inlägg", "status.pin": "Fäst i profil", "status.quote_error.filtered": "Dolt på grund av ett av dina filter", + "status.quote_error.not_available": "Inlägg ej tillgängligt", "status.read_more": "Läs mer", "status.reblog": "Boosta", "status.reblog_private": "Boosta med ursprunglig synlighet", @@ -946,5 +952,10 @@ "video.skip_forward": "Hoppa framåt", "video.unmute": "Avtysta", "video.volume_down": "Volym ned", - "video.volume_up": "Volym upp" + "video.volume_up": "Volym upp", + "visibility_modal.header": "Synlighet och interaktion", + "visibility_modal.privacy_label": "Integritet", + "visibility_modal.quote_followers": "Endast följare", + "visibility_modal.quote_nobody": "Ingen", + "visibility_modal.quote_public": "Alla" } diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index bee00c0f06f..c8f7b7e4153 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -274,6 +274,7 @@ "domain_pill.your_handle": "Ваша адреса:", "domain_pill.your_server": "Ваш цифровий дім, де живуть усі ваші дописи. Не подобається цей? Перенесіть сервери в будь-який час і залучайте своїх підписників.", "domain_pill.your_username": "Ваш унікальний ідентифікатор на цьому сервері. Ви можете знайти користувачів з однаковими іменами на різних серверах.", + "dropdown.empty": "Оберіть варіант", "embed.instructions": "Вбудуйте цей допис до вашого вебсайту, скопіювавши код нижче.", "embed.preview": "Ось який вигляд це матиме:", "emoji_button.activity": "Діяльність", @@ -919,5 +920,10 @@ "video.fullscreen": "На весь екран", "video.hide": "Приховати відео", "video.pause": "Призупинити", - "video.play": "Програвати" + "video.play": "Програвати", + "visibility_modal.privacy_label": "Конфіденційність", + "visibility_modal.quote_followers": "Тільки для підписників", + "visibility_modal.quote_label": "Змінити хто може цитувати", + "visibility_modal.quote_nobody": "Ніхто", + "visibility_modal.quote_public": "Будь-хто" } diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index 37e8ecee8d4..f5d3b1b3451 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Địa chỉ Mastodon của bạn:", "domain_pill.your_server": "Nơi lưu trữ tút của bạn. Không thích ở đây? Chuyển sang máy chủ khác và giữ nguyên người theo dõi của bạn.", "domain_pill.your_username": "Chỉ riêng bạn trên máy chủ này. Những máy chủ khác có thể cũng có tên người dùng giống vậy.", + "dropdown.empty": "Chọn một lựa chọn", "embed.instructions": "Sao chép đoạn mã dưới đây và chèn vào trang web của bạn.", "embed.preview": "Nó sẽ hiển thị như vầy:", "emoji_button.activity": "Hoạt động", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Tút đang chờ duyệt", "status.quote_error.pending_approval_popout.body": "Các trích dẫn được chia sẻ trên Fediverse có thể mất thời gian để hiển thị vì các máy chủ khác nhau có giao thức khác nhau.", "status.quote_error.pending_approval_popout.title": "Đang chờ trích dẫn? Hãy bình tĩnh", + "status.quote_policy_change": "Thay đổi người có thể trích dẫn", "status.quote_post_author": "Trích dẫn từ tút của @{name}", "status.read_more": "Đọc tiếp", "status.reblog": "Đăng lại", @@ -959,5 +961,17 @@ "video.skip_forward": "Tua tới", "video.unmute": "Bật tiếng", "video.volume_down": "Giảm âm lượng", - "video.volume_up": "Tăng âm lượng" + "video.volume_up": "Tăng âm lượng", + "visibility_modal.button_title": "Thay đổi quyền riêng tư", + "visibility_modal.header": "Hiển thị và tương tác", + "visibility_modal.helper.direct_quoting": "Không thể trích dẫn nhắn riêng.", + "visibility_modal.helper.privacy_editing": "Không thể thay đổi kiểu hiển thị của tút đã đăng.", + "visibility_modal.helper.private_quoting": "Không thể trích dẫn những tút chỉ dành cho người theo dõi.", + "visibility_modal.helper.unlisted_quoting": "Khi ai đó trích dẫn bạn, tút của họ cũng sẽ bị ẩn khỏi bảng tin công khai.", + "visibility_modal.instructions": "Kiểm soát những ai có thể tương tác với tút này. Cài đặt chung trong Thiết lập > Khác.", + "visibility_modal.privacy_label": "Riêng tư", + "visibility_modal.quote_followers": "Chỉ người theo dõi", + "visibility_modal.quote_label": "Thay đổi người có thể trích dẫn", + "visibility_modal.quote_nobody": "Không ai", + "visibility_modal.quote_public": "Bất cứ ai" } diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 9dc2998504e..2627fe21e3e 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "您的帳號:", "domain_pill.your_server": "您數位世界的家,您所有的嘟文都在這裡。不喜歡這台伺服器嗎?您能隨時搬家至其他伺服器並且仍保有您的跟隨者。", "domain_pill.your_username": "您於您的伺服器中獨一無二的識別。於不同的伺服器上可能找到具有相同帳號的使用者。", + "dropdown.empty": "選取選項", "embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。", "embed.preview": "它將顯示成這樣:", "emoji_button.activity": "活動", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "嘟文正在發送中", "status.quote_error.pending_approval_popout.body": "因為伺服器間可能運行不同協定,顯示聯邦宇宙間之引用嘟文會有些許延遲。", "status.quote_error.pending_approval_popout.title": "引用嘟文正在發送中?別著急,請稍候片刻", + "status.quote_policy_change": "變更可以引用的人", "status.quote_post_author": "已引用 @{name} 之嘟文", "status.read_more": "閱讀更多", "status.reblog": "轉嘟", @@ -959,5 +961,17 @@ "video.skip_forward": "下一部", "video.unmute": "取消靜音", "video.volume_down": "降低音量", - "video.volume_up": "提高音量" + "video.volume_up": "提高音量", + "visibility_modal.button_title": "設定可見性", + "visibility_modal.header": "可見性與互動", + "visibility_modal.helper.direct_quoting": "無法引用私人提及。", + "visibility_modal.helper.privacy_editing": "無法變更已發佈的嘟文的可見性。", + "visibility_modal.helper.private_quoting": "無法引用僅追蹤者的嘟文。", + "visibility_modal.helper.unlisted_quoting": "當期他人引用您時,他們的嘟文也會自熱門時間軸隱藏。", + "visibility_modal.instructions": "控制誰能與此嘟文互動。可在偏好設定 > 其他下找到全域設定。", + "visibility_modal.privacy_label": "隱私權", + "visibility_modal.quote_followers": "僅限跟隨者", + "visibility_modal.quote_label": "變更可以引用的人", + "visibility_modal.quote_nobody": "沒有人", + "visibility_modal.quote_public": "所有人" } diff --git a/config/locales/az.yml b/config/locales/az.yml index 1e66e71bed3..302dccdecb7 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -2,6 +2,9 @@ az: about: about_mastodon_html: 'Gələcəyin sosial şəbəkəsi: Reklam yoxdur, korporativ müşahidə yoxdur, etik dizayn və mərkəziyyətsizlik! Mastodon ilə öz verilərinizə sahib çıxın!' + contact_missing: Ayarlanmayıb + contact_unavailable: Mövcud deyil + title: Haqqında admin: account_actions: title: "%{acct} üzərində moderasiya əməliyyatını icra et" diff --git a/config/locales/devise.az.yml b/config/locales/devise.az.yml index 1df8c31cd69..108f6bc685a 100644 --- a/config/locales/devise.az.yml +++ b/config/locales/devise.az.yml @@ -100,3 +100,17 @@ az: already_signed_out: Uğurla hesabdan çıxış edildi. signed_in: Uğurla hesaba daxil olundu. signed_out: Uğurla hesabdan çıxış edildi. + unlocks: + send_instructions: Bir neçə dəqiqə ərzində e-poçt ünvanınızın kilidini necə açmaqla bağlı təlimatlar olan bir e-poçt alacaqsınız. Bu e-poçtu almamısınızsa, lütfən spam qovluğunuzu yoxlayın. + send_paranoid_instructions: Əgər hesabınız mövcuddursa, bir neçə dəqiqə ərzində e-poçt ünvanınızın kilidini necə açmaqla bağlı təlimatlar olan bir e-poçt alacaqsınız. Bu e-poçtu almamısınızsa, lütfən spam qovluğunuzu yoxlayın. + unlocked: Hesabınızın kilidi uğurla açıldı. Davam etmək üçün lütfən giriş edin. + errors: + messages: + already_confirmed: artıq təsdiqlənib, lütfən hesabınıza daxil olmağa çalışın + confirmation_period_expired: "%{period} ərzində təsdiqlənməsi lazımdır, lütfən yenisini tələb edin" + expired: müddəti bitib, lütfən yenisini tələb edin + not_found: tapılmadı + not_locked: kilidlənməyib + not_saved: + one: '1 xəta, bu %{resource} resursunu saxlamağı qadağan etdi:' + other: "%{count} xəta, bu %{resource} resursunu saxlamağı qadağan etdi:" diff --git a/config/locales/doorkeeper.az.yml b/config/locales/doorkeeper.az.yml index 210506ffeb4..7cf62a7af9d 100644 --- a/config/locales/doorkeeper.az.yml +++ b/config/locales/doorkeeper.az.yml @@ -1,42 +1,175 @@ --- az: + activerecord: + attributes: + doorkeeper/application: + name: Tətbiq adı + redirect_uri: Yönləndirmə URL-si + website: Tətbiq veb saytı + errors: + models: + doorkeeper/application: + attributes: + redirect_uri: + fragment_present: fraqment ehtiva edə bilməz. + invalid_uri: yararlı bir URL olmalıdır. + relative_uri: mütləq bir URL olmalıdır. + secured_uri: HTTPS/SSL URL olmalıdır. doorkeeper: + applications: + buttons: + authorize: Səlahiyyət ver + cancel: İmtina + destroy: Məhv et + edit: Düzəliş et + submit: Təqdim et + confirmations: + destroy: Əminsiniz? + edit: + title: Tətbiqə düzəliş et + form: + error: Ups! Mümkün xətalar üçün formunuzu yoxlayın + help: + native_redirect_uri: Lokal testlər üçün %{native_redirect_uri} istifadə edin + redirect_uri: Hər URL üçün bir sətir istifadə edin + index: + application: Tətbiq + delete: Sil + empty: Heç bir tətbiqiniz yoxdur. + name: Ad + new: Yeni tətbiq + show: Göstər + title: Tətbiqləriniz + new: + title: Yeni tətbiq + show: + actions: Əməliyyatlar + application_id: Client açarı + secret: Client sirri + title: 'Tətbiq: %{name}' + authorizations: + buttons: + authorize: Səlahiyyət ver + deny: Rədd et + error: + title: Bir xəta baş verdi + new: + prompt_html: "%{client_name} hesabınıza erişmək üçün icazə istəyir. Yalnız bu mənbəni tanıyırsınızsa və ona güvənirsinizsə bu istəyi təsdiqləyin." + review_permissions: İcazələri incələ + title: Səlahiyyət tələb olunur + show: + title: Bu səlahiyyət kodunu kopyalayın və tətbiqə yapışdırın. authorized_applications: + buttons: + revoke: Ləğv et + confirmations: + revoke: Əminsiniz? index: authorized_at: "%{date} tarixində səlahiyyət verilib" description_html: Bunlar, API istifadə edərək hesabınıza erişəbilən tətbiqlərdir. Əgər burada tanımadığınız tətbiqlər və ya yanlış davranan bir tətbiq varsa, erişimini ləğv edə bilərsiniz. last_used_at: Ən son %{date} istifadə edilib + never_used: Heç vaxt istifadə edilməyib + scopes: İcazələr + superapp: Daxili title: Səlahiyyətli tətbiqləriniz errors: messages: + access_denied: Resurs sahibi və ya avtorizasiya serveri istəyə rədd cavabı verdi. invalid_token: expired: Erişim tokeninin vaxtı bitib revoked: Erişim tokeni ləğv edilib unknown: Erişim tokeni yararsızdır + flash: + applications: + create: + notice: Tətbiq yaradıldı. + destroy: + notice: Tətbiq silindi. + update: + notice: Tətbiq güncəlləndi. + authorized_applications: + destroy: + notice: Tətbiq ləğv edildi. grouped_scopes: access: read: Yalnız oxuma erişimi read/write: Oxuma və yazma erişimi write: Yalnız yazma erişimi title: + accounts: Hesablar + admin/accounts: Hesabların administrasiyası + admin/all: Bütün administrativ funksiyalar + admin/reports: Şikayətlərin administrasiyası all: Mastodon hesabınıza tam erişim + blocks: Əngəlləmələr + bookmarks: Əlfəcinlər + conversations: Danışıqlar + crypto: Ucdan-uca şifrələmə + favourites: Sevimlilər filters: Filtrlər follow: İzləmələr, səsi kəsmələr və əngəlləmələr follows: İzlənilənlər + lists: Siyahılar + media: Media qoşmaları + mutes: Səssizdəkilər + notifications: Bildirişlər + profile: Mastodon profiliniz + push: Ani bildirişlər + reports: Şikayətlər + search: Axtar + statuses: Göndərişlər + layouts: + admin: + nav: + applications: Tətbiqlər + oauth2_provider: OAuth2 Provayderi + application: + title: OAuth səlahiyyəti tələb olunur scopes: - admin:read: serverdəki bütün veriləri oxuma - admin:read:domain_blocks: əngəllənən bütün domenlərin həssas məlumatlarını oxuma - admin:read:email_domain_blocks: əngəllənən bütün e-poçt domenlərinin həssas məlumatlarını oxuma - admin:write: serverdəki bütün veriləri dəyişdirmə - admin:write:accounts: hesablarda moderasiya əməliyyatlarını icra et - admin:write:canonical_email_blocks: - admin:write:domain_allows: domen icazələri üzərində moderasiya əməliyyatlarını icra et - admin:write:domain_blocks: əngəllənən domenlər üzərində moderasiya fəaliyyətlərini icra et - admin:write:email_domain_blocks: əngəllənən e-poçt domenləri üzərində moderasiya fəaliyyətlərini icra et - admin:write:ip_blocks: IP əngəlləmələri üzrə moderasiya əməliyyatlarını icra et - admin:write:reports: hesabatlarda moderasiya əməliyyatlarını icra et - read: hesabınızın bütün verilərini oxuma - read:filters: filtrlərinizə baxın - read:follows: izlədiklərinizə baxın - write: hesabınızın bütün verilərini dəyişdirmə - write:filters: filtrlər yarat + admin:read: serverdəki bütün veriləri oxuyur + admin:read:accounts: bütün hesabların həssas məlumatlarını oxuyur + admin:read:canonical_email_blocks: bütün kanonik e-poçt əngəlləmələrinin həssas məlumatlarını oxuyur + admin:read:domain_allows: bütün domen icazələrinin həssas məlumatlarını oxuyur + admin:read:domain_blocks: bütün əngəllənən domenlərin həssas məlumatlarını oxuyur + admin:read:email_domain_blocks: bütün əngəllənən e-poçt domenlərinin həssas məlumatlarını oxuyur + admin:read:ip_blocks: bütün IP əngəlləmələrinin həssas məlumatlarını oxuyur + admin:read:reports: bütün şikayətlərin və şikayət edilən hesabların həssas məlumatlarını oxuyur + admin:write: serverdəki bütün veriləri dəyişdirir + admin:write:accounts: hesablarda moderasiya əməliyyatlarını icra edir + admin:write:canonical_email_blocks: əngəllənən kanonik e-poçtlar üzərində moderasiya əməliyyatlarını icra edir + admin:write:domain_allows: icazə verilən domenlər üzərində moderasiya əməliyyatlarını icra edir + admin:write:domain_blocks: əngəllənən domenlər üzərində moderasiya əməliyyatlarını icra edir + admin:write:email_domain_blocks: əngəllənən e-poçt domenləri üzərində moderasiya əməliyyatlarını icra edir + admin:write:ip_blocks: əngəllənən IP-lər üzərində moderasiya əməliyyatlarını icra edir + admin:write:reports: hesabatlarda moderasiya əməliyyatlarını icra edir + crypto: ucdan-uca şifrələməni istifadə edir + follow: hesab münasibətlərini dəyişdirir + profile: hesabınızın yalnız profil məlumatlarını oxuyur + push: ani bildirişlərinizi alır + read: hesabınızın bütün verilərini oxuyur + read:accounts: hesab məlumatlarını görür + read:blocks: əngəllədiklərinizi görür + read:bookmarks: əlfəcinlərinizi görür + read:favourites: sevimlilərinizi görür + read:filters: filtrlərinizi görür + read:follows: izlədiklərinizi görür + read:lists: siyahılarınızı görür + read:mutes: səssizə aldıqlarınızı görür + read:notifications: bildirişlərinizi görür + read:reports: hesabatlarınızı görür + read:search: sizin adınıza axtarış edir + read:statuses: bütün göndərişləri görür + write: hesabınızın bütün verilərini dəyişdirir + write:accounts: profilinizi dəyişdirir + write:blocks: hesabları və domenləri əngəlləyir + write:bookmarks: göndərişləri əlfəcinlərə əlavə edir + write:conversations: danışıqları səssizə alır və silir + write:favourites: göndərişləri sevimlilərə əlavə edir + write:filters: filtrlər yaradır + write:follows: insanları izləyir + write:lists: siyahılar yaradır + write:media: media faylları yükləyir + write:mutes: insanları və danışıqları səssizə alır + write:notifications: bildirişlərinizi təmizləyir + write:reports: digər insanları şikayət edir + write:statuses: göndərişləri dərc edir diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 32ea9f26c3b..ac60d968d97 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -2063,6 +2063,9 @@ pl: recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. Trzymaj je w bezpiecznym miejscu. Na przykład, wydrukuj je i przechowuj z ważnymi dokumentami. webauthn: Klucze bezpieczeństwa user_mailer: + announcement_published: + subject: Ogłoszenie serwisu + title: Ogłoszenie serwisu %{domain} appeal_approved: action: Ustawienia Konta explanation: Twoje odwołanie dotyczące ostrzeżenia nałożonego na twoje konto dnia %{strike_date}, które zostało wysłane dnia %{appeal_date} zostało zatwierdzone. Twoje konto jest ponownie w dobrej kondycji. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 58f71bb4549..b34a8ab84ce 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1932,7 +1932,7 @@ ru: download: Скачать (%{count}) event_type: account_suspension: Пользователь был заблокирован модераторами (%{target_name}) - domain_block: Сервер был заблокирован модераторами (%{target_name}) + domain_block: Сервер %{target_name} был заблокирован модераторами user_domain_block: Вы заблокировали %{target_name} lost_followers: Потерянные подписчики lost_follows: Потерянные подписки diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 23204dea2db..7300d67c97c 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -150,6 +150,11 @@ pl: min_age: Nie powinien być niższy niż minimalny wiek wymagany przez prawo twojego państwa. user: chosen_languages: Jeżeli zaznaczone, tylko wpisy w wybranych językach będą wyświetlane na publicznych osiach czasu + date_of_birth: + few: Musimy upewnić się, że jesteś co najmniej %{count} aby użyć %{domain}. Nie będziemy tego przechowywać. + many: Musimy upewnić się, że jesteś co najmniej %{count} aby użyć %{domain}. Nie będziemy tego przechowywać. + one: Musimy upewnić się, że jesteś co najmniej %{count} aby użyć %{domain}. Nie będziemy tego przechowywać. + other: Musimy upewnić się, że jesteś co najmniej %{count} aby użyć %{domain}. Nie będziemy tego przechowywać. role: Rola kontroluje uprawnienia użytkownika. user_role: color: Kolor używany dla roli w całym interfejsie użytkownika, wyrażony jako RGB w formacie szesnastkowym diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 96f00e48c25..598617efdcd 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -367,6 +367,9 @@ sv: name: Namn permissions_as_keys: Behörigheter position: Prioritet + username_block: + allow_with_approval: Tillåt registreringar med godkännande + username: Ord att matcha webhook: events: Aktiverade händelser template: Lastmall diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 25bc593917a..594f4b97405 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -190,6 +190,7 @@ sv: create_relay: Skapa ombud create_unavailable_domain: Skapa otillgänglig domän create_user_role: Skapa roll + create_username_block: Skapa användarnamnsregel demote_user: Degradera användare destroy_announcement: Radera notis destroy_canonical_email_block: Ta bort e-post block @@ -203,6 +204,7 @@ sv: destroy_status: Radera inlägg destroy_unavailable_domain: Ta bort otillgänglig domän destroy_user_role: Förstör roll + destroy_username_block: Ta bort användarnamnsregel disable_2fa_user: Inaktivera 2FA disable_custom_emoji: Inaktivera egna emojis disable_relay: Inaktivera ombud @@ -237,6 +239,7 @@ sv: update_report: Uppdatera rapport update_status: Uppdatera inlägg update_user_role: Uppdatera roll + update_username_block: Uppdatera användarnamnsregel actions: approve_appeal_html: "%{name} godkände överklagande av modereringsbeslut från %{target}" approve_user_html: "%{name} godkände registrering från %{target}" @@ -1085,6 +1088,17 @@ sv: other: Använd av %{count} personer under den senaste veckan title: Rekommendationer & Trender trending: Trendande + username_blocks: + add_new: Lägg till ny + comparison: + contains: Innehåller + delete: Ta bort + edit: + title: Redigera användarnamnsregel + new: + create: Skapa regel + title: Skapa ny användarnamnsregel + not_permitted: Ej tillåtet warning_presets: add_new: Lägg till ny delete: Radera @@ -1880,6 +1894,8 @@ sv: ownership: Någon annans inlägg kan inte fästas reblog: En boost kan inte fästas quote_policies: + followers: Endast dina följare + nobody: Ingen public: Alla title: '%{name}: "%{quote}"' visibilities: From 83fcd1cf4f0d11cd34d79d6dcda8d827848d3bc7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:01:07 +0200 Subject: [PATCH 290/660] Update dependency rails-i18n to v8.0.2 (#35796) 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 b0bf819481f..6a4040cffbc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -688,7 +688,7 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) 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.0.1) + rails-i18n (8.0.2) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) railties (8.0.2.1) From a18e6199efb60be6d0f14bca176dfc37f1fdbc07 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:03:54 +0200 Subject: [PATCH 291/660] New Crowdin Translations (automated) (#35795) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/az.json | 3 +- app/javascript/mastodon/locales/be.json | 16 +++++++- app/javascript/mastodon/locales/bg.json | 28 ++++++++++++- app/javascript/mastodon/locales/ca.json | 18 ++++++++- app/javascript/mastodon/locales/cs.json | 16 +++++++- app/javascript/mastodon/locales/cy.json | 33 ++++++++++++++- app/javascript/mastodon/locales/da.json | 16 +++++++- app/javascript/mastodon/locales/el.json | 16 +++++++- app/javascript/mastodon/locales/eo.json | 14 ++++++- app/javascript/mastodon/locales/es-MX.json | 16 +++++++- app/javascript/mastodon/locales/es.json | 16 +++++++- app/javascript/mastodon/locales/fa.json | 20 ++++++++- app/javascript/mastodon/locales/fi.json | 16 +++++++- app/javascript/mastodon/locales/ga.json | 16 +++++++- app/javascript/mastodon/locales/gl.json | 16 +++++++- app/javascript/mastodon/locales/he.json | 16 +++++++- app/javascript/mastodon/locales/nan.json | 22 ++++++++-- app/javascript/mastodon/locales/zh-TW.json | 6 +-- config/locales/bg.yml | 38 +++++++++++++++++ config/locales/ca.yml | 2 +- config/locales/cy.yml | 31 ++++++++++++++ config/locales/devise.be.yml | 2 +- config/locales/fa.yml | 20 +++++++++ config/locales/fi.yml | 8 ++-- config/locales/nan.yml | 47 ++++++++++++++++++++-- config/locales/ru.yml | 22 +++++----- config/locales/simple_form.bg.yml | 15 +++++++ config/locales/simple_form.cy.yml | 10 +++++ config/locales/simple_form.fa.yml | 8 ++++ 29 files changed, 461 insertions(+), 46 deletions(-) diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index a243952ec7a..e9bcd2f34e0 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -174,7 +174,7 @@ "column.lists": "Siyahılar", "column.mutes": "Səssizləşdirilmiş istifadəçilər", "column.notifications": "Bildirişlər", - "column.pins": "Bərkidilmiş paylaşımlar", + "column.pins": "Sancılmış göndərişlər", "column.public": "Federasiya zaman qrafiki", "column_back_button.label": "Geriyə", "column_header.hide_settings": "Ayarları gizlət", @@ -483,6 +483,7 @@ "keyboard_shortcuts.my_profile": "Profilinizi açın", "keyboard_shortcuts.notifications": "Bildirişlər sütununu aç", "keyboard_shortcuts.open_media": "Medianı aç", + "keyboard_shortcuts.pinned": "Sancılmış göndərişlərin siyahısını aç", "keyboard_shortcuts.profile": "Müəllifin profilini aç", "keyboard_shortcuts.reply": "Göndərişə cavab ver", "keyboard_shortcuts.requests": "İzləmə istəyi siyahısını aç", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index a3caa5a0235..29ad29c4f52 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Ваш ідэнтыфікатар:", "domain_pill.your_server": "Ваш лічбавы дом, дзе захоўваюцца ўсе вашыя допісы. Не падабаецца гэты сервер? Змяніце сервер у любы час з захаваннем сваіх падпісчыкаў.", "domain_pill.your_username": "Ваш унікальны ідэнтыфікатар на гэтым серверы. Можна знайсці карыстальнікаў з аднолькавым іменем карыстальніка на розных серверах.", + "dropdown.empty": "Выбраць варыянт", "embed.instructions": "Убудуйце гэты допіс на свой сайт, скапіраваўшы прыведзены ніжэй код.", "embed.preview": "Вось як гэта будзе выглядаць:", "emoji_button.activity": "Актыўнасць", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Допіс чакае пацвярджэння", "status.quote_error.pending_approval_popout.body": "Допісы, якія былі цытаваныя паміж серверамі Fediverse, могуць доўга загружацца, паколькі розныя серверы маюць розныя пратаколы.", "status.quote_error.pending_approval_popout.title": "Цытаваны допіс чакае пацвярджэння? Захоўвайце спакой", + "status.quote_policy_change": "Змяніць, хто можа цытаваць", "status.quote_post_author": "Цытаваў допіс @{name}", "status.read_more": "Чытаць болей", "status.reblog": "Пашырыць", @@ -959,5 +961,17 @@ "video.skip_forward": "Праматаць уперад", "video.unmute": "Уключыць гук", "video.volume_down": "Паменшыць гучнасць", - "video.volume_up": "Павялічыць гучнасць" + "video.volume_up": "Павялічыць гучнасць", + "visibility_modal.button_title": "Вызначыць бачнасць", + "visibility_modal.header": "Бачнасць і ўзаемадзеянне", + "visibility_modal.helper.direct_quoting": "Прыватныя згадванні нельга цытаваць.", + "visibility_modal.helper.privacy_editing": "Апублікаваным допісам нельга змяняць бачнасць.", + "visibility_modal.helper.private_quoting": "Допісы для падпісчыкаў нельга цытаваць.", + "visibility_modal.helper.unlisted_quoting": "Калі людзі працытуюць Вас, іх допіс таксама будзе схаваны ад стужкі трэндаў.", + "visibility_modal.instructions": "Кантралюйце, хто можа ўзаемадзейнічаць з Вашым допісам. Глабальныя налады можна знайсці ў Налады > Іншае.", + "visibility_modal.privacy_label": "Прыватнасць", + "visibility_modal.quote_followers": "Толькі падпісчыкі", + "visibility_modal.quote_label": "Змяніць, хто можа цытаваць", + "visibility_modal.quote_nobody": "Ніхто", + "visibility_modal.quote_public": "Усе" } diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 6f0cf6f54fe..04abc227fa9 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -1,6 +1,7 @@ { "about.blocks": "Модерирани сървъри", "about.contact": "За контакти:", + "about.default_locale": "По подразбиране", "about.disclaimer": "Mastodon е безплатен софтуер с отворен изходен код и търговска марка на Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Няма налична причина", "about.domain_blocks.preamble": "Mastodon обикновено позволява да разглеждате съдържание и да взаимодействате с други потребители от всякакви сървъри във Федивселената. Има изключения, направени конкретно за този сървър.", @@ -244,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Премахване на последовател", "confirmations.remove_from_followers.message": "{name} ще спре да ви следва. Наистина ли искате да продължите?", "confirmations.remove_from_followers.title": "Премахвате ли последовател?", + "confirmations.revoke_quote.confirm": "Премахване на публикация", + "confirmations.revoke_quote.message": "Действието е неотменимо.", + "confirmations.revoke_quote.title": "Премахвате ли публикацията?", "confirmations.unfollow.confirm": "Без следване", "confirmations.unfollow.message": "Наистина ли искате вече да не следвате {name}?", "confirmations.unfollow.title": "Спирате ли да следвате потребителя?", @@ -288,6 +292,7 @@ "domain_pill.your_handle": "Вашият адрес:", "domain_pill.your_server": "Цифровият ви дом, където живеят всичките ви публикации. Не харесвате ли този? Прехвърляте се на сървъри по всяко време и докарвате последователите си също.", "domain_pill.your_username": "Неповторимият ви идентификатор на този сървър. Възможно е да се намерят потребители със същото потребителско име на други сървъри.", + "dropdown.empty": "Изберете възможност", "embed.instructions": "Вградете публикацията в уебсайта си, копирайки кода долу.", "embed.preview": "Ето как ще изглежда:", "emoji_button.activity": "Дейност", @@ -493,6 +498,8 @@ "keyboard_shortcuts.translate": "за превод на публикация", "keyboard_shortcuts.unfocus": "Разфокусиране на текстовото поле за съставяне/търсене", "keyboard_shortcuts.up": "Преместване нагоре в списъка", + "learn_more_link.got_it": "Разбрах", + "learn_more_link.learn_more": "Научете повече", "lightbox.close": "Затваряне", "lightbox.next": "Напред", "lightbox.previous": "Назад", @@ -587,6 +594,7 @@ "notification.label.mention": "Споменаване", "notification.label.private_mention": "Частно споменаване", "notification.label.private_reply": "Личен отговор", + "notification.label.quote": "{name} цитира ваша публикация", "notification.label.reply": "Отговор", "notification.mention": "Споменаване", "notification.mentioned_you": "{name} ви спомена", @@ -644,6 +652,7 @@ "notifications.column_settings.mention": "Споменавания:", "notifications.column_settings.poll": "Резултати от анкета:", "notifications.column_settings.push": "Изскачащи известия", + "notifications.column_settings.quote": "Цитати:", "notifications.column_settings.reblog": "Подсилвания:", "notifications.column_settings.show": "Показване в колоната", "notifications.column_settings.sound": "Пускане на звук", @@ -834,6 +843,8 @@ "status.bookmark": "Отмятане", "status.cancel_reblog_private": "Край на подсилването", "status.cannot_reblog": "Публикацията не може да се подсилва", + "status.context.load_new_replies": "Има нови отговори", + "status.context.loading": "Проверка за още отговори", "status.continued_thread": "Продължена нишка", "status.copy": "Копиране на връзката към публикация", "status.delete": "Изтриване", @@ -860,6 +871,10 @@ "status.open": "Разширяване на публикацията", "status.pin": "Закачане в профила", "status.quote_error.filtered": "Скрито поради един от филтрите ви", + "status.quote_error.not_available": "Неналична публикация", + "status.quote_error.pending_approval": "Публикацията чака одобрение", + "status.quote_policy_change": "Промяна кой може да цитира", + "status.quote_post_author": "Цитирах публикация от @{name}", "status.read_more": "Още за четене", "status.reblog": "Подсилване", "status.reblog_private": "Подсилване с оригиналната видимост", @@ -874,6 +889,7 @@ "status.reply": "Отговор", "status.replyAll": "Отговор на нишка", "status.report": "Докладване на @{name}", + "status.revoke_quote": "Премахване на моя публикация от публикацията на @{name}", "status.sensitive_warning": "Деликатно съдържание", "status.share": "Споделяне", "status.show_less_all": "Показване на по-малко за всички", @@ -933,5 +949,15 @@ "video.skip_forward": "Прескок напред", "video.unmute": "Без заглушаване", "video.volume_down": "Намаляване на звука", - "video.volume_up": "Увеличаване на звука" + "video.volume_up": "Увеличаване на звука", + "visibility_modal.button_title": "Задаване на видимост", + "visibility_modal.header": "Видимост и взаимодействие", + "visibility_modal.helper.direct_quoting": "Частни споменавания не може да се цитират.", + "visibility_modal.helper.privacy_editing": "Публикуваните публикации не може да променят видимостта си.", + "visibility_modal.instructions": "Управлявайте кой може да взаимодейства с тази публикация. Глобалните настройки може да се намерят под Предпочитания> Друго.", + "visibility_modal.privacy_label": "Поверителност", + "visibility_modal.quote_followers": "Само последователи", + "visibility_modal.quote_label": "Промяна кой може да цитира", + "visibility_modal.quote_nobody": "Никого", + "visibility_modal.quote_public": "Някой" } diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 103b4f3715d..288227053db 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -245,7 +245,9 @@ "confirmations.remove_from_followers.confirm": "Elimina el seguidor", "confirmations.remove_from_followers.message": "{name} deixarà de seguir-vos. Tirem endavant?", "confirmations.remove_from_followers.title": "Eliminem el seguidor?", + "confirmations.revoke_quote.confirm": "Eliminar la publicació", "confirmations.revoke_quote.message": "Aquesta acció no es pot desfer.", + "confirmations.revoke_quote.title": "Eliminar la publicació?", "confirmations.unfollow.confirm": "Deixa de seguir", "confirmations.unfollow.message": "Segur que vols deixar de seguir {name}?", "confirmations.unfollow.title": "Deixar de seguir l'usuari?", @@ -290,6 +292,7 @@ "domain_pill.your_handle": "El vostre identificador:", "domain_pill.your_server": "La vostra llar digital, on són totes les vostres publicacions. No us agrada aquesta? Canvieu de servidor quan vulgueu i emporteu-vos els vostres seguidors.", "domain_pill.your_username": "El vostre identificador únic en aquest servidor. Hi pot haver usuaris amb el mateix nom a diferents servidors.", + "dropdown.empty": "Seleccioneu una opció", "embed.instructions": "Incrusta aquest tut a la teva pàgina web copiant el codi següent.", "embed.preview": "Aquest aspecte tindrà:", "emoji_button.activity": "Activitat", @@ -881,6 +884,7 @@ "status.quote_error.pending_approval": "Publicació pendent", "status.quote_error.pending_approval_popout.body": "Les citacions compartides a través del Fediverse poden trigar en aparèixer, perquè diferents servidors tenen diferents protocols.", "status.quote_error.pending_approval_popout.title": "Publicació pendent? Mantinguem la calma", + "status.quote_policy_change": "Canvieu qui us pot citar", "status.quote_post_author": "S'ha citat una publicació de @{name}", "status.read_more": "Més informació", "status.reblog": "Impulsa", @@ -956,5 +960,17 @@ "video.skip_forward": "Salta endavant", "video.unmute": "Deixa de silenciar", "video.volume_down": "Abaixa el volum", - "video.volume_up": "Apuja el volum" + "video.volume_up": "Apuja el volum", + "visibility_modal.button_title": "Establiu la visibilitat", + "visibility_modal.header": "Visibilitat i interacció", + "visibility_modal.helper.direct_quoting": "No es poden citar les mencions privades.", + "visibility_modal.helper.privacy_editing": "No es pot canviar la visibilitat de les publicacions ja fetes.", + "visibility_modal.helper.private_quoting": "No es poden citar les publicacions només per a seguidors.", + "visibility_modal.helper.unlisted_quoting": "Quan la gent et citi les seves publicacions estaran amagades de les línies de temps de tendències.", + "visibility_modal.instructions": "Controleu qui pot interactuar amb aquesta publicació. La configuració global es troba a Preferències>Altres.", + "visibility_modal.privacy_label": "Privacitat", + "visibility_modal.quote_followers": "Només seguidors", + "visibility_modal.quote_label": "Canvieu qui us pot citar", + "visibility_modal.quote_nobody": "Ningú", + "visibility_modal.quote_public": "Qualsevol" } diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index bd8d5a5d8bc..dc314244a30 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tvůj handle:", "domain_pill.your_server": "Tvůj digitální domov, kde žijí všechny tvé příspěvky. Nelíbí se ti? Kdykoliv se přesuň na jiný server a vezmi si sebou i své sledující.", "domain_pill.your_username": "Tvůj jedinečný identifikátor na tomto serveru. Je možné najít uživatele se stejným uživatelským jménem na jiných serverech.", + "dropdown.empty": "Vyberte možnost", "embed.instructions": "Pro přidání příspěvku na vaši webovou stránku zkopírujte níže uvedený kód.", "embed.preview": "Takhle to bude vypadat:", "emoji_button.activity": "Aktivita", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Příspěvek čeká na schválení", "status.quote_error.pending_approval_popout.body": "Zobrazení citátů sdílených napříč Fediversem může chvíli trvat, protože různé servery používají různé protokoly.", "status.quote_error.pending_approval_popout.title": "Příspěvek čeká na schválení? Buďte klidní", + "status.quote_policy_change": "Změňte, kdo může citovat", "status.quote_post_author": "Citovali příspěvek od @{name}", "status.read_more": "Číst více", "status.reblog": "Boostnout", @@ -959,5 +961,17 @@ "video.skip_forward": "Přeskočit vpřed", "video.unmute": "Zrušit ztlumení", "video.volume_down": "Snížit hlasitost", - "video.volume_up": "Zvýšit hlasitost" + "video.volume_up": "Zvýšit hlasitost", + "visibility_modal.button_title": "Nastavit viditelnost", + "visibility_modal.header": "Viditelnost a interakce", + "visibility_modal.helper.direct_quoting": "Soukromé zmínky nemohou být citovány.", + "visibility_modal.helper.privacy_editing": "Publikované příspěvky nemohou změnit svou viditelnost.", + "visibility_modal.helper.private_quoting": "Nelze citovat příspěvky, které jsou pouze pro sledující.", + "visibility_modal.helper.unlisted_quoting": "Když vás lidé citují, jejich příspěvek bude v časové ose populárních příspěvků také skryt.", + "visibility_modal.instructions": "Kontrolujte, kdo může interagovat s tímto příspěvkem. Globální nastavení můžete najít pod Nastavení > Ostatní.", + "visibility_modal.privacy_label": "Soukromí", + "visibility_modal.quote_followers": "Pouze sledující", + "visibility_modal.quote_label": "Změňte, kdo může citovat", + "visibility_modal.quote_nobody": "Nikdo", + "visibility_modal.quote_public": "Kdokoliv" } diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 9dfc6b35a60..d88be09b56b 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Dileu dilynwr", "confirmations.remove_from_followers.message": "Bydd {name} yn rhoi'r gorau i'ch dilyn. A ydych yn siŵr eich bod am fwrw ymlaen?", "confirmations.remove_from_followers.title": "Tynnu dilynwr?", + "confirmations.revoke_quote.confirm": "Dileu'r postiad", + "confirmations.revoke_quote.message": "Does dim modd dadwneud y weithred hon.", + "confirmations.revoke_quote.title": "Dileu'r postiad?", "confirmations.unfollow.confirm": "Dad-ddilyn", "confirmations.unfollow.message": "Ydych chi'n siŵr eich bod am ddad-ddilyn {name}?", "confirmations.unfollow.title": "Dad-ddilyn defnyddiwr?", @@ -289,6 +292,7 @@ "domain_pill.your_handle": "Eich handlen:", "domain_pill.your_server": "Eich cartref digidol, lle mae'ch holl bostiadau'n byw. Ddim yn hoffi'r un hon? Trosglwyddwch weinyddion ar unrhyw adeg a dewch â'ch dilynwyr hefyd.", "domain_pill.your_username": "Eich dynodwr unigryw ar y gweinydd hwn. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.", + "dropdown.empty": "Dewis dewis", "embed.instructions": "Gosodwch y post hwn ar eich gwefan drwy gopïo'r côd isod.", "embed.preview": "Dyma sut olwg fydd arno:", "emoji_button.activity": "Gweithgarwch", @@ -498,6 +502,8 @@ "keyboard_shortcuts.translate": "i gyfieithu postiad", "keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio", "keyboard_shortcuts.up": "Symud yn uwch yn y rhestr", + "learn_more_link.got_it": "Iawn", + "learn_more_link.learn_more": "Dysgu rhagor", "lightbox.close": "Cau", "lightbox.next": "Nesaf", "lightbox.previous": "Blaenorol", @@ -558,7 +564,7 @@ "navigation_bar.favourites": "Ffefrynnau", "navigation_bar.filters": "Geiriau wedi'u tewi", "navigation_bar.follow_requests": "Ceisiadau dilyn", - "navigation_bar.followed_tags": "Hashnodau a ddilynir", + "navigation_bar.followed_tags": "Hashnodau sy'n cael eu dilyn", "navigation_bar.follows_and_followers": "Yn dilyn a dilynwyr", "navigation_bar.import_export": "Mewnforio ac allforio", "navigation_bar.lists": "Rhestrau", @@ -598,6 +604,7 @@ "notification.label.mention": "Crybwyll", "notification.label.private_mention": "Crybwyll preifat", "notification.label.private_reply": "Ymateb preifat", + "notification.label.quote": "Mae {name} wedi dyfynnu eich postiad", "notification.label.reply": "Ymateb", "notification.mention": "Crybwyll", "notification.mentioned_you": "Rydych wedi'ch crybwyll gan {name}", @@ -655,6 +662,7 @@ "notifications.column_settings.mention": "Crybwylliadau:", "notifications.column_settings.poll": "Canlyniadau pleidlais:", "notifications.column_settings.push": "Hysbysiadau gwthio", + "notifications.column_settings.quote": "Dyfyniadau:", "notifications.column_settings.reblog": "Hybiau:", "notifications.column_settings.show": "Dangos yn y golofn", "notifications.column_settings.sound": "Chwarae sain", @@ -845,6 +853,8 @@ "status.bookmark": "Nod tudalen", "status.cancel_reblog_private": "Dadhybu", "status.cannot_reblog": "Does dim modd hybu'r postiad hwn", + "status.context.load_new_replies": "Mae atebion newydd ar gael", + "status.context.loading": "Yn chwilio am fwy o atebion", "status.continued_thread": "Edefyn parhaus", "status.copy": "Copïo dolen i'r post", "status.delete": "Dileu", @@ -871,6 +881,12 @@ "status.open": "Ehangu'r post hwn", "status.pin": "Pinio ar y proffil", "status.quote_error.filtered": "Wedi'i guddio oherwydd un o'ch hidlwyr", + "status.quote_error.not_available": "Postiad ddim ar gael", + "status.quote_error.pending_approval": "Postiad yn yr arfaeth", + "status.quote_error.pending_approval_popout.body": "Gall dyfyniadau sy'n cael eu rhannu ar draws y Ffedysawd gymryd amser i'w dangos, gan fod gan wahanol weinyddion brotocolau gwahanol.", + "status.quote_error.pending_approval_popout.title": "Dyfyniad yn aros? Amynedd :-)", + "status.quote_policy_change": "Newid pwy all ddyfynnu", + "status.quote_post_author": "Wedi dyfynnu postiad gan @{name}", "status.read_more": "Darllen rhagor", "status.reblog": "Hybu", "status.reblog_private": "Hybu i'r gynulleidfa wreiddiol", @@ -885,6 +901,7 @@ "status.reply": "Ymateb", "status.replyAll": "Ateb edefyn", "status.report": "Adrodd ar @{name}", + "status.revoke_quote": "Dileu fy mhostiad o bostiad @{name}", "status.sensitive_warning": "Cynnwys sensitif", "status.share": "Rhannu", "status.show_less_all": "Dangos llai i bawb", @@ -944,5 +961,17 @@ "video.skip_forward": "Symud ymlaen", "video.unmute": "Dad-dewi", "video.volume_down": "Lefel sain i lawr", - "video.volume_up": "Lefel sain i fyny" + "video.volume_up": "Lefel sain i fyny", + "visibility_modal.button_title": "Gosod gwelededd", + "visibility_modal.header": "Gwelededd a rhyngweithio", + "visibility_modal.helper.direct_quoting": "Does dim modd dyfynnu crybwylliadau preifat.", + "visibility_modal.helper.privacy_editing": "Does dim modd newid gwelededd postiadau wedi'u cyhoeddi.", + "visibility_modal.helper.private_quoting": "Does dim modd dyfynnu postiadau dilynwyr yn unig.", + "visibility_modal.helper.unlisted_quoting": "Pan fydd pobl yn eich dyfynnu, bydd eu postiad hefyd yn cael ei guddio rhag llinellau amser sy'n trendio.", + "visibility_modal.instructions": "Rheolwch bwy all ryngweithio â'r postiad hwn. Mae modd dod o hyd i osodiadau eang o dan Dewisiadau > Arall.", + "visibility_modal.privacy_label": "Preifatrwydd", + "visibility_modal.quote_followers": "Dilynwyr yn unig", + "visibility_modal.quote_label": "Newid pwy all ddyfynnu", + "visibility_modal.quote_nobody": "Neb", + "visibility_modal.quote_public": "Pawb" } diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 597990435b6..1f36a14f7da 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Dit handle:", "domain_pill.your_server": "Dit digitale hjem, hvor alle dine indlæg lever. Synes ikke om den her server? Du kan til enhver tid rykke over på en anden server og beholde dine følgere.", "domain_pill.your_username": "Din entydige identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.", + "dropdown.empty": "Vælg en indstillingsmulighed", "embed.instructions": "Indlejr dette indlæg på din hjemmeside ved at kopiere nedenstående kode.", "embed.preview": "Sådan kommer det til at se ud:", "emoji_button.activity": "Aktivitet", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Afventende indlæg", "status.quote_error.pending_approval_popout.body": "Citater delt på tværs af Fediverset kan tage tid at vise, da forskellige servere har forskellige protokoller.", "status.quote_error.pending_approval_popout.title": "Afventende citat? Tag det roligt", + "status.quote_policy_change": "Ændr hvem der kan citere", "status.quote_post_author": "Citerede et indlæg fra @{name}", "status.read_more": "Læs mere", "status.reblog": "Fremhæv", @@ -959,5 +961,17 @@ "video.skip_forward": "Overspring fremad", "video.unmute": "Slå lyd tl", "video.volume_down": "Lydstyrke ned", - "video.volume_up": "Lydstyrke op" + "video.volume_up": "Lydstyrke op", + "visibility_modal.button_title": "Indstil synlighed", + "visibility_modal.header": "Synlighed og interaktion", + "visibility_modal.helper.direct_quoting": "Private omtaler kan ikke citeres.", + "visibility_modal.helper.privacy_editing": "Publicerede indlægs synlighed kan ikke ændres.", + "visibility_modal.helper.private_quoting": "Indlæg kun for følgere kan ikke citeres.", + "visibility_modal.helper.unlisted_quoting": "Når man citeres af andre, skjules deres indlæg også på tendenstidslinjer.", + "visibility_modal.instructions": "Styr, hvem der kan interagere med dette indlæg. Globale indstillinger findes under Præferencer > Andet.", + "visibility_modal.privacy_label": "Fortrolighed", + "visibility_modal.quote_followers": "Kun følgere", + "visibility_modal.quote_label": "Ændr hvem der kan citere", + "visibility_modal.quote_nobody": "Ingen", + "visibility_modal.quote_public": "Alle" } diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 5632def3eab..6f25078bf68 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Το πλήρες όνομα χρήστη σου:", "domain_pill.your_server": "Το ψηφιακό σου σπίτι, όπου ζουν όλες σου οι αναρτήσεις. Δε σ' αρέσει αυτός; Μετακινήσου σε διακομιστές ανά πάσα στιγμή και πάρε και τους ακόλουθούς σου.", "domain_pill.your_username": "Το μοναδικό σου αναγνωριστικό σε τούτο τον διακομιστή. Είναι πιθανό να βρεις χρήστες με το ίδιο όνομα χρήστη σε διαφορετικούς διακομιστές.", + "dropdown.empty": "Διαλέξτε μια επιλογή", "embed.instructions": "Ενσωμάτωσε αυτή την ανάρτηση στην ιστοσελίδα σου αντιγράφοντας τον παρακάτω κώδικα.", "embed.preview": "Ορίστε πως θα φαίνεται:", "emoji_button.activity": "Δραστηριότητα", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Ανάρτηση σε αναμονή", "status.quote_error.pending_approval_popout.body": "Οι παραθέσεις που μοιράζονται στο Fediverse μπορεί να χρειαστούν χρόνο για να εμφανιστούν, καθώς διαφορετικοί διακομιστές έχουν διαφορετικά πρωτόκολλα.", "status.quote_error.pending_approval_popout.title": "Παράθεση σε εκκρεμότητα; Μείνετε ψύχραιμοι", + "status.quote_policy_change": "Αλλάξτε ποιός μπορεί να κάνει παράθεση", "status.quote_post_author": "Παρατίθεται μια ανάρτηση από @{name}", "status.read_more": "Διάβασε περισότερα", "status.reblog": "Ενίσχυση", @@ -959,5 +961,17 @@ "video.skip_forward": "Παράλειψη εμπρός", "video.unmute": "Άρση σίγασης", "video.volume_down": "Μείωση έντασης", - "video.volume_up": "Αύξηση έντασης" + "video.volume_up": "Αύξηση έντασης", + "visibility_modal.button_title": "Ορισμός ορατότητας", + "visibility_modal.header": "Ορατότητα και αλληλεπίδραση", + "visibility_modal.helper.direct_quoting": "Ιδιωτικές επισημάνσεις δεν μπορούν να παρατεθούν.", + "visibility_modal.helper.privacy_editing": "Δημοσιευμένες αναρτήσεις δεν μπορούν να αλλάξουν την ορατότητά τους.", + "visibility_modal.helper.private_quoting": "Οι αναρτήσεις μόνο για ακολούθους δεν μπορούν να παρατεθούν.", + "visibility_modal.helper.unlisted_quoting": "Όταν οι άνθρωποι σας παραθέτουν, η ανάρτηση τους θα είναι επίσης κρυμμένη από τα δημοφιλή χρονοδιαγράμματα.", + "visibility_modal.instructions": "Ελέγξτε ποιός μπορεί να αλληλεπιδράσει με αυτή την ανάρτηση. Οι καθολικές ρυθμίσεις μπορούν να βρεθούν κάτω από Προτιμήσεις > Άλλα.", + "visibility_modal.privacy_label": "Απόρρητο", + "visibility_modal.quote_followers": "Μόνο ακόλουθοι", + "visibility_modal.quote_label": "Αλλάξτε ποιός μπορεί να κάνει παράθεση", + "visibility_modal.quote_nobody": "Κανένας", + "visibility_modal.quote_public": "Οποιοσδήποτε" } diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 938e9e706b6..c93cacf3aa8 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -287,6 +287,7 @@ "domain_pill.your_handle": "Via identigo:", "domain_pill.your_server": "Via cifereca hejmo, kie loĝas ĉiuj viaj afiŝoj. Ĉu vi ne ŝatas ĉi tiun? Transloku servilojn iam ajn kaj alportu ankaŭ viajn sekvantojn.", "domain_pill.your_username": "Via unika identigilo sur ĉi tiu servilo. Eblas trovi uzantojn kun la sama uzantnomo sur malsamaj serviloj.", + "dropdown.empty": "Elektu opcion", "embed.instructions": "Enkorpigu ĉi tiun afiŝon en vian retejon per kopio de la suba kodo.", "embed.preview": "Ĝi aperos tiel:", "emoji_button.activity": "Agadoj", @@ -940,5 +941,16 @@ "video.skip_forward": "Preterpasi antaŭen", "video.unmute": "Ne plu silentigi", "video.volume_down": "Laŭteco Malpliigi", - "video.volume_up": "Laŭteco pliigi" + "video.volume_up": "Laŭteco pliigi", + "visibility_modal.button_title": "Agordu videblon", + "visibility_modal.header": "Videblo kaj interago", + "visibility_modal.helper.direct_quoting": "Privataj mencioj ne povas esti cititaj.", + "visibility_modal.helper.privacy_editing": "Publikigitaj afiŝoj ne povas ŝanĝi sian videblon.", + "visibility_modal.helper.private_quoting": "Afiŝoj nur por sekvantoj ne povas esti cititaj.", + "visibility_modal.helper.unlisted_quoting": "Kiam homoj citas vin, ilia afiŝo ankaŭ estos kaŝita de tendencaj templinioj.", + "visibility_modal.privacy_label": "Privateco", + "visibility_modal.quote_followers": "Nur sekvantoj", + "visibility_modal.quote_label": "Ŝanĝi kiu povas citi", + "visibility_modal.quote_nobody": "Neniu", + "visibility_modal.quote_public": "Iu ajn" } diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 317f5311088..4fe9ff66a5a 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tu alias:", "domain_pill.your_server": "Tu hogar digital, donde residen todas tus publicaciones. ¿No te gusta este sitio? Muévete a otro servidor en cualquier momento y llévate a tus seguidores.", "domain_pill.your_username": "Tu identificador único en este servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.", + "dropdown.empty": "Elige una opción", "embed.instructions": "Añade esta publicación a tu sitio web con el siguiente código.", "embed.preview": "Así es como se verá:", "emoji_button.activity": "Actividad", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Publicación pendiente", "status.quote_error.pending_approval_popout.body": "Las citas compartidas en el Fediverso pueden tardar en mostrarse, ya que cada servidor tiene un protocolo diferente.", "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", + "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", @@ -959,5 +961,17 @@ "video.skip_forward": "Saltar adelante", "video.unmute": "Dejar de silenciar", "video.volume_down": "Bajar el volumen", - "video.volume_up": "Subir el volumen" + "video.volume_up": "Subir el volumen", + "visibility_modal.button_title": "Establece la visibilidad", + "visibility_modal.header": "Visibilidad e interacción", + "visibility_modal.helper.direct_quoting": "Las menciones privadas no pueden citarse.", + "visibility_modal.helper.privacy_editing": "Las publicaciones ya enviadas no pueden cambiar su visibilidad.", + "visibility_modal.helper.private_quoting": "Las publicaciones para seguidores no pueden citarse.", + "visibility_modal.helper.unlisted_quoting": "Cuando las personas te citen, sus publicaciones también se ocultarán de las cronologías de tendencias.", + "visibility_modal.instructions": "Controla quién puede interactuar con esta publicación. La configuración global se encuentra en Preferencias > Otros.", + "visibility_modal.privacy_label": "Privacidad", + "visibility_modal.quote_followers": "Solo seguidores", + "visibility_modal.quote_label": "Cambia quién puede citarte", + "visibility_modal.quote_nobody": "Nadie", + "visibility_modal.quote_public": "Cualquiera" } diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index d23f1f5c128..2ee502b0d4f 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tu alias:", "domain_pill.your_server": "Tu hogar digital, donde residen todas tus publicaciones. ¿No te gusta este sitio? Muévete a otro servidor en cualquier momento y llévate a tus seguidores.", "domain_pill.your_username": "Tu identificador único en este servidor. Es posible encontrar usuarios con el mismo nombre de usuario en diferentes servidores.", + "dropdown.empty": "Selecciona una opción", "embed.instructions": "Añade esta publicación a tu sitio web con el siguiente código.", "embed.preview": "Así es como se verá:", "emoji_button.activity": "Actividad", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Publicación pendiente", "status.quote_error.pending_approval_popout.body": "Las citas compartidas a través del Fediverso pueden tardar en mostrarse, ya que los diferentes servidores tienen diferentes protocolos.", "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", + "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", "status.read_more": "Leer más", "status.reblog": "Impulsar", @@ -959,5 +961,17 @@ "video.skip_forward": "Adelantar", "video.unmute": "Dejar de silenciar", "video.volume_down": "Bajar volumen", - "video.volume_up": "Subir volumen" + "video.volume_up": "Subir volumen", + "visibility_modal.button_title": "Configura la visibilidad", + "visibility_modal.header": "Visibilidad e interacciones", + "visibility_modal.helper.direct_quoting": "Las menciones privadas no se pueden citar.", + "visibility_modal.helper.privacy_editing": "Las publicaciones no pueden cambiar su visibilidad.", + "visibility_modal.helper.private_quoting": "Las publicaciones para seguidores no pueden citarse.", + "visibility_modal.helper.unlisted_quoting": "Cuando la gente te cite, su publicación tampoco se mostrará en las cronologías públicas.", + "visibility_modal.instructions": "Controla quién puede interactuar con esta publicación. Puedes encontrar los ajustes globales en Preferencias > Otros.", + "visibility_modal.privacy_label": "Privacidad", + "visibility_modal.quote_followers": "Sólo seguidores", + "visibility_modal.quote_label": "Cambia quién puede citarte", + "visibility_modal.quote_nobody": "Nadie", + "visibility_modal.quote_public": "Cualquiera" } diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 798be24ad65..b2ab96a37fa 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -39,7 +39,7 @@ "account.featured_tags.last_status_at": "آخرین فرسته در {date}", "account.featured_tags.last_status_never": "بدون فرسته", "account.follow": "پی‌گرفتن", - "account.follow_back": "دنبال کردن متقابل", + "account.follow_back": "پی‌گیری متقابل", "account.followers": "پی‌گیرندگان", "account.followers.empty": "هنوز کسی پی‌گیر این کاربر نیست.", "account.followers_counter": "{count, plural, one {{counter} پی‌گیرنده} other {{counter} پی‌گیرنده}}", @@ -292,6 +292,7 @@ "domain_pill.your_handle": "شناسه‌تان:", "domain_pill.your_server": "خانهٔ رقمیتان. جایی که همهٔ فرسته‌هایتان می‌زیند. دوستش ندارید؟ در هر زمان کارسازتان را جابه‌جا کرده و پی‌گیرندگانتان را نیز بیاورید.", "domain_pill.your_username": "شناسهٔ یکتایتان روی این کارساز. ممکن است کاربرانی با نام کاربری مشابه روی کارسازهای دیگر باشند.", + "dropdown.empty": "انتخاب یک گزینه", "embed.instructions": "جاسازی این فرسته روی پایگاهتان با رونوشت کردن کد زیر.", "embed.preview": "این گونه دیده خواهد شد:", "emoji_button.activity": "فعالیت", @@ -881,7 +882,10 @@ "status.pin": "سنجاق به نمایه", "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", "status.quote_error.not_available": "فرسته در دسترس نیست", + "status.quote_error.pending_approval": "فرسته منتظر", "status.quote_error.pending_approval_popout.body": "نقل‌قول‌هایی که در سراسر فدیورس هم‌رسانی می‌شوند ممکن است زمان‌بر باشند تا نمایش داده شوند، چون کارسازهای مختلف از شیوه‌نامه‌های متفاوتی استفاده می‌کنند.", + "status.quote_error.pending_approval_popout.title": "منتظر نقل؟ خونسرد بمانید", + "status.quote_policy_change": "تغییر کسانی که می‌توانند نقل کنند", "status.quote_post_author": "فرسته‌ای از @{name} نقل شد", "status.read_more": "بیشتر بخوانید", "status.reblog": "تقویت", @@ -957,5 +961,17 @@ "video.skip_forward": "پرش به پیش", "video.unmute": "ناخموشی", "video.volume_down": "کاهش حجم صدا", - "video.volume_up": "افزایش حجم صدا" + "video.volume_up": "افزایش حجم صدا", + "visibility_modal.button_title": "تنظیم نمایانی", + "visibility_modal.header": "نمایانی و برهم‌کنش", + "visibility_modal.helper.direct_quoting": "نام‌بری‌های خصوصی قابل نقل نیستند.", + "visibility_modal.helper.privacy_editing": "نمی‌توان نمایانی فرسته‌های منتشر شده را تغییر داد.", + "visibility_modal.helper.private_quoting": "فرسته‌های فقط برای پی‌گیرندگان نمی‌توانند نقل شوند.", + "visibility_modal.helper.unlisted_quoting": "هنگامی که افراد نقلتان می‌کنند فرسته‌شان هم از خط‌زمانی‌های داغ پنهان خواهد بود.", + "visibility_modal.instructions": "واپایش کسانی که می‌توانند با این فرسته برهم‌کنش داشته باشند. تنظیمات سراسری می‌تواند در ترجیحات > دیگر پیدا شود.", + "visibility_modal.privacy_label": "محرمانگی", + "visibility_modal.quote_followers": "فقط پی‌گیرندگان", + "visibility_modal.quote_label": "تغییر کسانی که می‌توانند نقل کنند", + "visibility_modal.quote_nobody": "هیچ‌کس", + "visibility_modal.quote_public": "هرکسی" } diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index e1b8d49e112..81398669222 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Käyttäjätunnuksesi:", "domain_pill.your_server": "Digitaalinen kotisi, jossa kaikki julkaisusi sijaitsevat. Etkö pidä tästä? Siirry palvelimelta toiselle milloin tahansa ja tuo myös seuraajasi mukanasi.", "domain_pill.your_username": "Yksilöllinen tunnisteesi tällä palvelimella. Eri palvelimilta on mahdollista löytää käyttäjiä, joilla on sama käyttäjänimi.", + "dropdown.empty": "Valitse vaihtoehto", "embed.instructions": "Upota julkaisu verkkosivullesi kopioimalla alla oleva koodi.", "embed.preview": "Tältä se näyttää:", "emoji_button.activity": "Aktiviteetit", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Julkaisu odottaa", "status.quote_error.pending_approval_popout.body": "Saattaa viedä jonkin ainaa ennen kuin fediversumin kautta jaetut julkaisut tulevat näkyviin, sillä eri palvelimet käyttävät eri protokollia.", "status.quote_error.pending_approval_popout.title": "Odottava lainaus? Pysy rauhallisena", + "status.quote_policy_change": "Vaihda, kuka voi lainata", "status.quote_post_author": "Lainaa käyttäjän @{name} julkaisua", "status.read_more": "Näytä enemmän", "status.reblog": "Tehosta", @@ -959,5 +961,17 @@ "video.skip_forward": "Siirry eteenpäin", "video.unmute": "Poista mykistys", "video.volume_down": "Vähennä äänenvoimakkuutta", - "video.volume_up": "Lisää äänenvoimakkuutta" + "video.volume_up": "Lisää äänenvoimakkuutta", + "visibility_modal.button_title": "Aseta näkyvyys", + "visibility_modal.header": "Näkyvyys ja vuorovaikutus", + "visibility_modal.helper.direct_quoting": "Yksityismainintoja ei voi lainata.", + "visibility_modal.helper.privacy_editing": "Lähetettyjen julkaisujen näkyvyyttä ei voi vaihtaa.", + "visibility_modal.helper.private_quoting": "Vain seuraajille tarkoitettuja julkaisuja ei voi lainata.", + "visibility_modal.helper.unlisted_quoting": "Kun ihmiset lainaavat sinua, myös heidän julkaisunsa piilotetaan suosittujen julkaisujen aikajanoilta.", + "visibility_modal.instructions": "Hallitse, kuka voi olla vuorovaikutuksessa tämän julkaisun kanssa. Yleiset asetukset sijaitsevat kohdassa Asetukset > Muut.", + "visibility_modal.privacy_label": "Yksityisyys", + "visibility_modal.quote_followers": "Vain seuraajat", + "visibility_modal.quote_label": "Vaihda, kuka voi lainata", + "visibility_modal.quote_nobody": "Ei kukaan", + "visibility_modal.quote_public": "Kuka tahansa" } diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 4760b64626e..1b730557c90 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Do láimhseáil:", "domain_pill.your_server": "Do theach digiteach, áit a bhfuil do phoist go léir ina gcónaí. Nach maith leat an ceann seo? Aistrigh freastalaithe am ar bith agus tabhair leat do leantóirí freisin.", "domain_pill.your_username": "D'aitheantóir uathúil ar an bhfreastalaí seo. Is féidir teacht ar úsáideoirí leis an ainm úsáideora céanna ar fhreastalaithe éagsúla.", + "dropdown.empty": "Roghnaigh rogha", "embed.instructions": "Embed this status on your website by copying the code below.", "embed.preview": "Seo an chuma a bheidh air:", "emoji_button.activity": "Gníomhaíocht", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Post ar feitheamh", "status.quote_error.pending_approval_popout.body": "D’fhéadfadh sé go dtógfadh sé tamall le Sleachta a roinntear ar fud Fediverse a thaispeáint, toisc go mbíonn prótacail éagsúla ag freastalaithe éagsúla.", "status.quote_error.pending_approval_popout.title": "Ag fanacht le luachan? Fan socair", + "status.quote_policy_change": "Athraigh cé a fhéadann luachan a thabhairt", "status.quote_post_author": "Luaigh mé post le @{name}", "status.read_more": "Léan a thuilleadh", "status.reblog": "Treisiú", @@ -959,5 +961,17 @@ "video.skip_forward": "Scipeáil ar aghaidh", "video.unmute": "Díbhalbhú", "video.volume_down": "Toirt síos", - "video.volume_up": "Toirt suas" + "video.volume_up": "Toirt suas", + "visibility_modal.button_title": "Socraigh infheictheacht", + "visibility_modal.header": "Infheictheacht agus idirghníomhaíocht", + "visibility_modal.helper.direct_quoting": "Ní féidir lua a thabhairt ar luanna príobháideacha.", + "visibility_modal.helper.privacy_editing": "Ní féidir infheictheacht postálacha foilsithe a athrú.", + "visibility_modal.helper.private_quoting": "Ní féidir poist atá dírithe ar leanúna amháin a lua.", + "visibility_modal.helper.unlisted_quoting": "Nuair a luann daoine thú, beidh a bpost i bhfolach ó amlínte treochta freisin.", + "visibility_modal.instructions": "Rialú cé a fhéadfaidh idirghníomhú leis an bpost seo. Is féidir socruithe domhanda a fháil faoi Sainroghanna > Eile.", + "visibility_modal.privacy_label": "Príobháideacht", + "visibility_modal.quote_followers": "Leantóirí amháin", + "visibility_modal.quote_label": "Athraigh cé a fhéadann luachan a thabhairt", + "visibility_modal.quote_nobody": "Níl aon duine", + "visibility_modal.quote_public": "Aon duine" } diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index c6d0fbd8066..775af830562 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "O teu alcume:", "domain_pill.your_server": "O teu fogar dixital, onde están as túas publicacións. Non é do teu agrado? Podes cambiar de servidor cando queiras levando as túas seguidoras contigo.", "domain_pill.your_username": "O teu identificador único neste servidor. É posible que atopes usuarias co mesmo nome de usuaria en outros servidores.", + "dropdown.empty": "Escolle unha opción", "embed.instructions": "Inclúe esta publicación no teu sitio web copiando o seguinte código.", "embed.preview": "Vaise ver así:", "emoji_button.activity": "Actividade", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "Publicación pendente", "status.quote_error.pending_approval_popout.body": "As citas compartidas no Fediverso poderían tardar en mostrarse, xa que os diferentes servidores teñen diferentes protocolos.", "status.quote_error.pending_approval_popout.title": "Cita pendente? Non te apures", + "status.quote_policy_change": "Cambia quen pode citarte", "status.quote_post_author": "Citou unha publicación de @{name}", "status.read_more": "Ler máis", "status.reblog": "Promover", @@ -959,5 +961,17 @@ "video.skip_forward": "Avanzar", "video.unmute": "Non silenciar", "video.volume_down": "Baixar volume", - "video.volume_up": "Subir volume" + "video.volume_up": "Subir volume", + "visibility_modal.button_title": "Establece a visibilidade", + "visibility_modal.header": "Visibilidade e interaccións", + "visibility_modal.helper.direct_quoting": "A mencións privadas non se poden citar.", + "visibility_modal.helper.privacy_editing": "Non se pode cambiar a visibilidade das publicacións xa publicadas.", + "visibility_modal.helper.private_quoting": "As publicacións só para seguidoras non se poden citar.", + "visibility_modal.helper.unlisted_quoting": "Cando alguén te cite, a súa publicación non aparecerá nas cronoloxías de popularidade.", + "visibility_modal.instructions": "Controla quen pode interactuar con esta publicación. Os axustes xerais están en Preferencias > Outros.", + "visibility_modal.privacy_label": "Privacidade", + "visibility_modal.quote_followers": "Só para seguidoras", + "visibility_modal.quote_label": "Cambia quen pode citarte", + "visibility_modal.quote_nobody": "Ninguén", + "visibility_modal.quote_public": "Calquera" } diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index bdb23bc4cbe..69e1f870ed6 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "הכינוי שלך:", "domain_pill.your_server": "הבית המקוון שלך, היכן ששוכנות כל הודעותיך. לא מוצא חן בעיניך? ניתן לעבור שרתים בכל עת וגם לשמור על העוקבים.", "domain_pill.your_username": "המזהה הייחודי שלך על שרת זה. ניתן למצוא משתמשים עם שם משתמש זהה על שרתים שונים.", + "dropdown.empty": "בחירת אפשרות", "embed.instructions": "ניתן להטמיע את ההודעה הזו באתרך ע\"י העתקת הקוד שלהלן.", "embed.preview": "דוגמא כיצד זה יראה:", "emoji_button.activity": "פעילות", @@ -884,6 +885,7 @@ "status.quote_error.pending_approval": "ההודעה בהמתנה לאישור", "status.quote_error.pending_approval_popout.body": "ציטוטים ששותפו בפדיוורס עשויים להתפרסם אחרי עיכוב קל, כיוון ששרתים שונים משתמשים בפרוטוקולים שונים.", "status.quote_error.pending_approval_popout.title": "ההודעה בהמתנה? המתינו ברוגע", + "status.quote_policy_change": "הגדרת הרשאה לציטוט הודעותיך", "status.quote_post_author": "ההודעה צוטטה על ידי @{name}", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", @@ -959,5 +961,17 @@ "video.skip_forward": "דילוג קדימה", "video.unmute": "ביטול השתקה", "video.volume_down": "הנמכת עוצמת השמע", - "video.volume_up": "הגברת עוצמת שמע" + "video.volume_up": "הגברת עוצמת שמע", + "visibility_modal.button_title": "בחירת רמת חשיפה", + "visibility_modal.header": "חשיפה והידוּד (אינטראקציה)", + "visibility_modal.helper.direct_quoting": "הודעות פרטיות לא ניתנות לציטוט.", + "visibility_modal.helper.privacy_editing": "לא ניתן לשנות את דרגת החשיפה של הודעות שפורסמו.", + "visibility_modal.helper.private_quoting": "לא ניתן לצטט הודעות שחשופות לעוקבים בלבד.", + "visibility_modal.helper.unlisted_quoting": "כאשר אחרים מצטטים אותך, ההודעות שלהם יוסתרו גם מ\"נושאים חמים\".", + "visibility_modal.instructions": "שליטה בהרשאה להידוּד (תגובות וציטוטים) עם הודעה זו. הגדרות ברירת המחדל ניתן למצוא תחת העדפות > אחרים.", + "visibility_modal.privacy_label": "פרטיות", + "visibility_modal.quote_followers": "לעוקבים בלבד", + "visibility_modal.quote_label": "הגדרת הרשאה לציטוט הודעותיך", + "visibility_modal.quote_nobody": "אף אחד", + "visibility_modal.quote_public": "כולם" } diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 33cb8cac4bf..da133ef612e 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Lí ê口座:", "domain_pill.your_server": "Lí數位ê厝,內底有lí所有ê PO文。無kah意?Ē當轉kàu別ê服侍器,koh保有跟tuè lí êl âng。.", "domain_pill.your_username": "Lí 佇tsit ê服侍器獨一ê稱呼。佇無kâng ê服侍器有可能tshuē著kāng名ê用者。", + "dropdown.empty": "揀選項", "embed.instructions": "Khóo-pih 下kha ê程式碼,來kā tsit篇PO文tàu佇lí ê網站。", "embed.preview": "伊e án-ne顯示:\n", "emoji_button.activity": "活動", @@ -570,7 +571,7 @@ "navigation_bar.live_feed_local": "即時ê內容(本地)", "navigation_bar.live_feed_public": "即時ê內容(公開)", "navigation_bar.logout": "登出", - "navigation_bar.moderation": "審核", + "navigation_bar.moderation": "管理", "navigation_bar.more": "其他", "navigation_bar.mutes": "消音ê用者", "navigation_bar.opened_in_classic_interface": "PO文、口座kap其他指定ê頁面,預設ē佇經典ê網頁界面內phah開。", @@ -608,11 +609,11 @@ "notification.mention": "提起", "notification.mentioned_you": "{name}kā lí提起", "notification.moderation-warning.learn_more": "看詳細", - "notification.moderation_warning": "Lí有收著審核ê警告", + "notification.moderation_warning": "Lí有收著管理ê警告", "notification.moderation_warning.action_delete_statuses": "Lí ê一寡PO文hōo lâng thâi掉ah。", "notification.moderation_warning.action_disable": "Lí ê口座hōo lâng停止使用ah。", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Lí ê一寡PO文,hōo lâng標做敏感ê內容。", - "notification.moderation_warning.action_none": "Lí ê口座有收著審核ê警告。", + "notification.moderation_warning.action_none": "Lí ê口座有收著管理ê警告。", "notification.moderation_warning.action_sensitive": "Tuì tsit-má開始,lí êPO文ē標做敏感ê內容。", "notification.moderation_warning.action_silence": "Lí ê口座hōo lâng限制ah。", "notification.moderation_warning.action_suspend": "Lí ê口座ê權限已經停止ah。", @@ -883,6 +884,7 @@ "status.quote_error.pending_approval": "鋪文當咧送", "status.quote_error.pending_approval_popout.body": "因為無kâng ê服侍器有無kâng ê協定,佇聯邦宇宙分享ê引文可能愛開時間來顯示。", "status.quote_error.pending_approval_popout.title": "Leh送引文?請sió等leh", + "status.quote_policy_change": "改通引用ê lâng", "status.quote_post_author": "引用 @{name} ê PO文ah", "status.read_more": "讀詳細", "status.reblog": "轉送", @@ -957,5 +959,17 @@ "video.skip_forward": "跳kah頭前", "video.unmute": "取消消音", "video.volume_down": "變khah細聲", - "video.volume_up": "變khah大聲" + "video.volume_up": "變khah大聲", + "visibility_modal.button_title": "設定通看ê程度", + "visibility_modal.header": "通看ê程度kap互動", + "visibility_modal.helper.direct_quoting": "私人ê提起bē當引用。", + "visibility_modal.helper.privacy_editing": "公開ê PO文bē當改in通看ê程度。", + "visibility_modal.helper.private_quoting": "Bē當引用kan-ta跟tuè ê通看ê PO文。", + "visibility_modal.helper.unlisted_quoting": "若別lâng引用lí,in ê PO文mā ē tuì趨勢時間線隱藏。", + "visibility_modal.instructions": "控制ē當kap tsit篇PO文互動ê lâng,Ē當佇 偏愛ê設定>其他tshuē tio̍h全地ê設定。", + "visibility_modal.privacy_label": "隱私權", + "visibility_modal.quote_followers": "Kan-ta hōo跟tuè ê lâng", + "visibility_modal.quote_label": "改通引用ê lâng", + "visibility_modal.quote_nobody": "無半位", + "visibility_modal.quote_public": "Ta̍k ê lâng" } diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 2627fe21e3e..67e10ce2aa7 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -292,7 +292,7 @@ "domain_pill.your_handle": "您的帳號:", "domain_pill.your_server": "您數位世界的家,您所有的嘟文都在這裡。不喜歡這台伺服器嗎?您能隨時搬家至其他伺服器並且仍保有您的跟隨者。", "domain_pill.your_username": "您於您的伺服器中獨一無二的識別。於不同的伺服器上可能找到具有相同帳號的使用者。", - "dropdown.empty": "選取選項", + "dropdown.empty": "選項", "embed.instructions": "若您欲於您的網站嵌入此嘟文,請複製以下程式碼。", "embed.preview": "它將顯示成這樣:", "emoji_button.activity": "活動", @@ -965,9 +965,9 @@ "visibility_modal.button_title": "設定可見性", "visibility_modal.header": "可見性與互動", "visibility_modal.helper.direct_quoting": "無法引用私人提及。", - "visibility_modal.helper.privacy_editing": "無法變更已發佈的嘟文的可見性。", + "visibility_modal.helper.privacy_editing": "無法變更已發佈的嘟文之可見性。", "visibility_modal.helper.private_quoting": "無法引用僅追蹤者的嘟文。", - "visibility_modal.helper.unlisted_quoting": "當期他人引用您時,他們的嘟文也會自熱門時間軸隱藏。", + "visibility_modal.helper.unlisted_quoting": "當其他人引用您時,他們的嘟文也會自熱門時間軸隱藏。", "visibility_modal.instructions": "控制誰能與此嘟文互動。可在偏好設定 > 其他下找到全域設定。", "visibility_modal.privacy_label": "隱私權", "visibility_modal.quote_followers": "僅限跟隨者", diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 077a729b919..f32f69406e1 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -190,6 +190,7 @@ bg: create_relay: Създаване на пренасочване create_unavailable_domain: Създаване на недостъпен домейн create_user_role: Създаване на роля + create_username_block: Създаване на правило за потебителско име demote_user: Понижаване на потребител destroy_announcement: Изтриване на оповестяване destroy_canonical_email_block: Изтриване на блокиране за е-поща @@ -202,6 +203,7 @@ bg: destroy_status: Изтриване на публикация destroy_unavailable_domain: Изтриване на недостъпен домейн destroy_user_role: Унищожаване на роля + destroy_username_block: Изтриване на правило за потебителско име disable_2fa_user: Изкл. на 2факт.удостов. disable_custom_emoji: Деактивиране на персонализирано емоджи disable_sign_in_token_auth_user: Изключване на удостоверяването с код по имейл за потребител @@ -234,6 +236,7 @@ bg: update_report: Обновяване на доклада update_status: Обновяване на публикация update_user_role: Обновяване на ролята + update_username_block: Обновяване на правило за потеб. име actions: approve_appeal_html: "%{name} одобри обжалването на решение за модериране от %{target}" approve_user_html: "%{name} одобри регистрирането от %{target}" @@ -251,6 +254,7 @@ bg: create_ip_block_html: "%{name} създаде правило за IP %{target}" create_unavailable_domain_html: "%{name} спря доставянето до домейн %{target}" create_user_role_html: "%{name} създаде роля %{target}" + create_username_block_html: "%{name} добави правило за потребителско име, съдържащо %{target}" demote_user_html: "%{name} понижи потребителя %{target}" destroy_announcement_html: "%{name} изтри оповестяване %{target}" destroy_canonical_email_block_html: "%{name} отблокира имейла с хеш %{target}" @@ -263,6 +267,7 @@ bg: destroy_status_html: "%{name} премахна публикация от %{target}" destroy_unavailable_domain_html: "%{name} възобнови доставката до домейн %{target}" destroy_user_role_html: "%{name} изтри роля %{target}" + destroy_username_block_html: "%{name} премахна правило за потребителско име, съдържащо %{target}" disable_2fa_user_html: "%{name} изключи двуфакторното изискване за потребител %{target}" disable_custom_emoji_html: "%{name} изключи емоджито %{target}" disable_sign_in_token_auth_user_html: "%{name} изключва удостоверението с код по е-поща за %{target}" @@ -295,6 +300,7 @@ bg: update_report_html: "%{name} осъвремени доклад %{target}" update_status_html: "%{name} обнови публикация от %{target}" update_user_role_html: "%{name} промени ролята %{target}" + update_username_block_html: "%{name} обнови правило за потребителско име, съдържащо %{target}" deleted_account: изтрит акаунт empty: Няма намерени дневници. filter_by_action: Филтриране по действие @@ -791,12 +797,16 @@ bg: title: Сървърни правила translation: Превод translations: Преводи + translations_explanation: Може по избор да добавите преводи за правилата. Стандартната стойност ще се показва, ако няма налична преведена версия. Винаги се уверявайте, че всеки предоставен превод е синхронизиран със стандартната стойност. settings: about: manage_rules: Управление на правилата на сървъра preamble: Предложете задълбочена информация за това как вашият сървър се управлява, модерира и финансира. rules_hint: Съществува специална част за правилата, които се очаква вашите потребители да спазват. title: Относно + allow_referrer_origin: + desc: Когато потребителите ви щракнат върху връзки към външни сайтове, то браузърите им може да изпращат адреса на сървъра ви в Mastodon както референтът. Изключете това, ако то неповторимо би установило самоличността на потребителя ви, например ако това е личен сървър в Mastodon. + title: Позволяване на външни сайтове да виждат сървъра ви в Mastodon като източник на трафик appearance: preamble: Настройване на мрежовия интерфейс на Mastodon. title: Външен вид @@ -1067,6 +1077,23 @@ bg: other: Използвано от %{count} души през последната седмица title: Препоръки и насоки на развитие trending: Изгряващи + username_blocks: + add_new: Добавяне на ново + block_registrations: Блокиране на регистрациите + comparison: + contains: Съдържа + contains_html: Съдържа %{string} + created_msg: Успешно създадено правило за потребителско име + delete: Изтриване + edit: + title: Промяна на правило за потреб. име + new: + create: Създаване на правило + title: Създаване на ново правило за потреб. име + no_username_block_selected: Няма променени правила за потребителско име, тъй като нищо не бе избрано + not_permitted: Няма позволение + title: Правила за потребителско име + updated_msg: Успешно обновено правило за потребителско име warning_presets: add_new: Добавяне на ново delete: Изтриване @@ -1331,6 +1358,10 @@ bg: basic_information: Основна информация hint_html: "Персонализирайте какво хората виждат в обществения ви профил и до публикациите ви. Другите хора са по-склонни да ви последват и да взаимодействат с вас, когато имате попълнен профил и снимка на профила." other: Друго + emoji_styles: + auto: Автоматично + native: Естествено системно + twemoji: Twemoji errors: '400': Подадохте невалидна или деформирана заявка. '403': Нямате позволение да разгледате тази страница. @@ -1640,6 +1671,10 @@ bg: title: Ново споменаване poll: subject: Анкетата от %{name} приключи + quote: + body: 'Ваша публикация беше цитирана от %{name}:' + subject: "%{name} цитира ваша публикация" + title: Нов цитат reblog: body: 'Ваша публикация беше раздута от %{name}:' subject: "%{name} разду ваша публикация" @@ -1850,6 +1885,7 @@ bg: edited_at_html: Редактирано на %{date} errors: in_reply_not_found: Изглежда, че публикацията, на която се опитвате да отговорите, не съществува. + quoted_status_not_found: Публикацията, която се опитвате да цитирате, не съществува. over_character_limit: прехвърлен лимит от %{max} символа pin_errors: direct: Публикациите, които са видими само за потребители споменати в тях, не могат да бъдат закачани @@ -1857,6 +1893,8 @@ bg: ownership: Публикация на някого другиго не може да бъде закачена reblog: Раздуване не може да бъде закачано quote_policies: + followers: Само последователите ви + nobody: Никого public: Всеки title: "%{name}: „%{quote}“" visibilities: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 7dfa928f57b..65299b87bcc 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1713,7 +1713,7 @@ ca: too_few_options: ha de tenir més d'una opció too_many_options: no pot contenir més de %{max} opcions preferences: - other: Altre + other: Altres posting_defaults: Valors per defecte de publicació public_timelines: Línies de temps públiques privacy: diff --git a/config/locales/cy.yml b/config/locales/cy.yml index e8990d34820..8860bcb0a67 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -202,6 +202,7 @@ cy: create_relay: Creu Cyfnewidiad create_unavailable_domain: Creu Parth Ddim ar Gael create_user_role: Creu Rôl + create_username_block: Creu Rheol Enw Defnyddiwr demote_user: Diraddio Defnyddiwr destroy_announcement: Dileu Cyhoeddiad destroy_canonical_email_block: Dileu Rhwystr E-bost @@ -215,6 +216,7 @@ cy: destroy_status: Dileu Postiad destroy_unavailable_domain: Dileu Parth Ddim ar Gael destroy_user_role: Dileu Rôl + destroy_username_block: Dileu Rheol Enw Defnyddiwr disable_2fa_user: Diffodd 2FA disable_custom_emoji: Analluogi Emoji Addasedig disable_relay: Analluogi Cyfnewidiad @@ -249,6 +251,7 @@ cy: update_report: Diweddaru Adroddiad update_status: Diweddaru Postiad update_user_role: Diweddaru Rôl + update_username_block: Diweddaru Rheol Enw Defnyddiwr actions: approve_appeal_html: Mae %{name} wedi cymeradwyo penderfyniad cymedroli gan %{target} approve_user_html: Mae %{name} wedi cymeradwyo cofrestru gan %{target} @@ -267,6 +270,7 @@ cy: create_relay_html: Creodd %{name} gyfnewidiad %{target} create_unavailable_domain_html: Mae %{name} wedi stopio danfon i barth %{target} create_user_role_html: Mae %{name} wedi creu rôl %{target} + create_username_block_html: Mae %{name} wedi ychwanegu rheol ar gyfer enwau defnyddwyr sy'n cynnwys %{target} 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} @@ -280,6 +284,7 @@ cy: destroy_status_html: Mae %{name} wedi tynnu postiad gan %{target} destroy_unavailable_domain_html: Mae %{name} wedi ailddechrau anfon i barth %{target} destroy_user_role_html: Mae %{name} wedi dileu rôl %{target} + destroy_username_block_html: Mae %{name} wedi dileu rheol ar gyfer enwau defnyddwyr sy'n cynnwys %{target} disable_2fa_user_html: Mae %{name} wedi analluogi gofyniad dau ffactor ar gyfer defnyddiwr %{target} disable_custom_emoji_html: Mae %{name} wedi analluogi emoji %{target} disable_relay_html: Analluogodd %{name} y cyfnewidiad %{target} @@ -314,6 +319,7 @@ cy: update_report_html: Mae %{name} wedi diweddaru adroddiad %{target} update_status_html: Mae %{name} wedi diweddaru postiad gan %{target} update_user_role_html: Mae %{name} wedi newid rôl %{target} + update_username_block_html: Mae %{name} wedi diweddaru rheol ar gyfer enwau defnyddwyr sy'n cynnwys %{target} deleted_account: cyfrif wedi'i ddileu empty: Dim logiau ar gael. filter_by_action: Hidlo yn ôl gweithred @@ -1157,6 +1163,25 @@ cy: zero: Wedi'i ddefnyddio gan %{count} o bobl dros yr wythnos ddiwethaf title: Argymhellion a Threndiau trending: Trendio + username_blocks: + add_new: Ychwanegu newydd + block_registrations: Rhwystro cofrestriadau + comparison: + contains: Yn cynnwys + equals: Yn hafal i + contains_html: Yn cynnwys %{string} + created_msg: Rheol enw defnyddiwr wedi'i chreu'n llwyddiannus + delete: Dileu + edit: + title: Golygu rheol enw defnyddiwr + matches_exactly_html: Yn hafal i %{string} + new: + create: Creu rheol + title: Creu rheol enw defnyddiwr newydd + no_username_block_selected: Heb newid unrhyw reolau enw defnyddiwr gan nad oes un ohonyn nhw wedi'u dewis + not_permitted: Dim caniatâd + title: Rheolau enw defnyddiwr + updated_msg: Rheol enw defnyddiwr wedi'i diweddaru'n llwyddiannus warning_presets: add_new: Ychwanegu newydd delete: Dileu @@ -1817,6 +1842,10 @@ cy: title: Crywbylliad newydd poll: subject: Mae arolwg gan %{name} wedi dod i ben + quote: + body: 'Mae %{name} wedi dyfynnu eich postiad :' + subject: Mae %{name} wedi dyfynnu eich postiad + title: Dyfyniad newydd reblog: body: 'Cafodd eich postiad ei hybu gan %{name}:' subject: Rhoddodd %{name} hwb i'ch postiad @@ -2051,6 +2080,8 @@ cy: ownership: Nid oes modd pinio postiad rhywun arall reblog: Nid oes modd pinio hwb quote_policies: + followers: Dim ond eich dilynwyr + nobody: Neb public: Pawb title: '%{name}: "%{quote}"' visibilities: diff --git a/config/locales/devise.be.yml b/config/locales/devise.be.yml index 8aebd2b198e..21dd93e31ab 100644 --- a/config/locales/devise.be.yml +++ b/config/locales/devise.be.yml @@ -58,7 +58,7 @@ be: subtitle: Для вашага ўліковага запісу была ўключаная двухфактарная аўтэнтыфікацыя. title: двухэтапнае спраўджанне уключана two_factor_recovery_codes_changed: - explanation: Папярэднія коды аднаўлення былі ануляваны і былі створаныя новыя. + explanation: Папярэднія коды аднаўлення былі ануляваны і створаны новыя. subject: 'Mastodon: створаныя новыя коды аднаўлення' subtitle: Папярэднія коды аднаўлення былі ануляваны і замест іх былі створаныя новыя. title: 2FA коды аднаўлення былі зменены diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 913f3848844..e3e8348ef01 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -258,6 +258,7 @@ fa: create_relay_html: "%{name} یک رله %{target} ایجاد کرد" create_unavailable_domain_html: "%{name} تحویل محتوا به دامنه %{target} را متوقف کرد" create_user_role_html: "%{name} نقش %{target} را ایجاد کرد" + create_username_block_html: "‫‏%{name} قانونی برای نام‌های کاربری دارای %{target} افزود" demote_user_html: "%{name} کاربر %{target} را تنزل داد" destroy_announcement_html: "%{name} اعلامیهٔ %{target} را حذف کرد" destroy_canonical_email_block_html: "%{name} رایانامه با درهم‌ریزی %{target} را نامسدود کرد" @@ -271,6 +272,7 @@ fa: destroy_status_html: "%{name} وضعیت %{target} را برداشت" destroy_unavailable_domain_html: "%{name} تحویل محتوا به دامنه %{target} را از سر گرفت" destroy_user_role_html: "%{name} نقش %{target} را حذف کرد" + destroy_username_block_html: "‫‏%{name} قانون نام‌های کاربری دارای %{target} را برداشت" disable_2fa_user_html: "%{name} ضرورت ورود دو مرحله‌ای را برای کاربر %{target} غیر فعال کرد" disable_custom_emoji_html: "%{name} شکلک %{target} را غیرفعال کرد" disable_relay_html: "%{name} رله %{target} را غیرفعال کرد" @@ -305,6 +307,7 @@ fa: update_report_html: "%{name} گزارش %{target} را به‌روز کرد" update_status_html: "%{name} نوشتهٔ %{target} را به‌روز کرد" update_user_role_html: "%{name} نقش %{target} را تغییر داد" + update_username_block_html: "‫‏%{name} قانونی برای نام‌های کاربری دارای %{target} را به‌روز کرد" deleted_account: حساب حذف شد empty: هیچ گزارشی پیدا نشد. filter_by_action: پالایش بر اساس کنش @@ -1089,7 +1092,24 @@ fa: title: توصیه ها و روندها trending: پرطرفدار username_blocks: + add_new: افزودن جدید + block_registrations: انسداد ثبت‌نام‌ها + comparison: + contains: دارای + equals: برابر + contains_html: دارای %{string} + created_msg: قانون نام کاربری با موفّقیت ایجاد شد delete: حذف + edit: + title: ویرایش قانون نام‌کاربری + matches_exactly_html: برابر %{string} + new: + create: ایجاد قانون + title: ایجاد قانون نام‌کاربری جدید + no_username_block_selected: هیچ قاعدهٔ نام کاربری‌ای تغییری نکرد زیرا هیچ‌کدام گزیده نشده بودند + not_permitted: مجاز نیست + title: قانون نام کاربری + updated_msg: قانون نام کاربری با موفّقیت به‌روز شد warning_presets: add_new: افزودن تازه delete: زدودن diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 64752d55556..1c6ebec1963 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -18,8 +18,8 @@ fi: pin_errors: following: Sinun täytyy seurata käyttäjää, jota haluat tukea posts: - one: Julkaisu - other: Julkaisua + one: julkaisu + other: julkaisua posts_tab_heading: Julkaisut self_follow_error: Oman tilisi seuraaminen ei ole sallittua admin: @@ -507,14 +507,14 @@ fi: registration_requested: Rekisteröintiä pyydetty registrations: confirm: Vahvista - description: Sait rekisteröinnin FASP:lta. Hylkää se, jos et aloittanut sitä. Jos aloitat tämän, vertaile huolellisesti nimeä ja sormenjälkeä ennen rekisteröinnin vahvistamista. + description: Vastaanotit rekisteröintymisen FASP-palvelusta. Hylkää rekisteröintyminen, jos et aloittanut sitä. Jos aloitit sen, vertaile huolellisesti nimeä ja avaimen sormenjälkeä ennen kuin hyväksyt rekisteröintymisen. reject: Hylkää title: Vahvista FASP-rekisteröinti save: Tallenna select_capabilities: Valitse kyvykkyydet sign_in: Kirjaudu sisään status: Tila - title: Fediversumin Tukitoimintojen Tarjoajat + title: Fediversumin tukitoimintojen tarjoajat title: FASP follow_recommendations: description_html: "Seurantasuositukset auttavat uusia käyttäjiä löytämään nopeasti kiinnostavaa sisältöä. Kun käyttäjä ei ole ollut tarpeeksi vuorovaikutuksessa muiden kanssa, jotta hänelle olisi muodostunut henkilökohtaisia seuraamissuosituksia, suositellaan niiden sijaan näitä tilejä. Ne lasketaan päivittäin uudelleen yhdistelmästä tilejä, jotka ovat viime aikoina olleet aktiivisimmin sitoutuneita ja joilla on suurimmat paikalliset seuraajamäärät tietyllä kielellä." diff --git a/config/locales/nan.yml b/config/locales/nan.yml index f6b11afc930..51d365229b2 100644 --- a/config/locales/nan.yml +++ b/config/locales/nan.yml @@ -25,7 +25,7 @@ nan: action: 執行動作 already_silenced: Tsit ê口座有受著限制。 already_suspended: Tsit ê口座ê權限已經hōo lâng停止。 - title: Kā %{acct} 做審核ê動作 + title: Kā %{acct} 做管理ê動作 account_moderation_notes: create: 留記錄 created_msg: 管理記錄成功建立! @@ -238,7 +238,7 @@ nan: update_user_role: 更新角色 update_username_block: 更新使用者號名規則 actions: - approve_appeal_html: "%{name} 允准 %{target} 所寫ê tuì審核決定ê投訴" + approve_appeal_html: "%{name} 允准 %{target} 所寫ê tuì管理決定ê投訴" approve_user_html: "%{name} 允准 %{target} ê 註冊" assigned_to_self_report_html: "%{name} kā報告 %{target} 分配hōo家tī" change_email_user_html: "%{name} 改變 %{target} ê電子phue地址" @@ -282,7 +282,7 @@ nan: memorialize_account_html: "%{name} kā %{target} 設做故人口座" promote_user_html: "%{name} kā 用者 %{target} 升級" publish_terms_of_service_html: "%{name} 公佈服務規則ê更新" - reject_appeal_html: "%{name} 拒絕 %{target} 所寫ê tuì審核決定ê投訴" + reject_appeal_html: "%{name} 拒絕 %{target} 所寫ê tuì管理決定ê投訴" reject_user_html: "%{name} 拒絕 %{target} ê 註冊" remove_avatar_user_html: "%{name} thâi掉 %{target} ê標頭" reopen_report_html: "%{name} 重開 %{target} ê檢舉" @@ -667,7 +667,7 @@ nan: none: 無 comment_description_html: 為著提供其他資訊,%{name} 寫: confirm: 確認 - confirm_action: 確認kā %{acct} 審核ê動作 + confirm_action: 確認kā %{acct} 管理ê動作 created_at: 檢舉tī delete_and_resolve: Thâi掉PO文 forwarded: 轉送ah @@ -720,9 +720,48 @@ nan: target_origin: 受檢舉ê口座ê來源 title: 檢舉 unassign: 取消分配 + unknown_action_msg: M̄知影ê動作:%{action} + unresolved: 無解決 + updated_at: 更新 + view_profile: 看個人資料 roles: + add_new: 加添角色 + assigned_users: + other: "%{count} ê用者" + categories: + administration: 管理員 + devops: DevOps + invites: 邀請 + moderation: 管理 + special: 特別 + delete: Thâi掉 + description_html: 用用者角色,lí通自訂lí ê用者ē當接近使用Mastodon ê siánn物功能kap區域。 + edit: 編「%{name}」ê角色 + everyone: 預設ê權限 + everyone_full_description_html: Tse是ē影響所有用者ê基本角色,就算是無分配tio̍h角色ê mā kâng款。所有其他ê角色繼承伊ê權限。 + permissions_count: + other: "%{count} ê權限" privileges: + administrator: 管理員 + administrator_description: 有tsit ê權限ê用者ē忽略所有ê權限。 + delete_user_data: Thâi掉用者ê資料 + delete_user_data_description: 允准用者liâm-mi thâi掉其他用者ê資料 + invite_users: 邀請用者 + invite_users_description: 允准用者邀請新lâng來tsit ê服侍器 manage_announcements: 管理公告 + manage_announcements_description: 允准用者管理佇tsit ê服侍器ê公告 + manage_appeals: 管理投訴 + manage_appeals_description: 允准用者審查tuì管理動作ê投訴 + manage_blocks: 管理封鎖 + manage_blocks_description: 允准用者封鎖電子phue ê 提供者kap IP地址 + manage_custom_emojis: 管理自訂ê Emoji + manage_custom_emojis_description: 允准用者管理佇tsit ê服侍器ê自訂Emoji + manage_federation: 管理聯邦 + manage_federation_description: 允准用者封鎖á是允准kap其他域名相連,mā控制寄送ê能力 + manage_invites: 管理邀請 + manage_invites_description: 允准用者瀏覽kap停止使用邀請連結 + manage_reports: 管理檢舉 + manage_reports_description: 允准用者審查檢舉kap執行對in ê管理ê動作。 statuses: language: 語言 trends: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b34a8ab84ce..9153e01a3be 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1996,7 +1996,7 @@ ru: ignore_favs: Не учитывать добавление в избранное ignore_reblogs: Не учитывать продвижения interaction_exceptions: Исключения на основе взаимодействий - interaction_exceptions_explanation: 'Обратите внимание: нет никаких гарантий, что посты будут удалены, после того, как они единожды перешли порог по отметкам «избранного» или продвижений.' + interaction_exceptions_explanation: 'Обратите внимание: даже если количество взаимодействий опустится ниже установленного значения после того, как оно однажды превысило его, то всё равно может случиться так, что пост не будет удалён.' keep_direct: Не удалять личные сообщения keep_direct_hint: Те ваши посты, которые видны только упомянутым в них людям, не будут удалены keep_media: Не удалять посты с вложениями @@ -2040,29 +2040,29 @@ ru: contrast: Mastodon (высококонтрастная) default: Mastodon (тёмная) mastodon-light: Mastodon (светлая) - system: Автоматически (используйте системную тему) + system: Автоматически (как в системе) time: formats: default: "%d %b %Y, %H:%M" month: "%m.%Y" time: "%H:%M" - with_time_zone: "%b %d, %Y, %H:%M %Z" + with_time_zone: "%d %b %Y, %H:%M %Z" translation: errors: quota_exceeded: Превышена квота использования службы перевода в масштабах всего сервера. too_many_requests: В последнее время в службу переводов поступает слишком много запросов. two_factor_authentication: add: Добавить - disable: Отключить - disabled_success: Двухфакторная аутентификация успешно отключена + disable: Отключить 2FA + disabled_success: Двухфакторная аутентификация отключена edit: Изменить - enabled: Двухфакторная аутентификация настроена - enabled_success: Двухфакторная авторизация успешно настроена - generate_recovery_codes: Сгенерировать коды восстановления + enabled: Двухфакторная аутентификация включена + enabled_success: Двухфакторная аутентификация настроена + generate_recovery_codes: Сгенерировать резервные коды lost_recovery_codes: Коды восстановления позволяются войти в учётную запись в случае утери смартфона. Если вы потеряли свои коды восстановления, вы можете создать новые здесь. Прошлые коды работать перестанут. - methods: Методы двухфакторной аутентификации - otp: Приложение для проверки подлинности - recovery_codes: Коды восстановления + methods: Способы подтверждения входа + otp: Приложение-аутентификатор + recovery_codes: Резервные коды recovery_codes_regenerated: Коды восстановления успешно сгенерированы recovery_instructions_html: 'Пожалуйста, сохраните коды ниже в надёжном месте: они понадобятся, чтобы войти в учётную запись, если вы потеряете доступ к своему смартфону. Вы можете вручную переписать их, распечатать и спрятать среди важных документов или, например, в любимой книжке. Каждый код действителен только один раз.' webauthn: Электронные ключи diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml index 928b17ed1d1..87ebe45e570 100644 --- a/config/locales/simple_form.bg.yml +++ b/config/locales/simple_form.bg.yml @@ -56,10 +56,12 @@ bg: scopes: Указва до кои API има достъп приложението. Ако изберете диапазон от най-високо ниво, няма нужда да избирате индивидуални. setting_aggregate_reblogs: Без показване на нови подсилвания за публикации, които са неотдавна подсилени (засяга само новополучени подсилвания) setting_always_send_emails: Обикновено известията по имейл няма да са изпратени при дейна употреба на Mastodon + setting_default_quote_policy: Тази настройка ще се отрази само за публикациите, създадени със следващата версия на Mastodon, но може да изберете предпочитанията си в подготовката. setting_default_sensitive: Деликатната мултимедия е скрита по подразбиране и може да се разкрие с едно щракване setting_display_media_default: Скриване на мултимедия отбелязана като деликатна setting_display_media_hide_all: Винаги скриване на мултимедията setting_display_media_show_all: Винаги показване на мултимедията + setting_emoji_style: Как се показват емоджита. "Автоматично" ще опита да използва естествените за системата емоджита, но се връща към Twemoji за остарели браузъри. setting_system_scrollbars_ui: Прилага се само към настолни браузъри, основаващи се на Safari и Chrome setting_use_blurhash: Преливането е въз основа на цветовете на скритите визуализации, но се замъгляват подробностите setting_use_pending_items: Да се показват обновявания на часовата ос само след щракване вместо автоматично превъртане на инфоканала @@ -147,6 +149,9 @@ bg: min_age: Не трябва да е под изискваната минимална възраст от закона на юрисдикцията ви. user: chosen_languages: Само публикации на отметнатите езици ще се показват в публичните часови оси + date_of_birth: + one: Трябва да се уверим, че сте поне на %{count}, за да употребявате %{domain}. Няма да съхраняваме това. + other: Трябва да се уверим, че сте поне на %{count}, за да употребявате %{domain}. Няма да съхраняваме това. role: Ролята управлява какви позволения има потребителят. user_role: color: Цветът, използван за ролите в потребителския интерфейс, като RGB в шестнадесетичен формат @@ -154,6 +159,10 @@ bg: name: Публично име на ролята, ако ролята е зададена да се показва като значка permissions_as_keys: Потребители с тази роля ще имат достъп до... position: По-висшата роля може да разреши конфликти в някои ситуации. Някои действия могат да бъдат извършени само за роли с по-нисък приоритет + username_block: + allow_with_approval: Вместо напълно да се предотвратява регистрирането, съвпадащите регистрирания ще изискват одобрението ви + comparison: Имайте предвид проблема със Scunthorpe, блокирайки частично съвпаденията + username: Ще се намира съвпадение независимо от главните и малките букви и често срещаните приличащи си знаци като "4" за "a" или "3" за "e" webhook: events: Изберете събития за изпращане template: Съставете свой полезен товар на JSON посредством променлива интерполация. Оставете празно за подразбиращ се JSON. @@ -236,6 +245,7 @@ bg: setting_display_media_default: Стандартно setting_display_media_hide_all: Скриване на всичко setting_display_media_show_all: Показване на всичко + setting_emoji_style: Стил на емоджито setting_expand_spoilers: Винаги разширяване на публикации, отбелязани с предупреждения за съдържание setting_hide_network: Скриване на социалния ви свързан граф setting_missing_alt_text_modal: Показване на диалогов прозорец потвърждение преди публикуване на мултимедия без алт. текст @@ -318,6 +328,7 @@ bg: follow_request: Някой пожела да ви последва mention: Някой ви спомена pending_account: Новите акаунти се нуждаят от преглед + quote: Някой ви цитира reblog: Някой подсили ваша публикация report: Новият доклад е подаден software_updates: @@ -364,6 +375,10 @@ bg: name: Име permissions_as_keys: Разрешения position: Приоритет + username_block: + allow_with_approval: Позволяване на регистрации с одобрение + comparison: Начин на сравнение + username: Думи, които трябва да пасват webhook: events: Включване на събития template: Шаблон за полезен товар diff --git a/config/locales/simple_form.cy.yml b/config/locales/simple_form.cy.yml index 260d7530005..8a727fcd780 100644 --- a/config/locales/simple_form.cy.yml +++ b/config/locales/simple_form.cy.yml @@ -56,6 +56,7 @@ cy: scopes: Pa APIs y bydd y rhaglen yn cael mynediad iddynt. Os dewiswch gwmpas lefel uchaf, nid oes angen i chi ddewis rhai unigol. setting_aggregate_reblogs: Peidiwch â dangos hybiau newydd ar bostiadau sydd wedi cael eu hybu'n ddiweddar (dim ond yn effeithio ar hybiau newydd ei dderbyn) setting_always_send_emails: Fel arfer ni fydd hysbysiadau e-bost yn cael eu hanfon pan fyddwch chi wrthi'n defnyddio Mastodon + setting_default_quote_policy: Dim ond ar gyfer fersiynau nesaf Mastodon bydd y gosodiadau hyn yn dod i rym, ond gallwch wneud eich dewis ymlaen llaw. setting_default_sensitive: Mae cyfryngau sensitif wedi'u cuddio yn rhagosodedig a gellir eu datgelu trwy glicio setting_display_media_default: Cuddio cyfryngau wedi eu marcio'n sensitif setting_display_media_hide_all: Cuddio cyfryngau bob tro @@ -163,6 +164,10 @@ cy: name: Enw cyhoeddus y rôl, os yw'r rôl wedi'i gosod i'w dangos fel bathodyn permissions_as_keys: Bydd defnyddwyr sydd â'r rôl hon yn cael mynediad at... position: Mae rôl uwch yn penderfynu ar ddatrys gwrthdrawiadau mewn rhai sefyllfaoedd. Dim ond ar rolau â blaenoriaeth is y gellir cyflawni rhai gweithredoedd + username_block: + allow_with_approval: Yn lle atal cofrestru’n llwyr, bydd angen eich cymeradwyaeth rhag cofrestru cyfatebol + comparison: Byddwch yn ymwybodol o Broblem Scunthorpe wrth rwystro cyfatebion rhannol. + username: Bydd yn cael ei gyfatebu waeth beth fo'r nodau bach a mawr a homoglyffau cyffredin fel "4" ar gyfer "a" neu "3" ar gyfer "e" webhook: events: Dewiswch ddigwyddiadau i'w hanfon template: Cyfansoddwch eich llwyth tâl JSON eich hun gan ddefnyddio rhyngosod newidiol. Gadewch yn wag ar gyfer JSON rhagosodedig. @@ -328,6 +333,7 @@ cy: follow_request: Mae rhywun yn ceisio eich dilyn chi mention: Mae rhywun yn sôn amdanoch chi pending_account: Mae cyfrif newydd angen adolygiad + quote: Dyfynnodd rhywun chi reblog: Mae rhywun wedi hybu eich postiad report: Cyflwynwyd adroddiad newydd software_updates: @@ -374,6 +380,10 @@ cy: name: Enw permissions_as_keys: Caniatâd position: Blaenoriaeth + username_block: + allow_with_approval: Caniatáu cofrestru gyda chymeradwyaeth + comparison: Dull cymharu + username: Gair i gyfatebu webhook: events: Digwyddiadau wedi'u galluogi template: Templed prif lwyth diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index c9842f07ea0..19697b72aa1 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -160,6 +160,10 @@ fa: name: نام عمومی نقش، اگر قرار است نقش به عنوان یک نشان نمایش داده شود permissions_as_keys: کاربرانی که این نقش را دارند به... position: نقش بالاتر در موقعیت‌های خاص حل تعارض را تعیین می‌کند. برخی اقدامات را فقط می توان روی نقش هایی با اولویت کمتر انجام داد + username_block: + allow_with_approval: به جای جلوگیری از ثبت‌نام، ثبت‌نام‌های مطابق نیازمند تأییدتان خواهند بود + comparison: لطفاً هنگام مسدود کردن تطابق‌های جزیی به مشکل اسکان‌ثورپ توجّه داشته باشید + username: تطبیق بدون توجه به بزرگی و کوچکی یا استفاده از نویسه‌های مشابه مثل 4 به جای a یا 3 به جای e webhook: events: گزینش رویدادها برای فرستادن template: بار JSON خود را با استفاده از درون یابی متغیر بنویسید. JSON پیش فرض را خالی بگذارید. @@ -372,6 +376,10 @@ fa: name: نام permissions_as_keys: اجازه‌ها position: اولویت + username_block: + allow_with_approval: اجازهٔ ثبت‌نام با تأیید + comparison: روش مقایسه + username: واژه برای تطبیق webhook: events: رویدادهای به کار افتاده template: قالب بار From 9d15b85d3b2ddfe964148eadba55e4ab87c571c7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 18 Aug 2025 03:35:56 -0400 Subject: [PATCH 292/660] Include `update` in the resources args for api/web/push_subscriptions route (#35801) --- config/routes/api.rb | 6 +----- spec/requests/api/web/push_subscriptions_spec.rb | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/config/routes/api.rb b/config/routes/api.rb index f8b903c7b94..34b2e255da6 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -369,10 +369,6 @@ namespace :api, format: false do namespace :web do resource :settings, only: [:update] resources :embeds, only: [:show] - resources :push_subscriptions, only: [:create, :destroy] do - member do - put :update - end - end + resources :push_subscriptions, only: [:create, :destroy, :update] end end diff --git a/spec/requests/api/web/push_subscriptions_spec.rb b/spec/requests/api/web/push_subscriptions_spec.rb index 05e6f28d1ff..21830d1b1c1 100644 --- a/spec/requests/api/web/push_subscriptions_spec.rb +++ b/spec/requests/api/web/push_subscriptions_spec.rb @@ -162,7 +162,7 @@ RSpec.describe 'API Web Push Subscriptions' do end end - describe 'PUT /api/web/push_subscriptions' do + describe 'PUT /api/web/push_subscriptions/:id' do before { sign_in Fabricate :user } let(:subscription) { Fabricate :web_push_subscription } From 72bd1ed4b3df9188a1ad3e664c86fe2e928bd134 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 18 Aug 2025 10:34:45 +0200 Subject: [PATCH 293/660] Fix updates to quote policy not being federated (#35804) --- .../api/v1/statuses/interaction_policies_controller.rb | 2 +- spec/requests/api/v1/statuses/interaction_policies_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/statuses/interaction_policies_controller.rb b/app/controllers/api/v1/statuses/interaction_policies_controller.rb index 8b822185f69..b8ec4fe1400 100644 --- a/app/controllers/api/v1/statuses/interaction_policies_controller.rb +++ b/app/controllers/api/v1/statuses/interaction_policies_controller.rb @@ -28,6 +28,6 @@ class Api::V1::Statuses::InteractionPoliciesController < Api::V1::Statuses::Base def broadcast_updates! DistributionWorker.perform_async(@status.id, { 'update' => true }) - ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id) + ActivityPub::StatusUpdateDistributionWorker.perform_async(@status.id, { 'updated_at' => Time.now.utc.iso8601 }) end end diff --git a/spec/requests/api/v1/statuses/interaction_policies_spec.rb b/spec/requests/api/v1/statuses/interaction_policies_spec.rb index 6b988bb523d..cdc33e40d7e 100644 --- a/spec/requests/api/v1/statuses/interaction_policies_spec.rb +++ b/spec/requests/api/v1/statuses/interaction_policies_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Interaction policies', feature: :outgoing_quotes do expect(DistributionWorker) .to have_enqueued_sidekiq_job(status.id, { 'update' => true }) expect(ActivityPub::StatusUpdateDistributionWorker) - .to have_enqueued_sidekiq_job(status.id) + .to have_enqueued_sidekiq_job(status.id, { 'updated_at' => anything }) end end From f5754f2a3600f6ae9c9d7d89fe5aa5ecb85e7bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ev=D0=B0n=20Summers?= Date: Mon, 18 Aug 2025 16:13:44 +0200 Subject: [PATCH 294/660] Mention admin@localhost in DEVELOPMENT.md for non-Vagrant setups (#35704) --- docs/DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 79153a20213..0425f8892b6 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -91,7 +91,7 @@ development environment configured with the software needed for this project. ## Next steps - Once you have successfully set up a development environment, it will be available on http://localhost:3000 -- Log in as the default admin user with the username `admin@mastodon.local` and the password `mastodonadmin`. +- Log in as the default admin user with the username `admin@mastodon.local` or `admin@localhost` (depending on your setup) and the password `mastodonadmin`. - Check out the [Mastodon docs] for tips on working with emails in development (you'll need this when creating new user accounts) as well as a list of useful commands for testing and updating your dev instance. - You can optionally populate your database with sample data by running `bin/rails dev:populate_sample_data`. This will create a `@showcase_account` account with various types of contents. From c2fcf4183ce1c4f78097be032fdee2fc354b66d4 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 18 Aug 2025 16:51:16 +0200 Subject: [PATCH 295/660] =?UTF-8?q?Fix=20=E2=80=9CDelete=20&=20Redraft?= =?UTF-8?q?=E2=80=9D=20as=20well=20as=20=E2=80=9CEdit=E2=80=9D=20unexpecte?= =?UTF-8?q?dly=20reseting=20quote=20policy=20(#35808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/javascript/mastodon/reducers/compose.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index c5b3c22ec14..bb4d917cca5 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -512,6 +512,8 @@ export const composeReducer = (state = initialState, action) => { map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); map.set('id', null); + // Mastodon-authored posts can be expected to have at most one automatic approval policy + map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); if (action.status.get('spoiler_text').length > 0) { map.set('spoiler', true); @@ -541,6 +543,8 @@ export const composeReducer = (state = initialState, action) => { map.set('idempotencyKey', uuid()); map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); + // Mastodon-authored posts can be expected to have at most one automatic approval policy + map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); if (action.spoiler_text.length > 0) { map.set('spoiler', true); From 95111e88e3c9d44efdc4a18a80a7b082cf340655 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 18 Aug 2025 09:55:38 -0600 Subject: [PATCH 296/660] Update color alpha blending to use Sass color functions (#35787) --- .../styles/mastodon-light/diff.scss | 18 +-- app/javascript/styles/mastodon/_mixins.scss | 3 +- app/javascript/styles/mastodon/accounts.scss | 15 ++- app/javascript/styles/mastodon/admin.scss | 3 +- .../styles/mastodon/components.scss | 125 +++++++++--------- .../styles/mastodon/css_variables.scss | 10 +- app/javascript/styles/mastodon/forms.scss | 23 ++-- app/javascript/styles/mastodon/polls.scss | 5 +- app/javascript/styles/mastodon/widgets.scss | 3 +- 9 files changed, 108 insertions(+), 97 deletions(-) diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 874c44240a4..d92012a1573 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -16,7 +16,7 @@ .status-card__actions button, .status-card__actions a { - color: rgba($white, 0.8); + color: color.change($white, $alpha: 0.8); &:hover, &:active, @@ -298,14 +298,14 @@ .simple_form { .warning { box-shadow: none; - background: rgba($error-red, 0.5); + background: color.change($error-red, $alpha: 0.5); text-shadow: none; } .recommended { border-color: $ui-highlight-color; color: $ui-highlight-color; - background-color: rgba($ui-highlight-color, 0.1); + background-color: color.change($ui-highlight-color, $alpha: 0.1); } input[type='text'], @@ -327,7 +327,7 @@ .compose-form .compose-form__warning { border-color: $ui-highlight-color; - background-color: rgba($ui-highlight-color, 0.1); + background-color: color.change($ui-highlight-color, $alpha: 0.1); &, a { @@ -375,10 +375,10 @@ } .status__wrapper-direct { - background-color: rgba($ui-highlight-color, 0.1); + background-color: color.change($ui-highlight-color, $alpha: 0.1); &:focus { - background-color: rgba($ui-highlight-color, 0.15); + background-color: color.change($ui-highlight-color, $alpha: 0.15); } } @@ -433,8 +433,8 @@ } .inline-follow-suggestions { - background-color: rgba($ui-highlight-color, 0.1); - border-bottom-color: rgba($ui-highlight-color, 0.3); + background-color: color.change($ui-highlight-color, $alpha: 0.1); + border-bottom-color: color.change($ui-highlight-color, $alpha: 0.3); } .inline-follow-suggestions__body__scrollable__card { @@ -508,7 +508,7 @@ a.sparkline { @supports not selector(::-webkit-scrollbar) { html { - scrollbar-color: rgba($action-button-color, 0.25) + scrollbar-color: color.change($action-button-color, $alpha: 0.25) var(--background-border-color); } } diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index b7d9203e3f3..d66fa405819 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'variables' as *; @mixin search-input { @@ -22,7 +23,7 @@ padding-bottom: 14px; margin-top: 10px; color: $light-text-color; - box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); + box-shadow: 2px 4px 15px color.change($base-shadow-color, $alpha: 0.4); h4 { text-transform: uppercase; diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 3854db0c396..da4b9bdaa81 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'variables' as *; @use 'functions' as *; @@ -161,7 +162,7 @@ .nothing-here { background: $ui-base-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); color: $darker-text-color; font-size: 14px; font-weight: 500; @@ -205,8 +206,8 @@ .simple_form .overridden, .simple_form .recommended, .simple_form .not_recommended { - background-color: rgba($ui-secondary-color, 0.1); - border: 1px solid rgba($ui-secondary-color, 0.5); + background-color: color.change($ui-secondary-color, $alpha: 0.1); + border: 1px solid color.change($ui-secondary-color, $alpha: 0.5); } .account-role { @@ -240,8 +241,8 @@ .information-badge { &.superapp { color: $success-green; - background-color: rgba($success-green, 0.1); - border-color: rgba($success-green, 0.5); + background-color: color.change($success-green, $alpha: 0.1); + border-color: color.change($success-green, $alpha: 0.5); } } @@ -302,8 +303,8 @@ } .verified { - border: 1px solid rgba($valid-value-color, 0.5); - background: rgba($valid-value-color, 0.25); + border: 1px solid color.change($valid-value-color, $alpha: 0.5); + background: color.change($valid-value-color, $alpha: 0.25); a { color: $valid-value-color; diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 3709a4f504c..e9b584f3966 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'sass:math'; @use 'functions' as *; @use 'variables' as *; @@ -1255,7 +1256,7 @@ a.name-tag, } path:first-child { - fill: rgba($highlight-text-color, 0.25) !important; + fill: color.change($highlight-text-color, $alpha: 0.25) !important; fill-opacity: 1 !important; } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1de7b74c501..48a7ed12cc1 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -311,7 +311,7 @@ &:active, &:focus-visible { color: lighten($action-button-color, 7%); - background-color: rgba($action-button-color, 0.15); + background-color: color.change($action-button-color, $alpha: 0.15); } &:focus-visible { @@ -331,7 +331,7 @@ &:active, &:focus-visible { color: darken($lighter-text-color, 7%); - background-color: rgba($lighter-text-color, 0.15); + background-color: color.change($lighter-text-color, $alpha: 0.15); } &:focus-visible { @@ -361,14 +361,14 @@ &.overlayed { box-sizing: content-box; - background: rgba($black, 0.65); + background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; - color: rgba($white, 0.7); + color: color.change($white, $alpha: 0.7); border-radius: 4px; padding: 2px; &:hover { - background: rgba($black, 0.9); + background: color.change($black, $alpha: 0.9); } } @@ -391,7 +391,7 @@ border-color: $valid-value-color; color: $valid-value-color; transition: none; - background-color: rgba($valid-value-color, 0.15); + background-color: color.change($valid-value-color, $alpha: 0.15); } } @@ -411,7 +411,7 @@ &:active, &:focus { color: darken($lighter-text-color, 7%); - background-color: rgba($lighter-text-color, 0.15); + background-color: color.change($lighter-text-color, $alpha: 0.15); } &:focus-visible { @@ -618,7 +618,7 @@ body > [data-popper-placement] { &__warning { color: $inverted-text-color; background: $ui-primary-color; - box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3); + box-shadow: 0 2px 6px color.change($base-shadow-color, $alpha: 0.3); padding: 8px 10px; border-radius: 4px; font-size: 13px; @@ -694,7 +694,7 @@ body > [data-popper-placement] { .spoiler-input__input { padding: 12px 12px - 5px; - background: rgba($ui-highlight-color, 0.05); + background: color.change($ui-highlight-color, $alpha: 0.05); color: $highlight-text-color; } @@ -1428,7 +1428,7 @@ body > [data-popper-placement] { .focusable { &:focus-visible { outline: 0; - background: rgba($ui-highlight-color, 0.05); + background: color.change($ui-highlight-color, $alpha: 0.05); box-shadow: inset 0 0 0 2px $ui-button-focus-outline-color; } } @@ -1683,10 +1683,10 @@ body > [data-popper-placement] { .notification-ungrouped--direct, .notification-group--direct, .notification-group--annual-report { - background: rgba($ui-highlight-color, 0.05); + background: color.change($ui-highlight-color, $alpha: 0.05); &:focus { - background: rgba($ui-highlight-color, 0.1); + background: color.change($ui-highlight-color, $alpha: 0.1); } } @@ -2016,7 +2016,7 @@ body > [data-popper-placement] { &__domain-pill { display: inline-flex; - background: rgba($highlight-text-color, 0.2); + background: color.change($highlight-text-color, $alpha: 0.2); border-radius: 4px; border: 0; color: $highlight-text-color; @@ -2077,7 +2077,7 @@ body > [data-popper-placement] { &__handle { border: 2px dashed $highlight-text-color; - background: rgba($highlight-text-color, 0.1); + background: color.change($highlight-text-color, $alpha: 0.1); padding: 12px 8px; color: $highlight-text-color; border-radius: 4px; @@ -3283,7 +3283,7 @@ a.account__display-name { .columns-area__panels__pane--overlay { pointer-events: auto; - background: rgba($base-overlay-background, 0.5); + background: color.change($base-overlay-background, $alpha: 0.5); z-index: 3; .columns-area__panels__pane__inner { @@ -3820,7 +3820,10 @@ a.account__display-name { border-radius: 10px; padding: 0; user-select: none; - -webkit-tap-highlight-color: rgba($base-overlay-background, 0); + -webkit-tap-highlight-color: color.change( + $base-overlay-background, + $alpha: 0 + ); -webkit-tap-highlight-color: transparent; } @@ -4069,7 +4072,7 @@ a.account__display-name { cursor: pointer; & > div { - background: rgba($base-shadow-color, 0.6); + background: color.change($base-shadow-color, $alpha: 0.6); border-radius: 8px; padding: 12px 9px; backdrop-filter: $backdrop-blur-filter; @@ -4399,7 +4402,7 @@ a.status-card { z-index: 1; &.active { - box-shadow: 0 1px 0 rgba($highlight-text-color, 0.3); + box-shadow: 0 1px 0 color.change($highlight-text-color, $alpha: 0.3); &::before { display: block; @@ -4415,8 +4418,8 @@ a.status-card { z-index: 1; background: radial-gradient( ellipse, - rgba($ui-highlight-color, 0.23) 0%, - rgba($ui-highlight-color, 0) 60% + color.change($ui-highlight-color, $alpha: 0.23) 0%, + color.change($ui-highlight-color, $alpha: 0) 60% ); } } @@ -4510,7 +4513,7 @@ a.status-card { &.active { .column-header__icon { color: $highlight-text-color; - text-shadow: 0 0 10px rgba($highlight-text-color, 0.4); + text-shadow: 0 0 10px color.change($highlight-text-color, $alpha: 0.4); } } @@ -4855,7 +4858,7 @@ a.status-card { font-size: 14px; &__label { - background-color: rgba($black, 0.45); + background-color: color.change($black, $alpha: 0.45); backdrop-filter: $backdrop-blur-filter; border-radius: 8px; padding: 12px 16px; @@ -4875,7 +4878,7 @@ a.status-card { &:hover, &:focus { .spoiler-button__overlay__label { - background-color: rgba($black, 0.9); + background-color: color.change($black, $alpha: 0.9); } } } @@ -5266,7 +5269,7 @@ a.status-card { .upload-area { align-items: center; - background: rgba($base-overlay-background, 0.8); + background: color.change($base-overlay-background, $alpha: 0.8); display: flex; height: 100vh; justify-content: center; @@ -5301,7 +5304,7 @@ a.status-card { z-index: -1; border-radius: 4px; background: $ui-base-color; - box-shadow: 0 0 5px rgba($base-shadow-color, 0.2); + box-shadow: 0 0 5px color.change($base-shadow-color, $alpha: 0.2); } .upload-area__content { @@ -5939,7 +5942,7 @@ a.status-card { align-items: center; .icon-button { - color: rgba($white, 0.7); + color: color.change($white, $alpha: 0.7); padding: 8px; .icon { @@ -5952,11 +5955,11 @@ a.status-card { &:focus, &:active { color: $white; - background-color: rgba($white, 0.15); + background-color: color.change($white, $alpha: 0.15); } &:focus { - background-color: rgba($white, 0.3); + background-color: color.change($white, $alpha: 0.3); } } } @@ -5997,7 +6000,7 @@ a.status-card { background: transparent; box-sizing: border-box; border: 0; - color: rgba($white, 0.7); + color: color.change($white, $alpha: 0.7); cursor: pointer; display: flex; align-items: center; @@ -6051,11 +6054,11 @@ a.status-card { &:focus, &:active { color: $white; - background-color: rgba($white, 0.15); + background-color: color.change($white, $alpha: 0.15); } &:focus { - background-color: rgba($white, 0.3); + background-color: color.change($white, $alpha: 0.3); } &.active { @@ -6064,11 +6067,11 @@ a.status-card { &:hover, &:focus, &:active { - background: rgba($highlight-text-color, 0.15); + background: color.change($highlight-text-color, $alpha: 0.15); } &:focus { - background: rgba($highlight-text-color, 0.3); + background: color.change($highlight-text-color, $alpha: 0.3); } } @@ -6078,11 +6081,11 @@ a.status-card { &:hover, &:focus, &:active { - background: rgba($gold-star, 0.15); + background: color.change($gold-star, $alpha: 0.15); } &:focus { - background: rgba($gold-star, 0.3); + background: color.change($gold-star, $alpha: 0.3); } } @@ -6954,7 +6957,7 @@ a.status-card { display: block; color: $white; border: 0; - background: rgba($black, 0.65); + background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; padding: 3px 12px; border-radius: 99px; @@ -6979,7 +6982,7 @@ a.status-card { text-align: center; color: $white; border: 0; - background: rgba($black, 0.65); + background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; padding: 3px 8px; border-radius: 4px; @@ -7001,7 +7004,7 @@ a.status-card { } .media-gallery__alt__popover { - background: rgba($black, 0.65); + background: color.change($black, $alpha: 0.65); backdrop-filter: $backdrop-blur-filter; border-radius: 4px; box-shadow: var(--dropdown-shadow); @@ -7023,7 +7026,7 @@ a.status-card { p { font-size: 15px; line-height: 20px; - color: rgba($white, 0.85); + color: color.change($white, $alpha: 0.85); white-space: pre-line; } } @@ -7467,8 +7470,8 @@ a.status-card { box-sizing: border-box; background: linear-gradient( 0deg, - rgba($base-shadow-color, 0.85) 0, - rgba($base-shadow-color, 0.45) 60%, + color.change($base-shadow-color, $alpha: 0.85) 0, + color.change($base-shadow-color, $alpha: 0.45) 60%, transparent ); padding: 0 15px; @@ -7564,7 +7567,7 @@ a.status-card { flex: 0 0 auto; background: transparent; border: 0; - color: rgba($white, 0.75); + color: color.change($white, $alpha: 0.75); font-weight: 500; &:active, @@ -7628,7 +7631,7 @@ a.status-card { &::before { content: ''; width: 50px; - background: rgba($white, 0.35); + background: color.change($white, $alpha: 0.35); border-radius: 4px; display: block; position: absolute; @@ -7660,7 +7663,7 @@ a.status-card { margin-inline-start: -6px; transform: translate(0, -50%); background: $white; - box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); + box-shadow: 1px 2px 6px color.change($base-shadow-color, $alpha: 0.2); opacity: 0; .no-reduce-motion & { @@ -7698,7 +7701,7 @@ a.status-card { &::before { content: ''; width: 100%; - background: rgba($white, 0.35); + background: color.change($white, $alpha: 0.35); border-radius: 4px; display: block; position: absolute; @@ -7717,7 +7720,7 @@ a.status-card { } &__buffer { - background: rgba($white, 0.2); + background: color.change($white, $alpha: 0.2); } &__handle { @@ -7730,7 +7733,7 @@ a.status-card { top: 10px; margin-inline-start: -6px; background: $white; - box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); + box-shadow: 1px 2px 6px color.change($base-shadow-color, $alpha: 0.2); .no-reduce-motion & { transition: opacity 0.1s ease; @@ -7754,7 +7757,7 @@ a.status-card { top: 50%; inset-inline-start: 50%; transform: translate(-50%, -50%); - background: rgba($base-shadow-color, 0.45); + background: color.change($base-shadow-color, $alpha: 0.45); backdrop-filter: $backdrop-blur-filter; color: $white; border-radius: 8px; @@ -8089,7 +8092,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - background: rgba($base-overlay-background, 0.5); + background: color.change($base-overlay-background, $alpha: 0.5); } .focal-point { @@ -8111,7 +8114,7 @@ noscript { transform: translate(-50%, -50%); border: 2px solid #fff; border-radius: 50%; - box-shadow: 0 0 0 9999em rgba($base-shadow-color, 0.35); + box-shadow: 0 0 0 9999em color.change($base-shadow-color, $alpha: 0.35); pointer-events: none; } } @@ -8353,7 +8356,7 @@ noscript { } .verified { - border: 1px solid rgba($valid-value-color, 0.5); + border: 1px solid color.change($valid-value-color, $alpha: 0.5); margin-top: -1px; margin-inline: -1px; @@ -8573,7 +8576,7 @@ noscript { width: 50px; path:first-child { - fill: rgba($highlight-text-color, 0.25) !important; + fill: color.change($highlight-text-color, $alpha: 0.25) !important; fill-opacity: 1 !important; } @@ -8598,7 +8601,7 @@ noscript { .trends__item__sparkline { path:first-child { - fill: rgba($gold-star, 0.25) !important; + fill: color.change($gold-star, $alpha: 0.25) !important; } path:last-child { @@ -9763,7 +9766,7 @@ noscript { margin-bottom: 5px; border-radius: 8px; border: 1px solid $highlight-text-color; - background: rgba($highlight-text-color, 0.15); + background: color.change($highlight-text-color, $alpha: 0.15); overflow: hidden; flex-shrink: 0; @@ -9818,7 +9821,7 @@ noscript { } .button-tertiary { - background: rgba($ui-base-color, 0.15); + background: color.change($ui-base-color, $alpha: 0.15); backdrop-filter: blur(8px); } } @@ -9835,7 +9838,7 @@ noscript { .warning-banner { border: 1px solid $warning-red; - background: rgba($warning-red, 0.15); + background: color.change($warning-red, $alpha: 0.15); &__message { h1 { @@ -10170,13 +10173,13 @@ noscript { padding: 15px; margin: 0; color: $white; - background: rgba($black, 0.85); + background: color.change($black, $alpha: 0.85); backdrop-filter: blur(8px); border: 1px solid rgba(lighten($classic-base-color, 4%), 0.85); border-radius: 8px; box-shadow: - 0 10px 15px -3px rgba($base-shadow-color, 0.25), - 0 4px 6px -4px rgba($base-shadow-color, 0.25); + 0 10px 15px -3px color.change($base-shadow-color, $alpha: 0.25), + 0 4px 6px -4px color.change($base-shadow-color, $alpha: 0.25); cursor: default; font-size: 15px; line-height: 21px; @@ -10214,7 +10217,7 @@ noscript { &:hover, &:focus, &:active { - background: rgba($ui-base-color, 0.85); + background: color.change($ui-base-color, $alpha: 0.85); } } @@ -10312,7 +10315,7 @@ noscript { padding: 16px 0; padding-bottom: 0; border-bottom: 1px solid var(--background-border-color); - background: rgba($ui-highlight-color, 0.05); + background: color.change($ui-highlight-color, $alpha: 0.05); &__header { display: flex; diff --git a/app/javascript/styles/mastodon/css_variables.scss b/app/javascript/styles/mastodon/css_variables.scss index 7f27c12f774..78915ae10ee 100644 --- a/app/javascript/styles/mastodon/css_variables.scss +++ b/app/javascript/styles/mastodon/css_variables.scss @@ -6,10 +6,10 @@ --dropdown-border-color: #{lighten($ui-base-color, 4%)}; --dropdown-background-color: #{rgba(darken($ui-base-color, 8%), 0.9)}; --dropdown-shadow: - 0 20px 25px -5px #{rgba($base-shadow-color, 0.25)}, - 0 8px 10px -6px #{rgba($base-shadow-color, 0.25)}; + 0 20px 25px -5px #{color.change($base-shadow-color, $alpha: 0.25)}, + 0 8px 10px -6px #{color.change($base-shadow-color, $alpha: 0.25)}; --modal-background-color: #{rgba(darken($ui-base-color, 8%), 0.7)}; - --modal-background-variant-color: #{rgba($ui-base-color, 0.7)}; + --modal-background-variant-color: #{color.change($ui-base-color, $alpha: 0.7)}; --modal-border-color: #{lighten($ui-base-color, 4%)}; --background-border-color: #{lighten($ui-base-color, 4%)}; --background-color: #{darken($ui-base-color, 8%)}; @@ -21,7 +21,9 @@ --on-surface-color: #{color.adjust($ui-base-color, $alpha: -0.5)}; --avatar-border-radius: 8px; --media-outline-color: #{rgba(#fcf8ff, 0.15)}; - --overlay-icon-shadow: drop-shadow(0 0 8px #{rgba($base-shadow-color, 0.35)}); + --overlay-icon-shadow: drop-shadow( + 0 0 8px #{color.change($base-shadow-color, $alpha: 0.35)} + ); --error-background-color: #{darken($error-red, 16%)}; --error-active-background-color: #{darken($error-red, 12%)}; --on-error-color: #fff; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 4762168bd89..b3708b722e3 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'variables' as *; @use 'functions' as *; @@ -733,20 +734,20 @@ code { text-align: center; &.notice { - border: 1px solid rgba($valid-value-color, 0.5); - background: rgba($valid-value-color, 0.25); + border: 1px solid color.change($valid-value-color, $alpha: 0.5); + background: color.change($valid-value-color, $alpha: 0.25); color: $valid-value-color; } &.warning { - border: 1px solid rgba($gold-star, 0.5); - background: rgba($gold-star, 0.25); + border: 1px solid color.change($gold-star, $alpha: 0.5); + background: color.change($gold-star, $alpha: 0.25); color: $gold-star; } &.alert { - border: 1px solid rgba($error-value-color, 0.5); - background: rgba($error-value-color, 0.1); + border: 1px solid color.change($error-value-color, $alpha: 0.5); + background: color.change($error-value-color, $alpha: 0.1); color: $error-value-color; } @@ -965,7 +966,7 @@ code { background: $simple-background-color; padding: 4px; margin: 0 10px 20px 0; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); display: inline-block; svg { @@ -988,10 +989,10 @@ code { .simple_form { .warning { box-sizing: border-box; - background: rgba($error-value-color, 0.5); + background: color.change($error-value-color, $alpha: 0.5); color: $primary-text-color; - text-shadow: 1px 1px 0 rgba($base-shadow-color, 0.3); - box-shadow: 0 2px 6px rgba($base-shadow-color, 0.4); + text-shadow: 1px 1px 0 color.change($base-shadow-color, $alpha: 0.3); + box-shadow: 0 2px 6px color.change($base-shadow-color, $alpha: 0.4); border-radius: 4px; padding: 10px; margin-bottom: 15px; @@ -1350,7 +1351,7 @@ code { width: 100%; height: 100%; position: absolute; - background: rgba($error-value-color, 0.25); + background: color.change($error-value-color, $alpha: 0.25); z-index: 2; border-radius: 8px; } diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index f49ce3c413b..4402e51b9ba 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'variables' as *; @use 'functions' as *; @@ -206,7 +207,7 @@ &:active, &:focus { - background-color: rgba($dark-text-color, 0.1); + background-color: color.change($dark-text-color, $alpha: 0.1); } } @@ -225,7 +226,7 @@ background: rgba(darken($ui-primary-color, 14%), 0.7); &.leading { - background: rgba($ui-highlight-color, 0.5); + background: color.change($ui-highlight-color, $alpha: 0.5); } } } diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 8d09c7d5839..266a9ca9307 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -1,3 +1,4 @@ +@use 'sass:color'; @use 'variables' as *; @use 'functions' as *; @@ -16,7 +17,7 @@ padding: 15px; text-decoration: none; color: inherit; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + box-shadow: 0 0 15px color.change($base-shadow-color, $alpha: 0.2); } & > a { From 255d8f3f8c6ece6a9f1fce9ea901d71577b92099 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 18 Aug 2025 18:37:13 +0200 Subject: [PATCH 297/660] Fix e-mail confirmation bypassing username approval (#35806) --- app/models/user.rb | 8 +++-- .../models/concerns/user/confirmation.rb | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index aca72daff04..8e0785e7fdd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -412,7 +412,7 @@ class User < ApplicationRecord def set_approved self.approved = begin - if sign_up_from_ip_requires_approval? || sign_up_email_requires_approval? || sign_up_username_requires_approval? + if requires_approval? false else open_registrations? || valid_invitation? || external? @@ -426,7 +426,11 @@ class User < ApplicationRecord def grant_approval_on_confirmation? # Re-check approval on confirmation if the server has switched to open registrations - open_registrations? && !sign_up_from_ip_requires_approval? && !sign_up_email_requires_approval? + open_registrations? && !requires_approval? + end + + def requires_approval? + sign_up_from_ip_requires_approval? || sign_up_email_requires_approval? || sign_up_username_requires_approval? end def wrap_email_confirmation diff --git a/spec/support/examples/models/concerns/user/confirmation.rb b/spec/support/examples/models/concerns/user/confirmation.rb index 4edc402f950..bb4d1b52b9c 100644 --- a/spec/support/examples/models/concerns/user/confirmation.rb +++ b/spec/support/examples/models/concerns/user/confirmation.rb @@ -86,6 +86,42 @@ RSpec.shared_examples 'User::Confirmation' do end end + context 'when the user requires explicit approval because of signup IP address' do + let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email, approved: false, sign_up_ip: '192.0.2.5') } + + before do + Setting.registrations_mode = 'open' + Fabricate(:ip_block, ip: '192.0.2.5', severity: :sign_up_requires_approval) + end + + it 'sets email to new_email and marks user as confirmed, but not as approved and does not trigger `account.approved` web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + .and change { user.reload.confirmed_at }.from(nil) + .and not_change { user.reload.approved }.from(false) + expect(TriggerWebhookWorker) + .to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + + context 'when the user requires explicit approval because of username' do + let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email, approved: false, account_attributes: { username: 'VeryStrangeUsername' }) } + + before do + Setting.registrations_mode = 'open' + Fabricate(:username_block, username: 'StrangeUser', exact: false, allow_with_approval: true) + end + + it 'sets email to new_email and marks user as confirmed, but not as approved and does not trigger `account.approved` web hook' do + expect { subject } + .to change { user.reload.email }.to(new_email) + .and change { user.reload.confirmed_at }.from(nil) + .and not_change { user.reload.approved }.from(false) + expect(TriggerWebhookWorker) + .to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + context 'when registrations mode is approved' do before { Setting.registrations_mode = 'approved' } From 28bf811a070f4c4fcc8a81378b89173e5766f433 Mon Sep 17 00:00:00 2001 From: Michael Stanclift Date: Mon, 18 Aug 2025 11:45:31 -0500 Subject: [PATCH 298/660] Update Dockerfile to use Debian 'trixie' (#35768) --- Dockerfile | 35 ++++++++++++++++------------------- streaming/Dockerfile | 6 +++--- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/Dockerfile b/Dockerfile index 23214c2483c..38723228547 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,11 +17,11 @@ ARG RUBY_VERSION="3.4.5" # # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # renovate: datasource=node-version depName=node ARG NODE_MAJOR_VERSION="22" -# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] -ARG DEBIAN_VERSION="bookworm" -# Node.js image to use for base image based on combined variables (ex: 20-bookworm-slim) +# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] +ARG DEBIAN_VERSION="trixie" +# Node.js image to use for base image based on combined variables (ex: 20-trixie-slim) FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node -# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm) +# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-trixie) FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA @@ -96,9 +96,6 @@ RUN \ # Set /opt/mastodon as working directory WORKDIR /opt/mastodon -# Add backport repository for some specific packages where we need the latest version -RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list - # hadolint ignore=DL3008,DL3005 RUN \ # Mount Apt cache and lib directories from Docker buildx caches @@ -161,11 +158,11 @@ RUN \ libexif-dev \ libexpat1-dev \ libgirepository1.0-dev \ - libheif-dev/bookworm-backports \ + libheif-dev \ + libhwy-dev \ libimagequant-dev \ libjpeg62-turbo-dev \ liblcms2-dev \ - liborc-dev \ libspng-dev \ libtiff-dev \ libwebp-dev \ @@ -209,7 +206,7 @@ FROM build AS ffmpeg # ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"] # renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg -ARG FFMPEG_VERSION=7.1 +ARG FFMPEG_VERSION=7.1.1 # ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"] ARG FFMPEG_URL=https://ffmpeg.org/releases @@ -327,28 +324,28 @@ RUN \ # Apt update install non-dev versions of necessary components apt-get install -y --no-install-recommends \ libexpat1 \ - libglib2.0-0 \ - libicu72 \ + libglib2.0-0t64 \ + libicu76 \ libidn12 \ libpq5 \ - libreadline8 \ - libssl3 \ + libreadline8t64 \ + libssl3t64 \ libyaml-0-2 \ # libvips components libcgif0 \ libexif12 \ - libheif1/bookworm-backports \ + libheif1 \ + libhwy1t64 \ libimagequant0 \ libjpeg62-turbo \ liblcms2-2 \ - liborc-0.4-0 \ libspng0 \ libtiff6 \ libwebp7 \ libwebpdemux2 \ libwebpmux3 \ # ffmpeg components - libdav1d6 \ + libdav1d7 \ libmp3lame0 \ libopencore-amrnb0 \ libopencore-amrwb0 \ @@ -358,9 +355,9 @@ RUN \ libvorbis0a \ libvorbisenc2 \ libvorbisfile3 \ - libvpx7 \ + libvpx9 \ libx264-164 \ - libx265-199 \ + libx265-215 \ ; # Copy Mastodon sources into final layer diff --git a/streaming/Dockerfile b/streaming/Dockerfile index 14f2d3c7e71..0ddde2f8dc6 100644 --- a/streaming/Dockerfile +++ b/streaming/Dockerfile @@ -11,9 +11,9 @@ ARG BASE_REGISTRY="docker.io" # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # renovate: datasource=node-version depName=node ARG NODE_MAJOR_VERSION="22" -# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] -ARG DEBIAN_VERSION="bookworm" -# Node image to use for base image based on combined variables (ex: 20-bookworm-slim) +# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="trixie"] +ARG DEBIAN_VERSION="trixie" +# Node image to use for base image based on combined variables (ex: 20-trixie-slim) FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS streaming # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA From d4b2e7f77139e27cbde83034fe96dbd189ac56c9 Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 18 Aug 2025 18:52:28 +0200 Subject: [PATCH 299/660] Composer Quote UI (#35805) Co-authored-by: diondiondion --- .../components/autosuggest_textarea.jsx | 3 ++- app/javascript/mastodon/components/status.jsx | 19 +++++++++++++ .../mastodon/containers/status_container.jsx | 7 +++++ .../compose/components/compose_form.jsx | 3 +++ .../compose/components/quoted_post.tsx | 27 +++++++++++++++++++ .../containers/poll_button_container.js | 14 +++++++--- .../containers/upload_button_container.js | 3 ++- app/javascript/mastodon/locales/en.json | 1 + app/javascript/mastodon/reducers/compose.js | 12 +++++++-- app/javascript/mastodon/selectors/filters.ts | 2 +- .../mastodon/store/typed_functions.ts | 2 +- .../styles/mastodon/components.scss | 24 +++++++++++++++++ 12 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/components/quoted_post.tsx diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index c7ec3779f3f..de5accc4b28 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -53,6 +53,7 @@ const AutosuggestTextarea = forwardRef(({ onFocus, autoFocus = true, lang, + className, }, textareaRef) => { const [suggestionsHidden, setSuggestionsHidden] = useState(true); @@ -192,7 +193,7 @@ const AutosuggestTextarea = forwardRef(({ }; return ( -
+