From 5f486a1424fe32ef78e5594f7c1de7eb75385739 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:29:06 +0100 Subject: [PATCH 01/13] New Crowdin Translations (automated) (#34027) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/bg.json | 1 + app/javascript/mastodon/locales/es-MX.json | 2 +- app/javascript/mastodon/locales/fa.json | 5 +++++ app/javascript/mastodon/locales/is.json | 6 +++--- app/javascript/mastodon/locales/lv.json | 6 ++++++ config/locales/ko.yml | 4 ++-- config/locales/simple_form.fa.yml | 3 +++ config/locales/simple_form.ko.yml | 1 + config/locales/simple_form.pl.yml | 2 ++ config/locales/sk.yml | 2 ++ 10 files changed, 26 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index aebf906f94..2eec42633d 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -697,6 +697,7 @@ "poll_button.remove_poll": "Премахване на анкета", "privacy.change": "Промяна на поверителността на публикация", "privacy.direct.long": "Споменатите в публикацията", + "privacy.direct.short": "Частно споменаване", "privacy.private.long": "Само последователите ви", "privacy.private.short": "Последователи", "privacy.public.long": "Всеки във и извън Mastodon", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 36609f6ec1..273f8c3aeb 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -195,7 +195,7 @@ "compose_form.publish": "Publicar", "compose_form.publish_form": "Nueva publicación", "compose_form.reply": "Respuesta", - "compose_form.save_changes": "Actualización", + "compose_form.save_changes": "Actualizar", "compose_form.spoiler.marked": "Quitar advertencia de contenido", "compose_form.spoiler.unmarked": "Añadir advertencia de contenido", "compose_form.spoiler_placeholder": "Advertencia de contenido (opcional)", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index f934277a28..629ee484ae 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -218,6 +218,10 @@ "confirmations.logout.confirm": "خروج از حساب", "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": "خموش", "confirmations.redraft.confirm": "حذف و بازنویسی", "confirmations.redraft.message": "مطمئنید که می‌خواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویت‌ها و پسندهایش از دست رفته و پاسخ‌ها به آن بی‌مرجع می‌شود.", @@ -693,6 +697,7 @@ "poll_button.remove_poll": "برداشتن نظرسنجی", "privacy.change": "تغییر محرمانگی فرسته", "privacy.direct.long": "هرکسی که در فرسته نام برده شده", + "privacy.direct.short": "ذکر خصوصی", "privacy.private.long": "تنها پی‌گیرندگانتان", "privacy.private.short": "پی‌گیرندگان", "privacy.public.long": "هرکسی در و بیرون از ماستودون", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index dfc227fb77..0b6333c44e 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -598,11 +598,11 @@ "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Samþykkja beiðni} other {Samþykkja beiðnir}}", "notification_requests.confirm_accept_multiple.message": "Þú ert að fara að samþykkja {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Ertu viss um að þú viljir halda áfram?", "notification_requests.confirm_accept_multiple.title": "Samþykkja beiðnir um tilkynningar?", - "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Afgreiða beiðni} other {Afgreiða beiðnir}}", + "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Hafna beiðni} other {Hafna beiðnum}}", "notification_requests.confirm_dismiss_multiple.message": "Þú ert að fara að hunsa {count, plural, one {eina beiðni um tilkynningar} other {# beiðnir um tilkynningar}}. Þú munt ekki eiga auðvelt með að skoða {count, plural, one {hana} other {þær}} aftur síðar. Ertu viss um að þú viljir halda áfram?", "notification_requests.confirm_dismiss_multiple.title": "Hunsa beiðnir um tilkynningar?", - "notification_requests.dismiss": "Afgreiða", - "notification_requests.dismiss_multiple": "{count, plural, one {Afgreiða # beiðni…} other {Afgreiða # beiðnir…}}", + "notification_requests.dismiss": "Hafna", + "notification_requests.dismiss_multiple": "{count, plural, one {Hafna # beiðni…} other {Hafna # beiðnum…}}", "notification_requests.edit_selection": "Breyta", "notification_requests.exit_selection": "Lokið", "notification_requests.explainer_for_limited_account": "Tilkynningar frá þessum notanda hafa verið síaðar þar sem aðgangur hans hefur verið takmarkaður af umsjónarmanni.", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 040da68243..10b09e6947 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -311,6 +311,7 @@ "filter_modal.select_filter.subtitle": "Izmanto esošu kategoriju vai izveido jaunu", "filter_modal.select_filter.title": "Filtrēt šo ziņu", "filter_modal.title.status": "Filtrēt ziņu", + "filtered_notifications_banner.title": "Filtrētie paziņojumi", "firehose.all": "Visi", "firehose.local": "Šis serveris", "firehose.remote": "Citi serveri", @@ -321,6 +322,8 @@ "follow_suggestions.dismiss": "Vairs nerādīt", "follow_suggestions.friends_of_friends_longer": "Populārs to cilvēku vidū, kuriem tu seko", "follow_suggestions.personalized_suggestion": "Pielāgots ieteikums", + "follow_suggestions.popular_suggestion": "Populārs ieteikums", + "follow_suggestions.popular_suggestion_longer": "Populārs {domain}", "follow_suggestions.similar_to_recently_followed_longer": "Līdzīgi profieliem, kuriem nesen sāki sekot", "follow_suggestions.view_all": "Skatīt visu", "follow_suggestions.who_to_follow": "Kam sekot", @@ -496,6 +499,7 @@ "notifications.column_settings.filter_bar.category": "Atrās atlasīšanas josla", "notifications.column_settings.follow": "Jauni sekotāji:", "notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:", + "notifications.column_settings.group": "Grupēt", "notifications.column_settings.mention": "Pieminēšanas:", "notifications.column_settings.poll": "Aptaujas rezultāti:", "notifications.column_settings.push": "Uznirstošie paziņojumi", @@ -520,6 +524,7 @@ "notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta", "notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.", "notifications.policy.accept": "Pieņemt", + "notifications.policy.drop": "Ignorēt", "notifications.policy.filter_new_accounts_title": "Jauni konti", "notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko", "notifications.policy.filter_not_following_hint": "Līdz tos pašrocīgi apstiprināsi", @@ -527,6 +532,7 @@ "notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus", "notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.", "notifications_permission_banner.title": "Nekad nepalaid neko garām", + "onboarding.follows.back": "Atpakaļ", "onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.", "onboarding.profile.discoverable": "Padarīt manu profilu atklājamu", "onboarding.profile.display_name": "Attēlojamais vārds", diff --git a/config/locales/ko.yml b/config/locales/ko.yml index a688a2a7d2..09825046fc 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -194,7 +194,7 @@ ko: destroy_domain_allow: 도메인 허용 삭제 destroy_domain_block: 도메인 차단 삭제 destroy_email_domain_block: 이메일 도메인 차단 삭제 - destroy_instance: 도메인 제거 + destroy_instance: 도메인 퍼지 destroy_ip_block: IP 규칙 삭제 destroy_relay: 릴레이 삭제 destroy_status: 게시물 삭제 @@ -536,7 +536,7 @@ ko: title: 중재 private_comment: 비공개 주석 public_comment: 공개 주석 - purge: 제거 + purge: 퍼지 purge_description_html: 이 도메인이 영구적으로 오프라인 상태라고 생각되면, 스토리지에서 이 도메인의 모든 계정 레코드와 관련 데이터를 삭제할 수 있습니다. 이 작업은 시간이 좀 걸릴 수 있습니다. title: 연합 total_blocked_by_us: 우리에게 차단 됨 diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index c3cf3692be..0f73f7f6a0 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -137,6 +137,7 @@ fa: admin_email: اخطارهای حقوقی شامل اخطارهای متقابل، دستورها دادگاه، درخواست‌های حذف و درخواست‌های اجرای قانون است. arbitration_address: می‌تواند مانند آدرس فیزیکی بالا باشد، یا در صورت استفاده از ایمیل، «N/A» باشد arbitration_website: اگر از ایمیل استفاده می کنید، می تواند یک فرم وب یا "N/A" باشد + choice_of_law: شهر، منطقه، قلمرو یا ایالتی که قوانین ماهوی داخلی آن بر هر یک از دعاوی حاکم است. dmca_address: برای اپراتورهای ایالات متحده، از آدرس ثبت شده در دایرکتوری نماینده تعیین شده DMCA استفاده کنید. یک P.O. فهرست جعبه در صورت درخواست مستقیم در دسترس است، از درخواست چشم پوشی از صندوق پست دفتر پست DMCA برای ایمیل به دفتر حق نسخه برداری استفاده کنید و توضیح دهید که شما یک ناظر محتوای خانگی هستید که از انتقام یا تلافی برای اعمال خود می ترسید و باید از P.O استفاده کنید. کادری برای حذف آدرس خانه شما از نمای عمومی. dmca_email: می‌تواند همان ایمیلی باشد که برای “آدرس ایمیل برای اطلاعیه‌های قانونی“ در بالا استفاده شده است domain: شناسایی منحصر به فرد سرویس آنلاینی که ارائه می کنید. @@ -233,6 +234,7 @@ fa: setting_display_media_show_all: نمایش همه setting_expand_spoilers: همیشه فرسته‌هایی را که هشدار محتوا دارند کامل نشان بده setting_hide_network: نهفتن شبکهٔ ارتباطی + setting_missing_alt_text_modal: نمایش گفتگوی تایید قبل از ارسال رسانه بدون متن جایگزین setting_reduce_motion: کاستن از حرکت در پویانمایی‌ها setting_system_font_ui: به‌کاربردن قلم پیش‌فرض سیستم setting_system_scrollbars_ui: از نوار اسکرول پیش فرض سیستم استفاده کنید @@ -337,6 +339,7 @@ fa: admin_email: آدرس ایمیل برای اطلاعیه های حقوقی arbitration_address: آدرس فیزیکی اعلامیه های داوری arbitration_website: وب سایت ارسال اخطارهای داوری + choice_of_law: انتخاب قانون dmca_address: آدرس فیزیکی اعلامیه‌های DMCA/حق نسخه‌برداری dmca_email: آدرس ایمیل برای اعلامیه‌های DMCA/حق نسخه‌برداری domain: دامنه diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 9d0b55c9bc..d70639e4a5 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -337,6 +337,7 @@ ko: admin_email: 법적 조치를 위한 이메일 주소 arbitration_address: 중재 통지를 위한 실제 주소 arbitration_website: 중재 통지를 제출하기 위한 웹사이트 + choice_of_law: 준거법 지정 dmca_address: DMCA/저작권 통지를 위한 실제 주소 dmca_email: DMCA/저작권 통지를 위한 이메일 주소 domain: 도메인 diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index a048fdb99e..d016aee1b3 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -137,6 +137,7 @@ pl: admin_email: Zawiadomienia prawne obejmują środki zapobiegawcze, nakazy sądowe, wnioski o popełnienie sprawy oraz wnioski organów ścigania. arbitration_address: 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: Adres email może być taki sam jak wcześniejszy "adres e-mail przeznaczony do celów prawnych" domain: Unikalny numer identyfikacji świadczonej przez Ciebie usługi online. @@ -338,6 +339,7 @@ pl: admin_email: Adres e-mail przeznaczony do celów prawnych arbitration_address: Adres fizyczny powiadomień arbitrażowych arbitration_website: Strona internetowa do składania zgłoszeń arbitrażowych + choice_of_law: Wybór prawa dmca_address: Adres fizyczny dla zgłoszeń naruszenia DMCA/praw autorskich dmca_email: Adres e-mail dla zgłoszeń naruszenia DMCA/praw autorskich domain: Domena diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 0ee3ccd9aa..6ad76c0fcf 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -25,10 +25,12 @@ sk: one: Príspevok other: Príspevkov posts_tab_heading: Príspevky + self_follow_error: Nieje povolené nasledovať svoj vlastný účet admin: account_actions: action: Vykonaj already_silenced: Tento účet už bol obmedzený. + already_suspended: Tento účet už bol vylúčený. title: Vykonaj moderovací úkon voči %{acct} account_moderation_notes: create: Zanechaj poznámku From febcd0a76c19e95bb749c28fc32a3dd056835883 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Fri, 28 Feb 2025 11:53:39 +0100 Subject: [PATCH 02/13] Expose status language via og:locale (#34012) --- app/views/statuses/show.html.haml | 2 + spec/requests/status_show_page_spec.rb | 66 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 spec/requests/status_show_page_spec.rb diff --git a/app/views/statuses/show.html.haml b/app/views/statuses/show.html.haml index f669885de0..cc779f4370 100644 --- a/app/views/statuses/show.html.haml +++ b/app/views/statuses/show.html.haml @@ -12,6 +12,8 @@ = opengraph 'og:title', "#{display_name(@account)} (#{acct(@account)})" = opengraph 'og:url', short_account_status_url(@account, @status) = opengraph 'og:published_time', @status.created_at.iso8601 + - if @status.language.present? + = opengraph 'og:locale', @status.language = opengraph 'profile:username', acct(@account)[1..] = render 'og_description', activity: @status diff --git a/spec/requests/status_show_page_spec.rb b/spec/requests/status_show_page_spec.rb new file mode 100644 index 0000000000..758f9dcfd8 --- /dev/null +++ b/spec/requests/status_show_page_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Statuses' do + describe 'GET /@:account_username/:id' do + include AccountsHelper + + def site_hostname + Rails.configuration.x.web_domain || Rails.configuration.x.local_domain + end + + it 'has valid opengraph tags' do + account = Fabricate(:account, username: 'alice', display_name: 'Alice') + status = Fabricate(:status, account: account, text: 'Hello World') + + get "/@#{account.username}/#{status.id}" + + expect(head_link_icons.size).to eq(3) # Three favicons with sizes + + expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})" + expect(head_meta_content('og:type')).to eq 'article' + expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601 + expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id) + expect(head_meta_exists('og:locale')).to be false + end + + it 'has og:locale opengraph tag if the status has is written in a given language' do + status_text = "Una prova d'estatus català" + account = Fabricate(:account, username: 'alice', display_name: 'Alice') + status = Fabricate(:status, account: account, text: status_text, language: 'ca') + + get "/@#{account.username}/#{status.id}" + + expect(head_meta_content('og:title')).to match "#{display_name(account)} (#{acct(account)})" + expect(head_meta_content('og:type')).to eq 'article' + expect(head_meta_content('og:published_time')).to eq status.created_at.iso8601 + expect(head_meta_content('og:url')).to eq short_account_status_url(account_username: account.username, id: status.id) + + expect(head_meta_exists('og:locale')).to be true + expect(head_meta_content('og:locale')).to eq 'ca' + expect(head_meta_content('og:description')).to eq status_text + end + + def head_link_icons + response + .parsed_body + .search('html head link[rel=icon]') + end + + def head_meta_content(property) + response + .parsed_body + .search("html head meta[property='#{property}']") + .attr('content') + .text + end + + def head_meta_exists(property) + !response + .parsed_body + .search("html head meta[property='#{property}']") + .empty? + end + end +end From 3aaa388ce45a037c58b2f5f176da86208c63834c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 28 Feb 2025 05:55:37 -0500 Subject: [PATCH 03/13] Convert `admin/trends/*` spec controller->system (#34003) --- .../preview_card_providers_controller_spec.rb | 21 ------------------- .../admin/trends/links_controller_spec.rb | 21 ------------------- .../admin/trends/statuses_controller_spec.rb | 21 ------------------- .../admin/trends/tags_controller_spec.rb | 21 ------------------- .../links/preview_card_providers_spec.rb | 2 ++ spec/system/admin/trends/links_spec.rb | 2 ++ spec/system/admin/trends/statuses_spec.rb | 2 ++ spec/system/admin/trends/tags_spec.rb | 2 ++ 8 files changed, 8 insertions(+), 84 deletions(-) delete mode 100644 spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb delete mode 100644 spec/controllers/admin/trends/links_controller_spec.rb delete mode 100644 spec/controllers/admin/trends/statuses_controller_spec.rb delete mode 100644 spec/controllers/admin/trends/tags_controller_spec.rb diff --git a/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb b/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb deleted file mode 100644 index aadf002dd6..0000000000 --- a/spec/controllers/admin/trends/links/preview_card_providers_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::Trends::Links::PreviewCardProvidersController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #index' do - it 'returns http success' do - get :index - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/controllers/admin/trends/links_controller_spec.rb b/spec/controllers/admin/trends/links_controller_spec.rb deleted file mode 100644 index cd2cf6b852..0000000000 --- a/spec/controllers/admin/trends/links_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::Trends::LinksController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #index' do - it 'returns http success' do - get :index - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/controllers/admin/trends/statuses_controller_spec.rb b/spec/controllers/admin/trends/statuses_controller_spec.rb deleted file mode 100644 index 6570340b2d..0000000000 --- a/spec/controllers/admin/trends/statuses_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::Trends::StatusesController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #index' do - it 'returns http success' do - get :index - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/controllers/admin/trends/tags_controller_spec.rb b/spec/controllers/admin/trends/tags_controller_spec.rb deleted file mode 100644 index 83ea23ed21..0000000000 --- a/spec/controllers/admin/trends/tags_controller_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::Trends::TagsController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #index' do - it 'returns http success' do - get :index - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/system/admin/trends/links/preview_card_providers_spec.rb b/spec/system/admin/trends/links/preview_card_providers_spec.rb index 159a5b720a..4636ca86b2 100644 --- a/spec/system/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/system/admin/trends/links/preview_card_providers_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Links::PreviewCardProviders' do context 'without selecting any records' do it 'displays a notice about selection' do visit admin_trends_links_preview_card_providers_path + expect(page) + .to have_title(I18n.t('admin.trends.preview_card_providers.title')) click_on button_for_allow diff --git a/spec/system/admin/trends/links_spec.rb b/spec/system/admin/trends/links_spec.rb index 879bbe8ad9..6140ea8154 100644 --- a/spec/system/admin/trends/links_spec.rb +++ b/spec/system/admin/trends/links_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Links' do context 'without selecting any records' do it 'displays a notice about selection' do visit admin_trends_links_path + expect(page) + .to have_title(I18n.t('admin.trends.links.title')) click_on button_for_allow diff --git a/spec/system/admin/trends/statuses_spec.rb b/spec/system/admin/trends/statuses_spec.rb index be081df989..6e1aa17b7d 100644 --- a/spec/system/admin/trends/statuses_spec.rb +++ b/spec/system/admin/trends/statuses_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Statuses' do context 'without selecting any records' do it 'displays a notice about selection' do visit admin_trends_statuses_path + expect(page) + .to have_title(I18n.t('admin.trends.statuses.title')) click_on button_for_allow diff --git a/spec/system/admin/trends/tags_spec.rb b/spec/system/admin/trends/tags_spec.rb index a71d9ba8ca..a7f00c0232 100644 --- a/spec/system/admin/trends/tags_spec.rb +++ b/spec/system/admin/trends/tags_spec.rb @@ -11,6 +11,8 @@ RSpec.describe 'Admin::Trends::Tags' do context 'without selecting any records' do it 'displays a notice about selection' do visit admin_trends_tags_path + expect(page) + .to have_title(I18n.t('admin.trends.tags.title')) click_on button_for_allow From bff53a637ee8ea6407618d378a8927ceb0dc23a1 Mon Sep 17 00:00:00 2001 From: Darren VanBuren Date: Fri, 28 Feb 2025 03:16:49 -0800 Subject: [PATCH 04/13] Fix up the off by one error on displayed rank for trending tags admin page. (#34016) --- app/views/admin/trends/tags/_tag.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml index 1686c46652..91a484d46c 100644 --- a/app/views/admin/trends/tags/_tag.html.haml +++ b/app/views/admin/trends/tags/_tag.html.haml @@ -13,7 +13,7 @@ - if tag.trendable? · - %abbr{ title: t('admin.trends.tags.current_score', score: tag.trend.score) }= t('admin.trends.tags.trending_rank', rank: tag.trend.rank + 1) + %abbr{ title: t('admin.trends.tags.current_score', score: tag.trend.score) }= t('admin.trends.tags.trending_rank', rank: tag.trend.rank) - if tag.decaying? · From b56e90d049987be2fbaee48137fff7c6135609e1 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 28 Feb 2025 06:17:13 -0500 Subject: [PATCH 05/13] Convert `admin/relationships` spec controller->system (#34002) --- .../admin/relationships_controller_spec.rb | 23 ------------------- spec/system/admin/relationships_spec.rb | 18 +++++++++++++++ 2 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 spec/controllers/admin/relationships_controller_spec.rb create mode 100644 spec/system/admin/relationships_spec.rb diff --git a/spec/controllers/admin/relationships_controller_spec.rb b/spec/controllers/admin/relationships_controller_spec.rb deleted file mode 100644 index 1d300459f6..0000000000 --- a/spec/controllers/admin/relationships_controller_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::RelationshipsController do - render_views - - let(:user) { Fabricate(:admin_user) } - - before do - sign_in user, scope: :user - end - - describe 'GET #index' do - let(:account) { Fabricate(:account) } - - it 'returns http success' do - get :index, params: { account_id: account.id } - - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/system/admin/relationships_spec.rb b/spec/system/admin/relationships_spec.rb new file mode 100644 index 0000000000..2d64857c48 --- /dev/null +++ b/spec/system/admin/relationships_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Admin Relationships' do + before { sign_in(admin_user) } + + describe 'Viewing account relationships page' do + let(:account) { Fabricate(:account) } + + it 'shows page with relationships for account' do + visit admin_account_relationships_path(account.id) + + expect(page) + .to have_title(I18n.t('admin.relationships.title', acct: account.pretty_acct)) + end + end +end From e71fb450e08515b53a33bdef1744e0ba7ce6f23b Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 28 Feb 2025 14:39:01 +0100 Subject: [PATCH 06/13] Add optional `delete_media` parameter to `DELETE /api/v1/statuses/:id` (#33988) --- app/controllers/api/v1/statuses_controller.rb | 2 +- app/javascript/mastodon/actions/statuses.js | 2 +- spec/requests/api/v1/statuses_spec.rb | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 19cc71ae58..2027bc6016 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -111,7 +111,7 @@ class Api::V1::StatusesController < Api::BaseController @status.account.statuses_count = @status.account.statuses_count - 1 json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true - RemovalWorker.perform_async(@status.id, { 'redraft' => true }) + RemovalWorker.perform_async(@status.id, { 'redraft' => !truthy_param?(:delete_media) }) render json: json end diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 1e4e545d8c..1e5b53c687 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -138,7 +138,7 @@ export function deleteStatus(id, withRedraft = false) { dispatch(deleteStatusRequest(id)); - api().delete(`/api/v1/statuses/${id}`).then(response => { + api().delete(`/api/v1/statuses/${id}`, { params: { delete_media: !withRedraft } }).then(response => { dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); dispatch(importFetchedAccount(response.data.account)); diff --git a/spec/requests/api/v1/statuses_spec.rb b/spec/requests/api/v1/statuses_spec.rb index ddf5945d25..285fa93655 100644 --- a/spec/requests/api/v1/statuses_spec.rb +++ b/spec/requests/api/v1/statuses_spec.rb @@ -257,13 +257,30 @@ RSpec.describe '/api/v1/statuses' do it_behaves_like 'forbidden for wrong scope', 'read read:statuses' - it 'removes the status', :aggregate_failures do + it 'discards the status and schedules removal as a redraft', :aggregate_failures do subject expect(response).to have_http_status(200) expect(response.content_type) .to start_with('application/json') expect(Status.find_by(id: status.id)).to be_nil + expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => true }) + end + + context 'when called with truthy delete_media' do + subject do + delete "/api/v1/statuses/#{status.id}?delete_media=true", headers: headers + end + + it 'discards the status and schedules removal without the redraft flag', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + expect(Status.find_by(id: status.id)).to be_nil + expect(RemovalWorker).to have_enqueued_sidekiq_job(status.id, { 'redraft' => false }) + end end end From 4960193ed0db67dde94b5acd5f983957ffca2a80 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Fri, 28 Feb 2025 14:41:42 +0100 Subject: [PATCH 07/13] Add API to delete media attachments that are not in use (#33991) --- app/controllers/api/v1/media_controller.rb | 17 ++++++- config/routes/api.rb | 2 +- spec/requests/api/v1/media_spec.rb | 53 ++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb index 5ea26d55bd..c427e055ea 100644 --- a/app/controllers/api/v1/media_controller.rb +++ b/app/controllers/api/v1/media_controller.rb @@ -3,8 +3,8 @@ class Api::V1::MediaController < Api::BaseController before_action -> { doorkeeper_authorize! :write, :'write:media' } before_action :require_user! - before_action :set_media_attachment, except: [:create] - before_action :check_processing, except: [:create] + before_action :set_media_attachment, except: [:create, :destroy] + before_action :check_processing, except: [:create, :destroy] def show render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment @@ -25,6 +25,15 @@ class Api::V1::MediaController < Api::BaseController render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment end + def destroy + @media_attachment = current_account.media_attachments.find(params[:id]) + + return render json: in_usage_error, status: 422 unless @media_attachment.status_id.nil? + + @media_attachment.destroy + render_empty + end + private def status_code_for_media_attachment @@ -54,4 +63,8 @@ class Api::V1::MediaController < Api::BaseController def processing_error { error: 'Error processing thumbnail for uploaded media' } end + + def in_usage_error + { error: 'Media attachment is currently used by a status' } + end end diff --git a/config/routes/api.rb b/config/routes/api.rb index 34a267b35d..ce63cda1bf 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -77,7 +77,7 @@ namespace :api, format: false do end end - resources :media, only: [:create, :update, :show] + resources :media, only: [:create, :update, :show, :destroy] resources :blocks, only: [:index] resources :mutes, only: [:index] resources :favourites, only: [:index] diff --git a/spec/requests/api/v1/media_spec.rb b/spec/requests/api/v1/media_spec.rb index d7d0b92f11..4d6e250207 100644 --- a/spec/requests/api/v1/media_spec.rb +++ b/spec/requests/api/v1/media_spec.rb @@ -193,4 +193,57 @@ RSpec.describe 'Media' do end end end + + describe 'DELETE /api/v1/media/:id' do + subject do + delete "/api/v1/media/#{media.id}", headers: headers + end + + context 'when media is not attached to a status' do + let(:media) { Fabricate(:media_attachment, account: user.account, status: nil) } + + it 'returns http empty response' do + subject + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + + expect(MediaAttachment.where(id: media.id)).to_not exist + end + end + + context 'when media is attached to a status' do + let(:media) { Fabricate(:media_attachment, account: user.account, status: Fabricate.build(:status)) } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + expect(response.content_type) + .to start_with('application/json') + expect(response.parsed_body).to match( + a_hash_including( + error: 'Media attachment is currently used by a status' + ) + ) + + expect(MediaAttachment.where(id: media.id)).to exist + end + end + + context 'when the media belongs to somebody else' do + let(:media) { Fabricate(:media_attachment, status: nil) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + expect(response.content_type) + .to start_with('application/json') + + expect(MediaAttachment.where(id: media.id)).to exist + end + end + end end From bdc9cb27e247a7c667f32dd29f0174db985c9d44 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 28 Feb 2025 09:26:43 -0500 Subject: [PATCH 08/13] Update rubocop to version 1.73.1 (#34034) --- .rubocop_todo.yml | 2 +- Gemfile.lock | 6 +++--- app/models/concerns/account/interactions.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5cf43a3d5b..e612721ac7 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.72.2. +# using RuboCop version 1.73.1. # 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 3e97c94d35..19bb8ccc92 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -719,7 +719,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.13.2) - rubocop (1.72.2) + rubocop (1.73.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -730,7 +730,7 @@ GEM rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.38.0) + rubocop-ast (1.38.1) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -854,7 +854,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.2) + uri (1.0.3) useragent (0.16.11) validate_email (0.1.6) activemodel (>= 3.0) diff --git a/app/models/concerns/account/interactions.rb b/app/models/concerns/account/interactions.rb index 6e2dc9126c..33d51abed9 100644 --- a/app/models/concerns/account/interactions.rb +++ b/app/models/concerns/account/interactions.rb @@ -115,7 +115,7 @@ module Account::Interactions end def follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false) - rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) + rel = active_relationships.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, languages: languages, uri: uri, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) .find_or_create_by!(target_account: other_account) rel.show_reblogs = reblogs unless reblogs.nil? @@ -128,7 +128,7 @@ module Account::Interactions end def request_follow!(other_account, reblogs: nil, notify: nil, languages: nil, uri: nil, rate_limit: false, bypass_limit: false) - rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) + rel = follow_requests.create_with(show_reblogs: reblogs.nil? || reblogs, notify: notify.nil? ? false : notify, uri: uri, languages: languages, rate_limit: rate_limit, bypass_follow_limit: bypass_limit) .find_or_create_by!(target_account: other_account) rel.show_reblogs = reblogs unless reblogs.nil? From 894b96cf1fef16416d2780565e19f59e9cf6c6dc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 28 Feb 2025 09:33:15 -0500 Subject: [PATCH 09/13] Rely on `haml-lint` parallel default (#34036) --- .github/workflows/lint-haml.yml | 2 +- lint-staged.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index 9361358078..499be2010a 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -43,4 +43,4 @@ jobs: - name: Run haml-lint run: | echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json" - bin/haml-lint --parallel --reporter github + bin/haml-lint --reporter github diff --git a/lint-staged.config.js b/lint-staged.config.js index baf5d0d454..63f5258a94 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -3,7 +3,7 @@ const config = { 'Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a', '*.{js,jsx,ts,tsx}': 'eslint --fix', '*.{css,scss}': 'stylelint --fix', - '*.haml': 'bin/haml-lint -a --parallel', + '*.haml': 'bin/haml-lint -a', '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit', }; From d50110a17aa35fb2e836a676ae9d3a8559bedb12 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 28 Feb 2025 19:02:58 +0100 Subject: [PATCH 10/13] Change alt text reminder to not appear for private mentions in web UI (#33784) --- .../mastodon/features/compose/components/compose_form.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 2cd88aab41..75f4720fb3 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -120,7 +120,7 @@ class ComposeForm extends ImmutablePureComponent { return; } - this.props.onSubmit(missingAltTextModal && this.props.missingAltText); + this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct'); if (e) { e.preventDefault(); From d399244d4de929b94ae192d285df2653f0b4eb29 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 3 Mar 2025 09:24:06 +0100 Subject: [PATCH 11/13] Fix moved notice on profiles in web UI (#34052) --- .../components/account_header.tsx | 6 +-- .../{memorial_note.jsx => memorial_note.tsx} | 9 ++-- .../components/moved_note.jsx | 39 -------------- .../components/moved_note.tsx | 53 +++++++++++++++++++ 4 files changed, 61 insertions(+), 46 deletions(-) rename app/javascript/mastodon/features/account_timeline/components/{memorial_note.jsx => memorial_note.tsx} (53%) delete mode 100644 app/javascript/mastodon/features/account_timeline/components/moved_note.jsx create mode 100644 app/javascript/mastodon/features/account_timeline/components/moved_note.tsx 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 9970e27d06..f9c01eba6b 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -58,8 +58,8 @@ import { import { getAccountHidden } from 'mastodon/selectors/accounts'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; -import MemorialNote from './memorial_note'; -import MovedNote from './moved_note'; +import { MemorialNote } from './memorial_note'; +import { MovedNote } from './moved_note'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -833,7 +833,7 @@ export const AccountHeader: React.FC<{
{!hidden && account.memorial && } {!hidden && account.moved && ( - + )}
( +export const MemorialNote: React.FC = () => (
- +
); - -export default MemorialNote; diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx deleted file mode 100644 index 2c996ff769..0000000000 --- a/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import { FormattedMessage } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { AvatarOverlay } from '../../../components/avatar_overlay'; -import { DisplayName } from '../../../components/display_name'; - -export default class MovedNote extends ImmutablePureComponent { - - static propTypes = { - from: ImmutablePropTypes.map.isRequired, - to: ImmutablePropTypes.map.isRequired, - }; - - render () { - const { from, to } = this.props; - - return ( -
-
- }} /> -
- -
- -
- - - - -
-
- ); - } - -} diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx b/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx new file mode 100644 index 0000000000..51dbb93c8b --- /dev/null +++ b/app/javascript/mastodon/features/account_timeline/components/moved_note.tsx @@ -0,0 +1,53 @@ +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import { AvatarOverlay } from 'mastodon/components/avatar_overlay'; +import { DisplayName } from 'mastodon/components/display_name'; +import { useAppSelector } from 'mastodon/store'; + +export const MovedNote: React.FC<{ + accountId: string; + targetAccountId: string; +}> = ({ accountId, targetAccountId }) => { + const from = useAppSelector((state) => state.accounts.get(accountId)); + const to = useAppSelector((state) => state.accounts.get(targetAccountId)); + + return ( +
+
+ + + + ), + }} + /> +
+ +
+ +
+ +
+ + + + + + +
+
+ ); +}; From 102807519be1d4456b259b3586c44ff9f37930b7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 08:24:42 +0000 Subject: [PATCH 12/13] chore(deps): update dependency rubocop-rails to v2.30.3 (#34056) 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 19bb8ccc92..e89e762265 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -738,7 +738,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.72.1, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.30.2) + rubocop-rails (2.30.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) From e5655a5f65eb8116640ae434125553e0fe77f35e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 08:25:22 +0000 Subject: [PATCH 13/13] chore(deps): update dependency @types/lodash to v4.17.16 (#34055) 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 0d78276bb3..fe861986aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3907,9 +3907,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.195": - version: 4.17.15 - resolution: "@types/lodash@npm:4.17.15" - checksum: 10c0/2eb2dc6d231f5fb4603d176c08c8d7af688f574d09af47466a179cd7812d9f64144ba74bb32ca014570ffdc544eedc51b7a5657212bad083b6eecbd72223f9bb + version: 4.17.16 + resolution: "@types/lodash@npm:4.17.16" + checksum: 10c0/cf017901b8ab1d7aabc86d5189d9288f4f99f19a75caf020c0e2c77b8d4cead4db0d0b842d009b029339f92399f49f34377dd7c2721053388f251778b4c23534 languageName: node linkType: hard